Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion cmd/publisher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@ 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
- **`init`** - Generate server.json templates with auto-detection
- **`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
Expand Down
18 changes: 12 additions & 6 deletions cmd/publisher/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -81,7 +83,10 @@ func printUsage() {
_, _ = fmt.Fprintln(os.Stdout, "Use 'mcp-publisher <command> --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")
Expand Down Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions docs/modelcontextprotocol-io/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions docs/modelcontextprotocol-io/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <command> --help' for more information about a command.
```

## Step 4: Create `server.json`
Expand Down
30 changes: 22 additions & 8 deletions docs/reference/cli/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 <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)
Expand Down Expand Up @@ -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
Expand All @@ -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`.
Expand Down
Loading