chore(conformance): meeting requirements for tier 1 (#1087)
* chore(conformance): meeting requirements for tier 1 * chore: address PR feedback
This commit is contained in:
parent
1d0473ba75
commit
51c8ee60e3
4 changed files with 617 additions and 83 deletions
65
DEPENDENCY_POLICY.md
Normal file
65
DEPENDENCY_POLICY.md
Normal 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
423
README.md
|
|
@ -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).
|
||||
|
|
|
|||
146
ROADMAP.md
146
ROADMAP.md
|
|
@ -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
66
VERSIONING.md
Normal 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.
|
||||
Loading…
Reference in a new issue