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>
371 lines
12 KiB
Markdown
371 lines
12 KiB
Markdown
# Publisher CLI Commands Reference
|
|
|
|
Complete command reference for the `mcp-publisher` CLI tool.
|
|
|
|
See the [publishing guide](../../modelcontextprotocol-io/quickstart.mdx) for a walkthrough of using the CLI to publish a server.
|
|
|
|
## Installation
|
|
|
|
Install via Homebrew (macOS/Linux):
|
|
|
|
```bash
|
|
$ brew install mcp-publisher
|
|
```
|
|
|
|
## Global Options
|
|
|
|
All commands support:
|
|
- `--help`, `-h` - Show command help
|
|
|
|
`--registry` is a flag on `login` only (default: `https://registry.modelcontextprotocol.io`). The
|
|
other commands read the registry URL from the stored login token, so passing `--registry` to
|
|
`publish` would be interpreted as the `server.json` path.
|
|
|
|
## Commands
|
|
|
|
### `mcp-publisher init`
|
|
|
|
Generate a `server.json` template with automatic detection.
|
|
|
|
**Usage:**
|
|
```bash
|
|
mcp-publisher init
|
|
```
|
|
|
|
**Behavior:**
|
|
- Creates `server.json` in current directory
|
|
- Auto-detects package managers (`package.json`, `setup.py`, etc.)
|
|
- Pre-fills fields where possible
|
|
- Writes `TODO:` placeholders for fields it cannot detect — it is non-interactive and takes no flags
|
|
|
|
**Example output:**
|
|
```json
|
|
{
|
|
"name": "io.github.username/server-name",
|
|
"description": "TODO: Add server description",
|
|
"version": "1.0.0",
|
|
"packages": [
|
|
{
|
|
"registryType": "npm",
|
|
"identifier": "detected-package-name",
|
|
"version": "1.0.0"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### `mcp-publisher login <method>`
|
|
|
|
Authenticate with the registry.
|
|
|
|
**Authentication Methods:**
|
|
|
|
#### GitHub Interactive
|
|
```bash
|
|
mcp-publisher login github [--token=PAT] [--registry=URL]
|
|
```
|
|
- Opens browser for GitHub OAuth flow
|
|
- Grants access to `io.github.{username}/*` and `io.github.{org}/*` namespaces
|
|
- `--token` supplies a GitHub Personal Access Token instead of the interactive flow, which is how
|
|
[publishing from GitHub Actions](../../modelcontextprotocol-io/github-actions.mdx) authenticates
|
|
without a browser. This flag is accepted by `login github` only.
|
|
|
|
#### GitHub OIDC (CI/CD)
|
|
```bash
|
|
mcp-publisher login github-oidc [--registry=URL]
|
|
```
|
|
- Uses GitHub Actions OIDC tokens automatically
|
|
- Requires `id-token: write` permission in workflow
|
|
- No browser interaction needed
|
|
|
|
The CLI derives the OIDC `aud` claim from `--registry` (scheme + host, e.g.
|
|
`https://registry.modelcontextprotocol.io`) so tokens are bound to the specific
|
|
deployment they were minted for. Self-hosters must set
|
|
`MCP_REGISTRY_GITHUB_OIDC_AUDIENCE` on the registry to the matching value;
|
|
publishers running an older `mcp-publisher` will fail with `invalid audience`
|
|
and need to upgrade.
|
|
|
|
Also see [the guide to publishing from GitHub Actions](../../modelcontextprotocol-io/github-actions.mdx).
|
|
|
|
#### DNS Verification
|
|
```bash
|
|
mcp-publisher login dns --domain=example.com --private-key=HEX_KEY [--algorithm=ed25519|ecdsap384] [--registry=URL]
|
|
```
|
|
- Verifies domain ownership via DNS TXT record
|
|
- Grants access to `com.example.*` namespaces
|
|
- Requires Ed25519 private key (64-character hex) or ECDSA P-384 private key (96-character hex)
|
|
- `--algorithm` defaults to `ed25519`. **For an ECDSA P-384 key you must pass `--algorithm ecdsap384`**, otherwise the key is rejected with `invalid seed length: expected 32 bytes, got 48`.
|
|
- The private key can be stored in a cloud signing provider like Google KMS or Azure Key Vault. Cloud providers derive the algorithm from the key itself, so `--algorithm` does not apply to them.
|
|
|
|
**Setup:** (for Ed25519, recommended)
|
|
```bash
|
|
# Generate keypair
|
|
openssl genpkey -algorithm Ed25519 -out key.pem
|
|
|
|
# Get public key for DNS record
|
|
openssl pkey -in key.pem -pubout -outform DER | tail -c 32 | base64
|
|
|
|
# Add DNS TXT record:
|
|
# example.com. IN TXT "v=MCPv1; k=ed25519; p=PUBLIC_KEY"
|
|
|
|
# Extract private key for login
|
|
openssl pkey -in key.pem -noout -text | grep -A3 "priv:" | tail -n +2 | tr -d ' :\n'
|
|
```
|
|
|
|
**Setup:** (for ECDSA P-384)
|
|
```bash
|
|
# Generate keypair
|
|
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out key.pem
|
|
|
|
# Get public key for DNS record
|
|
openssl ec -in key.pem -text -noout -conv_form compressed | grep -A4 "pub:" | tail -n +2 | tr -d ' :\n' | xxd -r -p | base64
|
|
|
|
# Add DNS TXT record:
|
|
# example.com. IN TXT "v=MCPv1; k=ecdsap384; p=PUBLIC_KEY"
|
|
|
|
# Extract private key for login
|
|
openssl ec -in <pem path> -noout -text | grep -A4 "priv:" | tail -n +2 | tr -d ' :\n'
|
|
|
|
# Log in, selecting the ECDSA P-384 algorithm explicitly
|
|
mcp-publisher login dns --algorithm ecdsap384 --domain=example.com --private-key=HEX_KEY
|
|
```
|
|
|
|
**Setup:** (for Google KMS signing)
|
|
|
|
This requires the [gcloud CLI](https://cloud.google.com/sdk/docs/install).
|
|
|
|
```bash
|
|
# log in and set default project
|
|
gcloud auth login
|
|
gcloud config set project myproject
|
|
|
|
# Create a keyring in your project
|
|
gcloud kms keyrings create mykeyring --location global
|
|
|
|
# Create an Ed25519 signing key
|
|
gcloud kms keys create mykey --default-algorithm=ec-sign-ed25519 --purpose=asymmetric-signing --keyring=mykeyring --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=example.com --resource=projects/myproject/locations/global/keyRings/mykeyring/cryptoKeys/mykey/cryptoKeyVersions/1
|
|
|
|
# Copy the "Expected proof record" and add the TXT record
|
|
# example.com. IN TXT "v=MCPv1; k=ed25519; p=PUBLIC_KEY"
|
|
|
|
# Re-run the login command
|
|
mcp-publisher login dns google-kms --domain=example.com --resource=projects/myproject/locations/global/keyRings/mykeyring/cryptoKeys/mykey/cryptoKeyVersions/1
|
|
```
|
|
|
|
**Setup:** (for Azure Key Vault signing)
|
|
|
|
This requires the [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest).
|
|
|
|
```bash
|
|
# log in and set default subscription
|
|
az login
|
|
az account set --subscription "My Subscription (name or ID)"
|
|
|
|
# Create a resource group
|
|
az group create --location westus --resource-group MyResourceGroup
|
|
|
|
# Create a Key Vault
|
|
az keyvault create --name MyKeyVault --location westus --resource-group MyResourceGroup
|
|
|
|
# Create an ECDSA P-384 signing key
|
|
az keyvault key create --name MyKey --vault-name MyKeyVault --curve P-384
|
|
|
|
# Attempt login to show the public key
|
|
mcp-publisher login dns azure-key-vault --domain=example.com --vault MyKeyVault --key MyKey
|
|
|
|
# Copy the "Expected proof record" and add the TXT record
|
|
# example.com. IN TXT "v=MCPv1; k=ecdsap384; p=PUBLIC_KEY"
|
|
|
|
# Re-run the login command
|
|
mcp-publisher login dns azure-key-vault --domain=example.com --vault MyKeyVault --key MyKey
|
|
```
|
|
|
|
#### HTTP Verification
|
|
```bash
|
|
mcp-publisher login http --domain=example.com --private-key=HEX_KEY [--algorithm=ed25519|ecdsap384] [--registry=URL]
|
|
```
|
|
- Verifies domain ownership via HTTPS endpoint
|
|
- Grants access to `com.example.*` namespaces
|
|
- Requires Ed25519 private key (64-character hex) or ECDSA P-384 private key (96-character hex)
|
|
- `--algorithm` defaults to `ed25519`. **For an ECDSA P-384 key you must pass `--algorithm ecdsap384`**.
|
|
- The private key can be stored in a cloud signing provider like Google KMS or Azure Key Vault. Cloud providers derive the algorithm from the key itself, so `--algorithm` does not apply to them.
|
|
|
|
**Setup:** (for Ed25519, recommended)
|
|
```bash
|
|
# Generate keypair (same as DNS)
|
|
openssl genpkey -algorithm Ed25519 -out key.pem
|
|
|
|
# Host public key at:
|
|
# https://example.com/.well-known/mcp-registry-auth
|
|
# Content: v=MCPv1; k=ed25519; p=PUBLIC_KEY
|
|
```
|
|
|
|
**Setup:** (for ECDSA P-384)
|
|
```bash
|
|
# Generate keypair (same as DNS)
|
|
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out key.pem
|
|
|
|
# Host public key at:
|
|
# https://example.com/.well-known/mcp-registry-auth
|
|
# Content: v=MCPv1; k=ecdsap384; p=PUBLIC_KEY
|
|
|
|
# Log in, selecting the ECDSA P-384 algorithm explicitly
|
|
mcp-publisher login http --algorithm ecdsap384 --domain=example.com --private-key=HEX_KEY
|
|
```
|
|
|
|
Cloud signing is also supported for HTTP authentication, similar to the DNS examples above. Just swap out the `dns` positional argument for `http`.
|
|
|
|
#### Anonymous (Testing)
|
|
```bash
|
|
mcp-publisher login none [--registry=URL]
|
|
```
|
|
- No authentication - for local testing only
|
|
- Only works with local registry instances
|
|
|
|
### `mcp-publisher validate`
|
|
|
|
Validate a `server.json` file without publishing.
|
|
|
|
**Usage:**
|
|
```bash
|
|
mcp-publisher validate [file]
|
|
```
|
|
|
|
**Arguments:**
|
|
- `file` - Path to server.json file (default: `./server.json`)
|
|
|
|
**Behavior:**
|
|
- Performs exhaustive validation, reporting all issues at once (not just the first error)
|
|
- Validates JSON syntax and schema compliance
|
|
- Runs semantic validation (business logic checks)
|
|
- Checks for deprecated schema versions and provides migration guidance
|
|
- Includes detailed error locations with JSON paths (e.g., `packages[0].transport.url`)
|
|
- Shows validation issue type (json, schema, semantic, linter)
|
|
- Displays severity level (error, warning, info)
|
|
- Provides schema references showing which validation rule triggered each error
|
|
|
|
**Example output:**
|
|
```bash
|
|
$ mcp-publisher validate
|
|
✅ server.json is valid
|
|
|
|
$ mcp-publisher validate custom-server.json
|
|
❌ Validation failed with 2 issue(s):
|
|
|
|
1. [error] repository.url (schema)
|
|
'' has invalid format 'uri'
|
|
Reference: #/definitions/Repository/properties/url/format from: [#/definitions/ServerDetail]/properties/repository/[#/definitions/Repository]/properties/url/format
|
|
|
|
2. [error] name (semantic)
|
|
server name must be in format 'dns-namespace/name'
|
|
Reference: invalid-server-name
|
|
```
|
|
|
|
### `mcp-publisher publish`
|
|
|
|
Publish server to the registry.
|
|
|
|
For detailed guidance on the publishing process, see the [publishing guide](../../modelcontextprotocol-io/quickstart.mdx).
|
|
|
|
**Usage:**
|
|
```bash
|
|
mcp-publisher publish [PATH]
|
|
```
|
|
|
|
**Options:**
|
|
- `PATH` - Path to server.json (default: `./server.json`)
|
|
|
|
**Process:**
|
|
1. Validates `server.json` against schema
|
|
2. Publishes the `server.json` to the registry server URL specified in the login token
|
|
3. Server: Verifies package ownership (see [Official Registry Requirements](../server-json/official-registry-requirements.md))
|
|
4. Server: Checks namespace authentication
|
|
5. Server: Publishes to registry
|
|
|
|
**Example:**
|
|
```bash
|
|
# Basic publish
|
|
mcp-publisher publish
|
|
|
|
# Custom file location
|
|
mcp-publisher publish ./config/server.json
|
|
```
|
|
|
|
### `mcp-publisher status`
|
|
|
|
Update the lifecycle status of a published server.
|
|
|
|
**Usage:**
|
|
```bash
|
|
mcp-publisher status --status <active|deprecated|deleted> [flags] <server-name> [version]
|
|
```
|
|
|
|
**Flags:**
|
|
- `--status` (required) - New status: `active`, `deprecated`, or `deleted`
|
|
- `--message` - Optional message explaining the status change (not allowed when status is `active`)
|
|
- `--all-versions` - Apply status change to all versions of the server
|
|
- `--yes`, `-y` - Skip confirmation prompt (only applies when using `--all-versions`)
|
|
|
|
**Arguments:**
|
|
- `server-name` - Full server name (e.g., `io.github.user/my-server`)
|
|
- `version` - Server version to update (required unless `--all-versions` is set)
|
|
|
|
**Status Values:**
|
|
- `active` - Server is active and visible in default listings
|
|
- `deprecated` - Server is deprecated but still visible with a warning message
|
|
- `deleted` - Server is hidden from default listings
|
|
|
|
**Examples:**
|
|
```bash
|
|
# Deprecate a specific version
|
|
mcp-publisher status --status deprecated --message "Please upgrade to 2.0.0" \
|
|
io.github.user/my-server 1.0.0
|
|
|
|
# Delete a version with security issues
|
|
mcp-publisher status --status deleted --message "Critical security vulnerability" \
|
|
io.github.user/my-server 1.0.0
|
|
|
|
# Restore a version to active
|
|
mcp-publisher status --status active io.github.user/my-server 1.0.0
|
|
|
|
# Deprecate all versions at once
|
|
mcp-publisher status --status deprecated --all-versions --message "Project archived" \
|
|
io.github.user/my-server
|
|
```
|
|
|
|
**Requirements:**
|
|
- Must be logged in with `publish` or `edit` permission for the server namespace
|
|
|
|
### `mcp-publisher logout`
|
|
|
|
Clear stored authentication credentials.
|
|
|
|
**Usage:**
|
|
```bash
|
|
mcp-publisher logout
|
|
```
|
|
|
|
**Behavior:**
|
|
- Removes `~/.config/mcp-publisher/token.json`
|
|
- Also cleans up legacy token files (`~/.mcp_publisher_token`, `.mcpregistry_*`)
|
|
- Does not revoke tokens on server side
|
|
|
|
## Configuration
|
|
|
|
### Token Storage
|
|
Authentication tokens are stored in `~/.config/mcp-publisher/token.json` as JSON:
|
|
```json
|
|
{
|
|
"token": "jwt-token-here",
|
|
"method": "github",
|
|
"registry": "https://registry.modelcontextprotocol.io"
|
|
}
|
|
```
|
|
|
|
> **Note:** Tokens were previously stored in `~/.mcp_publisher_token`. If you are upgrading, run `mcp-publisher logout` followed by `mcp-publisher login` to migrate to the new location.
|