feat: add cargo (crates.io) as a package registry type#1207
Open
Wolfe-Jam wants to merge 2 commits intomodelcontextprotocol:mainfrom
Open
feat: add cargo (crates.io) as a package registry type#1207Wolfe-Jam wants to merge 2 commits intomodelcontextprotocol:mainfrom
Wolfe-Jam wants to merge 2 commits intomodelcontextprotocol:mainfrom
Conversation
modelcontextprotocol#1055 noted that crates.io has ~1,800 MCP-related packages with no direct path into the registry, only the MCPB binary-packaging workaround. This commit adds the schema-side wiring for `registryType: cargo`: - `pkg/model/constants.go`: `RegistryTypeCargo` + `RegistryURLCrates` - `server.json` schema and `openapi.yaml`: `cargo` in the example enum for `registryType`; `https://crates.io` in `registryBaseUrl` - `generic-server-json.md`: new minimal Cargo example, with a runtime-model note. `cargo install` puts the binary on PATH at `~/.cargo/bin` and the MCP client invokes it by name. `npx` (npm), `uvx` (PyPI), and `dnx` (NuGet, .NET 10 SDK) were the cross-ecosystem precedents considered; cargo has no single-shot analog, so `runtimeHint` is omitted. - `tools/validate-examples/main.go`: `expectedServerJSONCount` bumped 16 → 17 to match the new example (caught by `make validate`). Validator and `publish-cargo.md` follow on this branch once the schema-side direction is settled. Refs modelcontextprotocol#1055
Closes modelcontextprotocol#1055. Verification mirrors the PyPI validator: substring-match `mcp-name: <serverName>` against the package's rendered README. The publisher adds a single line to their README before publishing. Two-call retrieval pattern: 1. `GET /api/v1/crates/{name}/{version}/readme` returns 200 with a JSON pointer `{"url": "https://static.crates.io/readmes/.../...html"}` — crates.io hands us the URL rather than emitting a 302. 2. Follow the pointer to the rendered HTML. The two-call pattern stays on the documented public crates.io API surface. The CDN URL layout is observed-stable, but treating it as the entry point would mean depending on an undocumented path. With two calls, crates.io controls where the README lives. Missing crates and missing versions surface as 403 from the CDN (S3's default for missing keys), not 404. The validator treats any non-200 as "not found" and surfaces the actual status code in the error message. Tests are integration-only (matching the npm/pypi pattern). 16 sub-cases across input validation, registry-baseURL rejection (four variants), ownership against real crates (serde, tokio, rand), and server-name format variations. The positive-path case is gated on `rust-faf-mcp` v0.2.3+ being published with `mcp-name: io.github.Wolfe-Jam/rust-faf-mcp` in its README — the commented-out test in `cargo_test.go` will uncomment to become the live anchor once that publish happens. Refs modelcontextprotocol#1055
26eb180 to
6b2b006
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds support for
registryType: cargoso Rust MCP servers published to crates.io can be registered through thedocumented validation flow rather than the MCPB binary-packaging workaround. Closes #1055.
Motivation and Context
#1055 noted that crates.io has ~1,800 MCP-related packages with no direct path into the registry — only the MCPB
binary-packaging workaround. This PR adds first-class support for cargo as a package registry type: schema +
API surface, validator, integration tests, and a documented example. The publishing guide
(
docs/guides/publishing/publish-cargo.md) follows on this branch once the runtime-model direction inAdditional context is settled.
How Has This Been Tested?
make validate— clean. Schema-vs-openapi sync verified; 17/17 examples ingeneric-server-json.mdvalidate against the schema;
expectedServerJSONCountbumped 16 → 17 to match the new Cargo example.go test ./internal/validators/...— passes. 16 cargo validator sub-cases across 4 test functions, runagainst real crates.io (~2.5s wall):
serde,tokio,rand) — all correctly rejected for missingmcp-nametokenio.github.OWNER/REPO, multi-hyphen, underscore, numeric suffix)rust-faf-mcpv0.2.3+ being published withmcp-name: io.github.Wolfe-Jam/rust-faf-mcpin its README. Reserved as a TODO incargo_test.go; uncomments to become thelive anchor once that publish lands.
make test-unit— not run due to a Go toolchain version quirk on my workstation (projectauto-downloads 1.26.0; my local
gois 1.25.6, causing compile mismatch). CI will exercise the fullPostgreSQL-backed suite.
Breaking Changes
None. This is an additive change —
cargojoins the supportedregistryTypeenum alongsidenpm/pypi/nuget/oci/mcpb. Existing publishes continue to work unchanged.Types of changes
generic-server-json.md)Checklist
Additional context
Validator design — two-call retrieval on the documented API
internal/validators/registries/cargo.gomirrors the PyPI validator's README-token approach (substring-matchmcp-name: <serverName>), with a two-call retrieval pattern to stay on the documented public crates.io API:GET /api/v1/crates/{name}/{version}/readmereturns 200 OK with a JSON pointer ({"url": "https://static.crates.io/readmes/.../...html"}) — crates.io hands us the URL rather than emitting a 302.The two-call pattern stays on the documented public surface. The CDN URL layout is observed-stable, but treating
it as the entry point would mean depending on an undocumented path. With two calls, crates.io controls where
the README lives — if they move it, the metadata endpoint hands us the new URL.
Missing crates and missing versions surface as 403 from the CDN (S3's default for missing keys), not 404.
The validator treats any non-200 as "not found" and surfaces the actual status code in the error message.
Open question — runtime model
Cargo's runtime model is genuinely different from npm/PyPI/NuGet.
cargo installis one-time (binary lands onPATH at
~/.cargo/bin), not per-invocation likenpx,uvx, ordnx(the new one in .NET 10 SDK Preview 6+).The new Cargo example in
generic-server-json.mdannotates this honestly and omitsruntimeHint.If a different framing is preferred — e.g. recommending MCPB (prebuilt binary distribution via GitHub
Releases) as the primary path for Rust MCP servers instead of cargo — happy to adjust the documentation
accordingly. The schema + validator code in this PR is additive and doesn't force a recommendation either
way; a Rust author who chooses cargo can use it, and one who prefers MCPB still can.
Out of scope (deferred)
docs/guides/publishing/publish-cargo.md— follows on this branch once the runtime-model direction aboveis settled.
Cargo.tomlautodetect incmd/publisher/commands/init.go— separate concern, separate PR.rust-faf-mcpv0.2.3+ publishing as noted in How Has This BeenTested?.