Skip to main content

RequestHandlerExtra

Struct RequestHandlerExtra 

Source
#[non_exhaustive]
pub struct RequestHandlerExtra {
Show 14 fields pub cancellation_token: CancellationToken, pub request_id: String, pub session_id: Option<String>, pub auth_info: Option<AuthInfo>, pub auth_context: Option<AuthContext>, pub metadata: HashMap<String, String>, pub progress_reporter: Option<Arc<dyn ProgressReporter>>, pub log_sink: Option<Arc<dyn Fn(Notification) + Send + Sync>>, pub log_level: Option<LoggingLevel>, pub task_request: Option<Value>, pub request_meta: Option<Value>, pub protocol_context: Option<ProtocolContext>, pub extensions: Extensions, pub peer: Option<Arc<dyn PeerHandle>>, /* private fields */
}
Available on non-WebAssembly only.
Expand description

Extra context passed to request handlers.

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§cancellation_token: CancellationToken

Cancellation token for the request

§request_id: String

Request ID

§session_id: Option<String>

Session ID

§auth_info: Option<AuthInfo>

Authentication info

§auth_context: Option<AuthContext>

Validated authentication context (if auth is enabled)

§metadata: HashMap<String, String>

Custom metadata for middleware (e.g., OAuth tokens, session data)

Security Note: Metadata may contain sensitive values like OAuth tokens. The Debug implementation redacts these values to prevent accidental logging.

§progress_reporter: Option<Arc<dyn ProgressReporter>>

Optional progress reporter for this request

§log_sink: Option<Arc<dyn Fn(Notification) + Send + Sync>>

Request-scoped notification sink for notifications/message records emitted by RequestHandlerExtra::log / RequestHandlerExtra::log_with_data.

UNGATED by any progress token — a log record is not progress, and a client that never sent a progressToken still gets its logs. None when the enclosing dispatch path attached no sink (unit-test fixtures, RequestHandlerExtra::default(), transports without a back-channel); emission is then a silent no-op returning Ok(()). See RequestHandlerExtra::log for the accepted cost of that silence.

§log_level: Option<LoggingLevel>

The log level resolved for this request, if anything set one.

None means nothing set one, and the effective level falls back to DEFAULT_LOG_LEVEL. Records strictly below the effective level are dropped by RequestHandlerExtra::log before anything is constructed.

§task_request: Option<Value>

Task augmentation request from the client (MCP Tasks).

When Some, the client supports async task polling and requested task-augmented behavior for this call. The tool handler can check this to decide between a sync path (await and return results) or an async path (create task, return immediately).

When None, the client does not support tasks or did not request task mode — the tool should return results synchronously.

§request_meta: Option<Value>

The request’s _meta object as raw JSON (MCP _meta).

Populated by the dispatcher from the incoming tools/call request so handlers can read arbitrary namespaced _meta keys (beyond the typed progress_token/_task_id) without a typed dependency on RequestMeta. None when the request carried no _meta.

§protocol_context: Option<ProtocolContext>

The per-request protocol context resolved once at ingress (MCP v2.5 version plumbing).

When Some, the dispatcher resolved the negotiated protocol version into an Era plus optional self-reported client identity and surfaced it here so handlers can era-gate behavior via the typed era / protocol_version / client_info / client_capabilities accessors instead of reading ambient session state. None when no context was resolved (unit-test fixtures, pre-v2.5 dispatch paths).

Security: the client identity carried here is SELF-REPORTED and informational only — see the accessor rustdoc. It MUST NOT be used as an authorization anchor; real identity binds to the OAuth token.

§extensions: Extensions

Typed request-scoped state for middleware→handler transfer.

Inserting values requires T: Clone + Send + Sync + 'static. Debug prints type names only, not values, making this safe for logging. Cloning RequestHandlerExtra clones the entire extensions map — prefer Arc<T> for large values.

§peer: Option<Arc<dyn PeerHandle>>

Server-to-client back-channel for sample / list_roots / progress_notify.

None when the request originated from a code path (wasm dispatch, unit-test fixture, any ServerCore::new(...) without crate::server::core::ServerCore::with_server_request_dispatcher) that did not configure a ServerRequestDispatcher. Tool/prompt/resource handlers running under a fully-configured ServerCore receive a populated peer handle that routes sample / list_roots / progress_notify back to the originating client.

§Session isolation

Each Server instance owns its own dispatcher, so peer handles are scoped to the enclosing Server — cross-session confusion requires cross-process access.

Implementations§

Source§

impl RequestHandlerExtra

Source

pub fn new(request_id: String, cancellation_token: CancellationToken) -> Self

Create new handler extra context.

Source

pub fn with_session_id(self, session_id: Option<String>) -> Self

Set the session ID.

Source

pub fn with_auth_info(self, auth_info: Option<AuthInfo>) -> Self

Set the auth info.

Source

pub fn with_auth_context(self, auth_context: Option<AuthContext>) -> Self

Set the auth context.

Source

pub fn with_progress_reporter( self, progress_reporter: Option<Arc<dyn ProgressReporter>>, ) -> Self

Attach a progress reporter.

Source

pub fn with_log_sink( self, sink: Arc<dyn Fn(Notification) + Send + Sync>, ) -> Self

Attach the request-scoped notification sink for log records.

The construction seam the dispatch layer uses to wire log / log_with_data to whatever back-channel this request has. Without it both emitters are silent no-ops that still return Ok(()).

Source

pub fn with_log_level(self, level: LoggingLevel) -> Self

Set the log level for this request.

Records strictly below level are dropped by the emitters. When this is never called the effective level is DEFAULT_LOG_LEVEL.

Source

pub fn with_task_request(self, task_request: Option<Value>) -> Self

Set the task request from the client’s tools/call params.

When present, the tool handler knows the client supports task-augmented responses and can choose an async path (create task, return immediately) instead of awaiting the full result.

Source

pub fn with_request_meta(self, meta: Option<Value>) -> Self

Attach the request’s _meta object (raw JSON) for handler inspection.

Handlers can then read arbitrary namespaced _meta keys (e.g. team-guard depth/ancestor state) via extra.request_meta. Pass None when the request carried no _meta.

Source

pub fn with_protocol_context(self, ctx: Option<ProtocolContext>) -> Self

Attach the per-request ProtocolContext resolved at ingress (MCP v2.5 version plumbing).

Populated by the dispatch layer (Plan 04) after negotiating the protocol version; handlers then read the era and client identity via the typed era / protocol_version / client_info / client_capabilities accessors. Pass None when no context was resolved.

Source

pub fn era(&self) -> Option<Era>

Returns the resolved protocol Era for this request, or None when no ProtocolContext was attached.

Security: the era is derived from the negotiated protocol version — it is a behavioral switch, NOT an identity or authorization signal.

Source

pub fn protocol_version(&self) -> Option<&ProtocolVersion>

Returns the exact negotiated ProtocolVersion for this request, or None when no ProtocolContext was attached.

Source

pub fn client_info(&self) -> Option<&Implementation>

Returns the client’s SELF-REPORTED implementation info, or None when absent.

Security — self-reported, not for authorization: this value is the client-supplied clientInfo surfaced verbatim from initialization. It is informational ONLY (telemetry, feature-hints, logging) and MUST NOT be used as an authorization anchor or trusted identity. Real identity binds to the OAuth token, enforced in Phase 114 (TASK-05). No authorization decision is made from this accessor in this phase.

Source

pub fn client_capabilities(&self) -> Option<&ClientCapabilities>

Returns the client’s SELF-REPORTED advertised capabilities, or None when absent.

§Both eras reach this accessor

On MCP 2026-07-28 the value comes from the per-request _meta["io.modelcontextprotocol/clientCapabilities"], resolved once at ingress. On MCP 2025-11-25 it comes from the initialize handshake, folded into the same ProtocolContext at the dispatch root by core::fold_v1_handshake_capabilities (Phase 118.1-08, G-9). This method reads ONLY protocol_context.client_capabilities in either case — it deliberately does NOT reach for the server-level client_capabilities lock, because it is a sync method on a value already moved into the handler while the dispatch path holds that lock, and that shape deadlocks.

Absent both signals the answer is None, never a fabricated default.

Security — self-reported, not for authorization: like client_info, these capabilities are client-supplied and informational ONLY. They MUST NOT be used as an authorization anchor; real identity binds to the OAuth token (Phase 114 / TASK-05).

Source

pub fn input_responses(&self) -> Option<&InputResponses>

The client’s answers to a PREVIOUS round’s inputRequests (MCP 2026-07-28 multi-round-trip elicitation, HTTP-03).

Returns None when the request carried no inputResponses, on v1, and on the re-elicitation path (an unknown-key or expired requestState is re-run as a pristine FIRST call, so nothing MRTR-shaped is observable).

Security — CLIENT-SUPPLIED and UNTRUSTED. Every value here came off the wire. It is bounded in count, per-entry size, total size and nesting depth at transport ingress, and each entry is proven to be one of the three spec-permitted result shapes — but nothing validates it against the schema the handler asked for. A handler MUST schema-validate an entry before acting on it, exactly as it would validate tool arguments. Compare mrtr_continuation, which is server-minted.

Source

pub fn mrtr_continuation(&self) -> Option<&Value>

The DECRYPTED continuation state from a VERIFIED requestState token (MCP 2026-07-28 multi-round-trip elicitation, HTTP-03).

Security — SERVER-MINTED and TRUSTED. This value is whatever the handler itself sealed on a previous round. It reaches here only after the server-owned AEAD codec authenticated the token against the authenticated principal, the live method and a digest of the request’s salient parameters, so a tampered, cross-principal or cross-request token never produces a Some — it produces a JSON-RPC error instead.

None on a first call, on v1, and inside a re-run handler.

Source

pub fn mrtr_round(&self) -> Option<u8>

The multi-round-trip round counter carried by a verified requestState.

A handler uses this to decide it has asked enough times and should fail or degrade rather than elicit again (D-09). Counts from 0 for the round the FIRST continuation was minted in; None on a first call, on v1, and inside a re-run handler.

Source

pub fn trace_context(&self) -> Option<TraceContext>

Extracts the W3C trace-context self-reported in the request _meta (MCP v2.5, VERS-09), reading the existing request_meta — no dedicated field is stored.

Returns Some only when request_meta carries an in-bounds traceparent string; None when request_meta is None or lacks a valid traceparent. Delegates to TraceContext::from_meta.

Security — raw, bounded, untrusted: the returned traceparent/tracestate/baggage values are RAW and UNVALIDATED self-reported client data, only length-bounded at ingress (MAX_TRACE_VALUE_LEN) per the Plan-01 contract — see TraceContext. They MUST NOT be treated as trusted or safe to interpolate into logs/queries without independent sanitization.

Source

pub fn extensions(&self) -> &Extensions

Returns a reference to the typed extensions map.

§Example
use pmcp::RequestHandlerExtra;
let extra = RequestHandlerExtra::default();
let _: Option<&String> = extra.extensions().get::<String>();
Source

pub fn extensions_mut(&mut self) -> &mut Extensions

Returns a mutable reference to the typed extensions map.

§Example
use pmcp::RequestHandlerExtra;
let mut extra = RequestHandlerExtra::default();
extra.extensions_mut().insert(42u64);
Source

pub fn with_peer(self, peer: Arc<dyn PeerHandle>) -> Self

Attach a peer handle for server-to-client RPCs.

When set, tool/prompt/resource handlers can invoke extra.peer().unwrap().sample(...) (or list_roots / progress_notify) to make outbound requests back to the originating client. The peer handle is populated by the enclosing ServerCore at each dispatch site when a [crate::server::server_request_dispatcher::ServerRequestDispatcher] has been attached — tests or ad-hoc constructions that skip the dispatcher leave this as None, and handlers should treat None as “no client available for back-channel”.

§Example
use pmcp::RequestHandlerExtra;
use std::sync::Arc;
let extra = RequestHandlerExtra::default().with_peer(build_peer());
Source

pub fn peer(&self) -> Option<&Arc<dyn PeerHandle>>

Returns the peer handle, if one was attached.

Handlers should treat None as “no client back-channel available” and skip any sample/list_roots-dependent code paths gracefully.

Source

pub fn set_result_meta(&self, meta: Map<String, Value>)

Merge handler-authored keys into the result _meta for this call.

This is the lowest-friction way for an existing hand-written handler (one that returns a plain Value and does NOT override ToolHandler::handle_output) to attach task augmentation or custom _meta without owning the full envelope. The keys are accumulated in an interior-mutable slot and merged onto the outgoing CallToolResult’s _meta after the handler returns.

Takes &self: the slot is interior-mutable, so this works even though the handler receives RequestHandlerExtra by value (the slot Arc is shared with the clone the dispatcher retained before the by-value move).

§Merge precedence

Keys MERGE — never a whole-map replace:

  • a handler-set key overwrites a same-name key (whether set by an earlier set_result_meta call or emitted by widget/native enrichment);
  • all unrelated existing _meta keys (widget/native emission) are preserved;
  • repeated calls accumulate into the same slot — a later call’s colliding keys win, non-colliding keys from both calls survive.
§Path scope

This applies on both tool-output paths, on both dispatchers (Server and ServerCore):

  • Payload path — the keys merge onto the envelope the dispatcher builds (text-wrap / widget enrichment / outputSchema bridge).
  • ToolOutput::Result (verbatim) path — since D-06 (Phase 118.1) the keys merge onto the envelope the HANDLER authored, immediately before it is emitted. That arm still bypasses response middleware, the task create-path gate and the text-wrap tail (D-04 / D-04a); the bypass covers the response pipeline, not the handler’s own _meta, which is authored by the same handler at the same trust level as the envelope itself. Before D-06 the keys were silently DROPPED here.

A verbatim handler may of course still set _meta on the value directly, e.g. via CallToolResult::with_related_task; when it does both, the merge precedence above decides — the set_result_meta key wins the collision, and the envelope’s unrelated keys survive.

Source

pub fn is_task_request(&self) -> bool

Returns true if the client requested task-augmented behavior.

Source

pub fn auth_context(&self) -> Option<&AuthContext>

Get the auth context if available.

Source

pub fn get_metadata(&self, key: &str) -> Option<&String>

Get metadata value by key.

Metadata is typically set by middleware (e.g., OAuth token injection).

Source

pub fn set_metadata(&mut self, key: String, value: String)

Set metadata value.

This is typically used by middleware to inject data for tools to consume.

Source

pub fn is_cancelled(&self) -> bool

Check if the request has been cancelled.

Source

pub async fn cancelled(&self)

Wait for cancellation.

Source

pub async fn report_progress( &self, progress: f64, total: Option<f64>, message: Option<String>, ) -> Result<()>

Report progress if a reporter is available.

Source

pub async fn report_percent( &self, percent: f64, message: Option<String>, ) -> Result<()>

Report percentage progress (0-100) if available.

Source

pub fn log(&self, level: LoggingLevel, message: impl Into<String>) -> Result<()>

Emit an MCP notifications/message log record to this request’s client.

The first PRODUCTION constructor of ServerNotification::LogMessage. Synchronous on purpose: the sink is a synchronous Arc<dyn Fn(Notification) + Send + Sync>, no trait is involved, and making this async would force every logging call site into an .await to buy nothing.

§Ok(()) is NOT delivery acknowledgement

Ok(()) means the record was handed to whatever sink this request has, or that there was none — it does NOT mean the client received it. The sink’s type is Fn(Notification) -> (): it returns unit and therefore cannot report failure, and the fallback notification_tx path in Server likewise ignores its own try_send result. Do not build retry logic on this Result. It exists so that a future “sink refused” signal is an additive change rather than a breaking one.

§No sink means silence, not an error

With no sink attached the call is a no-op returning Ok(()), exactly as report_progress is. That keeps a handler callable outside a server — RequestHandlerExtra::default() is documented for testing and simple tool invocations, and a handler that logs must not become un-unit-testable. The accepted cost, stated plainly: a MISPLUMBED transport looks identical to a quiet handler. The conformance fence — a test asserting records actually arrive over the wire — is what catches that, so this is a production-diagnostics hole rather than a false green in the gate.

§Level filtering

A record strictly below the effective level (this request’s with_log_level, else DEFAULT_LOG_LEVEL) is dropped before anything is constructed. Comparison is on the typed LoggingLevel, whose declaration order is syslog severity order — never on the serialized strings, where "critical" < "debug" inverts the filter.

§No rate limiting, and what that does and does not promise

Unlike progress reporting, this emitter applies NO rate limit. Progress values are idempotent — dropping an intermediate one loses nothing because the next supersedes it — whereas each log record is the only copy of its information, so a limiter would silently delete evidence.

That is a statement about the EMITTER, not a promise that no record can ever be dropped downstream. On MCP 2026-07-28 the vehicle a record rides is the request’s bounded progress queue: an mpsc::channel(64) with an explicit DROP-NEWEST try_send policy, because the sink closure is synchronous and must not block. A handler emitting more than that capacity of notifications in one call LOSES THE EXCESS, and every one of those calls still returns Ok(()). A handler that must not lose records should keep a single call under the queue capacity.

§When records go nowhere on 2026-07-28

That vehicle is attached only when the request is multi-frame eligible — era is 2026-07-28, the server is not configured for JSON responses, and the request’s Accept header includes text/event-stream. A 2026-07-28 client that asked for JSON only therefore receives no log records at all. That is correct behaviour for a client that declined the streaming channel, not a bug.

§Capabilities

Emission is deliberately NOT gated on ServerCapabilities::logging. The sink is per-request and the capability is advisory; gating here would make a correctly-plumbed server silently mute. Servers SHOULD still declare the capability so clients know to expect records.

§Example
use pmcp::types::LoggingLevel;
use pmcp::RequestHandlerExtra;

// No server, so no sink — the call succeeds and emits nothing.
let extra = RequestHandlerExtra::default();
assert!(extra.log(LoggingLevel::Info, "starting work").is_ok());
Source

pub fn log_with_data( &self, level: LoggingLevel, message: impl Into<String>, data: Value, ) -> Result<()>

Emit a notifications/message log record carrying structured data.

Identical to log in every respect — same synchronous emission, same level filtering, same no-sink silence, and the same Ok(())-is-not-acknowledgement caveat — except that data is attached to the record’s data member.

§Example
use pmcp::types::LoggingLevel;
use pmcp::RequestHandlerExtra;
use serde_json::json;

let extra = RequestHandlerExtra::default();
assert!(extra
    .log_with_data(LoggingLevel::Warning, "retrying", json!({ "attempt": 2 }))
    .is_ok());
Source

pub async fn report_count( &self, current: usize, total: usize, message: Option<String>, ) -> Result<()>

Report count-based progress if available.

Trait Implementations§

Source§

impl Clone for RequestHandlerExtra

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for RequestHandlerExtra

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for RequestHandlerExtra

Source§

fn default() -> Self

Create a default RequestHandlerExtra for testing and simple tool invocations.

Uses a generated UUID as request_id and an uncancellable (never-cancelled) CancellationToken. Not suitable for production use where cancellation tracking is needed.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more