Skip to content

Latest commit

 

History

History
375 lines (282 loc) · 12.5 KB

File metadata and controls

375 lines (282 loc) · 12.5 KB

Publisher CLI Commands Reference

Complete command reference for the mcp-publisher CLI tool.

See the publishing guide for a walkthrough of using the CLI to publish a server.

Installation

Install via Homebrew (macOS/Linux):

$ 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:

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:

{
  "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

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 authenticates without a browser. This flag is accepted by login github only.

Interactive login handles retryable OAuth errors in both HTTP 200 and HTTP 400 responses. Access denial, expired codes, and unrecognized OAuth errors still stop polling; an incorrect_device_code error receives at most two additional attempts per login.

GitHub OIDC (CI/CD)

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.

DNS Verification

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)

# 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)

# 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.

# 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.

# 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

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)

# 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)

# 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)

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:

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:

$ 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.

Usage:

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)
  4. Server: Checks namespace authentication
  5. Server: Publishes to registry

Example:

# 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:

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:

# 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:

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:

{
  "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.