Two real defects, plus the CLI reference claims that go with them. ## 1. ECDSA P-384 login instructions cannot work Every "ECDSA P-384" tab ran the *identical* command to the Ed25519 tab. Since `--algorithm` defaults to ed25519 ([login.go:100](https://github.com/modelcontextprotocol/registry/blob/main/cmd/publisher/commands/login.go#L100)), a 96-hex key is rejected outright. Verified against a freshly generated P-384 key: ``` $ mcp-publisher login dns --domain=example.com --private-key=$KEY Signing in process using key algorithm ed25519 Error: failed to get token: failed to sign timestamp: invalid seed length: expected 32 bytes, got 48 $ mcp-publisher login dns --algorithm ecdsap384 --domain=example.com --private-key=$KEY Signing in process using key algorithm ecdsap384 Expected proof record: # works ``` `--algorithm` is registered at [login.go:129](https://github.com/modelcontextprotocol/registry/blob/main/cmd/publisher/commands/login.go#L129) and described correctly by the CLI's own help, but `grep -rn algorithm docs/` had **zero** hits for it. Anyone following the ECDSA path in the docs hit that error with nothing to tell them why. Fixed at all four in-process ECDSA sites (2 in `authentication.mdx`, 2 in `cli/commands.md`), with a note that cloud signing providers derive the algorithm from the key itself so the flag does not apply to them. ## 2. `mcp-publisher validate --help` was broken ``` $ mcp-publisher validate --help Unknown command: validate ... Use 'mcp-publisher <command> --help' for more information about a command. ``` It printed the very instruction that had just failed. `main.go` intercepted `--help` for all subcommands and called `printCommandHelp`, whose switch had no `validate` case — making `ValidateCommand`'s own help block ([validate.go:120-129](https://github.com/modelcontextprotocol/registry/blob/main/cmd/publisher/commands/validate.go#L120)) unreachable. Rather than paste a third copy of the help text, `printCommandHelp` now returns whether it handled the command, and the caller falls through to normal dispatch when it did not. Commands own their own `--help`, and any future command with this shape works automatically. | Command | Before | After | |---|---|---| | `validate --help` | `Unknown command`, exit 0 | prints its help, exit 0 | | `publish --help` | works | works | | `init -h` | works | works | | `bogus --help` | `Unknown command`, exit 0 | `Unknown command`, **exit 1** | ## 3. CLI reference corrections - **`--registry` is not global.** It exists only on `login`; other commands read the URL from the stored token. `publish --registry=URL` silently treats the flag as the `server.json` path. `cmd/publisher/README.md` had exactly this broken example. - **`--token` was undocumented** despite `github-actions.mdx:123` depending on it. Now documented, noting it is `login github` only. - **`init` takes no flags** and does not prompt; it writes `TODO:` placeholders. The reference said `init [options]` and "Prompts for missing required fields". - **Refreshed the `--help` transcript** in `quickstart.mdx`, which listed only 4 of 6 commands. Now byte-for-byte identical to real output (diffed). - **Added `validate`** to the command list in `cmd/publisher/README.md`. ## Verification `go build` and `go test ./cmd/...` pass. All four help paths exercised with exit codes checked. The 23 `goconst` findings in `validate_test.go` are pre-existing — identical count on pristine `main`, none in `main.go`. Quickstart transcript diffed against real output. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
321 lines
13 KiB
Text
321 lines
13 KiB
Text
---
|
|
title: How to Authenticate When Publishing to the Official MCP Registry
|
|
sidebarTitle: Authentication
|
|
---
|
|
|
|
<Note>
|
|
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](https://github.com/modelcontextprotocol/registry/issues).
|
|
</Note>
|
|
|
|
You must authenticate before publishing to the official MCP Registry. The MCP Registry supports different authentication methods. Which authentication method you choose determines the namespace of your server's name.
|
|
|
|
If you choose GitHub-based authentication, your server's name in `server.json` **MUST** be of the form `io.github.username/*` (or `io.github.orgname/*`). For example, `io.github.alice/weather-server`.
|
|
|
|
If you choose domain-based authentication, your server's name in `server.json` **MUST** be of the form `com.example.*/*`, where `com.example` is the reverse-DNS form of your domain name. For example, `io.modelcontextprotocol/everything`.
|
|
|
|
| Authentication | Name Format | Example Name |
|
|
| -------------- | ----------------------------------------------- | ------------------------------------ |
|
|
| GitHub-based | `io.github.username/*` or `io.github.orgname/*` | `io.github.alice/weather-server` |
|
|
| domain-based | `com.example.*/*` | `io.modelcontextprotocol/everything` |
|
|
|
|
## GitHub Authentication
|
|
|
|
GitHub authentication uses an OAuth flow initiated by the `mcp-publisher` CLI tool.
|
|
|
|
To perform GitHub authentication, navigate to your server project directory and run:
|
|
|
|
```bash
|
|
mcp-publisher login github
|
|
```
|
|
|
|
You should see output like:
|
|
|
|
```text Output
|
|
Logging in with github...
|
|
|
|
To authenticate, please:
|
|
1. Go to: https://github.com/login/device
|
|
2. Enter code: ABCD-1234
|
|
3. Authorize this application
|
|
Waiting for authorization...
|
|
```
|
|
|
|
Visit the link, follow the prompts, and enter the authorization code that was printed in the terminal (e.g., `ABCD-1234` in the above output). Once complete, go back to the terminal, and you should see output like:
|
|
|
|
```text Output
|
|
Successfully authenticated!
|
|
✓ Successfully logged in
|
|
```
|
|
|
|
### Personal vs. organization namespaces
|
|
|
|
GitHub authentication always grants your personal namespace, `io.github.<your-username>/*`.
|
|
|
|
To publish under an **organization** namespace (`io.github.<orgname>/*`), you must be an **Owner** of that organization. Ordinary org membership is no longer sufficient: the registry checks your membership role and only grants the org namespace to admins. This prevents anyone who merely belongs to an org from publishing — or overwriting — servers under the org's name.
|
|
|
|
If you authenticate with a Personal Access Token (for example in CI), the token must let the registry read your organization role. A token that can't will still publish to your personal namespace, but org publishing will be silently unavailable. The exact requirement depends on the token type:
|
|
|
|
- **Classic PAT**: grant the `read:org` scope.
|
|
- **Fine-grained PAT**: grant the **Organization permissions → Members → Read-only** permission (the fine-grained equivalent of `read:org`). Without it, GitHub returns no organization membership for the token and you'll get your personal namespace only. Note that a fine-grained PAT is bound to a single resource owner, so it can only see the organization it was created for.
|
|
|
|
Either way the token needs **no** repository scopes — the registry never reads or writes your code.
|
|
|
|
## DNS Authentication
|
|
|
|
DNS authentication is a domain-based authentication method that relies on a DNS TXT record.
|
|
|
|
<Warning>
|
|
The TXT record must be placed on the **apex** of your domain (e.g. `example.com`), **not** under a selector like `_mcp-auth.example.com` or `_mcp-registry.example.com`. MCP DNS auth follows SPF-style placement (apex), not DKIM-style (selector). If you put the record under a selector, the registry will not see it and authentication will fail with a generic signature error.
|
|
|
|
If you rotate keys, also remember to remove the previous TXT record from the apex — a stale record left behind will be tried first and cause verification to fail.
|
|
</Warning>
|
|
|
|
To perform DNS authentication using the `mcp-publisher` CLI tool, run the following commands in your server project directory to generate a TXT record based on a public/private key pair:
|
|
|
|
<Note>
|
|
The Ed25519 codepath requires **OpenSSL 3.0 or later**. macOS ships with LibreSSL by default (the system `openssl` binary), which does not implement Ed25519 in `genpkey` and fails with `Algorithm Ed25519 not found`. On macOS, install OpenSSL 3 (`brew install openssl@3`) and invoke it explicitly — for example, replace `openssl` with `/opt/homebrew/opt/openssl@3/bin/openssl` (Apple Silicon) or `/usr/local/opt/openssl@3/bin/openssl` (Intel) in the commands below. The ECDSA P-384 codepath works on LibreSSL.
|
|
</Note>
|
|
|
|
<CodeGroup>
|
|
|
|
```bash Ed25519
|
|
MY_DOMAIN="example.com"
|
|
|
|
# Generate public/private key pair using Ed25519
|
|
openssl genpkey -algorithm Ed25519 -out key.pem
|
|
|
|
# Generate TXT record
|
|
PUBLIC_KEY="$(openssl pkey -in key.pem -pubout -outform DER | tail -c 32 | base64)"
|
|
echo "${MY_DOMAIN}. IN TXT \"v=MCPv1; k=ed25519; p=${PUBLIC_KEY}\""
|
|
```
|
|
|
|
```bash ECDSA P-384
|
|
MY_DOMAIN="example.com"
|
|
|
|
# Generate public/private key pair using ECDSA P-384
|
|
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out key.pem
|
|
|
|
# Generate TXT record
|
|
PUBLIC_KEY="$(openssl ec -in key.pem -text -noout -conv_form compressed | grep -A4 "pub:" | tail -n +2 | tr -d ' :\n' | xxd -r -p | base64)"
|
|
echo "${MY_DOMAIN}. IN TXT \"v=MCPv1; k=ecdsap384; p=${PUBLIC_KEY}\""
|
|
```
|
|
|
|
```bash Google KMS
|
|
MY_DOMAIN="example.com"
|
|
MY_PROJECT="myproject"
|
|
MY_KEYRING="mykeyring"
|
|
MY_KEY_NAME="mykey"
|
|
|
|
# Log in using gcloud CLI (https://cloud.google.com/sdk/docs/install)
|
|
gcloud auth login
|
|
|
|
# Set default project
|
|
gcloud config set project "${MY_PROJECT}"
|
|
|
|
# Create a keyring in your project
|
|
gcloud kms keyrings create "${MY_KEYRING}" --location global
|
|
|
|
# Create an Ed25519 signing key
|
|
gcloud kms keys create "${MY_KEY_NAME}" --default-algorithm=ec-sign-ed25519 --purpose=asymmetric-signing --keyring="${MY_KEYRING}" --location=global
|
|
|
|
# Enable Application Default Credentials (ADC) so the publisher tool can sign
|
|
gcloud auth application-default login
|
|
|
|
# Attempt login to show the public key
|
|
mcp-publisher login dns google-kms --domain="${MY_DOMAIN}" --resource="projects/${MY_PROJECT}/locations/global/keyRings/${MY_KEYRING}/cryptoKeys/${MY_KEY_NAME}/cryptoKeyVersions/1"
|
|
|
|
# Copy the "Expected proof record":
|
|
# ${MY_DOMAIN}. IN TXT "v=MCPv1; k=ed25519; p=${PUBLIC_KEY}"
|
|
```
|
|
|
|
```bash Azure Key Vault
|
|
MY_DOMAIN="example.com"
|
|
MY_SUBSCRIPTION="subscription name or ID"
|
|
MY_RESOURCE_GROUP="MyResourceGroup"
|
|
MY_KEY_VAULT="MyKeyVault"
|
|
MY_KEY_NAME="MyKey"
|
|
|
|
# Log in using Azure CLI (https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)
|
|
az login
|
|
|
|
# Set default subscription
|
|
az account set --subscription "${MY_SUBSCRIPTION}"
|
|
|
|
# Create a resource group
|
|
az group create --location westus --resource-group "${MY_RESOURCE_GROUP}"
|
|
|
|
# Create a key vault
|
|
az keyvault create --name "${MY_KEY_VAULT}" --location westus --resource-group "${MY_RESOURCE_GROUP}"
|
|
|
|
# Create an ECDSA P-384 signing key
|
|
az keyvault key create --name "${MY_KEY_NAME}" --vault-name "${MY_KEY_VAULT}" --curve P-384
|
|
|
|
# Attempt login to show the public key
|
|
mcp-publisher login dns azure-key-vault --domain="${MY_DOMAIN}" --vault "${MY_KEY_VAULT}" --key "${MY_KEY_NAME}"
|
|
|
|
# Copy the "Expected proof record":
|
|
# ${MY_DOMAIN}. IN TXT "v=MCPv1; k=ecdsap384; p=${PUBLIC_KEY}"
|
|
```
|
|
|
|
</CodeGroup>
|
|
|
|
Then add the TXT record using your DNS provider's control panel. It may take several minutes for the TXT record to propagate. After the TXT record has propagated, log in using the `mcp-publisher login` command:
|
|
|
|
<CodeGroup>
|
|
|
|
```bash Ed25519
|
|
MY_DOMAIN="example.com"
|
|
|
|
PRIVATE_KEY="$(openssl pkey -in key.pem -noout -text | grep -A3 "priv:" | tail -n +2 | tr -d ' :\n')"
|
|
mcp-publisher login dns --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"
|
|
```
|
|
|
|
```bash ECDSA P-384
|
|
MY_DOMAIN="example.com"
|
|
|
|
PRIVATE_KEY="$(openssl ec -in key.pem -noout -text | grep -A4 "priv:" | tail -n +2 | tr -d ' :\n')"
|
|
mcp-publisher login dns --algorithm ecdsap384 --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"
|
|
```
|
|
|
|
```bash Google KMS
|
|
MY_DOMAIN="example.com"
|
|
MY_PROJECT="myproject"
|
|
MY_KEYRING="mykeyring"
|
|
MY_KEY_NAME="mykey"
|
|
|
|
mcp-publisher login dns google-kms --domain="${MY_DOMAIN}" --resource="projects/${MY_PROJECT}/locations/global/keyRings/${MY_KEYRING}/cryptoKeys/${MY_KEY_NAME}/cryptoKeyVersions/1"
|
|
```
|
|
|
|
```bash Azure Key Vault
|
|
MY_DOMAIN="example.com"
|
|
MY_KEY_VAULT="MyKeyVault"
|
|
MY_KEY_NAME="MyKey"
|
|
|
|
mcp-publisher login dns azure-key-vault --domain="${MY_DOMAIN}" --vault "${MY_KEY_VAULT}" --key "${MY_KEY_NAME}"
|
|
```
|
|
|
|
</CodeGroup>
|
|
|
|
## HTTP Authentication
|
|
|
|
HTTP authentication is a domain-based authentication method that relies on a `/.well-known/mcp-registry-auth` file hosted on your domain. For example, `https://example.com/.well-known/mcp-registry-auth`.
|
|
|
|
To perform HTTP authentication using the `mcp-publisher` CLI tool, run the following commands in your server project directory to generate an `mcp-registry-auth` file based on a public/private key pair:
|
|
|
|
<Note>
|
|
As with DNS authentication, the Ed25519 codepath requires **OpenSSL 3.0 or later**. macOS's system LibreSSL does not support Ed25519 in `genpkey`. See the note in the [DNS Authentication](#dns-authentication) section for the macOS workaround.
|
|
</Note>
|
|
|
|
<CodeGroup>
|
|
|
|
```bash Ed25519
|
|
# Generate public/private key pair using Ed25519
|
|
openssl genpkey -algorithm Ed25519 -out key.pem
|
|
|
|
# Generate mcp-registry-auth file
|
|
PUBLIC_KEY="$(openssl pkey -in key.pem -pubout -outform DER | tail -c 32 | base64)"
|
|
echo "v=MCPv1; k=ed25519; p=${PUBLIC_KEY}" > mcp-registry-auth
|
|
```
|
|
|
|
```bash ECDSA P-384
|
|
# Generate public/private key pair using ECDSA P-384
|
|
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out key.pem
|
|
|
|
# Generate mcp-registry-auth file
|
|
PUBLIC_KEY="$(openssl ec -in key.pem -text -noout -conv_form compressed | grep -A4 "pub:" | tail -n +2 | tr -d ' :\n' | xxd -r -p | base64)"
|
|
echo "v=MCPv1; k=ecdsap384; p=${PUBLIC_KEY}" > mcp-registry-auth
|
|
```
|
|
|
|
```bash Google KMS
|
|
MY_DOMAIN="example.com"
|
|
MY_PROJECT="myproject"
|
|
MY_KEYRING="mykeyring"
|
|
MY_KEY_NAME="mykey"
|
|
|
|
# Log in using gcloud CLI (https://cloud.google.com/sdk/docs/install)
|
|
gcloud auth login
|
|
|
|
# Set default project
|
|
gcloud config set project "${MY_PROJECT}"
|
|
|
|
# Create a keyring in your project
|
|
gcloud kms keyrings create "${MY_KEYRING}" --location global
|
|
|
|
# Create an Ed25519 signing key
|
|
gcloud kms keys create "${MY_KEY_NAME}" --default-algorithm=ec-sign-ed25519 --purpose=asymmetric-signing --keyring="${MY_KEYRING}" --location=global
|
|
|
|
# Enable Application Default Credentials (ADC) so the publisher tool can sign
|
|
gcloud auth application-default login
|
|
|
|
# Attempt login to show the public key
|
|
mcp-publisher login http google-kms --domain="${MY_DOMAIN}" --resource="projects/${MY_PROJECT}/locations/global/keyRings/${MY_KEYRING}/cryptoKeys/${MY_KEY_NAME}/cryptoKeyVersions/1"
|
|
|
|
# Copy the "Expected proof record" to `./mcp-registry-auth`:
|
|
# v=MCPv1; k=ed25519; p=${PUBLIC_KEY}
|
|
```
|
|
|
|
```bash Azure Key Vault
|
|
MY_DOMAIN="example.com"
|
|
MY_SUBSCRIPTION="subscription name or ID"
|
|
MY_RESOURCE_GROUP="MyResourceGroup"
|
|
MY_KEY_VAULT="MyKeyVault"
|
|
MY_KEY_NAME="MyKey"
|
|
|
|
# Log in using Azure CLI (https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)
|
|
az login
|
|
|
|
# Set default subscription
|
|
az account set --subscription "${MY_SUBSCRIPTION}"
|
|
|
|
# Create a resource group
|
|
az group create --location westus --resource-group "${MY_RESOURCE_GROUP}"
|
|
|
|
# Create a key vault
|
|
az keyvault create --name "${MY_KEY_VAULT}" --location westus --resource-group "${MY_RESOURCE_GROUP}"
|
|
|
|
# Create an ECDSA P-384 signing key
|
|
az keyvault key create --name "${MY_KEY_NAME}" --vault-name "${MY_KEY_VAULT}" --curve P-384
|
|
|
|
# Attempt login to show the public key
|
|
mcp-publisher login http azure-key-vault --domain="${MY_DOMAIN}" --vault "${MY_KEY_VAULT}" --key "${MY_KEY_NAME}"
|
|
|
|
# Copy the "Expected proof record" to `./mcp-registry-auth`:
|
|
# v=MCPv1; k=ecdsap384; p=${PUBLIC_KEY}
|
|
```
|
|
|
|
</CodeGroup>
|
|
|
|
Then host the `mcp-registry-auth` file at `/.well-known/mcp-registry-auth` on your domain. After the file is hosted, log in using the `mcp-publisher login` command:
|
|
|
|
<CodeGroup>
|
|
|
|
```bash Ed25519
|
|
MY_DOMAIN="example.com"
|
|
PRIVATE_KEY="$(openssl pkey -in key.pem -noout -text | grep -A3 "priv:" | tail -n +2 | tr -d ' :\n')"
|
|
mcp-publisher login http --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"
|
|
```
|
|
|
|
```bash ECDSA P-384
|
|
MY_DOMAIN="example.com"
|
|
PRIVATE_KEY="$(openssl ec -in key.pem -noout -text | grep -A4 "priv:" | tail -n +2 | tr -d ' :\n')"
|
|
mcp-publisher login http --algorithm ecdsap384 --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"
|
|
```
|
|
|
|
```bash Google KMS
|
|
MY_DOMAIN="example.com"
|
|
MY_PROJECT="myproject"
|
|
MY_KEYRING="mykeyring"
|
|
MY_KEY_NAME="mykey"
|
|
|
|
mcp-publisher login http google-kms --domain="${MY_DOMAIN}" --resource="projects/${MY_PROJECT}/locations/global/keyRings/${MY_KEYRING}/cryptoKeys/${MY_KEY_NAME}/cryptoKeyVersions/1"
|
|
```
|
|
|
|
```bash Azure Key Vault
|
|
MY_DOMAIN="example.com"
|
|
MY_KEY_VAULT="MyKeyVault"
|
|
MY_KEY_NAME="MyKey"
|
|
|
|
mcp-publisher login http azure-key-vault --domain="${MY_DOMAIN}" --vault "${MY_KEY_VAULT}" --key "${MY_KEY_NAME}"
|
|
```
|
|
|
|
</CodeGroup>
|