Follow-up to my analysis on #1543.
Heads up on the shape first: this builds on #1290's poll-loop seams and
github wont let me target a fork branch, so the diff shows its two
commits too. Review only the last commit, the first two are #1290
unchanged, credit to @developer-ishan. Happy to rebase whichever way
#1290 goes, or split differently if that's easier to review.
What it does. pollForToken treats every non-pending answer as terminal,
so one flaky response kills a login the user may already have approved
in the browser. This spends a small grace budget (2 polls) on
incorrect_device_code before giving up, retries 5xx with a growing
backoff, and returns status plus body on other non-200s instead of dying
inside json.Unmarshal on an html error page. Terminal errors now carry
error_description and error_uri, and a timeout that happens after
polling recovered reports a plain timeout, not a stale error from
minutes earlier.
Also honours the interval and expires_in GitHub returns with the device
code (RFC 8628 3.2) instead of hardcoding 5/900, clamped (60s / 3600s)
so a misbehaving response can't stall the CLI or overflow the duration
math.
One thing I changed my mind on from the issue comment: I wrote there
that the client should wait one interval before the first poll. Dropped
that. My own repro test showed 40s of clean polling before authorize, so
the propagation theory doesn't hold, and the grace retry covers the same
window without taxing every fast login. First poll stays immediate,
pinned by a test.
Root cause note: I can't prove the GitHub-side race (cli/cli#9302 is the
same error class but was closed on an unconfirmed "platform race"). So
this isn't claimed as a root-cause fix, it makes the login recoverable
if the condition is transient and diagnosable if it isn't. Btw
incorrect_device_code is a GitHub extension, not an RFC 8628 3.5 code,
so there's no spec retry semantic being violated by the grace polls.
## Test
12 new tests in github_at_device_flow_internal_test.go, all green with
-race (33 in the package). #1290's four tests pass unmodified.
golangci-lint clean in the touched files under both v2.11.4 (this
branch's CI pin) and v2.13.1 (what main pins now).
## AI Disclosure
AI assistance (Claude) was used for issue research and during
development. All changes were reviewed and tested by the author.
Fixes#1543
---------
Co-authored-by: developer-ishan <igupta@groupon.com>
Co-authored-by: Ishan Gupta <62565307+developer-ishan@users.noreply.github.com>
Co-authored-by: developer-ishan <guptaishan849@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Radoslav Dimitrov <radoslav@stacklok.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
## Summary
- Handle GitHub's OAuth device-flow `slow_down` response per [RFC 8628
§3.5](https://datatracker.ietf.org/doc/html/rfc8628#section-3.5) — treat
it as retriable and bump the polling interval by 5 seconds, instead of
bailing with a fatal error.
Fixes#1289.
## Why
`pollForToken` in `cmd/publisher/auth/github-at.go` only treats
`authorization_pending` as retriable. Every other response from GitHub's
token endpoint — including the non-terminal `slow_down` signal — falls
through to:
```go
return "", fmt.Errorf("token request failed: %s", tokenResp.Error)
```
So users hitting GitHub's polling rate-limit get:
```
Error: login failed: error polling for token: token request failed: slow_down
```
…and have to re-run `mcp-publisher login github` from scratch, which is
especially painful because `slow_down` is more likely when the user
takes more than a few seconds to enter and authorize the device code.
Per RFC 8628 §3.5, `slow_down` is defined as:
> A variant of "authorization_pending", the authorization request is
still pending and polling should continue, but the interval MUST be
increased by 5 seconds for this and all subsequent requests.
## Change
Treat `slow_down` the same as `authorization_pending` but increase
`interval` by 5 seconds before sleeping, matching the RFC exactly.
A possible follow-up (not in this PR, to keep the diff minimal): seed
the initial `interval` from `DeviceCodeResponse.Interval` (already
parsed but currently unused) instead of hardcoding `5`. Happy to do that
here or as a separate PR if preferred.
## Test plan
- [x] `go build ./cmd/publisher/...` — clean
- [x] `go test ./cmd/publisher/auth/...` — passes
- [x] `gofmt -d` and `go vet ./cmd/publisher/auth/...` — clean
- [ ] CI `make check` (lint + integration tests)
I could not exercise `pollForToken` from a unit test without refactoring
`GitHubAccessTokenURL` to be injectable, which felt out of scope for a
bug-fix PR. Glad to add that refactor + a table-driven test for
`authorization_pending` / `slow_down` / fatal errors in a follow-up if
maintainers want it.
---------
Co-authored-by: developer-ishan <igupta@groupon.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Motivation and Context
Replace shared temp paths with `t.TempDir()`` so each test gets its own
cleanup-scoped directory.
- This avoids leftover files and cross-test interference. No production
code changed.
## How Has This Been Tested?
GitHub CI passed.
- https://github.com/yowainwright/registry/pull/1
## Breaking Changes
No breaking changes; just tests.
## Types of changes
- [x] chore
## 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
## Additional context
Small test-only cleanup. No production code paths are changed.
Co-authored-by: Radoslav Dimitrov <radoslav@stacklok.com>
Publishing a manifest with a stale version currently produces a
duplicate-version error without identifying the version that was
submitted. This made a request containing `0.4.5` look like a rejected
publication of `0.4.6` in #1615.
Include the submitted `name@version` in the CLI output before sending
the request and in the API duplicate-version error. HTTP 400, the
response structure, and duplicate-rejection behavior remain unchanged.
The service wraps the existing error sentinel so `errors.Is` continues
to work.
Adds a CLI regression test for a failed publication and updates the API
duplicate-error assertion.
Validation:
- Publish command tests passed (`go test ./cmd/publisher/commands -run
TestPublishCommand -count=1`).
- `git diff --check` passed.
- API endpoint tests were attempted but could not run because PostgreSQL
was unavailable on localhost:5432; Docker was also unavailable locally.
Related to #1615.
CI is red on `main` and on every open PR (including the Dependabot PRs
#1531, #1552–#1554) with:
```
panic: file requires newer Go version go1.27 (application built with go1.26)
golangci-lint exit with code 2
```
## Cause
`actions/setup-go` with `go-version: 'stable'` started resolving to
**go1.27.0**. The pinned golangci-lint **v2.11.4** release binary is
built with **go1.26**, and its `go/types` refuses to typecheck source
files declaring a newer language version. Nothing in the failing PRs is
at fault.
## Fix
Bump to **golangci-lint v2.13.1**, whose release binaries are built with
go1.27, so linting works under `stable` again.
That bump also pulls in newer linter versions, which surface 57 findings
this tree did not have before. Handled as follows:
| Linter | Count | Resolution |
|---|---|---|
| `goconst` | 50 | 39 were repeated literals in `_test.go` files —
goconst started counting those in v2.12.0 (golangci-lint#6480 exposed
`ignore-tests`). Fixture data like `"1.0.0"` (89 occurrences) is not a
missing constant, so `goconst.ignore-tests: true`. The other 11 were
real duplication in production code (OpenAPI tags, the `bearer` security
scheme name, `$ref`, `http`/`https`) and became named constants. |
| `gosec` G710 | 1 | **True positive, fixed.** See below. |
| `staticcheck` SA1019 | 6 | Go 1.26 deprecated `ecdsa.PublicKey.X/Y`
and `PrivateKey.D`. Annotated with the same targeted `nolint` +
rationale already used for `ScalarBaseMult` in
`cmd/publisher/auth/common.go`. |
### The open redirect is real
`TrailingSlashMiddleware` copied the entire `*url.URL` before rewriting
the path. For an absolute-form request URI — legal for a Go HTTP server
to parse, e.g. `GET http://evil.com/foo/ HTTP/1.1` — `r.URL.Scheme` and
`r.URL.Host` are populated, and they were echoed straight back in the
`Location` header. The redirect target is now built from the cleaned
path and query alone. The existing `path.Clean` hardening for
GHSA-v8vw-gw5j-w7m6 (protocol-relative `//evil.com/`) is unchanged.
### Also
Switched `gomodguard` → `gomodguard_v2`; the former is deprecated as of
v2.12.0 and was emitting a warning on every run.
## Verification
- `golangci-lint v2.13.1` against this tree: **0 issues**, no
deprecation warnings
- `make validate`: all validations passed
- `go build ./...` and tests for every touched package pass (DB-backed
tests need Postgres; left to CI)
## Note on migrating the ecdsa key paths
Moving those six sites to `PublicKey.Bytes` /
`ParseUncompressedPublicKey` / `crypto/ecdh` touches key parsing and
signature verification. It should be its own reviewed PR rather than
riding along on a CI unblock. Happy to open one.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
The Key Files list in `cmd/publisher/README.md` includes a bullet for
`build.sh`, but there is no `build.sh` anywhere in the repo. The
publisher is built with `make publisher`, which runs `go build -o
bin/mcp-publisher ./cmd/publisher` (Makefile), so there is no separate
cross-platform build script.
The other three entries in the same list (`main.go`, `commands/`,
`auth/`) all exist. This removes the stale bullet so the list matches
the actual files in the directory.
## Summary
- add a publisher preflight that rejects non-UTF-8 `server.json` bytes
before parsing
- reject unpaired UTF-16 surrogate escapes inside JSON string fields
before publish/validate requests are sent
- keep valid non-ASCII descriptions, including em dashes, round-tripping
through the publish path
- make the publisher test token helper set `USERPROFILE` as well as
`HOME` so Windows tests use the temp home directory
Closes#1306.
## To verify
- `go test ./cmd/publisher/commands -run
"TestPublishCommand_(Success|PreservesNonASCIIDescription|RejectsInvalidUTF8ServerJSON|RejectsUnpairedSurrogateEscape)$"
-count=1`
- `go test ./cmd/publisher/commands -run TestPublishCommand -count=1`
- `go test ./cmd/publisher/commands -run TestValidateCommand -count=1`
- `git diff --check`
Co-authored-by: Radoslav Dimitrov <radoslav@stacklok.com>
## What this does
The publisher CLI now derives the OIDC audience to request from the
`--registry` URL instead of using a fixed constant, and the server
validates the token's `aud` claim against a new per-deployment env var
(`MCP_REGISTRY_GITHUB_OIDC_AUDIENCE`).
Production sets this env var to
`https://registry.modelcontextprotocol.io` (and per-environment URL
elsewhere), so the deployment only accepts tokens that were requested
for it.
## Compatibility
This is a breaking change for older `mcp-publisher` binaries that still
request the legacy hardcoded audience. After this rolls to a deployment,
users on old `mcp-publisher` will see a clear `invalid audience:
expected …, got […]` error and need to upgrade. Users following the
documented `releases/latest` install pattern get the new binary
automatically.
## Test plan
- [ ] CI green
- [ ] Run a synthetic publish from a real GitHub Actions workflow
against staging after deploy
- [ ] Confirm an older `mcp-publisher` against the same staging deploy
returns the expected `invalid audience` error
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Follow-up to #1145. When `mcpName` is set in `package.json`, it should
be the server name used by `mcp-publisher init`. Two issues addressed:
### 1. `getNameFromPackageJSON` mangled `mcpName` through npm-name
transformation
`mcpName` values are already in MCP server name format (e.g.
`io.github.foo/bar`). The existing code ran them through the
`@scope/name → io.github.scope/name` transform and its
`io.github.<your-username>/*` fallback, producing a broken double-prefix
result.
Example with `"mcpName": "io.github.acme/weather"`:
- Before: `io.github.<your-username>/io.github.acme/weather` ❌
- After: `io.github.acme/weather` ✅
### 2. `detectServerName` consulted git before `mcpName`
Even with `mcpName` set in `package.json`, a github.com git remote (or a
`repository` field) silently won — the function returned
`io.github.<owner>/<repo>` and never looked at `mcpName`. Per the
[quickstart
docs](https://github.com/modelcontextprotocol/registry/blob/main/docs/modelcontextprotocol-io/quickstart.mdx),
*"The value of `mcpName` will be your server's name in the MCP
Registry"*, so `mcpName` should take precedence.
## Changes
- `cmd/publisher/commands/init.go`:
- New `getMcpNameFromPackageJSON()` helper that returns `mcpName` or
`""`.
- `detectServerName` checks `mcpName` first, before git remote and the
`name`-based fallback.
- `getNameFromPackageJSON` simplified — the now-redundant `mcpName`
branch was removed; it only handles the npm `name` transformation.
- `cmd/publisher/commands/init_test.go` (new): 5 cases through the
public `InitCommand` API covering `mcpName` as-is, fallback to scoped
`name`, fallback to placeholder, version fallback, and `mcpName` winning
over a GitHub `repository` URL.
This completes the fix for #737.
## Test plan
- [x] `go test ./cmd/publisher/commands/` passes (5 new subtests)
- [x] `golangci-lint run ./cmd/publisher/commands/` clean
- [ ] Manual: `mcp-publisher init` in a git-tracked directory with
`package.json` containing `mcpName` produces a `server.json` whose
`name` matches `mcpName`, not the repo slug
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Addresses two related enhancements to `mcp-publisher init`:
- **Copy `version` from `package.json`** (#736): When `package.json` is
present and contains a `version` field, it is now used for both the
top-level `version` and `packages[].version` in the generated
`server.json`. Falls back to `"1.0.0"` when unavailable. This aligns
with the recommended best practice of keeping server version in sync
with package version.
- **Prefer `mcpName` over `name`** (#737): The init command now checks
for `mcpName` in `package.json` first, falling back to `name` if
`mcpName` is not defined. This ensures the generated server name matches
the required `mcpName` property.
Closes#736Closes#737
## Changes
- `cmd/publisher/commands/init.go`:
- Added `getVersionFromPackageJSON()` helper to read version from
`package.json`
- Modified `InitCommand()` to use detected version with `"1.0.0"`
fallback
- Modified `getNameFromPackageJSON()` to prefer `mcpName` over `name`
## Test plan
- [x] `go build ./cmd/publisher/...` compiles without errors
- [x] `go test ./cmd/publisher/commands/...` passes all existing tests
- [ ] Manual: run `mcp-publisher init` in a directory with
`package.json` containing `version` and `mcpName` — verify both are
copied to `server.json`
- [ ] Manual: run `mcp-publisher init` in a directory with
`package.json` without `mcpName` — verify `name` is used as fallback
- [ ] Manual: run `mcp-publisher init` in a directory without
`package.json` — verify defaults (`"1.0.0"`, directory name) are used
## AI Disclosure
AI assistance (Claude) was used for initial code exploration and issue
research. The implementation was written and reviewed by the author.
Co-authored-by: Radoslav Dimitrov <radoslav@stacklok.com>
## Summary
When `detectRepoURL()` cannot determine the repository (no git remote,
no package.json repository field), return an empty string instead of a
placeholder URL (`https://github.com/YOUR_USERNAME/YOUR_REPO`). This
causes `createServerJSON()` to omit the `repository` field entirely,
preventing schema validation failures on publish.
Fixes#613
## Problem
`mcp-publisher init` generated a `repository` object with a placeholder
URL that fails the `uri` format validation on publish. 88 published
servers in the official registry had this issue.
## Change
- `cmd/publisher/commands/init.go`: `detectRepoURL()` returns `""`
instead of placeholder when no repo is found
- The existing guard in `createServerJSON()` (`if repoURL != "" &&
repoSource != ""`) already handles this correctly — it omits the
repository when URL is empty
## Test plan
- [x] `go build ./cmd/publisher/...` compiles
- [x] `go test ./cmd/publisher/commands/...` passes
- [ ] Manual: `mcp-publisher init` in a directory without git remote or
package.json repo — verify no `repository` field in generated
server.json
- [ ] Manual: `mcp-publisher init` in a git repo — verify `repository`
is populated correctly
## AI Disclosure
AI assistance (Claude) was used for issue research. The implementation
was written and reviewed by the author.
## Summary
Fixes#663
- **Security fix**: `mcp-publisher login` no longer writes
`.mcpregistry_github_token` and `.mcpregistry_registry_token` to the
current working directory. GitHub tokens are kept in memory during the
login→exchange flow and cleared immediately after.
- **Token location**: Unified token moved from `~/.mcp_publisher_token`
to `~/.config/mcp-publisher/token.json` (dir 0700, file 0600), following
the convention used by `gh`, `docker`, and `kubectl`.
- **Dead code removed**: `NeedsLogin()` was on the `Provider` interface
but never called — removed from interface and all 4 implementations.
`forceLogin` field also removed.
- **Auth tests added**: 11 new tests for `GitHubATProvider` (package
previously had 0 tests).
- **Test isolation**: `SetupTestToken` now uses `t.TempDir()` +
`t.Setenv("HOME", ...)` instead of writing to real `$HOME`.
### Breaking change
Users must re-login after upgrading (`mcp-publisher login <method>`).
Existing tokens at `~/.mcp_publisher_token` are not auto-migrated.
`logout` cleans up both old and new locations.
## Test plan
- [x] `make check` passes (lint, unit tests, integration tests)
- [x] `mcp-publisher login none` stores token at
`~/.config/mcp-publisher/token.json`
- [x] No `.mcpregistry_*` files created in cwd after login (with
`--token`, `MCP_GITHUB_TOKEN`, or device flow)
- [x] `mcp-publisher publish` reads token from new location
- [x] `mcp-publisher validate` reads registry URL from new location
- [x] `mcp-publisher logout` cleans up new location, legacy
`~/.mcp_publisher_token`, and cwd `.mcpregistry_*` files
- [x] `mcp-publisher logout` when not logged in shows "Not logged in"
- [x] File permissions: dir 0700, file 0600
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
<!-- Provide a brief summary of your changes -->
## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
We keep bumping `go 1.26.x` across the repo every time a new Go patch
drops
## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
- **`go.mod` / `deploy/go.mod`**: `go 1.25.8` → `go 1.26` (minor only —
"this module needs Go 1.26")
- **All CI workflows**: `go-version-file: 'go.mod'` → `go-version:
'stable'` — CI always uses the latest stable Go, no more
bump PRs
- **govulncheck**: removed hardcoded `go-version-input: '1.26.1'` →
`go-version-input: 'stable'`
## Breaking Changes
<!-- Will users need to update their code or configurations? -->
## 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)
- [ ] 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. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
(Updated auto-generated PR description from @tadasant / Claude on Feb
22, 2026)
## Motivation and Context
Fixes#637
Server lifecycle management (deprecation and yanking) is a standard
requirement for package registries. Use cases like rebranding, security
vulnerabilities, and end-of-life servers demand the ability to mark
servers as deprecated or deleted, with optional messaging to guide
consumers toward alternatives.
## Changes
### New API endpoints
- **`PATCH /v0/servers/{serverName}/versions/{version}/status`** —
Update the lifecycle status of a single server version
- **`PATCH /v0/servers/{serverName}/status`** — Update the lifecycle
status of all versions of a server in a single transaction
Both endpoints accept:
```json
{
"status": "active|deprecated|deleted",
"statusMessage": "Optional message (max 500 chars) explaining the change"
}
```
### New CLI command
- **`mcp-publisher status <serverName> [version]`** — Update server
lifecycle status from the command line
- `--status` (required): `active`, `deprecated`, or `deleted`
- `--message`: Optional status message
- `--all-versions`: Apply to all versions of the server (replaces the
`<version>` argument)
### API response changes
`RegistryExtensions` now includes two new fields:
- `statusChangedAt` — Timestamp of the last status change
- `statusMessage` — Optional free-form message (e.g., deprecation
reason, migration guidance)
### List endpoint changes
- New `include_deleted` query parameter on `GET /v0/servers` — defaults
to `false`, hiding deleted servers from listings
- When `updated_since` is provided, `include_deleted` is automatically
set to `true` for incremental sync
### Database
- Migration `013_add_status_fields.sql`: Adds `status_changed_at` (NOT
NULL, initialized from `published_at`) and `status_message` columns to
the `servers` table
### Design decisions (from review discussion)
- **Dedicated status endpoints** rather than overloading the existing
edit endpoint
- **Bidirectional status transitions** — all transitions between
`active`, `deprecated`, and `deleted` are allowed
- **`statusMessage` as free-form text** — no structured `nextName` or
`alternativeUrl` fields (per maintainer feedback, to avoid complexity
around chaining, trust transfer, etc.)
- **Publish permission** grants status change access (not limited to
admin/edit)
- **Status message auto-cleared** when transitioning back to `active`
Good, now I have the full picture. Here's the addendum:
### Consumer-facing API changes
The new status fields live inside
`_meta["io.modelcontextprotocol.registry/official"]` on every
`ServerResponse`. This is the existing `RegistryExtensions` object — two
new fields are added alongside the existing ones:
**Before:**
```json
{
"server": { "name": "com.example/my-server", "...": "..." },
"_meta": {
"io.modelcontextprotocol.registry/official": {
"status": "active",
"publishedAt": "2025-06-01T00:00:00Z",
"updatedAt": "2025-06-15T00:00:00Z",
"isLatest": true
}
}
}
```
**After:**
```json
{
"server": { "name": "com.example/my-server", "...": "..." },
"_meta": {
"io.modelcontextprotocol.registry/official": {
"status": "deprecated",
"statusChangedAt": "2026-02-01T12:00:00Z",
"statusMessage": "Deprecated in favor of com.example/my-server-v2",
"publishedAt": "2025-06-01T00:00:00Z",
"updatedAt": "2026-02-01T12:00:00Z",
"isLatest": true
}
}
}
```
| New field | Type | Present when |
|-----------|------|-------------|
| `statusChangedAt` | `string` (RFC3339) | Always — initialized to
`publishedAt` for existing records |
| `statusMessage` | `string` (max 500 chars) | Only when set —
`null`/omitted for active servers (auto-cleared on transition to active)
|
These fields appear on **every endpoint that returns a
`ServerResponse`**: `GET /v0/servers`, `GET /v0/servers/{name}`, `GET
/v0/servers/{name}/versions/{version}`, and the new PATCH status
endpoints.
### List endpoint filtering
`GET /v0/servers` gains an `include_deleted` query parameter:
| Scenario | Behavior |
|----------|----------|
| No `include_deleted` param | Deleted servers hidden (default `false`)
|
| `include_deleted=true` | Deleted servers included in results |
| `updated_since` provided | `include_deleted` forced to `true` for
incremental sync |
## How Has This Been Tested?
- Unit tests for status handler (`status_test.go`) covering status
transitions, permission checks, validation errors, and the all-versions
endpoint
- Unit tests for the CLI command (`status_test.go`)
- Integration with existing server/edit handler tests
- Database layer tests for the new status update and query methods
- Tested locally with `make dev-compose`
## Types of changes
- [x] New feature (non-breaking change which adds functionality)
## Checklist
- [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
## Open items from review
- [ ] `include_deleted` silent override: When `updated_since` is
provided and `include_deleted=false` is explicitly passed, should return
a 400 error rather than silently overriding
([thread](https://github.com/modelcontextprotocol/registry/pull/893#discussion_r2100879360))
— agreed upon but may not yet be implemented
## Motivation and Context
Per discussion with @rdimitrov - all validation now happens in a new
`/validate` endpoint. For details, see below.
## Breaking Changes
None. Validation on /publish only validates schema version and semantic
checks (manual checks) as before. We can add schema validation by
changing a flag in the called to `ValidateJSONSchema` when we're ready.
## Types of changes
- [X] New feature (non-breaking change which adds functionality)
## Checklist
- [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
## Additional context
## API Changes
### 1. New `/validate` Endpoint
A new public endpoint was added for validating `server.json` files
without publishing them.
**Details:**
- **Paths:** `/v0/validate` and `/v0.1/validate`
- **Method:** `POST`
- **Authentication:** None required (public endpoint)
- **Request:** JSON body containing `ServerJSON`
- **Response:** `200 OK` with `ValidationResult` containing:
- `valid` (boolean)
- `issues` (array of validation issues with type, path, message,
severity, reference)
**Implementation:**
- New handler file: `internal/api/handlers/v0/validate.go`
- Performs comprehensive validation using `ValidationAll` option:
- Schema version validation
- Full schema validation
- Semantic validation
- Always returns `200 OK` - validity is indicated in `result.Valid`
field
- Registered in router for both `/v0` and `/v0.1` prefixes
### 2. `/publish` Endpoint Validation Flow Changes
The publish endpoint now validates earlier in the request flow and
provides clearer error responses.
**Before:** Validation was mixed into `ValidatePublishRequest()` (which
also handled publisher extensions and registry ownership).
**After:**
1. Validates `ServerJSON` structure first using `ValidateServerJSON()`
(schema version + semantic validation)
2. Returns `422 Unprocessable Entity` with message: "Failed to publish
server, invalid schema: call /validate for details"
3. Only proceeds to publisher extensions/registry validation if schema
validation passes
4. No longer calls `ValidatePublishRequest()` internally for schema
validation
**Key Changes:**
- Validation happens before `CreateServer()` call
- Returns `422` on schema/semantic validation failures
- Error message directs users to `/validate` endpoint for details
- `ValidatePublishRequest()` no longer performs schema validation (added
note in comments)
### 3. `/edit` Endpoint Validation Flow Changes
Similar validation changes were applied to the edit endpoint.
**Changes:**
- Added `ValidateServerJSON()` call before server update
- Returns `422` with same error message format on validation failure
- Matches the `/publish` endpoint behavior
### 4. Validator Functions Refactoring
Updated validator functions to reflect that schema validation is now
done separately.
**Changes to `ValidatePublishRequest()`:**
- Removed internal `ValidateServerJSON()` call
- Added note: "ValidateServerJSON should be called separately before
this function"
- Now only validates publisher extensions and registry ownership
**Changes to `ValidateUpdateRequest()`:**
- Same change (removed schema validation, added note)
- Now only validates registry ownership
## CLI Changes
### 1. `validate` Command: Now Uses API Endpoint
**Before:** Performed local validation using
`validators.ValidateServerJSON()`.
**After:** Calls the `/v0/validate` endpoint on the registry.
**Details:**
- Reads `server.json` and sends it to the registry API
- Displays validation results returned by the server
- Uses the same error formatting as before
- Registry URL comes from token file (or default)
- No authentication required (public endpoint)
**Implementation:**
- Added `validateViaAPI()` function that POSTs to `/v0/validate`
- Parses `ValidationResult` response from server
- Uses shared `printValidationIssues()` for formatting
### 2. `publish` Command: Validation Only on 422 Errors
**Before:** Performed upfront local schema validation before publishing.
**After:** Attempts publish first, then validates only if server returns
422.
**Details:**
- Attempts publish immediately (no upfront validation)
- If publish returns 422 (Unprocessable Entity):
- Calls `/v0/validate` endpoint to get detailed validation errors
- Formats and displays them using same logic as `validate` command
- Returns error with migration links
- For non-422 errors (401, 403, etc.), returns original error (no
validation call)
**Implementation Changes:**
- Removed upfront `runValidationAndPrintIssues()` call
- Modified `publishToRegistry()` to return HTTP status code alongside
response/error
- Added 422 detection logic that calls `validateViaAPI()`
### 3. Shared Validation Error Formatting
**Before:** `runValidationAndPrintIssues()` function handled both
validation and printing together.
**After:** Refactored to separate printing logic.
**Details:**
- Created `printValidationIssues()` function - just prints validation
issues (no validation logic)
- Removed `runValidationAndPrintIssues()` function (no longer used)
- Both `validate` and `publish` commands use `printValidationIssues()`
for consistent output
**Behavior:**
- Prints schema validation errors with migration guidance
- Prints all other validation issues with formatting
- Returns formatted error message string for schema issues
## Summary
Both CLI commands now use the `/validate` API endpoint for validation
instead of local validation. The `publish` command validates only after
receiving a 422 error response, and both commands share the same error
formatting via `printValidationIssues()`.
### Benefits
- **Centralized validation logic** - All validation happens on the
server
- **Always up-to-date** - CLI always uses current server validation
rules
- **Consistent experience** - Same validation behavior whether using CLI
or API directly
- **Better separation of concerns** - Schema validation separated from
publisher-specific validation
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>
## Summary
- Added `printCommandHelp()` function to display help for each
subcommand
- `mcp-publisher init --help` now shows init command help instead of
running init
- `mcp-publisher login --help` now shows login methods instead of error
- `mcp-publisher publish --help` now shows publish usage instead of
attempting publish
Fixes#735
Signed-off-by: majiayu000 <1835304752@qq.com>
Co-authored-by: Radoslav Dimitrov <radoslav@stacklok.com>
Prepared by Claude Code, iterated and reviewed by me.
## Summary
This PR releases the server.json schema version `2025-12-11`,
incorporating URL template variables for remote servers from #570.
### What's New
- **URL Template Variables for Remote Servers** - Remote servers can now
define configurable variables in URLs using `{curly_braces}` notation,
enabling multi-tenant deployments.
### Schema Version
- `2025-10-17` → `2025-12-11`
## Release Sequence
1. ~~**First: Merge static repo PR** -
https://github.com/modelcontextprotocol/static/pull/11 (publishes schema
to versioned URL)~~
2. **Then: Merge this PR** - Updates registry code to reference the new
schema version
## Changes in This PR
- [x] Update CHANGELOG.md with release notes
- [x] Update `CurrentSchemaVersion` in `pkg/model/constants.go`
- [x] Update schema example URL in `docs/reference/api/openapi.yaml`
- [x] Update schema URL in `pkg/api/v0/types.go`
- [x] Update all documentation examples to use new schema version
- [x] Regenerate `server.schema.json`
## Test Plan
- [x] `make check` passes (lint, schema validation, example validation)
- [x] Unit tests pass for validators and publisher commands
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
<!-- Provide a brief summary of your changes -->
This PR aims to fix
https://github.com/modelcontextprotocol/registry/issues/743#event-20985516896
## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
This is needed to ensure we are sanitizing the URL before creating
server.json to be inline with the validations.
## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
Tested via UTs and also tested CLI locally to ensure the expected
behavior.
## Breaking Changes
<!-- Will users need to update their code or configurations? -->
## 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)
- [ ] 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. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
Co-authored-by: Avish Porwal <avishporwal@microsoft.com>
Follow-up to #728.
This fixes broken links due to file renames.
Fixes#788.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
<!-- Provide a brief summary of your changes -->
## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The following PR implements the missing support for logging in via
`--token` or `MCP_GITHUB_TOKEN`
## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
## Breaking Changes
<!-- Will users need to update their code or configurations? -->
## 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)
- [ ] 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. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
Fixes: #698
---------
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
<!-- Provide a brief summary of your changes -->
## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The Repository field in ServerJSON was not properly omitting empty
values
when marshaling to JSON. Even when a server had no repository
configured,
the JSON would include `"repository": {"url": "", "source": ""}` due to:
1. Repository struct fields (URL, Source) lacking omitempty tags
2. Repository being a value type instead of pointer in ServerJSON
This caused validation warnings for servers without repository URLs.
Changes:
- Add omitempty tags to Repository.URL and Repository.Source fields
- Change ServerJSON.Repository from model.Repository to
*model.Repository
- Update validateRepository to handle nil pointers
- Update all code creating Repository instances to use pointers
Now, servers without repository information correctly omit the field
entirely from JSON output, eliminating spurious validation warnings.
## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
## Breaking Changes
<!-- Will users need to update their code or configurations? -->
## 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)
- [ ] 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. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
<!-- Provide a brief summary of your changes -->
## Motivation and Context
This allows the private key used for DNS or HTTP based authentication to
be stored securely in a cloud key management system. These services
often provide HSM storage which makes it very hard to leak the private
key.
The full context is described in my design document here:
https://github.com/modelcontextprotocol/registry/issues/482#issuecomment-3437117171
## How Has This Been Tested?
I have tested it against PROD using an Ed25519 key stored in Google KMS.
I have added unit tests. I have tested both ECDSA P-384 and Ed25519 with
Az KV and Google KMS against a locally running server.
## Breaking Changes
None intended.
## 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)
- [x] 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
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
---------
Co-authored-by: adam jones <domdomegg+git@gmail.com>
<!-- Provide a brief summary of your changes -->
## Motivation and Context
Discussion from this issue:
https://github.com/modelcontextprotocol/registry/issues/538
We want to make it clearer that repo name & server name do not HAVE to
be the same, especially in monorepos.
@tadasant this seems like a minor thing that doesn't have to be
documented specifically, lmk if you disagree :)
## How Has This Been Tested?
Tested the init flow locally, running the built `init` command from a
subfolder and the repo root.
## Breaking Changes
No
## 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
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
<!-- Provide a brief summary of your changes -->
## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The following fixes the currently failing build on main because we
reverted to go 1.24 and the last commit was relying on a method solely
available in 1.25.
The method is quite straightforward so we just reimplemented it.
## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
## Breaking Changes
<!-- Will users need to update their code or configurations? -->
## 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)
- [ ] 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. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
## Motivation and Context
See
https://github.com/modelcontextprotocol/registry/issues/482#issuecomment-3436993157
for full details.
This adds ECDSA P-384 (a popular NIST approved curve) in addition to
Ed25519.
## How Has This Been Tested?
I have tested with a local deployment against `mcp.joelverhagen.com`.
Also, I have added significant tests.
## Breaking Changes
None. (hopefully)
## 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)
- [x] 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
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
@domdomegg and I spoke a bit about this on Discord:
https://discord.com/channels/1358869848138059966/1369487942862504016/1423710918180606052
---------
Co-authored-by: adam jones <domdomegg+git@gmail.com>
<!-- Provide a brief summary of your changes -->
## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The following PR:
* Makes mcpb package version property optional
* Bumps the schema and reflects that in the code
* Makes sure the publisher tests are being covered by doing `make check`
## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
## Breaking Changes
<!-- Will users need to update their code or configurations? -->
## 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)
- [ ] 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. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
---------
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
<!-- Provide a brief summary of your changes -->
## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The following PR:
* Enforces schema validation on the API level (requests without schema
or older schema will fail)
* Updates the unit tests and examples to match that
## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
## Breaking Changes
<!-- Will users need to update their code or configurations? -->
**Note this can be considered breaking if clients have not included it
before.**
## 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)
- [ ] 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. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
---------
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
## Summary
- Applied `gofmt -s -w` across the entire codebase to ensure consistent
Go formatting
- This will trigger a deployment with minimal code changes
## Test plan
- [x] Pre-commit hooks pass
- [x] No functional changes, formatting only
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
- Add workflow_dispatch to deploy.yml for manual deployment triggers
- Document that releases don't auto-deploy to production
- Add instructions for deploying releases to production
- Add verification steps for deployment
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Wielded Claude Code to do this; across many iterations and have reviewed
all the code + largely rewritten the PR commentary here.
This PR implements the server ID consistency changes from
[#396](https://github.com/modelcontextprotocol/registry/issues/396). Our
goal is to:
- Rename `id` to `versionId` to make it clear that it is the ID for a
specific version, not a server
- Introduce `serverId` as a net new concept. This will be 1:1 with
`name`. While not strictly necessary (we could just use name as a PK),
the convention of having a UUID as an ID on our objects is just a
helpful subjective architecture decision. Plus also leaves open the door
to reconsidering the "should we allow renames" question.
## Key Changes
### 🗄️ Database Schema Changes
- **Renamed**: `id` column → `version_id` (unique identifier for each
server version)
### 📋 Metadata Structure Updates
- **JSON Schema**: Updated
`_meta.io.modelcontextprotocol.registry/official` to include:
- `serverId`: Consistent ID across all versions of a server
- `versionId`: Unique ID for this specific version (renamed from `id`)
### 🔧 API Endpoint Updates
- **New endpoint structure**: Changed from version-ID-based to
server-ID-based URLs:
- `GET /v0/servers/{server_id}` - Get latest version of server
- `GET /v0/servers/{server_id}?version=X.X.X` - Get specific version
- `GET /v0/servers/{server_id}/versions` - List all versions of a server
- `PUT /v0/servers/{server_id}?version=X.X.X` - Edit specific version
- **Consistent URL patterns**: All server endpoints now use `server_id`
as the primary identifier
### Database Migration Strategy
The migration (`005_add_server_id_rename_version_id.sql`):
1. Adds new `version_id` column
2. Generates deterministic UUIDs for servers with the same name
3. Updates JSON metadata to add `serverId` and rename `id` to
`versionId`
4. Copies existing `id` values to `version_id` column
5. Drops old `id` column and makes `version_id` the new primary key
## Testing
Besides of course automated tests passing, I:
1. Set up my local db with the pre-005-migration state, imported all the
production data
2. Ran the migration
3. Spot checked some diffs of the data that resulted --
```json
// before
{
"name": "io.github.ruvnet/ruv-swarm",
"_meta": {
"io.modelcontextprotocol.registry/official": {
"id": "0a9cda7f-2b73-4839-844b-6582f335de42",
"is_latest": false,
"updated_at": "2025-09-10T17:21:34.92137012Z",
"published_at": "2025-09-10T17:15:32.69690433Z"
}
},
"version": "1.0.18",
"repository": {
"url": "",
"source": ""
},
"description": "Neural network swarm orchestration with WebAssembly acceleration and MCP integration"
}
// after
{
"name": "io.github.ruvnet/ruv-swarm",
"_meta": {
"io.modelcontextprotocol.registry/official": {
"serverId": "e8538952-96af-4a33-9e31-7fbed012521e",
"is_latest": false,
"versionId": "0a9cda7f-2b73-4839-844b-6582f335de42",
"updated_at": "2025-09-10T17:21:34.92137012Z",
"published_at": "2025-09-10T17:15:32.69690433Z"
}
},
"version": "1.0.18",
"repository": {
"url": "",
"source": ""
},
"description": "Neural network swarm orchestration with WebAssembly acceleration and MCP integration"
}
```
Servers that have multiple versions stayed consistent in their
`serverId`:
```json
// First version
{
"name": "app.getdialer/dialer",
"_meta": {
"io.modelcontextprotocol.registry/official": {
"serverId": "80f08bdb-f3e4-4f19-b072-1676ae49b663",
"is_latest": false,
"versionId": "04f25b17-331d-48d7-b08b-37baece208a7",
"updated_at": "2025-09-09T00:16:49.162620072Z",
"published_at": "2025-09-08T23:47:09.452137971Z"
}
},
"status": "active",
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"remotes": [
{
"url": "https://getdialer.app/mcp",
"type": "streamable-http"
},
{
"url": "https://getdialer.app/sse",
"type": "sse"
}
],
"version": "1.0.0",
"repository": {
"url": "",
"source": ""
},
"description": "An MCP server that provides your you make outbound phone calls using your own phone number"
}
// Second version
{
"name": "app.getdialer/dialer",
"_meta": {
"io.modelcontextprotocol.registry/official": {
"serverId": "80f08bdb-f3e4-4f19-b072-1676ae49b663",
"is_latest": true,
"versionId": "faa26cb9-3aad-415e-aece-f715fff5fe5e",
"updated_at": "2025-09-09T00:16:49.107842808Z",
"published_at": "2025-09-09T00:16:49.107842808Z"
}
},
"status": "active",
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"remotes": [
{
"url": "https://getdialer.app/mcp",
"type": "streamable-http"
},
{
"url": "https://getdialer.app/sse",
"type": "sse"
}
],
"version": "1.0.1",
"repository": {
"url": "",
"source": ""
},
"description": "An MCP server that provides lets you make outbound phone calls using your own phone number"
}
```
And I also tried publishing a test server anew; looked good:
```bash
⏺ Bash(curl -s "http://localhost:8080/v0/servers/102dc4e4-6ca8-40e6-aa11-cd45763d3352" | jq '.')
⎿ {
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/test-claude-migration-server",
"description": "A test server to verify the new migration and API endpoints work correctly",
"status": "active",
"repository": {
"url": "https://github.com/example/test-claude-migration-server",
"source": "github",
"id": "example/test-claude-migration-server"
},
"version": "1.0.0",
"_meta": {
"io.modelcontextprotocol.registry/official": {
"serverId": "102dc4e4-6ca8-40e6-aa11-cd45763d3352",
"versionId": "48c9cd55-7bc4-4ac2-896e-59e81eb653e4",
"published_at": "2025-09-12T18:33:23.264203-07:00",
"updated_at": "2025-09-12T18:33:23.264203-07:00",
"is_latest": true
}
}
}
⏺ Bash(curl -s "http://localhost:8080/v0/servers/102dc4e4-6ca8-40e6-aa11-cd45763d3352/versions" | jq '.')
⎿ {
"servers": [
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
"name": "io.modelcontextprotocol.anonymous/test-claude-migration-server",
"description": "A test server to verify the new migration and API endpoints work correctly",
"status": "active",
"repository": {
"url": "https://github.com/example/test-claude-migration-server",
"source": "github",
"id": "example/test-claude-migration-server"
},
"version": "1.0.0",
"_meta": {
"io.modelcontextprotocol.registry/official": {
"serverId": "102dc4e4-6ca8-40e6-aa11-cd45763d3352",
"versionId": "48c9cd55-7bc4-4ac2-896e-59e81eb653e4",
"published_at": "2025-09-12T18:33:23.264203-07:00",
"updated_at": "2025-09-12T18:33:23.264203-07:00",
"is_latest": true
}
}
}
],
"metadata": {
"count": 1
}
}
```
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Preeti Dewani <preetidewani1990@gmail.com>
Co-authored-by: adam jones <adamj+git@anthropic.com>
Co-authored-by: Adam Jones <adamj@anthropic.com>
When using `mcp-publisher publish ...`, output the new server `ID` if
publishing is successful.
## Motivation and Context
It's useful to know this during testing. It means that you don't have to
go and search for the new server ID afterwards. Once we have the
`v0/servers/{name}/{version}`, I think we could consider removing this
again.
## How Has This Been Tested?
I've been using this while I test publishing to locally running
registries.
## Breaking Changes
none
## 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
- [ ] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed
## Summary
Simplified the schema structure by:
- Replacing `version_detail.version` with direct `version` field
- Removing unused `release_date` field from seed data
- Updating all API types, models, and tests to match new structure
- Maintaining backwards compatibility in API responses
This change makes the schema more intuitive and reduces nesting depth.
<!-- Provide a brief summary of your changes -->
## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The following PR introduces Goreleaser for the releases of the registry
server and the publisher CLI.
**Details:**
- Builds registry and publisher binaries for Linux, macOS, and Windows
(amd64 + arm64)
- Leverage GitHub's release process by creating releases directly from
GitHub's interface
- Added .goreleaser.yaml configuration
- New .github/workflows/release.yml workflow triggered by GitHub
releases
- Updated Docker tagging in deploy workflow (:latest and :semver(the
tag) for releases, :main-YYYYMMDD-sha for testing/development)
- Consolidated version handling in main.go files
- Added release documentation
## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
Locally (but we have to test it by creating a release, i.e. v0.0.1 once
this gets merged)
## Breaking Changes
<!-- Will users need to update their code or configurations? -->
Yes, the notion of `latest` for the container image is now different
## 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. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
Related to #358
---------
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
## Motivation and Context
Docs currently don't have very clear organisation, and don't explain
core flows.
This fixes#89 (although we still might want to push these to
`https://modelcontextprotocol.io/docs/`), based on the plan there.
One minor change from that plan is that I merged tutorials and guides,
because they ended up basically being duplicates which I thought would
be annoying for us to keep in sync. Additionally in the Diátaxis
framework, tutorials and guides are separated by whether they are
focused on acquisition of skills (e.g. in a learning environment) or
application of skills (e.g. in a work environment). I expect the
registry to be something that will be used occasionally in a work
environment, rather than a fundamental 'learnable' skill so I think it's
reasonable not to have tutorials and fold these into guides.
## How Has This Been Tested?
- Read through all these updated docs
- Unit and integration tests (that previously used examples.md and use
the schema) working
- Ran `npx markdown-link-check .` to check all the links work
## Breaking Changes
Links to old docs will break, but I don't think there are any. All
internal links have been checked and updated.
## 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)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update
<!-- Provide a brief summary of your changes -->
## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The following PR:
* Adds a new `transport` type used in Package and Remotes
* Added `docker` to the `runtime_hint` examples
* Added support for templated URLs. It is available for streamable-http
and sse transports for Packages. I didn't thought of reasons to have
this for Remotes, but we can enable it if needed.
* Added validation for templated URLs so we can ensure all referenced
placeholder variables are available.
* For streamable-http and SSE transport types the URL is now a required
property
* Updated the publisher init command to reflect these changes
* Updated the seed.json file
* Added unit tests for the transport type related changes
* Updated the server.json schema and the external pkg structs to reflect
the new transport type
* Updated the examples
Thanks to @kkkarthik for spotting it! 🙏
## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
Ran the tests locally
## Breaking Changes
<!-- Will users need to update their code or configurations? -->
Yes
## 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)
- [ ] New feature (non-breaking change which adds functionality)
- [x] 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. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
---------
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Fixes#96
## Motivation and Context
See #96
## How Has This Been Tested?
Has some unit and integration tests, still needs proper testing of the
different registry types
## Breaking Changes
Yes, new package uploads will need extra verification
## 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)
- [x] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
<!-- Provide a brief summary of your changes -->
## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The following PR updates some of the hardcoded references to public
constants.
## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
Locally
## Breaking Changes
<!-- Will users need to update their code or configurations? -->
No
## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] 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. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Updates the registry type for all Docker Hub packages in the seed data
from `docker-hub` to `oci` to align with the standardized OCI registry
format. This change affects the `registry_type` field and simplifies the
`identifier` field by removing the full Docker Hub URL prefix.
Changes:
- Updated `registry_type` from `docker-hub` to `oci` for all Docker
packages
- Simplified `identifier` field to use just the repository name/path
instead of full URLs
- Maintained `registry_base_url` as `https://docker.io` for all entries
This refactoring improves consistency and aligns with OCI standards
while maintaining backward compatibility through the registry base URL.
## Summary
Move the publisher CLI from `tools/publisher/` to `cmd/publisher/` as
it's a proper application rather than a development utility.
This addresses feedback from @rdimitrov in PR #324 that the publisher
CLI should be under `cmd/` since it's not really a tool in the same
sense as other helpers in the `tools/` folder.
## Changes
- Moved `tools/publisher/` directory to `cmd/publisher/`
- Updated Makefile build and clean targets
- Updated README.md documentation links
- Updated VS Code launch configuration
- Updated integration test script
- Updated FAQ documentation
- Fixed Go import paths in publisher code
---------
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: adam jones <domdomegg@users.noreply.github.com>