registry/docs/reference/cli/commands.md
Bob Dickinson fc88cf0870
Enhanced server.json validation (phase 1) (#636)
Added schema validation and support for exhaustive and more detailed
validation to existing validators. Added new `mcp-publisher validate`
command. No change to any existing tests or behavior other than the new
command.

## Motivation and Context

Many currently published servers fail schema validation. Of those that
pass, many have semantic/logical errors or other errors that make them
impossible to configure, or their configuration when applied doesn't
generate correct MCP server configuration (per their own documentation).

To address this, we need better tooling to validate servers and to
inform server publishers of errors, issues, and best practices in a way
that is clear and actionable, both during server development and at time
of publishing.

I have started a discussion about the broader topic:
https://github.com/modelcontextprotocol/registry/discussions/635

There is also a fairly detailed document describing the design,
architecture, implementation, future plans, etc. See [Enhanced
Validation Design](docs/explanations/proposed-enhanced-validation.md)

This PR is the first step in the process of improving validation. It
adds schema validation and updates all existing validators to use a new
model that allows them to track context and return rich and exhaustive
results. This has been done in a way that is backward compatible
(ValidateServerJSON is unchanged as are all existing validation unit
tests).

There is a new `mcp-publisher` command called `validate` that is
currenty the only place that schema validation and enhanced
(exhaustive/rich) validation results are exposed.

### Changes

Internal Validation improvements
- Schema validation has been implemented and is available as an option
in validation methods (defaults to off)
- Existing validators track the JSON path (context) of the attribute
they're evaluating, passing it to any child validators
- Existing validators are exhaustive (return all issues, not failing
fast on the first error)
- Existing validators return rich validation results, including:
  - type (schema, semantic, linter)
  - severity (error, warn, info)
  - path (JSON path to server.json element)
  - reference (to the schema element or rule that triggered the result)
  - message (currently the same as previous error message)

A new `validate` command has been added to `mcp-publisher` so publishers
can evaluate their server.json before publishing
- Includes new schema validation and previous semantic validation
- Shows full details of all issues encountered during validation

### Usage

Given a server.json that looks like this:

```json
{
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
  "name": "io.github.BobDickinson/registry",
  "description": "An MCP server that provides [describe what your server does]",
  "repository": {
    "url": "",
    "source": ""
  },
  "version": "=1.0.0",
  "packages": [
    {
      "registryType": "oci",
      "registryBaseUrl": "https://docker.io",
      "identifier": "registry",
      "version": "1.0.0",
      "transport": {
        "type": "sse"
      },
      "packageArguments": [
        {
          "type": "named",
          "name": "--mode",
          "description": "Operation mode",
          "format": "foo"
        }
      ],
      "environmentVariables": [
        {
          "description": "Your API key for the service",
          "isRequired": true,
          "format": "string",
          "isSecret": true,
          "name": "YOUR_API_KEY"
        }
      ]
    }
  ]
}
```

Run the command: 

`mcp-publisher validate server.json`

Which will produce the output:

```bash
❌ Validation failed with 5 issue(s):

1. [error] packages.0.packageArguments.0.format (schema)
   value must be one of "string", "number", "boolean", "filepath"
   Reference: #/definitions/Input/properties/format/enum from: [#/definitions/ServerDetail]/properties/packages/items/[#/definitions/Package]/properties/packageArguments/items/[#/definitions/Argument]/else/[#/definitions/NamedArgument]/allOf/0/[#/definitions/InputWithVariables]/allOf/0/[#/definitions/Input]/properties/format/enum

2. [error] packages.0.transport (schema)
   missing required fields: 'url'
   Reference: #/definitions/SseTransport/required from: [#/definitions/ServerDetail]/properties/packages/items/[#/definitions/Package]/properties/transport/else/else/[#/definitions/SseTransport]/required

3. [error] repository.url (schema)
   '' has invalid format 'uri'
   Reference: #/definitions/Repository/properties/url/format from: [#/definitions/ServerDetail]/properties/repository/[#/definitions/Repository]/properties/url/format

4. [error] version (semantic)
   version must be a specific version, not a range: "=1.0.0"
   Reference: version-looks-like-range

5. [error] packages[0].transport.url (semantic)
   url is required for sse transport type
   Reference: streamable-transport-url-required

Error: validation failed
```

Note in the results above:
- All errors contain the JSON path of the triggering element from
server.json.
- The first three errors are schema errors detected during schema
validation. The `Reference` provided contains an absolute path to the
schema rule that was violated, followed by the full schema path that
enforced that rule (with $ref redirect values substited and enclosed in
square brackets).
- The error messages provided by the existing semantic validators are
exactly the same as the previous error messages those validators
produced.

In the final solution we would remove semantic validators that are
redundant to schema validation (as in number 5 above). We have the
option to use the structured data produced by schema validation to
replace the generic schema validation message with a more descriptive
message if that is an issue (in particular, if the only argument for an
ad-hoc validator is that is produces a better/cleaner message, we would
just move that message creation code to the schema error result
formatter and special case it as opposed to having a redundant
validator).

### What's Next

If this general direction is supported and this PR is accepted, a fast
follow would include:

- Add new validators (infliuenced by observed issues) for both semantic
errors and best practices
- Require passing schema validation (in addition to improved semantic
validation) in order to publish
- Remove existing validation code that is redundant to schema validation
(ensuring error messages remain intelligible)
- Update validation client code and tests to handle the new rich return
types natively

### Issues

Schema validation requires embedding the `server.schema.json` file into
the validators package via `go:embed`, which is restricted from
embedding files outside of the package. For this reason we copy the
schema file into a schema subdir (via a prep target in the makefile) and
we .gitignore it. I tried cleaning up the copy in the make clean target,
but then the linter would complain about the embed target being missing
if it wasn't there during development. I also considered just checking
in the schema file and assuming a separate workflow for updating the
embedded schema file, but it's not clear what that workflow would be or
how it would be maintained. I'm open to a different approach, but the
schema file does need to be embedded somehow for schema validation to
work.

## How Has This Been Tested?
All existing validation tests pass, new tests implemented and all pass.

## Breaking Changes
No breaking changes, no behavior change except new `validate` command

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [X] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [X] My code follows the repository's style guidelines
- [X] New and existing tests pass locally
- [X] I have added appropriate error handling
- [X] I have added or updated documentation as needed

---------

Co-authored-by: GitHub Action <action@github.com>
2026-01-16 00:49:39 +02:00

8.7 KiB

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 - Registry URL (default: https://registry.modelcontextprotocol.io)

Commands

mcp-publisher init

Generate a server.json template with automatic detection.

Usage:

mcp-publisher init [options]

Behavior:

  • Creates server.json in current directory
  • Auto-detects package managers (package.json, setup.py, etc.)
  • Pre-fills fields where possible
  • Prompts for missing required fields

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 [--registry=URL]
  • Opens browser for GitHub OAuth flow
  • Grants access to io.github.{username}/* and io.github.{org}/* namespaces

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

Also see the guide to publishing from GitHub Actions.

DNS Verification

mcp-publisher login dns --domain=example.com --private-key=HEX_KEY [--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)
    • The private key can be stored in a cloud signing provider like Google KMS or Azure Key Vault.

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'

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 [--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)
    • The private key can be stored in a cloud signing provider like Google KMS or Azure Key Vault.

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

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 logout

Clear stored authentication credentials.

Usage:

mcp-publisher logout

Behavior:

  • Removes ~/.mcp_publisher_token
  • Does not revoke tokens on server side

Configuration

Token Storage

Authentication tokens stored in ~/.mcp_publisher_token as JSON:

{
  "token": "jwt-token-here",
  "registry_url": "https://registry.modelcontextprotocol.io",
  "expires_at": "2024-12-31T23:59:59Z"
}