* fix(rmcp): route peer cancellation by lifecycle
Cancel inbound legacy requests by their exact request ID. Preserve modern
subscription waiter cleanup using the peer lifecycle and outbound request kind.
Suppress cancelled handler results and errors during EOF draining without
dropping uncancelled responses during graceful shutdown.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* test(rmcp): parameterize reverse cancellation cases
Use rstest to report each startup and request ID combination independently.
Keep the existing per-case timeout and cancellation assertions.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* fix(rmcp): use canonical initialize result in cancellation test
Replace the stale ServerInfo reference after merging the upstream alias migration.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Hunter Sadler <aurokin@github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* fix(model): deprecate ServerInfo and ClientInfo aliases
Those names collide with the protocol's serverInfo/clientInfo fields,
which are only Implementation identity. Prefer InitializeResult and
InitializeRequestParams. Handler trait signatures, the tool_handler
macro output, tests, examples and docs now use the canonical types; the
aliases remain as deprecated shims for the rest of 3.x.
Fixes#1082
* fix(model): keep public signatures on the deprecated aliases
The public API check compares rendered signatures, and rustdoc records a
type alias by the name it was written with. Respelling
`ServerHandler::get_info`, `ClientHandler::get_info`,
`DiscoverResult::from_server_info` and `impl ClientHandler for ClientInfo`
onto `InitializeResult`/`InitializeRequestParams` therefore reads as
changed public items even though the aliases resolve to those exact types
and nothing downstream breaks.
Put those four signatures back on the aliases so the published API is
byte-identical, and note why at the alias definitions. The deprecation
still steers new code, and the macro output, tests, examples and docs
keep using the canonical names.
* fix(model): migrate leftover ClientInfo test sites
Two tests landed on main after the alias deprecation and still named
ClientInfo. Clippy -D warnings and the no-local feature test job both
failed on them.
`ClientLifecycleMode::Auto` only fell back from `server/discover` on
`-32601`, so legacy servers that reject the probe with other codes
(`-32600`, `-32602`, implementation-defined errors) failed to connect
even though `initialize` would have succeeded.
The previous attempt (indicates_legacy_server) classified the failure
after the fact by reverse-engineering the error type. This rewrite moves
the classification into `discover_startup` itself, where the full context
(request id, response correlation, transport state) is still available.
`discover_startup` now returns `DiscoverOutcome`: `Modern` on success,
`Legacy(error)` when the probe received a complete, correlated JSON-RPC
error whose code is not a modern-era rejection. Every other failure
becomes `Err`, so `Auto` simply matches the outcome — no methods on
`ClientInitializeError`, no downcast, no transport-specific types
leaking into the generic lifecycle layer.
Additional fixes that fall out naturally:
- Response correlation is now checked in `expect_response` for both
success and error branches. Previously error responses skipped id
correlation entirely. A new `UncorrelatedErrorResponse` variant
surfaces responses that cannot be tied to the request.
- When both discover and the legacy fallback fail, a
`LegacyFallbackFailed` compound error preserves both phases instead
of discarding the discover error.
Fixes#1040.
* refactor(service): express SEP-2260 receive-side association as an enum
Replaces the has_pending_outbound_request bool on
enforce_peer_request_association with PeerRequestAssociation, so a
stream-separating transport can report per-request association (#1033).
Behavior-preserving: the event loop still passes the coarse signal as
Unknown.
* feat(transport): record inbound stream origin on streamable HTTP client (#1033)
The worker attaches an InboundStreamOrigin extension to each inbound
server request: Unassociated for the standalone GET stream,
OutboundRequest(id) for a POST's SSE stream. Mirror of the
OriginatingRequestId marker used by the server side.
* feat(service): enforce SEP-2260 client receive-side check per stream origin (#1033)
The event loop maps InboundStreamOrigin plus the in-flight responder
pool to PeerRequestAssociation: restricted requests arriving on the
standalone GET stream are now rejected with -32602 even while unrelated
outbound requests are in flight.
* test: end-to-end SEP-2260 stream-based enforcement over streamable HTTP (#1033)
* docs: cross-link SEP-2260 stream markers
* test: origin marker survives SSE resumption per SEP-2260 (#1033)
A POST SSE stream resumed via GET + Last-Event-ID (SEP-1699) reconnects
beneath execute_sse_stream, so requests replayed after a resume keep
their OutboundRequest origin. Pins the layering invariant: hoisting
reconnection above the marker attach point would wrongly reject
associated requests with -32602.
* docs: trim SEP-2260 comments to spec rationale
* docs: align SEP-2577 expect reason with sibling suppressions
* test: harden SEP-2260 stream-enforcement e2e (#1033)
Detect handler invocation via a channel asserted empty instead of a
panic in a spawned task (swallowed, cannot fail the test); surface
scripted-server misuse as transport errors rather than panics in the
transport task; bound the tail awaits with 5s timeouts. Correct the
header comment: a 2026-07-28 server minting a session id is not
spec-legal (SEP-2567 removes sessions and the GET endpoint) — the
scripted server is deliberately non-conforming, which is the point of
receive-side enforcement.
* test: adapt SEP-2260 association tests to ServerPeerInfo
Rebase onto 3.0.0: RoleClient::PeerInfo is now ServerPeerInfo (#1065)
and its constructor takes the protocol version directly.
* chore: add 'Copy' macro to new PeerRequestAssociation
Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
---------
Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com>
* feat: implement SEP-2260 require server requests to associate with client requests
* feat(service): attach originating request id to in-handler outbound requests (SEP-2260)
Reuses the ORIGINATING_REQUEST task-local from #1027; the marker rides
the request's non-serialized Extensions so the streamable HTTP server
can route associated requests to the originating SSE stream.
* feat(transport): route associated server requests to originating SSE stream (SEP-2260)
* test: end-to-end SEP-2260 stream routing over streamable HTTP
* test: register SEP-2260 routing test with required-features; drop dead list_tools
* docs: document SEP-2260 association requirement and stream routing
* docs: fix redundant explicit link on OriginatingRequestId
* docs: note marker is role-agnostic; add reason to deprecated expect
Review follow-ups: the OriginatingRequestId rustdoc now states the marker
is attached for both roles with only the server transport reading it, and
the test's deprecation suppression carries a reason per house style.
* docs: condense inline comments to repo convention
Trims multi-line inline comments added in this branch down to the terse
one-to-two-line style used elsewhere in the codebase, keeping only the
non-obvious rationale (spec references, fallback invariant, version choice).
* docs: condense SEP-2260 rustdoc sections
Tightens the duplicated per-method association section from eleven lines
to five, and trims the marker and SessionManager docs to the same
information in fewer words.
* docs: single-source SEP-2260 caller requirements on OriginatingRequestId
* feat(service): reject unassociated server-to-client requests on the client (SEP-2260)
Implements the client receive-side of SEP-2260: restricted server
requests (sampling/createMessage, roots/list, elicitation/create)
received while the client has no outbound request in flight are
answered with -32602 invalid params instead of being dispatched to the
handler. Gated on negotiated protocol >= 2026-07-28; ping is exempt.
With a request in flight we cannot tell which one the server request
belongs to (SEP-2260 defines no wire field), so this is a deliberate
under-approximation of the spec's SHOULD; exact stream-based
enforcement for streamable HTTP needs receive-side provenance plumbing
and is left as a follow-up.
---------
Co-authored-by: Alex Hancock <alexhancock@block.xyz>
* feat!: implement SEP-2663 Tasks extension, removing the experimental 2025-11-25 tasks design
Reshape tasks from the experimental SEP-1319/1686 core-protocol feature into
the official io.modelcontextprotocol/tasks extension (SEP-2663):
Model:
- Re-model Task (statusMessage, ttlMs nullable, pollIntervalMs) and add
DetailedTask with status-discriminated payloads (inputRequests/result/error
inlined per spec)
- CreateTaskResult now flattens Task with resultType: "task";
add ResultType::TASK and CallToolResponse::Task
- Add tasks/update (UpdateTaskParams with MRTR InputResponses); tasks/get and
tasks/cancel reworked; remove tasks/list, tasks/result
- notifications/tasks now carries a full DetailedTask; removed from client
notifications (SEP-2260)
Capabilities:
- Remove core TasksCapability et al; tasks are declared via the extensions
map (enable_tasks() builders, supports_tasks() accessors)
- Remove tool-level execution.taskSupport and the _meta.task /
TaskMetadata / with_task() opt-in: task creation is server-directed and
gated on the per-request client capability
Runtime:
- Replace OperationProcessor with TaskManager: durable-before-response task
creation, input_required round-trips via tasks/update, cooperative
cancellation, TTL expiry
- Client peer helpers get_task/update_task/cancel_task
- Emit Mcp-Name routing header from params.taskId for tasks/* methods
(SEP-2243/2663)
Macros:
- Remove #[task_handler] and the tool execution() attribute
Tests/examples/docs updated; schema snapshots regenerated.
BREAKING CHANGE: the 2025-11-25 experimental tasks API is removed without a
compatibility shim. Clients that do not declare the tasks extension always
receive synchronous results.
* feat: gate tasks/* methods on the client tasks-extension capability (SEP-2663)
When the server advertises io.modelcontextprotocol/tasks but the client did
not declare it (per-request _meta clientCapabilities, or initialize-time
capabilities in session mode), tasks/get, tasks/update, and tasks/cancel now
return -32021 Missing Required Client Capability with the required extension
in the error data, instead of falling through to the handler's -32601
default. Servers that do not advertise the extension keep returning -32601.
Also add regression tests confirming that unknown taskIds yield -32602 and
that a legacy 2025-11-25 'task' param on tools/call is silently ignored.
* feat: pass SEP-2663 Tasks extension conformance suite
Conformance fixtures (conformance/src/bin/server.rs):
- Add the required fixture tools: greet (sync-only), slow_compute,
failing_job (task support: required), protocol_error_job, confirm_delete,
multi_input, and test_tool_with_task (MRTR -> task escalation), all backed
by TaskManager with server-directed task creation gated on the client's
tasks-extension capability
- Advertise io.modelcontextprotocol/tasks in server capabilities and wire
get_task/update_task/cancel_task handlers
- Task-required tools reject with -32021 when the client did not declare the
extension
SDK wire-shape fixes surfaced by the suite:
- Add resultType: "complete" to GetTaskResult and introduce TaskAckResult
so tasks/update and tasks/cancel acks carry the SEP-2322 discriminator
(spec: every non-CreateTaskResult response on the tasks surface is
resultType complete); dispatch now returns ServerResult::task_ack
- CreateTaskResult gets a strict deserializer requiring resultType: "task"
so it does not shadow task-shaped results in untagged unions
- Client update_task/cancel_task accept both TaskAckResult and EmptyResult
All 9 runnable Tasks extension server scenarios now pass (35/35 checks;
tasks-status-notifications remains upstream-skipped), so the corresponding
entries are removed from conformance/expected-failures-extensions.yaml.
2025-11-25 server suite (40/40), 2026-07-28 server suite (114/114), draft
client suite, and extensions client suite all pass their baselines.
* fix: address PR 1020 review feedback on DetailedTask schema and cooperative cancel
- DetailedTask's JsonSchema now derives from the actual flattened wire shape
(DetailedTaskWire: base Task + optional inputRequests/result/error) instead
of approximating with the base Task schema, so generated schemas for
GetTaskResult and notifications/tasks document the status-specific payload
fields. Golden message schemas regenerated.
- TaskManager::cancel_task no longer aborts the underlying future. It records
the observable cancelled state and acks immediately (spec's eventually
consistent semantics), but lets the operation keep running so it can observe
is_cancel_requested() or the error from a woken request_input() and perform
cleanup; any late result is discarded. New unit tests cover both the
cooperative-cleanup path and waking parked input requests.
* fix: make tasks/cancel truly cooperative per SEP-2663 (FEEDBACK_2)
- TaskManager::cancel_task no longer forces terminal 'cancelled'. It records
the cancellation intent, acks immediately, and wakes parked request_input
awaits, but the operation decides its own terminal state: a post-cancel
error settles as 'cancelled', while an operation that finishes its work
settles as 'completed' — per the spec, 'the task may still reach a
non-cancelled terminal status'.
- Add TaskContext::cancelled(), a watch-based await for use with
tokio::select! as the cooperative cancellation exit path.
- Correct the unsupported-notification method string from
'notifications/tasks/status' to 'notifications/tasks' and document that
task status notifications are not yet routable through subscriptions/listen
(SubscriptionFilter has no taskIds field; upstream check still skipped).
- Update conformance slow_compute fixture, task_demo example, and tests to
honor cancellation via ctx.cancelled(); lifecycle conformance still 8/8
(all 9 scenarios remain green, 35/35 checks).
* fix: sweep and evict expired tasks from TaskManager (FEEDBACK_3)
TTL handling previously only ran from get_task and only flipped overdue
non-terminal tasks to 'failed', never removing entries — an unbounded leak
for long-lived servers, and it made ttl_ms: None = 'unlimited retention'
meaningless since everything was retained forever.
- Rename expire_overdue to sweep_expired and run it from every entry point
(spawn, get_task, update_task, cancel_task).
- Track terminal_at on each entry; terminal tasks are evicted after being
retained for one further ttl_ms window past their terminal transition, so
well-behaved pollers can observe the final state before late tasks/get
calls return -32602 (spec: servers may delete expired tasks at any time,
and returning task-not-found for purged tasks is compliant behavior).
- ttl_ms: None entries are never evicted (spec: unlimited retention);
document the retention model on TaskManager, including that there is no
background sweeper.
- New tests: retention-window eviction, sweep of abandoned tasks via other
entry points, unlimited-TTL retention, and error-code assertions
(-32602) for unknown ids across get/update/cancel.
Reviewed the second feedback item (dedicated task-not-found error code)
against SEP-2663 §Protocol Errors and it is incorrect: the SEP explicitly
specifies -32602 (Invalid params) for invalid or nonexistent taskIds, which
is what unknown_task already returns. Kept -32602; strengthened tests to
assert the code.
* fix: address Copilot review feedback on PR #1020
- TaskManager: drop the JoinHandle as soon as the operation settles instead
of retaining it for the whole retention window, and only store it at spawn
time if the task is still non-terminal (avoids keeping a completed handle
that the completion path could never clear).
- Use RequestContext::client_capabilities() (which applies the initialize-time
fallback for session peers) instead of raw context.meta.client_capabilities()
in the task_demo example, the README snippet, and the test server — the
meta-only form would wrongly treat session-declared tasks clients as
unsupported.
All 9 Tasks extension conformance scenarios remain green (35/35 checks).
* fix: enforce SEP-2663 task-result gating in tools/call dispatch
A handler could return CallToolResponse::Task without checking whether the
request declared the io.modelcontextprotocol/tasks extension, sending a
CreateTaskResult to a client that cannot parse it. The SDK dispatch now
rejects that case with -32021 Missing Required Client Capability before the
response leaves the server, using RequestContext::client_capabilities()
(per-request _meta with initialize-time fallback).
Adds a regression test with a deliberately misbehaving handler that always
materializes a task; all Tasks extension conformance scenarios remain green
(35/35 checks).
* fix: align TTL boundary comparisons in sweep_expired
Copilot review: ttlMs: 0 never expired immediately because phase 1 used a
strict 'elapsed > ttl_ms' comparison, and phase 2 retained terminal tasks
at exactly the TTL boundary ('elapsed <= ttl_ms'). Treat elapsed >= ttl_ms
as expired in phase 1 and evict when elapsed >= ttl_ms in phase 2, so both
phases agree at the boundary and ttlMs: 0 expires/evicts on the first sweep.
* fix: strict TaskAckResult deserializer and TASK_REQUIRED_TOOLS consistency
- TaskAckResult carried only resultType (+ optional _meta) with a derived
Deserialize, so inside the untagged ServerResult union it greedily matched
any result object containing a resultType key, shadowing CustomResult and
losing data (verified with a probe). Replace with a strict deserializer:
deny_unknown_fields and require resultType == "complete". Regression
tests cover both the non-matching shapes and the genuine ack shape.
- Conformance fixtures: confirm_delete and multi_input rejected non-tasks
clients inline, contradicting the TASK_REQUIRED_TOOLS doc/constant. Move
them into TASK_REQUIRED_TOOLS (they park on in-task elicitation and have
no synchronous fallback) so the upfront -32021 gate is the single source
of truth, and drop the now-unreachable inline checks.
All 9 Tasks extension conformance scenarios remain green (35/35 checks).
* fix: let the operation decide its terminal state via TaskExit
After tasks/cancel, any operation error was coerced to terminal
'cancelled', masking real failures (e.g. an unrelated error landing just
after a late cancel request) and contradicting the documented 'operation
decides its own terminal state' contract.
Change TaskFuture's error type from McpError to a new TaskExit enum:
- TaskExit::Cancelled — an explicit cooperative-cancellation exit;
settles as terminal 'cancelled'.
- TaskExit::Error(McpError) — a real failure; settles as terminal
'failed' with the error inlined, even after tasks/cancel was received.
From<McpError> for TaskExit keeps '?' ergonomic in task bodies, and
request_input() now returns TaskExit directly (its wake-on-cancel path
yields TaskExit::Cancelled), so parked operations that propagate it with
'?' settle as 'cancelled' automatically.
Update the conformance fixtures, task_demo example, and tests; add a
regression test asserting a post-cancel unrelated error settles as
'failed' with its error payload preserved.
All 9 Tasks extension conformance scenarios remain green (35/35 checks).
* fix: allow TaskExit enum to be exhaustive
* fix: close spawn/shutdown race, sweep in running_task_count, clarify TTL retention docs
Addresses branch-review findings:
- spawn() raced with shutdown(): if shutdown drained the task map between
the entry insert and the JoinHandle store, the handle was dropped and the
operation kept running detached. If the entry is gone at store time, the
handle is now aborted instead.
- running_task_count() now runs the TTL sweep like every other entry point,
so it no longer reports overdue tasks as running (matching the documented
sweep-on-every-entry-point behavior). Regression test added.
- Document that terminal-task retention intentionally extends one ttl_ms
window past the terminal transition (observation grace period) beyond the
creation-based lifetime ttlMs advertises on the wire — compliant since
SEP-2663 allows deleting expired tasks at any time after the TTL.
* feat!: add MRTR behavior support
* feat: harden SEP-2322 MRTR support
* ci: diff public API only on features common to base and head
cargo public-api builds both revisions with the same feature set, so a
feature introduced (or removed) by a PR broke the build of the other
revision. Restrict the all-features diff to features present in both the
base and head revisions.
SEP-2577 deprecates the Roots, Sampling, and Logging features. The
deprecation is advisory: the features stay fully functional and there is
no wire-level change. Mark the corresponding Rust APIs as deprecated so
downstream users get compiler warnings and migration guidance.
- Forward attributes through the service `method!` macros and deprecate
`Peer::create_message`, `Peer::list_roots`, `Peer::set_level`, and
`Peer::notify_logging_message`.
- Forward per-field attributes through the capability `builder!` macro and
deprecate the generated `enable_roots`, `enable_sampling`, and
`enable_logging` builders, plus the hand-written
`enable_roots_list_changed`, `enable_sampling_tools`, and
`enable_sampling_context`.
- Document the deprecation on the capability types and fields, and in the
README feature sections.
- Allow `deprecated` at the crate's own call sites so the build stays
warning-clean, and refresh the message schema snapshots.
* feat: implement SEP-1319 Decouple Request Payload from RPC Methods
* test: update tests
* fix: update handler trait methods to use new types
* fix: update examples
* fix: correct deprecation version
* fix: update wrapper macros to use new *Params type names
* fix: handle logging and ping in handshake
We handle the initialization process more robustly.
- Allow logging and ping
- For other messages, we simply ignore it instead of rejecting right away
* fix: inject context to notification handler
* feat: implement MCP completion specification 2025-06-18
Complete implementation of MCP completion specification with performance optimizations:
Core Features:
- Add CompletionContext for context-aware completion with previously resolved arguments
- Implement CompletionProvider trait with async support and dyn compatibility
- Create DefaultCompletionProvider with optimized fuzzy matching algorithm
- Add comprehensive validation and helper methods to CompletionInfo
- Update ServerHandler to handle completion/complete requests
- Add client convenience methods for prompt and resource completion
Performance Optimizations:
- Zero-allocation fuzzy matching using index-based scoring
- Top-k selection with select_nth_unstable instead of full sorting
- Pre-allocated vectors to avoid reallocations during matching
- Char-based case-insensitive matching to minimize string operations
- 5-8x performance improvement for large candidate sets
API Design:
- Context-aware completion supporting multi-argument scenarios
- Type-safe validation with MAX_VALUES limit (100 per MCP spec)
- Helper methods: with_all_values, with_pagination, validate
- Reference convenience methods: for_prompt, for_resource
- Client methods: complete_prompt_argument, complete_resource_argument
Testing:
- 17 comprehensive tests covering all functionality
- Schema compliance tests for MCP 2025-06-18 specification
- Performance tests with <100ms target for 1000 candidates
- Edge case and validation tests
Schema Updates:
- Add CompletionContext to JSON schema
- Update CompleteRequestParam with optional context field
- Maintain backward compatibility with existing API
* test: add comprehensive fuzzy matching tests for completion
Add three new test cases to enhance coverage of fuzzy matching algorithm:
- test_fuzzy_matching_with_typos_and_missing_chars: Tests subsequence matching
with real-world scenarios including abbreviated patterns, case-insensitive
matching, and complex file/package name completion
- test_fuzzy_matching_scoring_priority: Validates scoring system prioritizes
exact matches > prefix matches > substring matches > subsequence matches
- test_fuzzy_matching_edge_cases: Covers boundary conditions including
single character queries, oversized queries, and repeated characters
These tests ensure robust fuzzy search functionality for MCP completion
specification implementation with proper handling of user typos and
incomplete input patterns.
* feat: improve completion algorithms, add comprehensive tests and example
- Enhance fuzzy matching algorithm with acronym support for multi-word entries
- Add comprehensive scoring system for better relevance ranking
- Implement multi-level matching: exact, prefix, word prefix, acronym, substring
- Add context-aware completion scoring with proper priority ordering
- Optimize performance through efficient character-by-character matching
- Support case-insensitive acronym matching
- Improve code quality with clippy fixes and async fn syntax
- Add comprehensive test suite covering edge cases and acronym matching
- Create completion example server demonstrating weather-related prompts
* fix(test): typos
* refactor: improve completion API and replace example with SQL query builder
- Remove DefaultCompletionProvider from library core
- Move completion logic to examples following review feedback
- Update CompletionContext.argument_names() to return Iterator for better performance
- Replace tech search example with SQL query builder demonstrating progressive completion
- Add context-aware completion that adapts based on filled arguments
- Use proper Option types for optional SQL fields (columns, where_clause, values)
- Demonstrate real-world value of argument_names() method for dynamic completion flow
The SQL query builder showcases:
• Progressive field availability based on operation type
• Context validation using argument_names()
• Proper Optional field handling
• Smart completion that guides user through multi-step form
* fix: fmt
* feat: better http server support
1. allow user get extensions in tool call.
2. allow user serve sse service without initialization.
* fix: fix document test
* refactor: a transport trait and streamable http client
* test: test with js streamable http server
* refactor: separate sse stream connection
* fix(test): streamable http client test with js
* fix(test): wait longer time for js server startup
1. Suppot revision 2025-03-26 data types
2. Support meta, progress tokne, and extensions in request/notification,
3. Remove `Message`, use `JsonRpcMessage` directly
1. wrap the error type for more standardized
2. add more information in error for debug trace
3. wrap helper func for more user-friendly code
Signed-off-by: jokemanfire <hu.dingyang@zte.com.cn>