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>
177 lines
7.6 KiB
Go
177 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
|
|
"github.com/modelcontextprotocol/registry/cmd/publisher/commands"
|
|
)
|
|
|
|
// Version info for the MCP Publisher tool
|
|
// These variables are injected at build time via ldflags by goreleaser
|
|
var (
|
|
// Version is the current version of the MCP Publisher tool
|
|
Version = "dev"
|
|
|
|
// BuildTime is the time at which the binary was built
|
|
BuildTime = "unknown"
|
|
|
|
// GitCommit is the git commit that was compiled
|
|
GitCommit = "unknown"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
printUsage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
// 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") {
|
|
if printCommandHelp(os.Args[1]) {
|
|
return
|
|
}
|
|
}
|
|
|
|
var err error
|
|
switch os.Args[1] {
|
|
case "init":
|
|
err = commands.InitCommand()
|
|
case "login":
|
|
err = commands.LoginCommand(os.Args[2:])
|
|
case "logout":
|
|
err = commands.LogoutCommand()
|
|
case "publish":
|
|
err = commands.PublishCommand(os.Args[2:])
|
|
case "status":
|
|
err = commands.StatusCommand(os.Args[2:])
|
|
case "validate":
|
|
err = commands.ValidateCommand(os.Args[2:])
|
|
case "--version", "-v", "version":
|
|
log.Printf("mcp-publisher %s (commit: %s, built: %s)", Version, GitCommit, BuildTime)
|
|
return
|
|
case "--help", "-h", "help":
|
|
printUsage()
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "Unknown command: %s\n\n", os.Args[1])
|
|
printUsage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func printUsage() {
|
|
_, _ = fmt.Fprintln(os.Stdout, "MCP Registry Publisher Tool")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Usage:")
|
|
_, _ = fmt.Fprintln(os.Stdout, " mcp-publisher <command> [arguments]")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Commands:")
|
|
_, _ = fmt.Fprintln(os.Stdout, " init Create a server.json file template")
|
|
_, _ = fmt.Fprintln(os.Stdout, " login Authenticate with the registry")
|
|
_, _ = fmt.Fprintln(os.Stdout, " logout Clear saved authentication")
|
|
_, _ = fmt.Fprintln(os.Stdout, " publish Publish server.json to the registry")
|
|
_, _ = fmt.Fprintln(os.Stdout, " status Update the status of a server version")
|
|
_, _ = fmt.Fprintln(os.Stdout, " validate Validate server.json without publishing")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Use 'mcp-publisher <command> --help' for more information about a command.")
|
|
}
|
|
|
|
// 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")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Usage:")
|
|
_, _ = fmt.Fprintln(os.Stdout, " mcp-publisher init")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "This command creates a server.json file in the current directory with")
|
|
_, _ = fmt.Fprintln(os.Stdout, "auto-detected values from your project (package.json, git remote, etc.).")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "After running init, edit the generated server.json to customize your")
|
|
_, _ = fmt.Fprintln(os.Stdout, "server's metadata before publishing.")
|
|
|
|
case "login":
|
|
_, _ = fmt.Fprintln(os.Stdout, "Authenticate with the registry")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Usage:")
|
|
_, _ = fmt.Fprintln(os.Stdout, " mcp-publisher login <method> [options]")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Methods:")
|
|
_, _ = fmt.Fprintln(os.Stdout, " github Interactive GitHub authentication")
|
|
_, _ = fmt.Fprintln(os.Stdout, " github-oidc GitHub Actions OIDC authentication")
|
|
_, _ = fmt.Fprintln(os.Stdout, " dns DNS-based authentication (requires --domain)")
|
|
_, _ = fmt.Fprintln(os.Stdout, " http HTTP-based authentication (requires --domain)")
|
|
_, _ = fmt.Fprintln(os.Stdout, " none Anonymous authentication (for testing)")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Examples:")
|
|
_, _ = fmt.Fprintln(os.Stdout, " mcp-publisher login github")
|
|
_, _ = fmt.Fprintln(os.Stdout, " mcp-publisher login dns --domain example.com --private-key <key>")
|
|
|
|
case "logout":
|
|
_, _ = fmt.Fprintln(os.Stdout, "Clear saved authentication")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Usage:")
|
|
_, _ = fmt.Fprintln(os.Stdout, " mcp-publisher logout")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "This command removes the saved authentication token from your system.")
|
|
|
|
case "publish":
|
|
_, _ = fmt.Fprintln(os.Stdout, "Publish server.json to the registry")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Usage:")
|
|
_, _ = fmt.Fprintln(os.Stdout, " mcp-publisher publish [server.json]")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Arguments:")
|
|
_, _ = fmt.Fprintln(os.Stdout, " server.json Path to the server.json file (default: ./server.json)")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "You must be logged in before publishing. Run 'mcp-publisher login' first.")
|
|
|
|
case "status":
|
|
_, _ = fmt.Fprintln(os.Stdout, "Update the status of a server version")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Usage:")
|
|
_, _ = fmt.Fprintln(os.Stdout, " mcp-publisher status --status <active|deprecated|deleted> [flags] <server-name> [version]")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Flags (must come before positional arguments):")
|
|
_, _ = fmt.Fprintln(os.Stdout, " --status string New status: active, deprecated, or deleted (required)")
|
|
_, _ = fmt.Fprintln(os.Stdout, " --message string Optional message explaining the status change")
|
|
_, _ = fmt.Fprintln(os.Stdout, " --all-versions Apply status change to all versions of the server")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Arguments:")
|
|
_, _ = fmt.Fprintln(os.Stdout, " server-name Full server name (e.g., io.github.user/my-server)")
|
|
_, _ = fmt.Fprintln(os.Stdout, " version Server version to update (required unless --all-versions is set)")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "Examples:")
|
|
_, _ = fmt.Fprintln(os.Stdout, " # Deprecate a specific version")
|
|
_, _ = fmt.Fprintln(os.Stdout, " mcp-publisher status --status deprecated --message \"Please upgrade to 2.0.0\" \\")
|
|
_, _ = fmt.Fprintln(os.Stdout, " io.github.user/my-server 1.0.0")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, " # Delete a version with security issues")
|
|
_, _ = fmt.Fprintln(os.Stdout, " mcp-publisher status --status deleted --message \"Critical security vulnerability\" \\")
|
|
_, _ = fmt.Fprintln(os.Stdout, " io.github.user/my-server 1.0.0")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, " # Restore a version to active")
|
|
_, _ = fmt.Fprintln(os.Stdout, " mcp-publisher status --status active io.github.user/my-server 1.0.0")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, " # Deprecate all versions")
|
|
_, _ = fmt.Fprintln(os.Stdout, " mcp-publisher status --status deprecated --all-versions --message \"Project archived\" \\")
|
|
_, _ = fmt.Fprintln(os.Stdout, " io.github.user/my-server")
|
|
_, _ = fmt.Fprintln(os.Stdout)
|
|
_, _ = fmt.Fprintln(os.Stdout, "You must be logged in before updating status. Run 'mcp-publisher login' first.")
|
|
|
|
default:
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|