Skip to content

feat: add cargo (crates.io) as a package registry type#1207

Open
Wolfe-Jam wants to merge 2 commits intomodelcontextprotocol:mainfrom
Wolfe-Jam:feat/cargo-package-registry-support
Open

feat: add cargo (crates.io) as a package registry type#1207
Wolfe-Jam wants to merge 2 commits intomodelcontextprotocol:mainfrom
Wolfe-Jam:feat/cargo-package-registry-support

Conversation

@Wolfe-Jam
Copy link
Copy Markdown

Adds support for registryType: cargo so Rust MCP servers published to crates.io can be registered through the
documented 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 in
Additional context is settled.

How Has This Been Tested?

  • make validate — clean. Schema-vs-openapi sync verified; 17/17 examples in generic-server-json.md
    validate against the schema; expectedServerJSONCount bumped 16 → 17 to match the new Cargo example.
  • go test ./internal/validators/... — passes. 16 cargo validator sub-cases across 4 test functions, run
    against real crates.io (~2.5s wall):
    • Input validation rejection paths
    • Registry-baseURL rejection (4 variants: different host, trailing slash, http-not-https, subdomain typo)
    • Ownership validation against real crates (serde, tokio, rand) — all correctly rejected for missing
      mcp-name token
    • Server-name format variations (canonical io.github.OWNER/REPO, multi-hyphen, underscore, numeric suffix)
  • Positive-path test — gated on rust-faf-mcp v0.2.3+ being published with mcp-name: io.github.Wolfe-Jam/rust-faf-mcp in its README. Reserved as a TODO in cargo_test.go; uncomments to become the
    live anchor once that publish lands.
  • Local make test-unit — not run due to a Go toolchain version quirk on my workstation (project
    auto-downloads 1.26.0; my local go is 1.25.6, causing compile mismatch). CI will exercise the full
    PostgreSQL-backed suite.

Breaking Changes

None. This is an additive change — cargo joins the supported registryType enum alongside
npm/pypi/nuget/oci/mcpb. Existing publishes continue to work unchanged.

Types of changes

  • New feature (non-breaking change which adds functionality)
  • Documentation update (new Cargo example in generic-server-json.md)

Checklist

  • I have read the MCP Documentation
  • 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

Validator design — two-call retrieval on the documented API

internal/validators/registries/cargo.go mirrors the PyPI validator's README-token approach (substring-match
mcp-name: <serverName>), with a two-call retrieval pattern to stay on the documented public crates.io API:

  1. GET /api/v1/crates/{name}/{version}/readme returns 200 OK 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; substring-match for the token.

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 install is one-time (binary lands on
PATH at ~/.cargo/bin), not per-invocation like npx, uvx, or dnx (the new one in .NET 10 SDK Preview 6+).
The new Cargo example in generic-server-json.md annotates this honestly and omits runtimeHint.

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 above
    is settled.
  • Publisher CLI Cargo.toml autodetect in cmd/publisher/commands/init.go — separate concern, separate PR.
  • Positive-path validator test — gated on rust-faf-mcp v0.2.3+ publishing as noted in How Has This Been
    Tested?
    .

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
@Wolfe-Jam Wolfe-Jam force-pushed the feat/cargo-package-registry-support branch from 26eb180 to 6b2b006 Compare April 26, 2026 04:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support crates.io as a package registry type

1 participant