The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on GitHub.
Step 1: Create a Workflow File
In your server project directory, create a.github/workflows/publish-mcp.yml file. Here is an example for npm-based local server, but the MCP Registry publishing steps are the same for all package types:
The examples pin mcp-publisher to a specific release and verify its Sigstore
bundle before extracting it. When upgrading, update MCP_PUBLISHER_VERSION to
the intended release tag deliberately; do not switch back to the mutable
releases/latest URL.
Step 2: Add Secrets
You may need to add a secret depending on which authentication method you choose:- GitHub OIDC Authentication: No dedicated secret necessary.
-
GitHub PAT Authentication: Add a
MCP_GITHUB_TOKENsecret with a GitHub Personal Access Token. The registry only needs to read your organization role to confirm you are an Owner before granting its namespace, so grant the minimum for that and no repository scopes (the registry never reads or writes your code):- Classic PAT: the
read:orgscope (read:useris not needed; a bare token still authenticates and publishes to your personal namespace). - Fine-grained PAT: the Organization permissions → Members → Read-only permission (the equivalent of
read:org). Without it you’ll get your personal namespace only. A fine-grained PAT is scoped to one resource owner, so create it for the organization you’re publishing to.
mcp-registry-publishenvironment rather than a plain repository secret — see “Securing your registry token in CI” below for why this matters and how to configure the environment. - Classic PAT: the
-
DNS Authentication: Add a
MCP_PRIVATE_KEYsecret with your Ed25519 private key.
NPM_TOKEN secret with your npm token.
For information about how to add secrets to a repository, see Using secrets in GitHub Actions.
Securing your registry token in CI
Whichever token you use to authenticate, treat it as a high-value credential. When you authenticate to an organization namespace, the resulting registry token can publish — and overwrite — any server underio.github.<org>/*, not just the one in this repository. The relevant question for your threat model is therefore: who can cause code to run in a job where this secret is exposed? By default that is every repository writer (via branch or tag pushes), not only org Owners — so a plain repo secret quietly widens publish access to everyone with write access.
GitHub gives you the tools to close that gap. We recommend, in increasing order of strength:
- Store the token as an Environment secret, not a repository or organization secret. A job can only read an environment secret when it declares
environment:(as the PAT example above does). Note that theenvironment:key alone protects nothing: if the environment does not exist yet, GitHub auto-creates it with no protection rules on first run, and a job that declaresenvironment:still also receives ordinary repository and organization secrets. The protection comes from storing the token as a secret on that environment and configuring the environment (steps 2–3 below) — not from theenvironment:key by itself. - Restrict that environment to your default branch and/or release tags with a deployment branch rule, and protect that branch with required pull request reviews. Now only reviewed, maintainer-approved code can ever reach the token.
- (Strongest) Add a required reviewer to the environment so each publish pauses for an explicit human approval.
- Never publish from a
pull_request_targetworkflow that checks out PR-head code. That trigger runs with your secrets in the base-repo context, so an untrusted fork PR could exfiltrate the token. Environment branch rules do not protect you here (the ref is still your base branch) — only required reviewers do. - Avoid self-hosted runners for the publish job on public repositories, where fork PRs may be able to schedule jobs onto them.