You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Registry-wide hardening of the `mcp-name:` ownership-token match, plus
the cargo follow-up fixes from the #1330 review — consolidated here per
maintainer request. Rebased onto current `main` (the merged #1330 cargo
commit drops out), so the diff is just the net-new work.
### Changes
- **PyPI/NuGet: boundary-anchored token match.** Replace
`strings.Contains(readme, "mcp-name: "+name)` with the shared
`containsMCPNameToken`, so a README declaring a longer name
(`…/widget-pro`) no longer satisfies an ownership claim for a shorter
prefix (`…/widget`). (Cargo already got this in #1330; NPM is unaffected
— it compares an exact metadata field.)
- **Matcher: treat the HTML comment close as a boundary.** `<!--
mcp-name: NAME-->` / `<!--mcp-name: NAME-->` (any spacing) validate
again — the documented hidden-comment form for PyPI/NuGet — while a
genuine longer name (`…/widget--pro`) still does not.
- **Cargo hardening (review of #1330):** pin scheme+port (not just host)
on the README fetch and redirects; a rate-limited/failed crate-version
existence probe now reports *transient* instead of "not found"; a 403
with the crate present no longer flatly asserts "no README".
- **Docs:** note the token must be followed by a boundary.
### ⚠️ Behavior change for PyPI/NuGet — read before merging
The match is **strictly stricter** (verified by fuzz, 2.3M execs: it can
only flip pass→fail, never fail→pass). After the comment-close fix, the
**only** forms that newly fail are unusual *inline* ones: the token
ending a sentence (`…/my-mcp.`) or glued to a trailing `/`. The
documented forms (own line, in `<!-- … -->`) are unaffected.
**Correcting an earlier claim:** this re-validates on **edits too**, not
just new versions — `edit.go → UpdateServer → ValidateUpdateRequest →
validateRegistryOwnership` runs the token check on any edit of a live
server (only delete-transitions skip it). So an existing PyPI/NuGet
server whose README uses one of the breaking inline forms would fail on
its next publish *or* edit. Given the v0.1 API-freeze posture, this
should land as a deliberate, noted change.
### Testing
`go build`, `vet`, `gofmt`, `golangci-lint` (prod files),
`check-schema`, `validate-examples` all clean. Hermetic + **live**
PyPI/NuGet/cargo suite passes. New: `TestCargoURLAllowed`, a
combined-fixture probe-429→transient case, comment-form matcher cases,
and `FuzzContainsMCPNameToken` (the strictly-stricter property, 2.3M
execs).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `mcp-name:` token must be followed by a boundary — a newline, whitespace, an HTML tag, or the comment close `-->`. Keep it on its own line or inside `<!-- … -->`; do not glue it directly to trailing characters such as a sentence-ending period (`…/database-query-mcp.`), which prevents the match.
91
+
90
92
## NuGet Packages
91
93
92
94
For NuGet packages, the MCP Registry currently supports the official NuGet registry (`https://api.nuget.org/v3/index.json`) only.
@@ -125,6 +127,8 @@ This MCP server manages Azure DevOps work items and pipelines.
The `mcp-name:` token must be followed by a boundary — a newline, whitespace, an HTML tag, or the comment close `-->`. Keep it on its own line or inside `<!-- … -->`; do not glue it directly to trailing characters such as a sentence-ending period (`…/azure-devops-mcp.`), which prevents the match.
131
+
128
132
## Cargo (Rust) Packages
129
133
130
134
For Cargo packages, the MCP Registry currently supports the official crates.io registry (`https://crates.io`) only.
returnfmt.Errorf("cargo package '%s' version '%s' exists on crates.io but has no rendered README. Add a README containing 'mcp-name: %s' and publish a new version", pkg.Identifier, pkg.Version, serverName)
// The crate/version exists but the README CDN returned 403. The likely
239
+
// cause is a missing README, but a 403 is not definitive proof (e.g. a
240
+
// transient CDN/WAF block), so don't flatly assert "no README".
241
+
returnfmt.Errorf("cargo package '%s' version '%s' exists on crates.io, but its rendered README could not be retrieved (status: 403). If it has no README, add one containing 'mcp-name: %s' and publish a new version", pkg.Identifier, pkg.Version, serverName)
242
+
casecargoVersionMissing:
204
243
returnfmt.Errorf("cargo package '%s' version '%s' not found on crates.io", pkg.Identifier, pkg.Version)
205
-
default:
206
-
returnfmt.Errorf("cargo package '%s' version '%s' not found on crates.io (status: 403)", pkg.Identifier, pkg.Version)
244
+
casecargoVersionTransient:
245
+
returnfmt.Errorf("crates.io could not confirm cargo package '%s' version '%s' (README status: 403, version check inconclusive) — likely transient, retry later", pkg.Identifier, pkg.Version)
246
+
casecargoVersionUnknown:
247
+
// Probe returned an unclassifiable status — fall through to the
248
+
// best-effort message below.
207
249
}
250
+
returnfmt.Errorf("cargo package '%s' version '%s' not found on crates.io (status: 403)", pkg.Identifier, pkg.Version)
208
251
}
209
252
210
253
// validateCargoREADME performs the two-call README fetch and the mcp-name token
returnfmt.Errorf("cargo package '%s': crates.io returned a README URL on unexpected host %q — refusing to fetch", pkg.Identifier, readmeParsed.Hostname())
returnfmt.Errorf("cargo package '%s': crates.io returned a README URL on an unexpected host/scheme %q — refusing to fetch", pkg.Identifier, readmeParsed.Redacted())
257
301
}
258
302
259
303
// Step 2: fetch the rendered README from the (now host-validated) URL.
returnfmt.Errorf("cargo package '%s' ownership validation failed: found 'mcp-name: %s' in the README, but it is immediately followed by %q rather than a boundary. The token must be followed by a space, newline, or an HTML tag — put it on its own line and publish a new version", pkg.Identifier, serverName, trailing)
338
+
}
339
+
290
340
returnfmt.Errorf("cargo package '%s' ownership validation failed. The server name '%s' must appear as 'mcp-name: %s' in the package README", pkg.Identifier, serverName, serverName)
0 commit comments