From 43402d8f6fc728dc027be96b7218fcb2495517dc Mon Sep 17 00:00:00 2001 From: Radoslav Dimitrov Date: Mon, 10 Aug 2026 16:25:00 +0300 Subject: [PATCH] fix(publisher): document --algorithm for ECDSA login and fix validate --help (#1519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --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 --- cmd/publisher/README.md | 6 +++- cmd/publisher/main.go | 18 +++++++---- .../authentication.mdx | 4 +-- docs/modelcontextprotocol-io/quickstart.mdx | 4 +++ docs/reference/cli/commands.md | 30 ++++++++++++++----- 5 files changed, 45 insertions(+), 17 deletions(-) diff --git a/cmd/publisher/README.md b/cmd/publisher/README.md index 235d189..3a5a792 100644 --- a/cmd/publisher/README.md +++ b/cmd/publisher/README.md @@ -14,9 +14,12 @@ make publisher make dev-compose # Start local registry ./bin/mcp-publisher init ./bin/mcp-publisher login none --registry=http://localhost:8080 -./bin/mcp-publisher publish --registry=http://localhost:8080 +./bin/mcp-publisher publish ``` +`publish` takes the registry URL from the token saved by `login`, so it accepts no `--registry` +flag — passing one would be read as the `server.json` path. + ## Architecture ### Commands @@ -24,6 +27,7 @@ make dev-compose # Start local registry - **`login`** - Handle authentication (github, dns, http, none) - **`publish`** - Validate and upload servers to registry - **`status`** - Update server lifecycle status (active, deprecated, deleted) +- **`validate`** - Validate server.json without publishing - **`logout`** - Clear stored credentials ### Authentication Providers diff --git a/cmd/publisher/main.go b/cmd/publisher/main.go index 20cea2b..8f7de09 100644 --- a/cmd/publisher/main.go +++ b/cmd/publisher/main.go @@ -27,10 +27,12 @@ func main() { os.Exit(1) } - // Check for help flag for subcommands + // Check for help flag for subcommands. Commands without an entry in + // printCommandHelp handle --help themselves, so fall through to dispatch. if len(os.Args) >= 3 && (os.Args[2] == "--help" || os.Args[2] == "-h") { - printCommandHelp(os.Args[1]) - return + if printCommandHelp(os.Args[1]) { + return + } } var err error @@ -81,7 +83,10 @@ func printUsage() { _, _ = fmt.Fprintln(os.Stdout, "Use 'mcp-publisher --help' for more information about a command.") } -func printCommandHelp(command string) { +// printCommandHelp prints help for a specific command and reports whether it had +// an entry here. Commands without one parse --help themselves, so the caller +// should fall through to normal dispatch when this returns false. +func printCommandHelp(command string) bool { switch command { case "init": _, _ = fmt.Fprintln(os.Stdout, "Create a server.json file template") @@ -165,7 +170,8 @@ func printCommandHelp(command string) { _, _ = fmt.Fprintln(os.Stdout, "You must be logged in before updating status. Run 'mcp-publisher login' first.") default: - fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command) - printUsage() + return false } + + return true } diff --git a/docs/modelcontextprotocol-io/authentication.mdx b/docs/modelcontextprotocol-io/authentication.mdx index 9812fcc..59d7218 100644 --- a/docs/modelcontextprotocol-io/authentication.mdx +++ b/docs/modelcontextprotocol-io/authentication.mdx @@ -174,7 +174,7 @@ mcp-publisher login dns --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}" 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 --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}" +mcp-publisher login dns --algorithm ecdsap384 --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}" ``` ```bash Google KMS @@ -298,7 +298,7 @@ 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 --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}" +mcp-publisher login http --algorithm ecdsap384 --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}" ``` ```bash Google KMS diff --git a/docs/modelcontextprotocol-io/quickstart.mdx b/docs/modelcontextprotocol-io/quickstart.mdx index 41bc8fc..6988689 100644 --- a/docs/modelcontextprotocol-io/quickstart.mdx +++ b/docs/modelcontextprotocol-io/quickstart.mdx @@ -135,6 +135,10 @@ Commands: login Authenticate with the registry logout Clear saved authentication publish Publish server.json to the registry + status Update the status of a server version + validate Validate server.json without publishing + +Use 'mcp-publisher --help' for more information about a command. ``` ## Step 4: Create `server.json` diff --git a/docs/reference/cli/commands.md b/docs/reference/cli/commands.md index e077019..c8d7615 100644 --- a/docs/reference/cli/commands.md +++ b/docs/reference/cli/commands.md @@ -16,7 +16,10 @@ $ brew install mcp-publisher All commands support: - `--help`, `-h` - Show command help -- `--registry` - Registry URL (default: `https://registry.modelcontextprotocol.io`) + +`--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 @@ -26,14 +29,14 @@ Generate a `server.json` template with automatic detection. **Usage:** ```bash -mcp-publisher init [options] +mcp-publisher init ``` **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 +- Writes `TODO:` placeholders for fields it cannot detect — it is non-interactive and takes no flags **Example output:** ```json @@ -59,10 +62,13 @@ Authenticate with the registry. #### GitHub Interactive ```bash -mcp-publisher login github [--registry=URL] +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 @@ -83,12 +89,13 @@ Also see [the guide to publishing from GitHub Actions](../../modelcontextprotoco #### DNS Verification ```bash -mcp-publisher login dns --domain=example.com --private-key=HEX_KEY [--registry=URL] +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) - - The private key can be stored in a cloud signing provider like Google KMS or Azure Key Vault. + - `--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 @@ -118,6 +125,9 @@ openssl ec -in key.pem -text -noout -conv_form compressed | grep -A4 "pub:" | ta # Extract private key for login openssl ec -in -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) @@ -178,12 +188,13 @@ mcp-publisher login dns azure-key-vault --domain=example.com --vault MyKeyVault #### HTTP Verification ```bash -mcp-publisher login http --domain=example.com --private-key=HEX_KEY [--registry=URL] +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) - - The private key can be stored in a cloud signing provider like Google KMS or Azure Key Vault. + - `--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 @@ -203,6 +214,9 @@ 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`.