pub struct McpContext {
pub client_name: Option<String>,
pub client_version: Option<String>,
pub session_id: Option<String>,
pub auth_claims: Option<String>,
pub progress_token: Option<String>,
/* private fields */
}Expand description
Per-request context passed to tool handlers registered via
McpServer::tool_with_context — caller identity and session info that a
plain Fn(&str) -> ... tool handler has no way to see.
Constructed in McpServer::execute from the current request’s headers
plus whatever clientInfo was recorded for this session at initialize
time (see McpServer::handle_request_with_context).
Fields§
§client_name: Option<String>clientInfo.name sent in this session’s initialize call, if the
client sent one and this request carries a recognized Mcp-Session-Id.
client_version: Option<String>clientInfo.version sent in this session’s initialize call, under
the same conditions as client_name.
session_id: Option<String>The Mcp-Session-Id header on this request, if present — the value
the server minted and returned in the initialize response header
for this session (see the module docs’ Sessions section).
auth_claims: Option<String>The verified caller’s claims as a JSON string: the serialized
[crate::sso::OidcClaims] when [McpServer::require_oauth] (sso
feature) is configured and this request’s bearer token verified, or
{"sub":"<identity>"} for a token from
McpServer::require_bearer_tokens. None if OAuth isn’t configured, or for a
hand-built context (e.g. handle_request in tests) that bypassed
McpServer::execute’s auth check entirely.
progress_token: Option<String>The raw JSON value of params._meta.progressToken from the
triggering tools/call request, if the client sent one — a spec
string | number, so this is stored pre-rendered (already correctly
quoted if it’s a string) rather than decoded, and spliced back
verbatim by Self::report_progress. None for anything other than
a tools/call whose caller asked for progress updates.
Implementations§
Source§impl McpContext
impl McpContext
Sourcepub fn report_progress(
&self,
progress: f64,
total: Option<f64>,
message: Option<&str>,
)
pub fn report_progress( &self, progress: f64, total: Option<f64>, message: Option<&str>, )
Push a notifications/progress event over the SSE channel for this
request’s progressToken, if the client asked for progress updates
(params._meta.progressToken on the triggering tools/call) and
this context was built through a live McpServer (via execute(),
not a bare McpContext { .. } — see the sse_clients field doc).
Silently does nothing in either case — a handler doesn’t need to branch on whether progress reporting is actually wired up before calling this; it’s always safe to call.
total and message are both optional, matching the spec’s
notifications/progress shape: {"progressToken":...,"progress":..., "total":...,"message":"..."} (with total/message omitted when not
given here).
use rust_web_server::mcp::{McpContent, McpServer};
let server = McpServer::new("my-server", "1.0")
.tool_with_context("long_job", "Do something slow", "{}", |ctx, _args| {
ctx.report_progress(0.0, Some(100.0), Some("starting"));
// ... do work ...
ctx.report_progress(100.0, Some(100.0), Some("done"));
Ok(McpContent::text("done"))
});Sourcepub fn is_cancelled(&self) -> bool
pub fn is_cancelled(&self) -> bool
true if the client sent notifications/cancelled referencing this
tools/call’s request id.
Rust cannot forcibly interrupt a running synchronous closure — same
limitation crate::timeout::with_timeout documents — so this is
cooperative cancellation: nothing happens automatically. A handler
that does its own iterative work (processing a batch of items, say)
can check this between steps and return early; a handler that never
checks it runs to completion regardless, exactly as before this
existed.
Always false if the client never sent a cancellation, if this
wasn’t dispatched as a tools/call (the only method cancellation
applies to), or if ctx was built without a live server behind it.
use rust_web_server::mcp::{McpContent, McpServer};
let server = McpServer::new("my-server", "1.0")
.tool_with_context("process_batch", "Process many items", "{}", |ctx, _args| {
for i in 0..1_000_000 {
if ctx.is_cancelled() {
return Err("cancelled by client".to_string());
}
// ... process item i ...
}
Ok(McpContent::text("done"))
});Sourcepub fn sample(
&self,
request: SamplingRequest,
timeout: Duration,
) -> Result<SamplingResponse, String>
pub fn sample( &self, request: SamplingRequest, timeout: Duration, ) -> Result<SamplingResponse, String>
Deprecated by MCP 2026-07-28 (SEP-2577): Sampling keeps working with clients that use it, for at least twelve months, but new servers shouldn’t rely on it.
Ask the connected client to run LLM inference (“sampling”) and block
until it replies or timeout elapses — the server-initiated half of
sampling/createMessage. This reverses the usual request direction:
the server sends a JSON-RPC request to the client over the GET /mcp SSE stream, and the client answers with its own POST /mcp
carrying a JSON-RPC response (no method) that
McpServer::handle_request_with_context recognizes and routes
back here instead of treating it as an invalid request.
Blocking (not async fn) is deliberate: tool handlers in this crate
are plain synchronous closures — there is no async tool handler
support yet (MCP_TODO.md’s TODO-17) for this to .await inside of.
The calling thread parks on the response channel for up to
timeout; on a thread-pool server this ties up one worker for that
long, same tradeoff crate::timeout::with_timeout already accepts
for bounding a slow handler’s caller.
Fails fast, before sending anything, if: the client’s initialize
call never declared capabilities.sampling (it wouldn’t know what
to do with the request); this request has no session id (nothing to
address the request to); or ctx has no live server behind it.
Otherwise fails with a timeout error if the client never responds —
including if it simply has no GET /mcp SSE connection open for
this session, since there’s no separate “not connected” signal.
use rust_web_server::mcp::{McpServer, PromptMessage, SamplingRequest};
use std::time::Duration;
let server = McpServer::new("my-server", "1.0")
.tool_with_context("ask_llm", "Ask the connected client's model a question", "{}", |ctx, _args| {
let response = ctx.sample(
SamplingRequest {
messages: vec![PromptMessage::user("What is 2+2?")],
max_tokens: 100,
system_prompt: None,
},
Duration::from_secs(30),
)?;
Ok(response.content)
});Sourcepub fn list_roots(&self, timeout: Duration) -> Result<Vec<McpRoot>, String>
pub fn list_roots(&self, timeout: Duration) -> Result<Vec<McpRoot>, String>
Deprecated by MCP 2026-07-28 (SEP-2577): Roots keeps working with clients that use it, for at least twelve months, but new servers shouldn’t rely on it.
Ask the connected client which filesystem roots (workspace directories, mounted volumes, …) it has access to — useful for a file-system-aware tool that should only operate within the client’s declared workspace rather than the whole filesystem.
Like Self::sample, this reverses the usual request direction (a
server-initiated roots/list request over SSE, answered by the
client’s own POST /mcp) and blocks the calling thread for up to
timeout — see sample’s docs for why blocking is deliberate here.
The result is cached per session: the first call after initialize
(or after the client sends notifications/roots/list_changed, which
invalidates the cache) does a live round trip; later calls in the
same session return the cached list without sending anything. A tool
handler can call this on every invocation without worrying about
spamming the client with redundant roots/list requests.
Fails fast, before sending anything, under the same conditions as
Self::sample (translated to roots): the client never declared
capabilities.roots, this request has no session id, or ctx has
no live server behind it.
use rust_web_server::mcp::McpServer;
use std::time::Duration;
let server = McpServer::new("my-server", "1.0")
.tool_with_context("list_workspace_files", "List files in the client's workspace", "{}", |ctx, _args| {
let roots = ctx.list_roots(Duration::from_secs(10))?;
let names: Vec<String> = roots.iter().map(|r| r.uri.clone()).collect();
Ok(rust_web_server::mcp::McpContent::text(names.join(", ")))
});