chore(conformance): meeting requirements for tier 1 (#1087)

* chore(conformance): meeting requirements for tier 1

* chore: address PR feedback
This commit is contained in:
Alex Hancock 2026-07-30 11:36:12 -04:00 • committed by GitHub
commit 51c8ee60e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 617 additions and 83 deletions

65
DEPENDENCY_POLICY.md Normal file
View file

@ -0,0 +1,65 @@
# Dependency Policy
This document describes how the Rust MCP SDK (`rmcp`, `rmcp-macros`) selects,
updates, and maintains its dependencies. It applies to all crates in the workspace.
## Goals
- Keep dependencies current and secure without churn that destabilizes the public API.
- Minimize the dependency footprint we impose on downstream users.
- Make dependency updates reviewable and traceable.
## Choosing dependencies
Before adding a new dependency we consider:
- **Necessity** — whether the functionality is worth an added dependency, or is small
enough to implement directly.
- **Maintenance & trust** — active maintenance, a healthy release history, and a
permissive, compatible license (the workspace is `Apache-2.0`).
- **Footprint** — transitive dependency count and compile-time cost.
- **Optionality** — anything not needed by every user should sit behind a Cargo
feature so downstream builds stay lean. The SDK is heavily feature-gated for this
reason (transports, `auth`, `schemars`, `reqwest`, etc.).
Version requirements are specified with caret (`^`, the Cargo default) constraints so
compatible upgrades are picked up automatically.
## Automated updates
Dependency updates are automated with [Dependabot](https://docs.github.com/en/code-security/dependabot),
configured in [`.github/dependabot.yml`](.github/dependabot.yml):
| Ecosystem | Cadence | PR label |
| -------------- | ------- | ----------------- |
| Cargo crates | Weekly | `T-dependencies` |
| GitHub Actions | Daily | `T-CI` |
Dependabot opens a limited number of PRs at a time (currently 3 per ecosystem) to
keep the review queue manageable. Update PRs run the full CI suite (build, format,
clippy, tests, and conformance) and must be green before merge.
## Review and merge
- Patch and minor updates that pass CI are reviewed and merged promptly by maintainers.
- Major (breaking) updates are evaluated individually; they may require code changes
and are scheduled deliberately rather than merged automatically.
- Any dependency change that affects the **public API** (for example a type re-exported
from a dependency) is treated as a potential breaking change and follows
[`VERSIONING.md`](VERSIONING.md).
## Security updates
- Security fixes take priority over routine updates and are expedited.
- Vulnerabilities in the SDK itself are handled per [`SECURITY.md`](SECURITY.md) via
GitHub's private Security Advisory process.
- Advisories affecting our dependencies (e.g. via the [RustSec Advisory Database](https://rustsec.org/))
are addressed as soon as a fixed version is available; if a released version of the
SDK is impacted, a patched release is published and, where warranted, the affected
versions are yanked from crates.io.
## Minimum Supported Rust Version
Dependency upgrades must not raise the SDK's declared MSRV (`rust-version` in
`Cargo.toml`) without an explicit, documented MSRV bump — which is itself treated as a
breaking change per [`VERSIONING.md`](VERSIONING.md).

423
README.md
View file

@ -28,6 +28,7 @@ below. For the full MCP specification, see
- [Resources](#resources)
- [Prompts](#prompts)
- [Sampling](#sampling)
- [Elicitation](#elicitation)
- [Roots](#roots)
- [Logging](#logging)
- [Completions](#completions)
@ -38,6 +39,10 @@ below. For the full MCP specification, see
- [Caching](#caching)
- [Standard HTTP Headers](#standard-http-headers)
- [Stateless Streamable HTTP](#stateless-streamable-http)
- [Transports](#transports)
- [Pagination](#pagination)
- [Capability & Protocol Version Negotiation](#capability--protocol-version-negotiation)
- [JSON Schema 2020-12](#json-schema-2020-12)
- [Examples](#examples)
- [OAuth Support](#oauth-support)
- [Related Resources](#related-resources)
@ -254,6 +259,75 @@ impl ServerHandler for Calculator {}
See [`crates/rmcp-macros`](crates/rmcp-macros/README.md) for full macro documentation.
#### Tool result content types
Beyond a plain `String`, tools can return images, audio, embedded resources, and
mixed content. Build a `CallToolResult` from a `Vec<ContentBlock>`:
```rust,ignore
use rmcp::model::{CallToolResult, ContentBlock, ResourceContents};
#[tool(description = "Render a chart")]
async fn chart(&self) -> Result<CallToolResult, McpError> {
let png_base64 = render_png(); // base64-encoded bytes
Ok(CallToolResult::success(vec![
// Text
ContentBlock::text("Here is your chart:"),
// Image — base64 data + MIME type
ContentBlock::image(png_base64, "image/png"),
// Audio — base64 data + MIME type
// ContentBlock::audio(wav_base64, "audio/wav"),
// Embedded resource — inline text (or ResourceContents::blob for binary)
ContentBlock::resource(ResourceContents::text(
"chart source data",
"chart://last/data.csv",
)),
]))
}
# fn render_png() -> String { String::new() }
```
Image and audio data are base64 strings with a MIME type. For embedded
resources, `ResourceContents::text(..)` inlines text and
`ResourceContents::blob(base64, uri)` inlines binary.
#### Error handling
Two failure modes, chosen by **whose problem it is**:
- **Tool-level error** — `Ok(CallToolResult::error(vec![...]))`. The tool ran but
failed in a way the caller should see (no rows matched, upstream 500). The
client renders your `content`, so the message reaches the user. Use this for
almost every "the tool ran and didn't work" case.
- **Protocol error** — `Err(McpError)` with a JSON-RPC code (e.g.
`McpError::invalid_params(..)`). Use this when the server can't route or process
the request at all; clients render these opaquely, so the caller does **not**
see your message.
```rust,ignore
use rmcp::model::{CallToolResult, ContentBlock};
use rmcp::ErrorData as McpError;
#[tool(description = "Look up a record")]
async fn lookup(&self, Parameters(args): Parameters<LookupArgs>) -> Result<CallToolResult, McpError> {
// Malformed request — the server can't run anything → protocol error.
if args.query.is_empty() {
return Err(McpError::invalid_params("query must be non-empty", None));
}
// Tool ran, no result → tool-level error the user should see.
let rows = self.run_query(&args.query).await;
if rows.is_empty() {
return Ok(CallToolResult::error(vec![ContentBlock::text(
format!("no rows matched '{}'", args.query),
)]));
}
Ok(CallToolResult::success(vec![ContentBlock::text(format_rows(&rows))]))
}
```
### Client-side
```rust,ignore
@ -328,6 +402,16 @@ impl ServerHandler for MyServer {
"memo://insights" => Ok(ReadResourceResult::new(vec![
ResourceContents::text("Analysis results...", &request.uri),
])),
// Binary resource — base64-encode the bytes and return a blob.
"file:///logo.png" => {
use base64::{Engine, prelude::BASE64_STANDARD};
let bytes = std::fs::read("logo.png").unwrap_or_default();
let blob = BASE64_STANDARD.encode(bytes);
Ok(ReadResourceResult::new(vec![
ResourceContents::blob(blob, &request.uri)
.with_mime_type("image/png"),
]))
}
_ => Err(McpError::resource_not_found(
"resource_not_found",
Some(json!({ "uri": request.uri })),
@ -487,6 +571,30 @@ Prompt functions support several return types:
- `GetPromptResult` -- messages with an optional description
- `Result<T, McpError>` -- either of the above, with error handling
#### Image and embedded-resource content
A `PromptMessage` can also carry an image or embedded resource. Use the
dedicated constructors (image/audio require the `base64` feature):
```rust,ignore
use rmcp::model::{PromptMessage, Role};
// Image content — raw bytes are base64-encoded for you.
let screenshot: &[u8] = load_png();
let msg = PromptMessage::new_image(Role::User, screenshot, "image/png", None, None);
// Embedded resource — inline a text resource by URI. Pass `Some(text)` for a
// text resource, or `None` for a blob resource.
let msg = PromptMessage::new_resource(
Role::User,
"file:///spec.md".to_string(),
Some("text/markdown".to_string()),
Some("# Specification\n...".to_string()),
None, None, None,
);
# fn load_png() -> &'static [u8] { &[] }
```
### Client-side
```rust
@ -589,6 +697,140 @@ impl ClientHandler for MyClient {
---
## Elicitation
Elicitation lets a server pause mid-operation to ask the user for input, in one
of two modes: **form mode** (structured fields with a JSON Schema) or **URL
mode** (send the user to a web page and wait for completion).
**MCP Spec:** [Elicitation](https://modelcontextprotocol.io/specification/2026-07-28/client/elicitation)
### Server-side (form mode)
Define a struct deriving `JsonSchema`, mark it `elicit_safe!`, and call
`elicit::<T>()` on the peer. Schema validation, defaults, and enum choices all
come from the type.
```rust,ignore
use rmcp::{elicit_safe, model::*, service::{RequestContext, RoleServer}};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
#[schemars(description = "User information")]
pub struct UserInfo {
#[schemars(description = "User's name")]
pub name: String,
// Optional field; omitted if the user doesn't provide it.
#[serde(default)]
#[schemars(description = "Preferred greeting")]
pub greeting: Option<String>,
}
// Whitelist the type for elicitation (schema-validated on both ends).
elicit_safe!(UserInfo);
#[tool(description = "Greet the user")]
async fn greet(&self, ctx: RequestContext<RoleServer>) -> Result<CallToolResult, McpError> {
// Returns Ok(Some(UserInfo)) if the user accepts.
// Decline and cancel are returned as ElicitationError variants.
match ctx.peer.elicit::<UserInfo>("Please provide your name").await {
Ok(Some(info)) => Ok(CallToolResult::success(vec![ContentBlock::text(
format!("Hello, {}!", info.name),
)])),
Ok(None) => Ok(CallToolResult::success(vec![ContentBlock::text(
"No name provided.",
)])),
Err(e) => Ok(CallToolResult::error(vec![ContentBlock::text(
format!("Elicitation failed: {e}"),
)])),
}
}
```
#### Enum values
Enum fields become a choice list. `schemars` needs two hints to inline and type
the enum correctly:
```rust,ignore
#[derive(Debug, Serialize, Deserialize, JsonSchema, Default)]
#[schemars(inline)] // inline the enum into the parent schema
#[schemars(extend("type" = "string"))] // schemars omits `type` for enums; add it
enum Priority {
#[schemars(title = "Low priority")]
#[default]
Low,
#[schemars(title = "High priority")]
High,
}
```
See [`examples/servers/src/elicitation_enum_inference.rs`](examples/servers/src/elicitation_enum_inference.rs)
for single-select, multi-select, titled, and defaulted enum forms.
### Server-side (URL mode)
For flows a form can't capture (OAuth consent, a payment page), send the user to
a URL. `elicit_url` returns the user's `ElicitationAction` rather than typed data:
```rust,ignore
use rmcp::model::ElicitationAction;
use url::Url;
let action = ctx.peer.elicit_url(
"Please complete setup in your browser",
Url::parse("https://example.com/setup").unwrap(),
"setup-123", // a unique elicitation id
).await?;
match action {
ElicitationAction::Accept => { /* user consented */ }
ElicitationAction::Decline => { /* user declined */ }
ElicitationAction::Cancel => { /* user aborted */ }
}
```
### Client-side
Implement `ClientHandler::create_elicitation()`, matching on the request variant
to handle form vs. URL mode:
```rust,ignore
use rmcp::{ClientHandler, model::*, service::{RequestContext, RoleClient}};
impl ClientHandler for MyClient {
async fn create_elicitation(
&self,
request: ElicitRequestParams,
_context: RequestContext<RoleClient>,
) -> Result<ElicitResult, ErrorData> {
match request {
ElicitRequestParams::FormElicitationParams { message, .. } => {
// Show `message` + the requested schema, collect input, then:
Ok(ElicitResult {
action: ElicitationAction::Accept,
content: Some(rmcp::object!({ "name": "Ada" })),
meta: None,
})
}
ElicitRequestParams::UrlElicitationParams { url, .. } => {
// Open `url`, wait for the user, then report the action.
let _ = url;
Ok(ElicitResult { action: ElicitationAction::Accept, content: None, meta: None })
}
}
}
}
```
On completion the client sends a `notifications/elicitation/response`
notification to release the waiting server-side `elicit_url` call.
**Example:** [`examples/servers/src/elicitation_stdio.rs`](examples/servers/src/elicitation_stdio.rs) (form + URL), [`examples/servers/src/elicitation_enum_inference.rs`](examples/servers/src/elicitation_enum_inference.rs) (enum forms)
---
## Roots
> **Deprecated (SEP-2577):** Roots is deprecated and will be removed in a future release. It remains fully functional for now. See [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577).
@ -1239,6 +1481,187 @@ let client = ClientInfo::default().serve(transport).await?;
---
## Transports
A transport moves JSON-RPC messages between client and server. Any `Transport`
impl can be passed to `.serve(..)`; `rmcp` ships the common ones behind Cargo
features.
**MCP Spec:** [Transports](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports)
| Transport | Feature(s) | Notes |
| ---------------------------- | -------------------------------------------- | ----- |
| **stdio** | `transport-io` (client + server) | Communicate over `stdin`/`stdout`; the standard way to launch local MCP servers as child processes. |
| **Child process** (client) | `transport-child-process` | Spawn a server binary and talk to it over its stdio. |
| **Streamable HTTP** (server) | `transport-streamable-http-server` | The current HTTP transport. Exposes a Tower service you can mount on any router. |
| **Streamable HTTP** (client) | `transport-streamable-http-client-reqwest` | HTTP client transport built on `reqwest`. |
| **Worker / in-process** | `transport-worker` | For embedding or testing without real I/O. |
### stdio
```rust,ignore
use rmcp::{ServiceExt, transport::stdio};
// Server: serve over stdin/stdout.
let server = MyServer.serve(stdio()).await?;
server.waiting().await?;
```
```rust,ignore
use rmcp::{ServiceExt, transport::{TokioChildProcess, ConfigureCommandExt}};
use tokio::process::Command;
// Client: launch a server binary and talk to it over its stdio.
let transport = TokioChildProcess::new(Command::new("uvx").configure(|cmd| {
cmd.arg("mcp-server-git");
}))?;
let client = ().serve(transport).await?;
```
### Streamable HTTP
`StreamableHttpService` is a Tower service — mount it on any `axum`/`hyper`
router (see [Stateless Streamable HTTP](#stateless-streamable-http) for the full
server example). The client transport connects with a single URI:
```rust,ignore
use rmcp::transport::StreamableHttpClientTransport;
let transport = StreamableHttpClientTransport::from_uri("http://localhost:8000/mcp");
let client = ClientInfo::default().serve(transport).await?;
```
#### A note on SSE
Streamable HTTP responses arrive as either a single `application/json` body or a
`text/event-stream` (Server-Sent Events) stream when the server pushes
notifications or requests before the result. `rmcp` handles both automatically
(SSE parsing lives behind the `client-side-sse` feature). There is no separate
"SSE transport" to configure — it's an implementation detail of Streamable HTTP.
> The standalone HTTP+SSE transport from `2024-11-05` is superseded by Streamable
> HTTP. For server-to-client streaming under `2026-07-28`, see
> [Subscriptions](#subscriptions).
---
## Pagination
List operations (`tools/list`, `prompts/list`, `resources/list`,
`resources/templates/list`) are paginated via a `next_cursor`. The `list_all_*`
helpers walk every page for you:
```rust,ignore
// Fetches all pages transparently.
let tools = client.list_all_tools().await?;
let prompts = client.list_all_prompts().await?;
let resources = client.list_all_resources().await?;
```
To page manually, call the single-page method and follow `next_cursor` until
it's `None`:
```rust,ignore
use rmcp::model::PaginatedRequestParams;
let mut cursor = None;
loop {
let page = client
.list_tools(Some(PaginatedRequestParams { meta: None, cursor }))
.await?;
for tool in &page.tools {
// handle each tool
}
cursor = page.next_cursor;
if cursor.is_none() {
break;
}
}
```
On the server, return a `next_cursor` from your `list_*` handler when more pages
remain (`None` when complete).
**MCP Spec:** [Pagination](https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/pagination)
---
## Capability & Protocol Version Negotiation
### Capabilities
At initialization, client and server exchange **capabilities** so each side
knows what the other supports. Declare yours with the `ServerCapabilities`
builder in `get_info()`:
```rust,ignore
use rmcp::model::{ServerCapabilities, ServerInfo};
fn get_info(&self) -> ServerInfo {
ServerInfo::new(
ServerCapabilities::builder()
.enable_tools()
.enable_prompts()
.enable_resources()
.enable_resources_subscribe()
.enable_tool_list_changed()
.enable_logging()
.build(),
)
}
```
Clients do the same via `ClientCapabilities::builder()`. Macros like
`#[tool_handler]` / `#[prompt_handler]` set the relevant flags automatically.
After connecting, read the peer's capabilities via `peer.peer_info()`.
### Protocol version
MCP is versioned by date. `rmcp` negotiates automatically on connect — the
client offers a preferred `ProtocolVersion` and falls back to one the server
supports:
```rust,ignore
use rmcp::model::ProtocolVersion;
ProtocolVersion::LATEST; // newest stable version this SDK defaults to
ProtocolVersion::V_2026_07_28; // a specific version constant
ProtocolVersion::KNOWN_VERSIONS; // every version this SDK understands
```
Version-specific behavior (SEP-2243 headers, SEP-2567 stateless serving, the
SEP-2575 subscription model) is gated on the negotiated version, so older clients
keep working while newer ones opt in.
**MCP Spec:** [Versioning and Compatibility](https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning)
---
## JSON Schema 2020-12
Deriving `schemars::JsonSchema` on your parameter and result types generates
[JSON Schema draft 2020-12](https://json-schema.org/) — the dialect the MCP spec
requires — for the tool's `inputSchema` and `outputSchema`.
```rust,ignore
use rmcp::schemars;
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
struct SearchParams {
/// Full-text query.
query: String,
/// Maximum number of results.
#[serde(default)]
limit: Option<u32>,
}
```
Field names, types, and doc comments flow into the schema — no manual authoring
needed. As of `2026-07-28` (SEP-2106), `outputSchema` may be any JSON Schema type
(not only `object`) and `structuredContent` may be any JSON value.
---
## Examples
See [examples](examples/README.md).

View file

@ -1,116 +1,96 @@
# RMCP Roadmap
This roadmap tracks the path to SEP-1730 Tier 1 for the Rust MCP SDK.
This roadmap tracks the path to [SEP-1730](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1730) Tier 1 for the Rust MCP SDK.
Spec 2025-11-25 (suite 0.1.16): Server 100% (30/30) · Client 100% (18/18)
Spec 2026-07-28 (suite 0.2.0-alpha.9): Server 97.5% (39/40) · Client 90.6% (29/32)
**Status (2026-07-29):** conformance is 100% across every date-versioned suite, and
the stable **v3.0.0** release has shipped. The remaining Tier 1 work is documentation
coverage and two governance documents.
Extension scenarios are reported separately below because they are
informational and do not count toward SDK tiering.
| Suite (date-versioned) | Server | Client |
| ---------------------- | ------------- | ------------- |
| 2025-11-25 | 100% (30/30) | 100% |
| 2026-07-28 | 100% (30/30) | 100% |
Only date-versioned scenarios count toward SDK tiering. `draft` (2026-07-28 draft)
and `extension` scenarios are informational and reported separately below.
---
## Target spec: 2026-07-28 (release 2026-07-28)
## Conformance
All 2026-07-28 work carries the `2026-07-28` label and the
[`2026-07-28 spec` milestone](https://github.com/modelcontextprotocol/rust-sdk/milestone/3).
Per-scenario conformance status is tracked in the epic issue:
[#977 — Tracking: 2026-07-28 spec conformance](https://github.com/modelcontextprotocol/rust-sdk/issues/977).
Per-scenario status for the current spec is tracked in the epic issue
[#977 — Tracking: 2026-07-28 spec conformance](https://github.com/modelcontextprotocol/rust-sdk/issues/977),
under the [`2026-07-28 spec` milestone](https://github.com/modelcontextprotocol/rust-sdk/milestone/3).
### Versioned-spec conformance (baseline 2026-07-21, suite `0.2.0-alpha.9`)
CI (`.github/workflows/conformance.yml`) runs the full `2025-11-25` and `2026-07-28`
server and client suites on every push and PR. Both suites are fully green.
- Server: 1 expected failure: `json-schema-2020-12`
- Client: 3 expected failures: `tools_call`, `auth/scope-step-up`, and `auth/authorization-server-migration`
- CI: runs the complete `2026-07-28` versioned-spec suites with a strict baseline; an unlisted failure or a listed scenario that starts passing fails the build
### Informational (not scored for tiering)
### Extension conformance (informational)
Extension-tagged scenarios are excluded by `--spec-version` filters, so CI runs them
in separate steps against `conformance/expected-failures-extensions.yaml`:
Extension-tagged scenarios are excluded by `--spec-version` filters, so CI
runs them in separate server and client steps with
`conformance/expected-failures-extensions.yaml`.
- SEP-2663 Tasks server: 9 expected failures; `tasks-status-notifications` is currently skipped by the upstream harness; tracked in #868
- Client extensions: `auth/client-credentials-basic` and `auth/client-credentials-jwt` pass; `auth/enterprise-managed-authorization` is an expected failure
| Scenario | Tag | Status |
| --------------------------------------- | ---------- | ------ |
| `auth/client-credentials-basic` | extension | ✅ Pass |
| `auth/client-credentials-jwt` | extension | ✅ Pass |
| `auth/enterprise-managed-authorization` | extension | ❌ Expected failure (not implemented by the conformance client) |
| `auth/wif-jwt-bearer` | 2026-07-28 draft | ❌ Expected failure (WIF / SEP-1933, draft) |
| `tasks-*` (SEP-2663) | extension | ❌ 9 expected failures · ⏭️ 1 upstream-skipped |
### Spec features without conformance scenarios
Conformance alone does not cover the full spec surface. Feature work tracked via the milestone:
Conformance does not cover the entire spec surface. Remaining feature work tracked via
the milestone:
- SEP-2567 sessionless MCP via explicit state handles (#870)
- SEP-2260 server requests must associate with a client request (#873)
- SEP-2549 follow-up: client-side TTL-honoring cache (#974)
(SEP-2575 discovery & negotiation is covered by the `server-stateless` conformance scenario;
implementation is in review — #869, PRs #973, #943.)
### Release
The 2026-07-28 implementation ships as **v3.0.0** (release PR #964): MRTR, SEP-2549 cache hints,
SEP-2243 standard headers, and the SEP-2106 relaxations are merged but unreleased — tiering and
relegation are evaluated against the latest stable release, so cutting v3.0.0 with the remaining
conformance fixes is on the critical path. Migration guide (draft, kept current until release):
[discussion #969](https://github.com/modelcontextprotocol/rust-sdk/discussions/969).
---
## Tier 1 (non-conformance requirements)
## Tier 1 — remaining work
Conformance, stable release, labels, issue triage, and spec-tracking already meet the
Tier 1 bar. What's left:
### Documentation (Tier 1 requires all non-experimental features documented with examples)
The README now documents core primitives comprehensively with linked examples.
### Governance & Policy
- [ ] Create `VERSIONING.md` — document semver scheme, what constitutes a breaking change, and how breaking changes are communicated
- [ ] Publish a dependency update policy (Tier 1 requires a published policy)
- [ ] Cut v3.0.0 (#964) including all conformance fixes (tier relegation is evaluated against the latest stable release)
- [ ] Add `VERSIONING.md` — document the semver scheme, what constitutes a breaking
change, and how breaking changes are communicated (migration guides are linked
from the README but the policy itself is not yet written down).
- [ ] Add `DEPENDENCY_POLICY.md` — a published dependency update policy (Dependabot is
configured in `.github/dependabot.yml`, but Tier 1 requires a written, findable policy).
- [ ] Re-triage mislabeled `P0` issues — #869 / #871 / #872 are SEP *feature*
implementation tasks, not critical bugs; they should not carry `P0`. Reserving
`P0` for genuine critical bugs keeps the SEP-1730 critical-bug-resolution metric
accurate.
### Documentation (26/48 → 48/48 features with prose + examples)
### Nice-to-have (scorecard hygiene)
#### Undocumented features (14)
- [ ] Tools — image results
- [ ] Tools — audio results
- [ ] Tools — embedded resources
- [ ] Prompts — embedded resources
- [ ] Prompts — image content
- [ ] Elicitation — URL mode
- [ ] Elicitation — default values
- [ ] Elicitation — complete notification
- [ ] Ping
- [ ] SSE transport — legacy (client)
- [ ] SSE transport — legacy (server)
- [ ] Pagination
- [ ] Protocol version negotiation
- [ ] JSON Schema 2020-12 support *(upgrade from partial)*
#### Partially documented features (7)
- [ ] Tools — error handling *(add dedicated prose + example)*
- [ ] Resources — reading binary *(add dedicated example)*
- [ ] Elicitation — form mode *(add prose docs, not just example README)*
- [ ] Elicitation — schema validation *(add prose docs)*
- [ ] Elicitation — enum values *(add prose docs)*
- [ ] Capability negotiation *(add dedicated prose explaining the builder API)*
- [ ] Protocol version negotiation *(document version negotiation behavior)*
- [ ] Add a top-level `CHANGELOG.md` (release notes are currently managed by release-plz).
- [ ] Add a top-level `CONTRIBUTING.md` (contributor docs currently live at `docs/CONTRIBUTE.MD`).
---
## Completed
- [x] 2025-11-25 server conformance 100% (30 scenarios + pending `json-schema-2020-12`, `server-sse-polling`)
- [x] 2025-11-25 client conformance 100% (18 scenarios + legacy `auth/2025-03-26-*`)
- [x] SEP-2322 MRTR (14 server scenarios + `sep-2322-client-request-state`)
- [x] **v3.0.0 stable released** (2026-07-28) — MRTR, SEP-2549 cache hints, SEP-2243
standard headers, SEP-2575 stateless MCP, and SEP-2106 relaxations
- [x] 2025-11-25 server conformance 100% (30/30)
- [x] 2025-11-25 client conformance 100%
- [x] 2026-07-28 server conformance 100% (30/30 dated)
- [x] 2026-07-28 client conformance 100% (dated)
- [x] SEP-2322 MRTR (server scenarios + `sep-2322-client-request-state`)
- [x] SEP-2575 Make MCP Stateless (`server-stateless`)
- [x] SEP-2164 resource not found
- [x] Cache hints (`caching`)
- [x] `http-header-validation`
- [x] Issue triage labels (bug, enhancement, needs confirmation, needs repro, ready for work, P0–P3)
---
## Informational (not scored for tiering)
These extension scenarios are tracked but do not count toward tier advancement:
| Scenario | Tag | Status |
|---|---|---|
| `auth/client-credentials-jwt` | extension | ✅ Passed |
| `auth/client-credentials-basic` | extension | ✅ Passed |
| `auth/enterprise-managed-authorization` | extension | ❌ Failed — scenario is not implemented by the conformance client |
| `tasks-*` | extension | ❌ 9 expected failures · ⏭️ 1 upstream-skipped scenario |
- [x] SEP-2549 cache hints (`caching`)
- [x] SEP-2243 HTTP standardization (`http-header-validation`, standard headers)
- [x] DNS rebinding protection
- [x] Full SEP-1730 issue-triage label taxonomy (bug, enhancement, question,
needs confirmation, needs repro, ready for work, good first issue, help wanted, P0–P3)
- [x] `SECURITY.md` and Dependabot configuration

66
VERSIONING.md Normal file
View file

@ -0,0 +1,66 @@
# Versioning Policy
`rmcp` and `rmcp-macros` follow [Semantic Versioning 2.0.0](https://semver.org/),
the standard versioning scheme for the Rust/Cargo ecosystem. Given a version
`MAJOR.MINOR.PATCH`:
- **MAJOR** — incremented for breaking changes to the public API.
- **MINOR** — incremented for new, backwards-compatible functionality.
- **PATCH** — incremented for backwards-compatible bug fixes.
The `rmcp` and `rmcp-macros` crates are versioned together and released with the
same version number.
## What counts as a breaking change
We treat a change as breaking if it would cause code that compiled against the
previous release to fail to compile, or to change behavior in a way callers could
not reasonably anticipate. This follows the Cargo SemVer guidelines
([The Cargo Book — SemVer Compatibility](https://doc.rust-lang.org/cargo/reference/semver.html)).
Examples include:
- Removing or renaming a public item (function, type, trait, module, variant, field).
- Changing a public function signature, trait method, or trait bound.
- Adding a required method to a public trait, or a field to a struct that callers
construct directly. (Public structs and enums are marked `#[non_exhaustive]` where
practical so that additive changes remain non-breaking.)
- Raising the Minimum Supported Rust Version (MSRV) — see below.
Additive changes — new functions, new types, new enum variants on
`#[non_exhaustive]` enums, new optional Cargo features — are **not** breaking and
ship in MINOR releases.
### Cargo features
The public API is feature-gated. SemVer compatibility is evaluated against the
crate's **default features** and all features except `local`.
## Pre-1.0 crates in the workspace
The core crates (`rmcp`, `rmcp-macros`) are `>= 1.0.0` and follow the rules above.
Any workspace crate still in `0.x` follows Cargo's pre-1.0 convention, where the
`MINOR` version acts as the breaking-change signal (`0.MINOR.PATCH`).
## Minimum Supported Rust Version (MSRV)
The MSRV is declared via `rust-version` in `Cargo.toml`. Raising the MSRV is
considered a breaking change and is only done in a MINOR or MAJOR release with a
note in the release notes.
## How breaking changes are communicated
- Release automation is handled by [release-plz](https://release-plz.dev/), which
derives version bumps from [Conventional Commits](https://www.conventionalcommits.org/)
(`feat!:` / `fix!:` and `BREAKING CHANGE:` footers trigger a MAJOR bump) and runs
`cargo-semver-checks` to catch unintended API breaks.
- Every release is published as a tagged [GitHub Release](https://github.com/modelcontextprotocol/rust-sdk/releases)
with generated notes.
- Significant migrations (for example the 2.x → 3.x upgrade) are accompanied by a
migration guide linked from the release notes and the README.
## Yanking
A published version that is later found to be broken or to contain a security issue
may be [yanked](https://doc.rust-lang.org/cargo/commands/cargo-yank.html) from
crates.io. Yanking prevents new dependents from selecting the version but does not
delete it; a fixed PATCH release is published alongside.