Skip to main content

pmcp/server/
mod.rs

1//! MCP server implementation.
2
3#[cfg(not(target_arch = "wasm32"))]
4use crate::error::{Error, Result};
5#[cfg(not(target_arch = "wasm32"))]
6use crate::shared::TransportMessage;
7#[cfg(not(target_arch = "wasm32"))]
8use crate::types::{
9    CallToolRequest, CallToolResult, ClientCapabilities, ClientRequest, GetPromptRequest,
10    Implementation, InitializeResult, JSONRPCResponse, ListPromptsRequest, ListPromptsResult,
11    ListResourceTemplatesRequest, ListResourceTemplatesResult, ListResourcesRequest,
12    ListResourcesResult, ListToolsRequest, ListToolsResult, Notification, ProtocolVersion,
13    ReadResourceRequest, Request, RequestId, ServerCapabilities, ServerNotification, ToolInfo,
14};
15#[cfg(not(target_arch = "wasm32"))]
16use async_trait::async_trait;
17#[cfg(not(target_arch = "wasm32"))]
18use serde_json::Value;
19#[cfg(not(target_arch = "wasm32"))]
20use std::collections::HashMap;
21#[cfg(not(target_arch = "wasm32"))]
22use std::collections::HashSet;
23#[cfg(not(target_arch = "wasm32"))]
24use std::sync::Arc;
25
26#[cfg(not(target_arch = "wasm32"))]
27use crate::runtime::RwLock;
28#[cfg(not(target_arch = "wasm32"))]
29use tokio::sync::mpsc;
30// Scrubs the by-value `[u8; 32]` / `Vec<[u8; 32]>` setter parameters on
31// `ServerBuilder` after their contents move into the zeroizing fields (D-113-P,
32// copy 2 of 3). `zeroize` is only compiled in under `streamable-http`, so the
33// import carries the same gate as the fields it serves.
34#[cfg(all(feature = "streamable-http", not(target_arch = "wasm32")))]
35use zeroize::Zeroize;
36
37// Core modules (currently native-only due to dependencies)
38#[cfg(not(target_arch = "wasm32"))]
39pub mod adapters;
40#[cfg(not(target_arch = "wasm32"))]
41pub mod builder;
42#[cfg(not(target_arch = "wasm32"))]
43// Dead by CONFIGURATION, not disuse: the dispatch paths that call into this
44// module are gated behind the transport features, so a `default-features = false`
45// build (as `pmcp-tasks` does) and a wasm32 build both compile the module with no
46// callers. Scoped so genuine dead code is still caught in a normal build.
47#[cfg_attr(
48    any(target_arch = "wasm32", not(feature = "streamable-http")),
49    allow(dead_code)
50)]
51pub mod core;
52pub mod limits;
53
54// Native-only modules (require tokio, threading, etc.)
55#[cfg(not(target_arch = "wasm32"))]
56pub mod auth;
57#[cfg(not(target_arch = "wasm32"))]
58pub mod batch;
59/// Builder-scoped middleware executor for workflow registration.
60#[cfg(not(target_arch = "wasm32"))]
61pub mod builder_middleware_executor;
62#[cfg(not(target_arch = "wasm32"))]
63pub mod cancellation;
64/// Dynamic resource provider system for pattern-based resource routing.
65#[cfg(not(target_arch = "wasm32"))]
66pub mod dynamic_resources;
67#[cfg(not(target_arch = "wasm32"))]
68pub mod http_middleware;
69/// Middleware executor abstraction for consistent tool execution.
70#[cfg(not(target_arch = "wasm32"))]
71pub mod middleware_executor;
72// Warn-only emit-time validation of `structuredContent` against a declared
73// `outputSchema` (no-op unless the `validation` feature is enabled).
74//
75// Deliberately NOT gated by target: the module compiles everywhere so dispatcher
76// call sites stay plain one-liners. The second `#[cfg]` widens the module's
77// visibility for the `fuzzing` feature ONLY, so
78// `fuzz/fuzz_targets/fuzz_schema_draft_pin.rs` can reach
79// `output_validation::fuzz_support` without any item becoming part of the
80// shipped public API (`fuzzing` is in neither `default` nor `full`, so
81// `cargo public-api` never sees it). This is verbatim the shape
82// `server::request_state` and `server::task_dispatch` already use.
83#[cfg(not(feature = "fuzzing"))]
84pub(crate) mod output_validation;
85/// Warn-only emit-time validation of `structuredContent` against a declared
86/// `outputSchema` (no-op unless the `validation` feature is enabled).
87#[cfg(feature = "fuzzing")]
88pub mod output_validation;
89/// Concrete `PeerHandle` implementation delegating to the
90/// `ServerRequestDispatcher`.
91#[cfg(not(target_arch = "wasm32"))]
92pub(crate) mod peer_impl;
93#[cfg(not(target_arch = "wasm32"))]
94pub mod preset;
95/// Progress reporting support for long-running operations.
96#[cfg(not(target_arch = "wasm32"))]
97pub mod progress;
98// Server-owned `requestState` AEAD continuation tokens (Phase 113, HTTP-02).
99//
100// D-14 locks MRTR AEAD to native + `streamable-http`: `ring` is only enabled by
101// that feature and the wasm server (`WasmServerCore`) gets no MRTR this phase.
102// The second `#[cfg]` widens the module's visibility for the `fuzzing` feature
103// ONLY, so `fuzz/fuzz_targets/fuzz_request_state.rs` can reach
104// `request_state::fuzz_support` without any item becoming part of the shipped
105// public API (`fuzzing` is in neither `default` nor `full`).
106#[cfg(all(feature = "streamable-http", not(target_arch = "wasm32")))]
107#[cfg(not(feature = "fuzzing"))]
108pub(crate) mod request_state;
109/// Server-owned `requestState` AEAD continuation tokens (Phase 113, HTTP-02).
110#[cfg(all(feature = "streamable-http", not(target_arch = "wasm32")))]
111#[cfg(feature = "fuzzing")]
112pub mod request_state;
113/// Outbound server-to-client request dispatcher with response correlation.
114#[cfg(not(target_arch = "wasm32"))]
115pub(crate) mod server_request_dispatcher;
116/// Simple prompt implementations with metadata support.
117#[cfg(not(target_arch = "wasm32"))]
118pub mod simple_prompt;
119/// Simple resource implementations with builder pattern support.
120#[cfg(not(target_arch = "wasm32"))]
121pub mod simple_resources;
122/// Simple tool implementations with schema support.
123#[cfg(not(target_arch = "wasm32"))]
124pub mod simple_tool;
125// Shared task-lifecycle dispatch unit used by both Server and ServerCore.
126//
127// The second `#[cfg]` widens the module's visibility for the `fuzzing` feature
128// ONLY, so `fuzz/fuzz_targets/fuzz_tasks_update.rs` can reach
129// `task_dispatch::fuzz_support` without any item becoming part of the shipped
130// public API (`fuzzing` is in neither `default` nor `full`, so `cargo public-api`
131// never sees it). This is verbatim the shape `server::request_state` already uses
132// for `fuzz_request_state`, so the crate has ONE convention for a fuzz seam
133// rather than two.
134#[cfg(not(target_arch = "wasm32"))]
135#[cfg(not(feature = "fuzzing"))]
136// Dead by CONFIGURATION, not disuse: the dispatch paths that call into this
137// module are gated behind the transport features, so a `default-features = false`
138// build (as `pmcp-tasks` does) and a wasm32 build both compile the module with no
139// callers. Scoped so genuine dead code is still caught in a normal build.
140#[cfg_attr(
141    any(target_arch = "wasm32", not(feature = "streamable-http")),
142    allow(dead_code)
143)]
144pub(crate) mod task_dispatch;
145/// Shared task-lifecycle dispatch unit used by both Server and ServerCore.
146#[cfg(not(target_arch = "wasm32"))]
147#[cfg(feature = "fuzzing")]
148pub mod task_dispatch;
149/// SDK-level task store trait and in-memory implementation.
150#[cfg(not(target_arch = "wasm32"))]
151pub mod task_store;
152/// Task routing trait for MCP Tasks integration.
153#[cfg(not(target_arch = "wasm32"))]
154pub mod tasks;
155/// Tool middleware for cross-cutting concerns in tool execution.
156#[cfg(not(target_arch = "wasm32"))]
157pub mod tool_middleware;
158
159/// Observability infrastructure for tracing, metrics, and logging.
160#[cfg(not(target_arch = "wasm32"))]
161pub mod observability;
162/// Workflow-based prompt system with type-safe handles and ergonomic builders.
163#[cfg(not(target_arch = "wasm32"))]
164pub mod workflow;
165
166/// State extractor for `#[mcp_tool]` shared state injection.
167#[cfg(not(target_arch = "wasm32"))]
168pub mod state;
169
170/// Typed tool implementations with automatic schema generation.
171#[cfg(not(target_arch = "wasm32"))]
172pub mod typed_tool;
173
174/// Typed prompt implementations with automatic argument schema generation.
175#[cfg(not(target_arch = "wasm32"))]
176pub mod typed_prompt;
177
178/// UI resource implementations for MCP Apps Extension (SEP-1865).
179#[cfg(not(target_arch = "wasm32"))]
180pub mod ui;
181
182/// MCP Apps Extension - Interactive UI support for multiple MCP hosts.
183///
184/// Provides adapters for `ChatGPT` Apps, MCP Apps (SEP-1865), and MCP-UI.
185#[cfg(all(not(target_arch = "wasm32"), feature = "mcp-apps"))]
186pub mod mcp_apps;
187
188/// Agent Skills (SEP-2640) — [`skills::Skill`] / [`skills::SkillReference`] /
189/// [`skills::Skills`] plus a dual-surface `PromptHandler` fallback.
190///
191/// Gated on `feature = "skills"` AND `not(target_arch = "wasm32")`: the
192/// module's contents consume [`ResourceHandler`] and [`PromptHandler`],
193/// which are themselves non-wasm-only.
194#[cfg(all(feature = "skills", not(target_arch = "wasm32")))]
195pub mod skills;
196
197/// Re-export the public Skills DX types so callers can `use
198/// pmcp::server::{Skill, SkillReference, Skills}` without descending
199/// into the `skills::` submodule path. The canonical path remains
200/// `pmcp::server::skills::*`.
201#[cfg(all(feature = "skills", not(target_arch = "wasm32")))]
202pub use skills::{Skill, SkillReference, Skills};
203
204/// Validation helpers for typed tools.
205#[cfg(not(target_arch = "wasm32"))]
206pub mod validation;
207
208/// Schema utilities for normalizing and inlining JSON schemas.
209#[cfg(feature = "schema-generation")]
210pub mod schema_utils;
211
212/// Standard error codes for validation with client elicitation support.
213#[cfg(not(target_arch = "wasm32"))]
214pub mod error_codes;
215
216/// Cross-platform path validation with security constraints.
217#[cfg(not(target_arch = "wasm32"))]
218pub mod path_validation;
219
220/// WASM-compatible typed tools with automatic schema generation.
221#[cfg(target_arch = "wasm32")]
222pub mod wasm_typed_tool;
223
224/// wasm32 stand-in for the native cancellation module.
225///
226/// wasm32 has no cancellation support, so this exposes only a zero-field
227/// [`cancellation::RequestHandlerExtra`] so handler signatures stay identical
228/// across targets.
229#[cfg(target_arch = "wasm32")]
230pub mod cancellation {
231    /// Stub for WASM - no cancellation support
232    #[derive(Debug, Clone, Default)]
233    pub struct RequestHandlerExtra;
234}
235/// Axum Router convenience function for secure MCP server hosting.
236#[cfg(feature = "streamable-http")]
237pub mod axum_router;
238#[cfg(not(target_arch = "wasm32"))]
239pub mod dynamic;
240#[cfg(not(target_arch = "wasm32"))]
241pub mod elicitation;
242#[cfg(not(target_arch = "wasm32"))]
243pub mod notification_debouncer;
244#[cfg(all(not(target_arch = "wasm32"), feature = "resource-watcher"))]
245pub mod resource_watcher;
246#[cfg(not(target_arch = "wasm32"))]
247pub mod roots;
248#[cfg(all(not(target_arch = "wasm32"), feature = "streamable-http"))]
249pub mod streamable_http_server;
250#[cfg(not(target_arch = "wasm32"))]
251// Dead by CONFIGURATION, not disuse: the dispatch paths that call into this
252// module are gated behind the transport features, so a `default-features = false`
253// build (as `pmcp-tasks` does) and a wasm32 build both compile the module with no
254// callers. Scoped so genuine dead code is still caught in a normal build.
255#[cfg_attr(
256    any(target_arch = "wasm32", not(feature = "streamable-http")),
257    allow(dead_code)
258)]
259pub mod subscriptions;
260/// Tower middleware layers for MCP HTTP security (DNS rebinding, security headers).
261#[cfg(feature = "streamable-http")]
262pub mod tower_layers;
263#[cfg(not(target_arch = "wasm32"))]
264pub mod transport;
265
266// WASM-specific modules and types
267#[cfg(target_arch = "wasm32")]
268pub mod wasi_adapter;
269#[cfg(target_arch = "wasm32")]
270pub mod wasm_core;
271#[cfg(target_arch = "wasm32")]
272pub mod wasm_server;
273#[cfg(all(test, target_arch = "wasm32"))]
274mod wasm_server_tests;
275
276// WASM-compatible protocol handler trait
277#[cfg(target_arch = "wasm32")]
278pub use wasi_protocol::ProtocolHandler;
279
280#[cfg(target_arch = "wasm32")]
281mod wasi_protocol {
282    use crate::error::Result;
283    use crate::types::{JSONRPCResponse, Notification, Request, RequestId};
284    use async_trait::async_trait;
285
286    /// Protocol-agnostic request handler trait for WASM.
287    ///
288    /// This is a simplified version of the ProtocolHandler trait that
289    /// doesn't depend on native-only types like handlers and managers.
290    #[async_trait(?Send)]
291    pub trait ProtocolHandler {
292        /// Handle a single request and return a response.
293        async fn handle_request(&self, id: RequestId, request: Request) -> JSONRPCResponse;
294
295        /// Handle a notification (no response expected).
296        async fn handle_notification(&self, notification: Notification) -> Result<()>;
297    }
298}
299
300#[cfg(test)]
301mod adapter_tests;
302#[cfg(test)]
303mod core_tests;
304#[cfg(all(test, not(target_arch = "wasm32")))]
305mod task_dispatch_tests;
306
307/// Output of a [`ToolHandler`] call: either a plain value the server wraps into a
308/// `CallToolResult`, or a fully-formed `CallToolResult` the handler owns end-to-end.
309///
310/// A handler that implements only [`ToolHandler::handle`] (the common case) never
311/// constructs this enum — the default [`ToolHandler::handle_output`] wraps the
312/// returned value in [`ToolOutput::Payload`], preserving today's behavior exactly.
313///
314/// This is a *control* enum consumed inside the server dispatch tail, NOT a wire
315/// type — it deliberately does not derive `Serialize`/`Deserialize`.
316///
317/// Marked `#[non_exhaustive]`: additional output modes may be added in a
318/// backwards-compatible way, so downstream `match`es must include a wildcard arm.
319#[cfg(not(target_arch = "wasm32"))]
320#[non_exhaustive]
321#[derive(Debug)]
322pub enum ToolOutput {
323    /// A plain value. The server runs it through the existing tail: response
324    /// middleware (redaction/sanitization), the Phase 102 task create-path gate,
325    /// and text-wrap / widget enrichment — byte-identical to returning a `Value`
326    /// from [`ToolHandler::handle`] today.
327    Payload(serde_json::Value),
328
329    /// A fully-formed `CallToolResult` the handler owns.
330    ///
331    /// # ⚠️ BYPASS WARNING — this variant is sent to the wire VERBATIM
332    ///
333    /// The contained [`CallToolResult`] is
334    /// serialized and returned to the client **exactly as provided**. It
335    /// **BYPASSES**:
336    /// - **response middleware** — redaction, sanitization, and audit hooks
337    ///   (`ToolMiddleware::on_response`) DO NOT run for this variant;
338    /// - **text-wrapping** — `content` is not synthesized from a stringified value;
339    /// - **widget enrichment** — `structured_content` / `_meta` are not injected.
340    ///
341    /// The handler is therefore responsible for its OWN redaction and
342    /// sanitization of both `content` and `_meta`, at the same trust level as
343    /// returning a raw `Value` today. This is a deliberate, user-approved design
344    /// choice (D-04a): a handler that needs to own the full envelope (e.g. to set
345    /// `_meta[relatedTask]`) opts into owning its security posture too.
346    ///
347    /// What is **NOT** bypassed: **request** middleware
348    /// (`ToolMiddleware::on_request`) still runs before the handler executes, and
349    /// handler errors still route through the normal error path. Only the
350    /// successful `Result` arm skips response middleware.
351    ///
352    /// See the phase migration guide for how to move a hand-written handler onto
353    /// this variant safely.
354    Result(crate::types::CallToolResult),
355}
356
357/// Handler for tool execution.
358#[cfg(not(target_arch = "wasm32"))]
359#[async_trait]
360pub trait ToolHandler: Send + Sync {
361    /// Handle a tool call with the given arguments.
362    async fn handle(&self, args: Value, extra: cancellation::RequestHandlerExtra) -> Result<Value>;
363
364    /// Get tool metadata including description and schema.
365    /// Returns None to use default empty metadata.
366    fn metadata(&self) -> Option<crate::types::ToolInfo> {
367        None
368    }
369
370    /// Produce the tool's [`ToolOutput`] for a call.
371    ///
372    /// The DEFAULT delegates to [`ToolHandler::handle`] and wraps the returned
373    /// value in [`ToolOutput::Payload`], so existing handlers (which implement
374    /// only `handle`) keep their exact current behavior — response middleware,
375    /// the create-path gate, and text-wrap all apply unchanged.
376    ///
377    /// Override this to return [`ToolOutput::Result`] and own the full
378    /// `CallToolResult` envelope. Read the [`ToolOutput::Result`] docs FIRST —
379    /// that path bypasses response middleware and you own your own redaction.
380    async fn handle_output(
381        &self,
382        args: Value,
383        extra: cancellation::RequestHandlerExtra,
384    ) -> Result<ToolOutput> {
385        self.handle(args, extra).await.map(ToolOutput::Payload)
386    }
387}
388
389/// Handler for prompt generation.
390#[cfg(not(target_arch = "wasm32"))]
391#[async_trait]
392pub trait PromptHandler: Send + Sync {
393    /// Generate a prompt with the given arguments.
394    async fn handle(
395        &self,
396        args: HashMap<String, String>,
397        extra: cancellation::RequestHandlerExtra,
398    ) -> Result<crate::types::GetPromptResult>;
399
400    /// Get prompt metadata including description and arguments schema.
401    /// Returns None to use default empty metadata.
402    fn metadata(&self) -> Option<crate::types::PromptInfo> {
403        None
404    }
405}
406
407/// Handler for resource access.
408#[cfg(not(target_arch = "wasm32"))]
409#[async_trait]
410pub trait ResourceHandler: Send + Sync {
411    /// Read a resource at the given URI.
412    async fn read(
413        &self,
414        uri: &str,
415        extra: cancellation::RequestHandlerExtra,
416    ) -> Result<crate::types::ReadResourceResult>;
417
418    /// List available resources.
419    async fn list(
420        &self,
421        _cursor: Option<String>,
422        extra: cancellation::RequestHandlerExtra,
423    ) -> Result<crate::types::ListResourcesResult>;
424}
425
426/// Handler for message sampling (LLM operations).
427#[cfg(not(target_arch = "wasm32"))]
428#[async_trait]
429pub trait SamplingHandler: Send + Sync {
430    /// Create a message using the language model.
431    async fn create_message(
432        &self,
433        params: crate::types::CreateMessageParams,
434        extra: cancellation::RequestHandlerExtra,
435    ) -> Result<crate::types::CreateMessageResult>;
436}
437
438/// MCP server implementation.
439///
440/// # Examples
441///
442/// ```rust,no_run
443/// use pmcp::{Server, ServerCapabilities, ToolHandler};
444/// use async_trait::async_trait;
445/// use serde_json::Value;
446///
447/// struct MyTool;
448///
449/// #[async_trait]
450/// impl ToolHandler for MyTool {
451///     async fn handle(&self, args: Value, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<Value> {
452///         Ok(serde_json::json!({"result": "success"}))
453///     }
454/// }
455///
456/// # async fn example() -> pmcp::Result<()> {
457/// let server = Server::builder()
458///     .name("my-server")
459///     .version("1.0.0")
460///     .tool("my-tool", MyTool)
461///     .build()?;
462///
463/// server.run_stdio().await?;
464/// # Ok(())
465/// # }
466/// ```
467#[cfg(not(target_arch = "wasm32"))]
468#[allow(dead_code)]
469pub struct Server {
470    info: Implementation,
471    capabilities: ServerCapabilities,
472    tools: HashMap<String, Arc<dyn ToolHandler>>,
473    tool_infos: HashMap<String, ToolInfo>,
474    /// Cached URI-to-tool-meta index for widget resource `_meta` propagation.
475    uri_to_tool_meta: HashMap<String, serde_json::Map<String, serde_json::Value>>,
476    prompts: HashMap<String, Arc<dyn PromptHandler>>,
477    resources: Option<Arc<dyn ResourceHandler>>,
478    /// Completion provider backing `completion/complete` (Phase 118.1-04,
479    /// CONF-05). Mirrors `ServerCore`'s field of the same name so BOTH native
480    /// dispatchers consult the same registered seam through the same shared
481    /// unit. `None` still answers the spec shape with an empty `values` array.
482    completions: Option<Arc<dyn crate::types::completable::CompletionProviderTrait>>,
483    sampling: Option<Arc<dyn SamplingHandler>>,
484    client_capabilities: Arc<RwLock<Option<ClientCapabilities>>>,
485    initialized: Arc<RwLock<bool>>,
486    /// Channel for sending notifications
487    notification_tx: Option<mpsc::Sender<Notification>>,
488    /// Cancellation manager for request cancellation
489    cancellation_manager: cancellation::CancellationManager,
490    /// Roots manager for directory/URI registration
491    roots_manager: Arc<RwLock<roots::RootsManager>>,
492    /// Subscription manager for resource subscriptions (v1 `resources/subscribe`)
493    subscription_manager: Arc<RwLock<subscriptions::SubscriptionManager>>,
494    /// The v2 `subscriptions/listen` stream registry (Phase 113, HTTP-04).
495    ///
496    /// Shared by the streamable-HTTP transport (which REGISTERS a stream) and
497    /// [`send_notification`](Self::send_notification) (which FANS OUT to it), so
498    /// a change notification emitted through the server's real notification path
499    /// reaches every live listen stream whose agreed filter covers it. Empty —
500    /// and therefore a no-op — on any server that never served a v2 listen
501    /// request, which is every v1 server.
502    listen_registry: Arc<subscriptions::ListenRegistry>,
503    /// Elicitation manager for user input requests
504    elicitation_manager: Option<Arc<elicitation::ElicitationManager>>,
505    /// Outbound server-to-client request dispatcher with response correlation.
506    /// Wired in `Server::run`; `None` outside the run lifecycle.
507    #[allow(clippy::struct_field_names)]
508    server_request_dispatcher: Option<Arc<server_request_dispatcher::ServerRequestDispatcher>>,
509    /// Cached peer handle built alongside the dispatcher so dispatch sites
510    /// clone the Arc rather than allocating a new `DispatchPeerHandle` per
511    /// request. `None` outside the run lifecycle.
512    peer_handle: Option<Arc<dyn crate::shared::peer::PeerHandle>>,
513    /// Authentication provider for validating requests
514    auth_provider: Option<Arc<dyn auth::AuthProvider>>,
515    /// Tool authorizer for fine-grained access control
516    tool_authorizer: Option<Arc<dyn auth::ToolAuthorizer>>,
517    /// Tool middleware chain for cross-cutting concerns in tool execution
518    #[cfg(not(target_arch = "wasm32"))]
519    tool_middleware_chain: Arc<RwLock<tool_middleware::ToolMiddlewareChain>>,
520    /// HTTP middleware chain for `StreamableHttpServer` (configured via `ServerBuilder`)
521    #[cfg(feature = "streamable-http")]
522    http_middleware: Option<Arc<http_middleware::ServerHttpMiddlewareChain>>,
523    /// Legacy experimental task router backend (fall-through path). Mirrors
524    /// `ServerCore`'s field; presence backs the `tasks/*` endpoints over the
525    /// router. Both backends feed the shared `task_dispatch` unit.
526    #[cfg(not(target_arch = "wasm32"))]
527    task_router: Option<Arc<dyn crate::server::tasks::TaskRouter>>,
528    /// Standard task store backend (polling path). Mirrors `ServerCore`'s field;
529    /// presence flips the `tasks` capability on at `build()` and backs the
530    /// `tasks/*` endpoints + create-path via the shared `task_dispatch` unit.
531    #[cfg(not(target_arch = "wasm32"))]
532    task_store: Option<Arc<dyn crate::server::task_store::TaskStore>>,
533    /// Per-tool TOUT-02 double-wrap tripwire opt-out set (D-08). A tool named
534    /// here has the tripwire suppressed at the Payload wrap site. Threaded from
535    /// `ServerBuilder::suppress_double_wrap_check`; `ServerCore` carries an
536    /// IDENTICAL set so both dispatchers consult the same suppression rule.
537    #[cfg(not(target_arch = "wasm32"))]
538    suppress_double_wrap: HashSet<String>,
539    /// Configured protocol-version accept-list (Phase 112, VERS-01/02). Mirrors
540    /// `ServerCore`'s field so this high-level `Server` dispatch site resolves the
541    /// per-request [`ProtocolContext`](crate::types::protocol::ProtocolContext)
542    /// through the SAME shared resolver. Default is v1-only (excludes
543    /// `2026-07-28`) — an un-opted-in server is byte-for-byte unchanged.
544    supported_protocol_versions: Vec<ProtocolVersion>,
545    /// The server-owned `requestState` codec (Phase 113, HTTP-02), resolved
546    /// EXACTLY ONCE at [`ServerBuilder::build`] time. `None` for a server that
547    /// did not opt into v2 — such a server reads no MRTR env var and pays
548    /// nothing (D-04). Deliberately an instance field, never a process-global:
549    /// see [`request_state`] for why.
550    #[cfg(feature = "streamable-http")]
551    request_state_codec: Option<Arc<request_state::RequestStateCodec>>,
552}
553
554#[cfg(not(target_arch = "wasm32"))]
555impl std::fmt::Debug for Server {
556    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557        f.debug_struct("Server")
558            .field("info", &self.info)
559            .field("capabilities", &self.capabilities)
560            .field("tools", &self.tools.keys().collect::<Vec<_>>())
561            .field("prompts", &self.prompts.keys().collect::<Vec<_>>())
562            .field("resources", &self.resources.is_some())
563            .field("sampling", &self.sampling.is_some())
564            .field("initialized", &self.initialized)
565            .finish()
566    }
567}
568
569// Every accessor in this block is read by the transport dispatch (the v2 envelope
570// and the `subscriptions/listen` + `tasks/update` paths). A transport-less build
571// compiles the block with no readers, which is a property of the feature set, not
572// of the code.
573#[cfg_attr(not(feature = "streamable-http"), allow(dead_code))]
574#[cfg(not(target_arch = "wasm32"))]
575impl Server {
576    /// The server-owned `requestState` codec, or `None` when this server did not
577    /// opt into the v2 (`2026-07-28`) era.
578    ///
579    /// Resolved once at build time; the production consumers
580    /// (`core::mrtr_ingest`'s `verify` and `core::mrtr_egress`'s `mint`) borrow
581    /// it from server state rather than reaching for a process-global.
582    #[cfg(feature = "streamable-http")]
583    pub(crate) fn request_state_codec(&self) -> Option<&request_state::RequestStateCodec> {
584        self.request_state_codec.as_deref()
585    }
586
587    /// The server's already-computed capabilities.
588    ///
589    /// A READ-ONLY borrow — the SAME value
590    /// [`handle_discover`](Self::handle_discover) projects onto the wire. The
591    /// `subscriptions/listen` gate reads it through
592    /// [`advertises_subscriptions`](crate::types::subscriptions::advertises_subscriptions)
593    /// so the advertisement and the implementation cannot drift (HTTP-04).
594    pub(crate) fn capabilities(&self) -> &ServerCapabilities {
595        &self.capabilities
596    }
597
598    /// The server's own [`Implementation`] identity.
599    ///
600    /// Borrowed by the v2 response envelope so a `subscriptions/listen` terminal
601    /// result carries the same `io.modelcontextprotocol/serverInfo` every other
602    /// v2 result carries.
603    pub(crate) fn info(&self) -> &Implementation {
604        &self.info
605    }
606
607    /// Check if a tool exists
608    pub fn has_tool(&self, name: &str) -> bool {
609        self.tools.contains_key(name)
610    }
611
612    /// Check if a prompt exists
613    pub fn has_prompt(&self, name: &str) -> bool {
614        self.prompts.contains_key(name)
615    }
616
617    /// Get a prompt handler by name.
618    ///
619    /// Returns a borrowed reference to the registered prompt handler `Arc`,
620    /// or `None` if no prompt with that name has been registered. Callers
621    /// who need ownership can `Arc::clone(...)` the returned reference.
622    ///
623    /// # Handler-level testing pattern
624    ///
625    /// This accessor is the public API surface for the handler-level
626    /// integration testing pattern documented in the testing chapter of
627    /// the PMCP book: build a `Server`, retrieve the handler by name,
628    /// invoke `.handle(...).await` directly with a synthetic
629    /// `RequestHandlerExtra`. It exercises handler logic in isolation
630    /// without spinning up a transport.
631    ///
632    /// # What this pattern skips
633    ///
634    /// This pattern exercises handler logic only. The JSONRPC dispatch
635    /// path (`Server::handle_request`) is bypassed, so `auth_provider`,
636    /// `tool_authorizer`, and `tool_middleware` are **not** invoked. For
637    /// full-pipeline tests that exercise the security pipeline, drive a
638    /// real transport (stdio or streamable-http) with a `pmcp::Client`.
639    ///
640    /// # Examples
641    ///
642    /// ```rust
643    /// use std::collections::HashMap;
644    /// use std::sync::Arc;
645    /// use async_trait::async_trait;
646    /// use pmcp::{PromptHandler, Server};
647    /// use pmcp::types::{GetPromptResult, PromptMessage, Content};
648    /// use pmcp::types::content::Role;
649    ///
650    /// struct GreetingPrompt;
651    ///
652    /// #[async_trait]
653    /// impl PromptHandler for GreetingPrompt {
654    ///     async fn handle(
655    ///         &self,
656    ///         args: HashMap<String, String>,
657    ///         _extra: pmcp::RequestHandlerExtra,
658    ///     ) -> pmcp::Result<GetPromptResult> {
659    ///         let who = args.get("name").cloned().unwrap_or_else(|| "world".to_string());
660    ///         Ok(GetPromptResult::new(
661    ///             vec![PromptMessage::new(Role::User, Content::text(format!("Hello, {}!", who)))],
662    ///             Some("Greeting prompt".to_string()),
663    ///         ))
664    ///     }
665    /// }
666    ///
667    /// # async fn example() -> pmcp::Result<()> {
668    /// let server = Server::builder()
669    ///     .name("demo")
670    ///     .version("0.1")
671    ///     .prompt_arc("greet", Arc::new(GreetingPrompt))
672    ///     .build()?;
673    ///
674    /// let handler = server.get_prompt("greet").expect("registered above");
675    /// let mut args = HashMap::new();
676    /// args.insert("name".to_string(), "claude".to_string());
677    /// let result = handler
678    ///     .handle(args, pmcp::RequestHandlerExtra::default())
679    ///     .await?;
680    /// assert_eq!(result.messages.len(), 1);
681    /// # Ok(())
682    /// # }
683    /// ```
684    pub fn get_prompt(&self, name: &str) -> Option<&Arc<dyn PromptHandler>> {
685        self.prompts.get(name)
686    }
687
688    /// Get a tool handler by name.
689    ///
690    /// Returns a borrowed reference to the registered tool handler `Arc`,
691    /// or `None` if no tool with that name has been registered. Callers
692    /// who need ownership can `Arc::clone(...)` the returned reference.
693    ///
694    /// # Handler-level testing pattern
695    ///
696    /// This accessor is the public API surface for the handler-level
697    /// integration testing pattern: build a `Server`, retrieve the
698    /// handler by name, invoke `.handle(...).await` directly with a
699    /// synthetic `RequestHandlerExtra`. It exercises handler logic in
700    /// isolation without spinning up a transport, which is the primary
701    /// shape downstream toolkit authors use to assert on a built
702    /// `pmcp::Server`'s registered handlers.
703    ///
704    /// # What this pattern skips
705    ///
706    /// This pattern exercises handler logic only. The JSONRPC dispatch
707    /// path (`Server::handle_request`) is bypassed, so `auth_provider`,
708    /// `tool_authorizer`, and `tool_middleware` are **not** invoked. For
709    /// full-pipeline tests that exercise the security pipeline, drive a
710    /// real transport (stdio or streamable-http) with a `pmcp::Client`.
711    ///
712    /// # Examples
713    ///
714    /// ```rust
715    /// use std::sync::Arc;
716    /// use async_trait::async_trait;
717    /// use pmcp::{Server, ToolHandler};
718    /// use serde_json::Value;
719    ///
720    /// struct EchoTool;
721    ///
722    /// #[async_trait]
723    /// impl ToolHandler for EchoTool {
724    ///     async fn handle(
725    ///         &self,
726    ///         args: Value,
727    ///         _extra: pmcp::RequestHandlerExtra,
728    ///     ) -> pmcp::Result<Value> {
729    ///         Ok(serde_json::json!({ "echoed": args }))
730    ///     }
731    /// }
732    ///
733    /// # async fn example() -> pmcp::Result<()> {
734    /// let server = Server::builder()
735    ///     .name("demo")
736    ///     .version("0.1")
737    ///     .tool_arc("echo", Arc::new(EchoTool))
738    ///     .build()?;
739    ///
740    /// let handler = server.get_tool("echo").expect("registered above");
741    /// let result = handler
742    ///     .handle(serde_json::json!({"msg": "hi"}), pmcp::RequestHandlerExtra::default())
743    ///     .await?;
744    /// assert_eq!(result, serde_json::json!({"echoed": {"msg": "hi"}}));
745    /// # Ok(())
746    /// # }
747    /// ```
748    pub fn get_tool(&self, name: &str) -> Option<&Arc<dyn ToolHandler>> {
749        self.tools.get(name)
750    }
751
752    /// Get the HTTP middleware chain configured via `ServerBuilder`.
753    ///
754    /// Returns the HTTP middleware chain that was set using
755    /// `ServerBuilder::with_http_middleware()`. This can be used when
756    /// creating a `StreamableHttpServer`.
757    ///
758    /// # Examples
759    ///
760    /// ```rust,no_run
761    /// # #[cfg(feature = "streamable-http")]
762    /// # {
763    /// use pmcp::Server;
764    /// use pmcp::server::streamable_http_server::StreamableHttpServerConfig;
765    ///
766    /// # async fn example() -> pmcp::Result<()> {
767    /// let server = Server::builder()
768    ///     .name("my-server")
769    ///     .version("1.0.0")
770    ///     // ... with_http_middleware() called here
771    ///     .build()?;
772    ///
773    /// let config = StreamableHttpServerConfig {
774    ///     http_middleware: server.http_middleware(),
775    ///     ..Default::default()
776    /// };
777    /// # Ok(())
778    /// # }
779    /// # }
780    /// ```
781    #[cfg(feature = "streamable-http")]
782    pub fn http_middleware(&self) -> Option<Arc<http_middleware::ServerHttpMiddlewareChain>> {
783        self.http_middleware.clone()
784    }
785
786    /// Get the authentication provider configured via `ServerBuilder`.
787    ///
788    /// Returns the authentication provider that was set using
789    /// `ServerBuilder::auth_provider()`. This can be used by transport
790    /// layers to validate incoming requests and extract auth context.
791    pub fn get_auth_provider(&self) -> Option<Arc<dyn auth::AuthProvider>> {
792        self.auth_provider.clone()
793    }
794
795    /// Build tool and resource registries for workflow expansion.
796    ///
797    /// Creates `HashMap` registries that can be used to build an `ExpansionContext`
798    /// for converting workflow prompts to protocol types. The registries are
799    /// automatically populated from all registered tools and resources.
800    ///
801    /// Returns a tuple of (`tools_map`, `resources_map`) that can be used with
802    /// `ExpansionContext`.
803    ///
804    /// # Examples
805    ///
806    /// ```rust,ignore
807    /// use pmcp::Server;
808    /// use pmcp::server::workflow::{InternalPromptMessage, ToolHandle, PromptContent, conversion::ExpansionContext};
809    /// use pmcp::types::Role;
810    ///
811    /// # async fn example() -> pmcp::Result<()> {
812    /// let server = Server::builder()
813    ///     .name("example-server")
814    ///     .version("1.0.0")
815    ///     .build()?;
816    ///
817    /// // Build registries from registered tools/resources
818    /// let (tools, resources) = server.build_expansion_registries();
819    ///
820    /// // Create expansion context
821    /// let ctx = ExpansionContext {
822    ///     tools: &tools,
823    ///     resources: &resources,
824    /// };
825    ///
826    /// // Use it to convert workflow prompts to protocol types
827    /// let msg = InternalPromptMessage::new(
828    ///     Role::System,
829    ///     PromptContent::ToolHandle(ToolHandle::new("my_tool"))
830    /// );
831    /// let protocol_msg = msg.to_protocol(&ctx)?;
832    /// # Ok(())
833    /// # }
834    /// ```
835    pub fn build_expansion_registries(
836        &self,
837    ) -> (
838        HashMap<Arc<str>, workflow::conversion::ToolInfo>,
839        HashMap<Arc<str>, workflow::conversion::ResourceInfo>,
840    ) {
841        use std::collections::HashMap;
842
843        // Build tools map from registered tool handlers
844        let mut tools_map = HashMap::new();
845        for (name, handler) in &self.tools {
846            if let Some(metadata) = handler.metadata() {
847                tools_map.insert(
848                    Arc::from(name.as_str()),
849                    workflow::conversion::ToolInfo {
850                        name: metadata.name,
851                        description: metadata.description.unwrap_or_default(),
852                        input_schema: metadata.input_schema,
853                    },
854                );
855            }
856        }
857
858        // Build resources map (currently empty - resources don't have metadata())
859        // This could be enhanced in the future when resources have better metadata
860        let resources_map = HashMap::new();
861
862        (tools_map, resources_map)
863    }
864
865    /// Send a notification.
866    ///
867    /// Sends a notification to the connected client. Notifications are one-way
868    /// messages that don't expect a response.
869    ///
870    /// # Arguments
871    ///
872    /// * `notification` - The server notification to send
873    ///
874    /// # Examples
875    ///
876    /// ```rust,no_run
877    /// use pmcp::{Server, ServerNotification, ProgressNotification, ProgressToken};
878    ///
879    /// # async fn example() -> pmcp::Result<()> {
880    /// let server = Server::builder()
881    ///     .name("example-server")
882    ///     .version("1.0.0")
883    ///     .build()?;
884    ///
885    /// // Send a progress notification
886    /// let progress = ProgressNotification::new(
887    ///     ProgressToken::String("task-123".to_string()),
888    ///     50.0,
889    ///     Some("Processing...".to_string()),
890    /// );
891    ///
892    /// server.send_notification(ServerNotification::Progress(progress)).await;
893    /// # Ok(())
894    /// # }
895    /// ```
896    pub async fn send_notification(&self, notification: ServerNotification) {
897        // HTTP-04: fan out to every live v2 `subscriptions/listen` stream FIRST,
898        // then take the existing v1 transport path unchanged. The registry is
899        // empty on any server that never served a listen request, so this is a
900        // map lookup on a v1 server and no wire byte changes there.
901        self.listen_registry.fan_out(&notification);
902        if let Some(tx) = &self.notification_tx {
903            let _ = tx.send(Notification::Server(notification)).await;
904        }
905    }
906
907    /// Gracefully close every open `subscriptions/listen` stream (HTTP-04).
908    ///
909    /// Call this from a shutdown handler: each stream receives its
910    /// [`SubscriptionsListenResult`](crate::types::subscriptions::SubscriptionsListenResult)
911    /// as the JSON-RPC response and is then ended. This is the ONLY one of the
912    /// three closure triggers that can send a terminal result — a client
913    /// disconnect cannot (the peer is gone) and the buffer-overflow policy cannot
914    /// (the buffer is full); both simply end the stream.
915    ///
916    /// # Examples
917    ///
918    /// ```rust,no_run
919    /// # async fn example() -> pmcp::Result<()> {
920    /// let server = pmcp::Server::builder()
921    ///     .name("example-server")
922    ///     .version("1.0.0")
923    ///     .build()?;
924    ///
925    /// // ... on shutdown:
926    /// server.close_subscription_streams();
927    /// # Ok(())
928    /// # }
929    /// ```
930    pub fn close_subscription_streams(&self) {
931        self.listen_registry.close_all();
932    }
933
934    /// The v2 `subscriptions/listen` registry this server fans notifications out
935    /// to.
936    ///
937    /// The streamable-HTTP transport clones this `Arc` to register a stream; the
938    /// `Arc` is cloned under the server lock and the lock is released
939    /// immediately, so a held-open stream never holds the server mutex.
940    pub(crate) fn listen_registry(&self) -> &Arc<subscriptions::ListenRegistry> {
941        &self.listen_registry
942    }
943
944    /// Get client capabilities.
945    ///
946    /// Returns the capabilities that the client declared during initialization.
947    /// This can be used to check if the client supports specific features.
948    ///
949    /// # Examples
950    ///
951    /// ```rust,no_run
952    /// use pmcp::Server;
953    ///
954    /// # async fn example() -> pmcp::Result<()> {
955    /// let server = Server::builder()
956    ///     .name("example-server")
957    ///     .version("1.0.0")
958    ///     .build()?;
959    ///
960    /// // Check client capabilities after initialization
961    /// if let Some(capabilities) = server.get_client_capabilities().await {
962    ///     if capabilities.sampling.is_some() {
963    ///         println!("Client supports LLM sampling requests");
964    ///     }
965    ///     if capabilities.elicitation.is_some() {
966    ///         println!("Client supports user input requests");
967    ///     }
968    /// }
969    /// # Ok(())
970    /// # }
971    /// ```
972    ///
973    /// # Returns
974    ///
975    /// - `Some(ClientCapabilities)` if the client has been initialized
976    /// - `None` if the client hasn't initialized yet
977    pub async fn get_client_capabilities(&self) -> Option<ClientCapabilities> {
978        self.client_capabilities.read().await.clone()
979    }
980
981    /// Check if the server is initialized.
982    ///
983    /// Returns true if the initialization handshake with a client has completed.
984    /// The server must be initialized before it can process most requests.
985    ///
986    /// # Examples
987    ///
988    /// ```rust,no_run
989    /// use pmcp::Server;
990    ///
991    /// # async fn example() -> pmcp::Result<()> {
992    /// let server = Server::builder()
993    ///     .name("example-server")
994    ///     .version("1.0.0")
995    ///     .build()?;
996    ///
997    /// if server.is_initialized().await {
998    ///     println!("Server is ready to handle requests");
999    /// } else {
1000    ///     println!("Waiting for client initialization");
1001    /// }
1002    /// # Ok(())
1003    /// # }
1004    /// ```
1005    pub async fn is_initialized(&self) -> bool {
1006        *self.initialized.read().await
1007    }
1008    /// Create a new server builder.
1009    ///
1010    /// Returns a `ServerBuilder` for configuring and constructing a new MCP server.
1011    /// The builder pattern allows you to set server information, capabilities,
1012    /// and register handlers before building the final server instance.
1013    ///
1014    /// # Examples
1015    ///
1016    /// ```rust,no_run
1017    /// use pmcp::{Server, ToolHandler};
1018    /// use async_trait::async_trait;
1019    /// use serde_json::Value;
1020    ///
1021    /// struct HelloTool;
1022    ///
1023    /// #[async_trait]
1024    /// impl ToolHandler for HelloTool {
1025    ///     async fn handle(&self, args: Value, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<Value> {
1026    ///         Ok(serde_json::json!({"message": "Hello, World!"}))
1027    ///     }
1028    /// }
1029    ///
1030    /// # async fn example() -> pmcp::Result<()> {
1031    /// let server = Server::builder()
1032    ///     .name("greeting-server")
1033    ///     .version("1.0.0")
1034    ///     .tool("hello", HelloTool{})
1035    ///     .build()?;
1036    /// # Ok(())
1037    /// # }
1038    /// ```
1039    pub fn builder() -> ServerBuilder {
1040        ServerBuilder::new()
1041    }
1042
1043    /// Run the server with stdio transport.
1044    ///
1045    /// Starts the server using stdin/stdout for communication.
1046    /// This is the standard way to run MCP servers as they communicate
1047    /// via JSON-RPC over stdio.
1048    ///
1049    /// # Examples
1050    ///
1051    /// ```rust,no_run
1052    /// use pmcp::{Server, ToolHandler};
1053    /// use async_trait::async_trait;
1054    /// use serde_json::Value;
1055    ///
1056    /// struct EchoTool;
1057    ///
1058    /// #[async_trait]
1059    /// impl ToolHandler for EchoTool {
1060    ///     async fn handle(&self, args: Value, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<Value> {
1061    ///         Ok(args) // Echo the input
1062    ///     }
1063    /// }
1064    ///
1065    /// # async fn example() -> pmcp::Result<()> {
1066    /// let server = Server::builder()
1067    ///     .name("echo-server")
1068    ///     .version("1.0.0")
1069    ///     .tool("echo", EchoTool{})
1070    ///     .build()?;
1071    ///
1072    /// // This will run indefinitely, handling client requests
1073    /// server.run_stdio().await?;
1074    /// # Ok(())
1075    /// # }
1076    /// ```
1077    ///
1078    /// # Security: task isolation
1079    ///
1080    /// Stdio (and any transport that does not resolve a per-request
1081    /// `AuthContext`) carries no authenticated principal, so every `tasks/*`
1082    /// request on a [`task_store`](ServerBuilder::task_store)-backed server is
1083    /// owned by the single `"local"` owner — there is NO per-user task isolation.
1084    /// This is correct for single-user CLI use; for multi-tenant deployments use
1085    /// an HTTP transport whose auth layer populates the OAuth subject.
1086    ///
1087    /// # Errors
1088    ///
1089    /// Returns an error if:
1090    /// - The stdio transport fails to initialize
1091    /// - Communication with the client fails
1092    /// - The server encounters an unrecoverable error
1093    pub async fn run_stdio(self) -> Result<()> {
1094        let transport = crate::shared::StdioTransport::new();
1095        self.run(transport).await
1096    }
1097
1098    /// Run the server with a custom transport.
1099    ///
1100    /// Starts the server using a custom transport implementation.
1101    /// This allows for different communication mechanisms beyond stdio,
1102    /// such as TCP sockets, `WebSockets`, or other protocols.
1103    ///
1104    /// # Arguments
1105    ///
1106    /// * `transport` - The transport implementation to use for communication
1107    ///
1108    /// # Examples
1109    ///
1110    /// ```rust,no_run
1111    /// use pmcp::{Server, StdioTransport, ToolHandler};
1112    /// use async_trait::async_trait;
1113    /// use serde_json::Value;
1114    ///
1115    /// struct CalculatorTool;
1116    ///
1117    /// #[async_trait]
1118    /// impl ToolHandler for CalculatorTool {
1119    ///     async fn handle(&self, args: Value, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<Value> {
1120    ///         let a = args["a"].as_f64().unwrap_or(0.0);
1121    ///         let b = args["b"].as_f64().unwrap_or(0.0);
1122    ///         Ok(serde_json::json!({"result": a + b}))
1123    ///     }
1124    /// }
1125    ///
1126    /// # async fn example() -> pmcp::Result<()> {
1127    /// let server = Server::builder()
1128    ///     .name("calculator-server")
1129    ///     .version("1.0.0")
1130    ///     .tool("add", CalculatorTool{})
1131    ///     .build()?;
1132    ///
1133    /// let transport = StdioTransport::new();
1134    /// server.run(transport).await?;
1135    /// # Ok(())
1136    /// # }
1137    /// ```
1138    ///
1139    /// # Security: task isolation
1140    ///
1141    /// Per-user `tasks/*` isolation requires the transport to resolve a per-request
1142    /// `AuthContext` carrying the OAuth subject. Transports without one (stdio and
1143    /// the like) own every task under the single `"local"` owner — no per-user
1144    /// isolation. Use an authenticating HTTP transport for multi-tenant task servers.
1145    ///
1146    /// # Errors
1147    ///
1148    /// Returns an error if:
1149    /// - The transport fails to initialize or operate
1150    /// - Communication with the client fails
1151    /// - The server encounters an unrecoverable error
1152    pub async fn run<T: crate::shared::Transport + 'static>(mut self, transport: T) -> Result<()> {
1153        let (notification_tx, notification_rx) = mpsc::channel(100);
1154        self.notification_tx = Some(notification_tx);
1155
1156        // Hook cancellation manager to send notifications via the same channel,
1157        // through the SINGLE `notification_tx`-to-sink conversion
1158        // ([`Server::notification_tx_sink`]). This site used to carry its own
1159        // `try_send` closure — a third copy of the same three lines, and a third
1160        // chance to disagree about the send discipline.
1161        if let Some(sender) = self.notification_tx_sink() {
1162            self.cancellation_manager.set_notification_sender(sender);
1163        }
1164
1165        // Outbound server-to-client request channel + dispatcher. Drain task
1166        // wraps each `(correlation_id, ServerRequest)` as
1167        // `TransportMessage::Request` and forwards to the transport;
1168        // `handle_transport_message` routes responses back through
1169        // `dispatcher.handle_response`.
1170        let (outbound_tx, outbound_rx) =
1171            mpsc::channel::<(String, crate::types::ServerRequest)>(100);
1172        let dispatcher = Arc::new(
1173            server_request_dispatcher::ServerRequestDispatcher::new_with_channel(outbound_tx),
1174        );
1175        let peer: Arc<dyn crate::shared::peer::PeerHandle> = Arc::new(
1176            crate::server::peer_impl::DispatchPeerHandle::new(dispatcher.clone()),
1177        );
1178        self.peer_handle = Some(peer);
1179        self.server_request_dispatcher = Some(dispatcher);
1180
1181        let server = Arc::new(self);
1182
1183        // Transport Actor design (Phase 108, D-01/D-02): the transport is OWNED
1184        // by exactly one actor task and is NEVER wrapped in a shared
1185        // `Arc<RwLock<T>>`. ALL outbound frames (responses, server-requests,
1186        // notifications) funnel through a single UNBOUNDED `send_tx`; inbound
1187        // requests go to a SINGLE sequential worker via an UNBOUNDED
1188        // `request_tx`. The receive/drain path therefore never blocks on request
1189        // execution, request-queue capacity, or a transport write-lock, so an
1190        // in-tool `peer.sample()` / `.list_roots()` round-trip cannot deadlock
1191        // the loop. Request handling stays serialized (one worker) — zero
1192        // behavior change for existing single-request servers.
1193        let (send_tx, send_rx) = mpsc::unbounded_channel::<TransportMessage>();
1194        let (request_tx, request_rx) = mpsc::unbounded_channel::<(RequestId, Request)>();
1195
1196        Self::spawn_notification_handler(send_tx.clone(), notification_rx);
1197        server_request_dispatcher::spawn_server_request_drain(send_tx.clone(), outbound_rx);
1198        Self::spawn_request_worker(server.clone(), request_rx, send_tx.clone());
1199        let actor = tokio::spawn(Self::run_transport_actor(
1200            server.clone(),
1201            transport,
1202            send_rx,
1203            request_tx,
1204        ));
1205
1206        // Return once the actor task ends (transport closed / all senders gone).
1207        Self::run_main_loop(actor).await
1208    }
1209
1210    /// Attach a peer handle to `extra`, preferring the REQUEST-SCOPED one.
1211    ///
1212    /// No-op on wasm32, and — when neither source is present — outside the
1213    /// `run()` lifecycle, exactly as before.
1214    ///
1215    /// # Precedence: the request-scoped transport handle wins (T-118.1-11-04)
1216    ///
1217    /// Two sources can supply a peer and they are NOT equivalent:
1218    ///
1219    /// 1. `self.peer_handle` — a SINGLE field on `Server`, set once by
1220    ///    [`Server::run`] for the in-process actor loop. One transport, one
1221    ///    client, so one handle says everything there is to say.
1222    /// 2. the [`TransportBackchannel`](crate::types::protocol::context::TransportBackchannel)
1223    ///    riding THIS request's `ProtocolContext`, attached by the
1224    ///    `StreamableHTTP` transport at the one site that knows which session the
1225    ///    request arrived on.
1226    ///
1227    /// On a MULTIPLEXED transport (1) cannot express "the session that issued
1228    /// this request": a handle set there is shared by every concurrent session,
1229    /// so one client's `sampling/createMessage` would be delivered to whichever
1230    /// session the global handle happened to be bound to — the T-113-07
1231    /// misbinding class. (2) is constructed per request and bound to the
1232    /// originating session, so it is always the more specific answer and is
1233    /// therefore read FIRST.
1234    ///
1235    /// The in-process path is untouched: `Server::run` attaches no backchannel,
1236    /// so the `self.peer_handle` fallback below is what runs there.
1237    ///
1238    /// # Ordering
1239    ///
1240    /// Every dispatch site calls this AFTER its `tool_authorizer` check, so an
1241    /// unauthorized caller returns before a handler body ever runs and therefore
1242    /// never sees `extra.peer()` — the invariant stated at `src/shared/peer.rs`.
1243    ///
1244    /// Delegates to [`crate::server::core::attach_request_peer`], the ONE unit
1245    /// `ServerCore::attach_peer` also calls — the precedence rule is defined
1246    /// once and merely invoked here (twin-site parity).
1247    ///
1248    /// # It attaches the LOG SINK too (Phase 118.2, CONF-10)
1249    ///
1250    /// The name is kept for its call-site history, but this is now the single
1251    /// post-authorization site where ALL of a request's server-to-client
1252    /// capability handles are attached: the peer, the log sink, and the resolved
1253    /// log level. They share one site DELIBERATELY — a second method a future
1254    /// dispatch site had to remember to call is exactly the drift the shared
1255    /// units exist to prevent. The `ServerCore` twin does the same, in the same
1256    /// order, through the same two units.
1257    #[inline]
1258    fn attach_peer(
1259        &self,
1260        extra: crate::server::cancellation::RequestHandlerExtra,
1261    ) -> crate::server::cancellation::RequestHandlerExtra {
1262        #[cfg(not(target_arch = "wasm32"))]
1263        {
1264            let extra = crate::server::core::attach_request_peer(extra, self.peer_handle.as_ref());
1265            // The fallback is passed as a THUNK: `attach_request_log_sink` prefers
1266            // this request's `TransportBackchannel` sink and never reads it on any
1267            // HTTP-served request, so building it eagerly allocated one
1268            // `Arc<dyn Fn(..)>` per dispatch only to drop it.
1269            crate::server::core::attach_request_log_sink(extra, || self.notification_tx_sink())
1270        }
1271        #[cfg(target_arch = "wasm32")]
1272        {
1273            extra
1274        }
1275    }
1276
1277    /// The server-wide notification channel expressed as a sink, or `None` when
1278    /// this server has no channel.
1279    ///
1280    /// The ONE place in the crate that turns `self.notification_tx` into an
1281    /// `Arc<dyn Fn(Notification) + Send + Sync>`. Two consumers read it — the
1282    /// progress-reporter path via [`Server::progress_notification_sink`] and the
1283    /// log-sink path via [`Server::attach_peer`] — and a second `try_send`
1284    /// closure would be a second chance to disagree about the send discipline.
1285    ///
1286    /// Both consumers call it only once they have decided they need it: the
1287    /// `Arc` allocation happens on the branch that uses the value, never
1288    /// speculatively ahead of the request-scoped sink that outranks it.
1289    ///
1290    /// `try_send` and a discarded result, deliberately: this is a bounded
1291    /// channel, and a full channel must never block or fail a handler. The cost
1292    /// is that a saturated channel drops records silently, which is why
1293    /// `RequestHandlerExtra::log`'s `Ok(())` is documented as NOT being delivery
1294    /// acknowledgement.
1295    ///
1296    /// It is `None` on every HTTP-served server, because `StreamableHttpServer`
1297    /// never calls [`Server::run`] — which is exactly why the request-scoped
1298    /// `TransportBackchannel` sink has to win over it.
1299    #[inline]
1300    fn notification_tx_sink(&self) -> Option<Arc<dyn Fn(Notification) + Send + Sync>> {
1301        let tx = self.notification_tx.as_ref()?.clone();
1302        Some(Arc::new(move |notification| {
1303            let _ = tx.try_send(notification);
1304        }))
1305    }
1306
1307    /// The one-way notification sink this request's progress reporter emits
1308    /// through, or `None` when the request has no vehicle at all.
1309    ///
1310    /// # Precedence mirrors [`Server::attach_peer`], for the same reason
1311    ///
1312    /// 1. the `TransportBackchannel`'s `notification_sink` on THIS request's
1313    ///    `ProtocolContext` — session-bound, supplied by the `StreamableHTTP`
1314    ///    transport at the one site that knows which session the request arrived
1315    ///    on;
1316    /// 2. `self.notification_tx`, the server-wide channel assigned by
1317    ///    [`Server::run`] and by nothing else.
1318    ///
1319    /// The second is `None` on every HTTP-served server, because
1320    /// `StreamableHttpServer` never calls `Server::run()`. That is precisely why
1321    /// `extra.report_progress(..)` was silently inert over HTTP before phase
1322    /// 118.1: `RequestHandlerExtra::report_progress` returns `Ok(())` when the
1323    /// reporter is `None`, so the gap produced no error anywhere.
1324    ///
1325    /// The sink is handed through with NO adapter — the transport chose its type
1326    /// to be `ServerProgressReporter::new`'s second parameter verbatim.
1327    #[inline]
1328    fn progress_notification_sink(
1329        &self,
1330        #[cfg_attr(target_arch = "wasm32", allow(unused_variables))] protocol_context: Option<
1331            &crate::types::protocol::ProtocolContext,
1332        >,
1333    ) -> Option<Arc<dyn Fn(Notification) + Send + Sync>> {
1334        #[cfg(not(target_arch = "wasm32"))]
1335        if let Some(sink) = protocol_context
1336            .and_then(crate::types::protocol::ProtocolContext::transport_backchannel)
1337            .and_then(crate::types::protocol::context::TransportBackchannel::notification_sink)
1338        {
1339            return Some(Arc::clone(sink));
1340        }
1341        // The single `notification_tx`-to-sink conversion lives in
1342        // `notification_tx_sink`; the log-sink path reads the same one.
1343        self.notification_tx_sink()
1344    }
1345
1346    /// Build this request's progress reporter, or `None` when the client asked
1347    /// for no progress or the request carries no notification vehicle.
1348    ///
1349    /// The `progress_token` lookup is unchanged: a request with no
1350    /// `params._meta.progressToken` still gets no reporter, so a handler that
1351    /// calls `extra.report_progress(..)` anyway stays silent. Only the SENDER
1352    /// resolution moved — see [`Server::progress_notification_sink`].
1353    ///
1354    /// ONE construction site for all three dispatchers (tools, prompts,
1355    /// resources): they had three byte-identical copies, and a fourth would have
1356    /// been the one that kept reading `self.notification_tx` alone.
1357    #[inline]
1358    fn progress_reporter_for(
1359        &self,
1360        meta: Option<&crate::types::protocol::RequestMeta>,
1361        protocol_context: Option<&crate::types::protocol::ProtocolContext>,
1362    ) -> Option<Arc<dyn crate::server::progress::ProgressReporter>> {
1363        // DELIBERATE, and NOT to be "unified" with the log path: Phase 118.2
1364        // D-07 removed the progress-token gate from the LOG sink only. Progress
1365        // stays opt-in — a client that sent no `progressToken` has nothing to
1366        // correlate progress notifications with — while `notifications/message`
1367        // is unconditional. See `core::attach_request_log_sink`.
1368        let token = meta.and_then(|meta| meta.progress_token.as_ref())?;
1369        let sink = self.progress_notification_sink(protocol_context)?;
1370        let reporter = crate::server::progress::ServerProgressReporter::new(token.clone(), sink);
1371        Some(Arc::new(reporter) as Arc<dyn crate::server::progress::ProgressReporter>)
1372    }
1373
1374    /// Spawn the outgoing-notification forwarder.
1375    ///
1376    /// Forwards each queued [`Notification`] onto the transport actor's
1377    /// unbounded `send_tx` as a [`TransportMessage::Notification`]. It no longer
1378    /// touches the transport directly — all outbound framing is serialized by
1379    /// the single actor task, so notifications can never starve on a transport
1380    /// write-lock held across an in-flight `receive()`.
1381    fn spawn_notification_handler(
1382        send_tx: mpsc::UnboundedSender<TransportMessage>,
1383        mut notification_rx: mpsc::Receiver<Notification>,
1384    ) {
1385        tokio::spawn(async move {
1386            while let Some(notification) = notification_rx.recv().await {
1387                if send_tx
1388                    .send(TransportMessage::Notification(notification))
1389                    .is_err()
1390                {
1391                    // Actor gone — nothing left to forward to.
1392                    break;
1393                }
1394            }
1395        });
1396    }
1397
1398    /// Spawn the single sequential request worker.
1399    ///
1400    /// Consumes inbound requests one at a time and forwards each response back
1401    /// through the actor's `send_tx`. Exactly ONE worker runs per server, so
1402    /// request handling stays serialized exactly as before this refactor. The
1403    /// actor merely decouples receiving from handling: a handler that awaits a
1404    /// peer round-trip no longer blocks the receive path, so the correlated
1405    /// client response can be read and routed while the handler is parked.
1406    fn spawn_request_worker(
1407        server: Arc<Self>,
1408        mut request_rx: mpsc::UnboundedReceiver<(RequestId, Request)>,
1409        send_tx: mpsc::UnboundedSender<TransportMessage>,
1410    ) {
1411        tokio::spawn(async move {
1412            while let Some((id, request)) = request_rx.recv().await {
1413                let response = server.handle_request(id, request, None).await;
1414                if send_tx.send(TransportMessage::Response(response)).is_err() {
1415                    // Actor gone — stop draining.
1416                    break;
1417                }
1418            }
1419        });
1420    }
1421
1422    /// Own the transport and interleave receive + send from a single task.
1423    ///
1424    /// The actor `select!`s over (a) the outbound `send_rx` and (b)
1425    /// `transport.receive()`. Inbound frames are routed by kind: a `Response`
1426    /// resolves the awaiting dispatcher IMMEDIATELY (unblocking an in-tool
1427    /// `peer.sample()`), a `Request` is queued to the sequential worker, and a
1428    /// `Notification` is handled inline. Routing NEVER touches the transport.
1429    ///
1430    /// When the send branch wins, the in-flight `receive()` future is DROPPED,
1431    /// so [`crate::shared::Transport::receive`] MUST be cancel-safe (see the
1432    /// trait's `# Cancellation` contract) — the stock `StdioTransport` persists
1433    /// its partial line across calls for exactly this reason. The real
1434    /// `transport.send(..)` runs AFTER the `select!` block so the receive
1435    /// future's mutable borrow is released first; this also keeps the borrow
1436    /// checker satisfied for the single `&mut self` transport object.
1437    async fn run_transport_actor<T: crate::shared::Transport + 'static>(
1438        server: Arc<Self>,
1439        mut transport: T,
1440        mut send_rx: mpsc::UnboundedReceiver<TransportMessage>,
1441        request_tx: mpsc::UnboundedSender<(RequestId, Request)>,
1442    ) {
1443        loop {
1444            let mut outbound: Option<TransportMessage> = None;
1445            tokio::select! {
1446                biased;
1447                maybe_frame = send_rx.recv() => {
1448                    match maybe_frame {
1449                        Some(frame) => outbound = Some(frame),
1450                        // All senders dropped — shut the actor down.
1451                        None => break,
1452                    }
1453                },
1454                received = transport.receive() => {
1455                    match received {
1456                        Ok(message) => {
1457                            if Self::route_inbound_message(&server, &request_tx, message)
1458                                .await
1459                                .is_break()
1460                            {
1461                                break;
1462                            }
1463                        },
1464                        Err(e) => {
1465                            Self::log_error(&format!("Transport receive error: {}", e)).await;
1466                            break;
1467                        },
1468                    }
1469                },
1470            }
1471            if let Some(frame) = outbound {
1472                if let Err(e) = transport.send(frame).await {
1473                    Self::log_error(&format!("Transport send error: {}", e)).await;
1474                    break;
1475                }
1476            }
1477        }
1478    }
1479
1480    /// Route one inbound transport frame. Never touches the transport, so it is
1481    /// safe to call from inside the actor's `select!` receive arm. Returns
1482    /// [`std::ops::ControlFlow::Break`] only when the request worker is gone.
1483    async fn route_inbound_message(
1484        server: &Arc<Self>,
1485        request_tx: &mpsc::UnboundedSender<(RequestId, Request)>,
1486        message: TransportMessage,
1487    ) -> std::ops::ControlFlow<()> {
1488        match message {
1489            TransportMessage::Request { id, request } => {
1490                // Unbounded queue: the receive branch MUST NOT await capacity
1491                // (the anti-deadlock invariant). A send error means the worker
1492                // is gone, so the actor should stop.
1493                if request_tx.send((id, request)).is_err() {
1494                    return std::ops::ControlFlow::Break(());
1495                }
1496            },
1497            TransportMessage::Response(response) => {
1498                Self::route_response(server, response).await;
1499            },
1500            TransportMessage::Notification(notification) => {
1501                Self::route_notification(server, notification).await;
1502            },
1503        }
1504        std::ops::ControlFlow::Continue(())
1505    }
1506
1507    /// Route a correlated client response through the dispatcher so a pending
1508    /// in-tool peer dispatch resolves. Behavior is lifted verbatim from the
1509    /// former `handle_transport_message` response arm — only the caller changed.
1510    async fn route_response(server: &Arc<Self>, response: JSONRPCResponse) {
1511        let Some(dispatcher) = &server.server_request_dispatcher else {
1512            Self::log_warning("Server received response but no dispatcher configured").await;
1513            return;
1514        };
1515        let correlation_id = response.id.to_string();
1516        let payload = match &response.payload {
1517            crate::types::jsonrpc::ResponsePayload::Result(value) => value.clone(),
1518            crate::types::jsonrpc::ResponsePayload::Error(err) => {
1519                // Represent errors as a JSON object so callers can distinguish —
1520                // dispatch() returns the Value as-is.
1521                serde_json::to_value(err).unwrap_or(Value::Null)
1522            },
1523        };
1524        if let Err(e) = dispatcher.handle_response(&correlation_id, payload).await {
1525            Self::log_warning(&format!(
1526                "Failed to route response {}: {}",
1527                correlation_id, e
1528            ))
1529            .await;
1530        }
1531    }
1532
1533    /// Handle an inbound notification (client cancellation). A failed
1534    /// cancellation lookup is logged rather than tearing down the actor loop.
1535    async fn route_notification(server: &Arc<Self>, notification: Notification) {
1536        if let Notification::Client(crate::types::ClientNotification::Cancelled(params)) =
1537            &notification
1538        {
1539            let request_id = params.request_id.to_string();
1540            if let Err(e) = server
1541                .cancellation_manager
1542                .cancel_request_silent(request_id)
1543                .await
1544            {
1545                Self::log_warning(&format!("Failed to process cancellation: {}", e)).await;
1546            }
1547        }
1548        Self::log_debug("Server received notification").await;
1549    }
1550
1551    /// Log an error message.
1552    async fn log_error(message: &str) {
1553        crate::log(crate::types::LogLevel::Error, message, None).await;
1554    }
1555
1556    /// Log a warning message.
1557    async fn log_warning(message: &str) {
1558        crate::log(crate::types::LogLevel::Warning, message, None).await;
1559    }
1560
1561    /// Log a debug message.
1562    async fn log_debug(message: &str) {
1563        crate::log(crate::types::LogLevel::Debug, message, None).await;
1564    }
1565
1566    /// Await the transport actor task; `run()` returns once it ends (transport
1567    /// closed or all outbound senders dropped). This is the shutdown join point.
1568    async fn run_main_loop(actor: tokio::task::JoinHandle<()>) -> Result<()> {
1569        if let Err(e) = actor.await {
1570            Self::log_error(&format!("Transport actor task ended abnormally: {}", e)).await;
1571        }
1572        Ok(())
1573    }
1574
1575    /// Resolve the per-request [`ProtocolContext`](crate::types::protocol::ProtocolContext)
1576    /// ONCE at this dispatch site's ingress via the SAME shared resolver
1577    /// `ServerCore` uses — the twin wiring (Pitfall 3). `Ok(None)` for a
1578    /// non-opted-in server (zero era-detection, D-04).
1579    ///
1580    /// `pub(crate)` so the streamable-HTTP layer (Plan 06) can resolve ONCE for
1581    /// its header gate and thread the SAME value into
1582    /// [`handle_request_with_context`](Self::handle_request_with_context) — the
1583    /// HTTP layer CONSUMES the resolved era, it never runs a second resolver
1584    /// (D-11 / Pitfall 2).
1585    /// The server's configured protocol-version accept-list.
1586    ///
1587    /// `pub(crate)` so the streamable-HTTP layer can put it in an
1588    /// `UNSUPPORTED_PROTOCOL_VERSION` (-32022) rejection's
1589    /// `error.data.supported` — the spec requires the rejection to tell the
1590    /// client which versions it COULD have asked for, so it can pick a mutually
1591    /// supported one instead of probing.
1592    pub(crate) fn supported_protocol_versions(&self) -> &[ProtocolVersion] {
1593        &self.supported_protocol_versions
1594    }
1595
1596    pub(crate) fn resolve_ingress_protocol_context(
1597        &self,
1598        request: &Request,
1599    ) -> std::result::Result<
1600        Option<crate::types::protocol::ProtocolContext>,
1601        crate::types::protocol::context::ProtocolNegotiationError,
1602    > {
1603        crate::server::core::resolve_ingress_protocol_context(
1604            &self.supported_protocol_versions,
1605            request,
1606        )
1607    }
1608
1609    /// Resolve the per-request `ProtocolContext` from a request's RAW
1610    /// `params._meta` value.
1611    ///
1612    /// # This is the era resolver the streamable-HTTP transport uses, for EVERY method
1613    ///
1614    /// Introduced in Phase 112 for the `server/discover` ingress (which has no
1615    /// parsed [`Request`] to read a typed field from), and generalized in Phase
1616    /// 113 plan 04 to every method (finding D-113-B).
1617    ///
1618    /// The typed
1619    /// [`resolve_ingress_protocol_context`](Self::resolve_ingress_protocol_context)
1620    /// can only see the three request structs that carry a `_meta` FIELD, so a
1621    /// stateless v2 `tools/list` — which has no handshake and therefore no other
1622    /// era channel — could not be expressed at all. Widening those `pub` structs
1623    /// would have been a MAJOR semver break (`cargo semver-checks`
1624    /// `constructible_struct_adds_field`), and the v2.5 milestone is scoped
1625    /// additive; reading the raw body needs no public API change and covers every
1626    /// method, so the HTTP transport routes ALL era detection through here.
1627    ///
1628    /// Mirrors the same non-opted-in short-circuit (D-04): a server that has NOT
1629    /// opted into v2 returns `Ok(None)` WITHOUT inspecting `_meta` at all, so the
1630    /// v1 request path runs zero era detection.
1631    pub(crate) fn resolve_raw_meta_protocol_context(
1632        &self,
1633        raw_meta: Option<&serde_json::Value>,
1634    ) -> std::result::Result<
1635        Option<crate::types::protocol::ProtocolContext>,
1636        crate::types::protocol::context::ProtocolNegotiationError,
1637    > {
1638        if !crate::types::protocol::context::is_v2_opted_in(&self.supported_protocol_versions) {
1639            return Ok(None);
1640        }
1641        crate::types::protocol::context::resolve_protocol_context(
1642            &self.supported_protocol_versions,
1643            raw_meta,
1644        )
1645    }
1646
1647    /// Handle the v2 `server/discover` request (Phase 112, VERS-04, D-09/D-10).
1648    ///
1649    /// The production discover caller: the streamable-HTTP transport classifies a
1650    /// `server/discover` POST as `HttpIngress::Discover` and, at the per-path
1651    /// response-assembly step, calls this THIN delegate. It projects the server's
1652    /// already-computed capabilities (incl. the `extensions` map) read-only via
1653    /// the ONE shared [`build_discover_response`](crate::server::core::build_discover_response)
1654    /// free fn — one projection/one envelope path, no duplicate capability type,
1655    /// no `is_initialized` mutation. The era gate inside the free fn yields the v2
1656    /// projection for an `Era::V2` context and `-32601` for v1 / non-opted-in.
1657    pub(crate) fn handle_discover(
1658        &self,
1659        id: RequestId,
1660        protocol_context: Option<&crate::types::protocol::ProtocolContext>,
1661    ) -> JSONRPCResponse {
1662        crate::server::core::build_discover_response(
1663            id,
1664            // The SINGLE accept-list source (G-7): the same slice
1665            // `negotiation_error_to_gate_reject` puts in an
1666            // `UNSUPPORTED_PROTOCOL_VERSION` rejection's `error.data.supported`
1667            // becomes the result's `supportedVersions`. There is no second list
1668            // to drift from.
1669            crate::server::core::DiscoverSource::new(
1670                &self.capabilities,
1671                self.supported_protocol_versions(),
1672            ),
1673            &self.info,
1674            protocol_context,
1675        )
1676    }
1677
1678    /// Handle the v2 `tasks/update` request (Phase 114 plan 13, TASK-02).
1679    ///
1680    /// The production `tasks/update` caller, and a THIN delegate exactly like
1681    /// [`handle_discover`](Self::handle_discover) beside it: the streamable-HTTP
1682    /// transport classifies a `tasks/update` POST as `HttpIngress::TasksUpdate`
1683    /// and, at the per-path response-assembly step, calls this. It constructs the
1684    /// SHARED [`TaskDispatch`](crate::server::task_dispatch::TaskDispatch) over
1685    /// this server's own backends — the same borrow-struct
1686    /// [`handle_client_request`](Self::handle_client_request) builds for the four
1687    /// `ClientRequest` tasks methods — and hands off.
1688    ///
1689    /// **It defines no gate of its own.** The era gate, the backend gate, the
1690    /// `-32021` client-declaration gate, the `-32003` identity table, the `-32602`
1691    /// params check, the four `inputResponses` bounds and the kind-directed decode
1692    /// all live in
1693    /// [`TaskDispatch::route_tasks_update`](crate::server::task_dispatch::TaskDispatch::route_tasks_update),
1694    /// in that order. This function's entire job is to pass the ALREADY-RESOLVED
1695    /// `auth_context` and `ProtocolContext` through unchanged, which is also what
1696    /// keeps it from ever re-reading `params._meta` for a second answer.
1697    ///
1698    /// `params` are the RAW value the classifier carried. Nothing between the wire
1699    /// and the router deserializes them, so a malformed body becomes a structured
1700    /// `-32602` AFTER the gates rather than a parse error before them — and the
1701    /// `inputResponses` map reaches the route UNDECODED, which is what lets the
1702    /// route bound it and then type it against the kinds the SERVER recorded
1703    /// rather than against whichever overlapping shape happened to fit (D-113-O).
1704    ///
1705    /// `async` since plan 114-14: the delivery reads the task record and writes
1706    /// the responses.
1707    ///
1708    /// # The v2 result envelope is injected HERE, for the same reason `server/discover`'s is
1709    ///
1710    /// `tasks/update` rides the crate-private internal-request route, so it does
1711    /// NOT pass through `process_client_request`, which is where every
1712    /// `ClientRequest` result gets its `resultType` + `_meta.serverInfo`. Left
1713    /// alone, the `UpdateTaskResult` acknowledgement would reach the wire as a
1714    /// bare `{}` — and the extension says its `resultType` field MUST be
1715    /// `"complete"`. `build_discover_response` solved the identical problem the
1716    /// identical way (Phase 112), so the internal route has ONE shape rather than
1717    /// two.
1718    ///
1719    /// [`ReservedFieldOwner::None`](crate::server::core::ReservedFieldOwner) is
1720    /// named explicitly and is correct: the acknowledgement is EMPTY, so this
1721    /// route mints no reserved result field at all — no `inputRequests` (that is
1722    /// `tasks/get`'s, plan 114-11) and no `requestState` (the tasks surface has no
1723    /// continuation token, D-17). A future change that made this ack non-empty
1724    /// would have to state its own owner here rather than inherit one.
1725    ///
1726    /// The call is a no-op on v1 and for every ERROR payload, so all seven of the
1727    /// route's refusals are byte-unchanged by it.
1728    #[cfg(not(target_arch = "wasm32"))]
1729    pub(crate) async fn handle_tasks_update(
1730        &self,
1731        id: RequestId,
1732        params: &serde_json::Value,
1733        auth_context: Option<&auth::AuthContext>,
1734        protocol_context: Option<&crate::types::protocol::ProtocolContext>,
1735    ) -> JSONRPCResponse {
1736        let mut response = self
1737            .task_dispatch()
1738            .route_tasks_update(id, params, auth_context, protocol_context)
1739            .await;
1740        crate::server::core::inject_v2_result_envelope(
1741            &mut response,
1742            protocol_context,
1743            &self.info,
1744            crate::server::core::ResponseDisposition::Complete,
1745            crate::server::core::ReservedFieldOwner::None,
1746            // The `tasks/update` acknowledgement is an `UpdateTaskResult`, which
1747            // does NOT extend `CacheableResult` — the tasks surface carries no
1748            // caching hint at all in the 2026-07-28 schema (D-07), and the
1749            // `ttlMs` that DOES live on `TaskV2` is a task LIFETIME, a different
1750            // concept in a different module (D-10). So this route gains neither
1751            // key, on either era.
1752            crate::types::caching::Cacheable::No,
1753        );
1754        response
1755    }
1756
1757    async fn handle_request(
1758        &self,
1759        id: RequestId,
1760        request: Request,
1761        auth_context: Option<auth::AuthContext>,
1762    ) -> JSONRPCResponse {
1763        // Resolve the per-request ProtocolContext ONCE at ingress (opted-in
1764        // only — D-04), through the single shared resolver, and thread it into
1765        // dispatch. Never re-derived downstream (D-11).
1766        let protocol_context = match self.resolve_ingress_protocol_context(&request) {
1767            Ok(ctx) => ctx,
1768            Err(negotiation_error) => {
1769                let (code, message) =
1770                    crate::server::core::negotiation_error_to_rejection(&negotiation_error);
1771                return JSONRPCResponse {
1772                    jsonrpc: "2.0".to_string(),
1773                    id,
1774                    payload: crate::types::jsonrpc::ResponsePayload::Error(
1775                        crate::types::jsonrpc::JSONRPCError {
1776                            code,
1777                            message,
1778                            data: None,
1779                        },
1780                    ),
1781                };
1782            },
1783        };
1784        self.handle_request_with_context(id, request, auth_context, protocol_context)
1785            .await
1786    }
1787
1788    /// Dispatch a request with an ALREADY-RESOLVED `ProtocolContext` threaded in.
1789    ///
1790    /// This is the pass-through seam Plan 06 relies on: the streamable-HTTP layer
1791    /// resolves the `ProtocolContext` ONCE (via
1792    /// [`resolve_ingress_protocol_context`](Self::resolve_ingress_protocol_context))
1793    /// for its header gate, then passes that SAME value here so dispatch does NOT
1794    /// re-resolve `_meta` — one authoritative era per request (D-11 / Pitfall 2).
1795    /// [`handle_request`](Self::handle_request) is the thin wrapper that resolves
1796    /// then calls this.
1797    pub(crate) async fn handle_request_with_context(
1798        &self,
1799        id: RequestId,
1800        request: Request,
1801        auth_context: Option<auth::AuthContext>,
1802        protocol_context: Option<crate::types::protocol::ProtocolContext>,
1803    ) -> JSONRPCResponse {
1804        // MRTR ingress (Plan 113-06, HTTP-03) — the SAME shared helper
1805        // `ServerCore` calls (twin-site parity; this site never defines its
1806        // own). Verifies a presented `requestState` against the live principal
1807        // and originating request, then folds the D-15 verdict into the context
1808        // threaded into dispatch. Inert on v1 / non-opted-in / non-eligible
1809        // requests, so the legacy path is byte-for-byte unchanged.
1810        #[cfg(feature = "streamable-http")]
1811        let (mrtr, protocol_context) = match crate::server::core::MrtrRound::begin(
1812            &request,
1813            protocol_context,
1814            auth_context.as_ref().map(|ctx| ctx.subject.as_str()),
1815            self.auth_provider.is_some(),
1816            self.request_state_codec(),
1817        ) {
1818            Ok(resolved) => resolved,
1819            // The single-source envelope builder, rather than a hand-written
1820            // `JSONRPCResponse` literal re-spelling `"2.0"` and `data: None`.
1821            Err((code, message)) => {
1822                return crate::server::task_dispatch::error_response(id, code, message)
1823            },
1824        };
1825
1826        // Capture the cacheability claim BEFORE the `match` below: arm 1 binds
1827        // `ref boxed_req` but arm 2 MOVES `boxed_req`, so `request` is gone by
1828        // the time the injection at the bottom of this function runs. Twin of
1829        // the `ServerCore` capture — and it CALLS the shared classifier in
1830        // `core.rs` rather than defining a second table, which is the twin-site
1831        // parity rule this file follows everywhere else.
1832        let cacheable = crate::server::core::request_is_cacheable(&request);
1833
1834        // G-9 / CONF-08 (Phase 118.1-08): fold the v1 `initialize` handshake's
1835        // advertised capabilities into the context threaded into DISPATCH — the
1836        // SAME shared unit `ServerCore` calls, never a second copy (twin-site
1837        // parity). The fold owns the lock, so the guard that keeps v2 traffic
1838        // off it (T-118.1-08-02) lives there too rather than being re-spelled
1839        // here; the read guard drops inside, before the `Initialize` arm below
1840        // takes the WRITE lock a few lines down. The EGRESS keeps the UNFOLDED
1841        // `protocol_context`: the fold is a handler-visibility concern, not a
1842        // wire-shape one.
1843        let dispatch_context = crate::server::core::fold_v1_handshake_capabilities(
1844            protocol_context.clone(),
1845            &self.client_capabilities,
1846            &self.supported_protocol_versions,
1847        )
1848        .await;
1849
1850        // The SECOND envelope claimant (Phase 114 plan 11), twin of the
1851        // `ServerCore` site: the `tasks/*` routes and the `tools/call` create
1852        // path state their own `resultType` and reserved-field ownership from the
1853        // site that writes them. `NONE` for every other dispatch.
1854        let mut dispatch_claim = crate::server::core::DispatchEnvelopeClaim::NONE;
1855        let mut response = match request {
1856            Request::Client(ref boxed_req)
1857                if matches!(**boxed_req, ClientRequest::Initialize(_)) =>
1858            {
1859                let ClientRequest::Initialize(init_req) = boxed_req.as_ref() else {
1860                    unreachable!("Pattern matched for Initialize");
1861                };
1862                // Store client capabilities
1863                *self.client_capabilities.write().await = Some(init_req.capabilities.clone());
1864                *self.initialized.write().await = true;
1865
1866                let negotiated_version =
1867                    crate::negotiate_protocol_version(&init_req.protocol_version);
1868
1869                let result = InitializeResult {
1870                    protocol_version: ProtocolVersion(negotiated_version.to_string()),
1871                    // Twin-site parity (114-05, D-02): the SAME shared v1
1872                    // projection `ServerCore::handle_initialize` uses — this
1873                    // site never defines its own. Without it the build-time
1874                    // tasks-extension entry, which is the v2 negotiation home,
1875                    // leaks onto the v1 `initialize` wire of every tasks server.
1876                    capabilities: crate::server::core::project_capabilities_for_v1(
1877                        &self.capabilities,
1878                    ),
1879                    server_info: self.info.clone(),
1880                    instructions: None,
1881                };
1882                JSONRPCResponse {
1883                    jsonrpc: "2.0".to_string(),
1884                    id: id.clone(),
1885                    payload: crate::types::jsonrpc::ResponsePayload::Result(
1886                        serde_json::to_value(result).unwrap(),
1887                    ),
1888                }
1889            },
1890            // `Box::pin`: the MRTR ingress/egress locals (Plan 113-06) push this
1891            // dispatch future past clippy's `large_futures` threshold. Boxing the
1892            // inner future keeps every CALLER of this method small without
1893            // changing behavior — the same treatment the two POST entrypoints and
1894            // the discover assembly already get.
1895            Request::Client(boxed_req) => {
1896                Box::pin(self.handle_client_request(
1897                    id,
1898                    *boxed_req,
1899                    auth_context,
1900                    dispatch_context,
1901                    &mut dispatch_claim,
1902                ))
1903                .await
1904            },
1905            Request::Server(_) => JSONRPCResponse {
1906                jsonrpc: "2.0".to_string(),
1907                id,
1908                payload: crate::types::jsonrpc::ResponsePayload::Error(
1909                    crate::types::jsonrpc::JSONRPCError {
1910                        code: crate::types::protocol::error_codes::METHOD_NOT_FOUND,
1911                        message: "Server requests not supported by server".to_string(),
1912                        data: None,
1913                    },
1914                ),
1915            },
1916        };
1917
1918        // Twin-site MRTR egress (Plan 113-06): the SAME shared helper `ServerCore`
1919        // calls. Converts a handler's "I need more input" signal into an
1920        // `input_required` result carrying a freshly minted `requestState`, and
1921        // STRIPS the pmcp-internal signal key on every other path.
1922        #[cfg(feature = "streamable-http")]
1923        let (disposition, reserved_field_owner) = mrtr.finish(
1924            &mut response,
1925            protocol_context.as_ref(),
1926            self.request_state_codec(),
1927        );
1928        #[cfg(not(feature = "streamable-http"))]
1929        let (disposition, reserved_field_owner) = {
1930            // No `mrtr_egress` on this build — strip the reserved signal key
1931            // here so it cannot reach the wire (see `core::scrub_mrtr_signal`).
1932            crate::server::core::scrub_mrtr_signal(&mut response);
1933            (
1934                crate::server::core::ResponseDisposition::Complete,
1935                crate::server::core::ReservedFieldOwner::None,
1936            )
1937        };
1938
1939        // Twin-site v2 envelope injection (VERS-07 / D-07 / D-08) plus the
1940        // caching-hint projection (SCHM-03): the ONE shared helper in `core.rs`
1941        // — the envelope half is v2-only, object-results-only, collision-safe,
1942        // so v1 / non-opted-in responses stay byte-identical; the caching half
1943        // runs on both eras, ensuring on v2 and STRIPPING on v1 (D-11). The
1944        // reserved-field owner comes from the egress that minted the fields,
1945        // never from the disposition (Phase 114 plan 10) — folded with the
1946        // dispatch's own claim through the SAME named rule `ServerCore` uses
1947        // (Phase 114 plan 11).
1948        let claim = dispatch_claim.or_egress(disposition, reserved_field_owner);
1949        crate::server::core::inject_v2_result_envelope(
1950            &mut response,
1951            protocol_context.as_ref(),
1952            &self.info,
1953            claim.disposition,
1954            claim.owner,
1955            cacheable,
1956        );
1957        response
1958    }
1959
1960    async fn handle_client_request(
1961        &self,
1962        id: RequestId,
1963        request: ClientRequest,
1964        auth_context: Option<auth::AuthContext>,
1965        protocol_context: Option<crate::types::protocol::ProtocolContext>,
1966        dispatch_claim: &mut crate::server::core::DispatchEnvelopeClaim,
1967    ) -> JSONRPCResponse {
1968        // ADAPTER (a) — tasks/* dispatch at the post-auth assembly layer.
1969        //
1970        // The four `tasks/*` variants are served by the SHARED `task_dispatch`
1971        // unit (the SAME `route_tasks_endpoint` `ServerCore` uses), returning a
1972        // full `JSONRPCResponse` DIRECTLY — no `JSONRPCResponse -> Result<Value>`
1973        // round-trip and no double-wrap, so the FROZEN `-32002` pending code
1974        // survives unchanged (T-102-07).
1975        //
1976        // SECURITY (T-102-04): this interception sits DOWNSTREAM of auth
1977        // resolution — `auth_context` is the already-resolved context the
1978        // transport layer passed into `handle_request`, threaded here unchanged.
1979        // The tasks/* path is therefore subject to the SAME auth as every other
1980        // request; owner-scoping inside `route_tasks_endpoint` derives the owner
1981        // from this `AuthContext` ONLY (never client params), enforcing
1982        // cross-owner isolation (T-102-05).
1983        #[cfg(not(target_arch = "wasm32"))]
1984        if matches!(
1985            request,
1986            ClientRequest::TasksGet(_)
1987                | ClientRequest::TasksResult(_)
1988                | ClientRequest::TasksList(_)
1989                | ClientRequest::TasksCancel(_)
1990        ) {
1991            let (response, claim) = self
1992                .task_dispatch()
1993                .route_tasks_endpoint(
1994                    id,
1995                    &request,
1996                    auth_context.as_ref(),
1997                    // The context resolved ONCE at transport ingress, CONSUMED
1998                    // here. Its `era` is read by the `tasks/result` pending
1999                    // refusal, so that a v2 request cannot elicit the
2000                    // spec-prohibited `-32002` (Finding 11;
2001                    // `task_dispatch::is_v1_task_era`), and by the two v2
2002                    // retirement gates for `tasks/list` / `tasks/result`
2003                    // (TASK-03; `task_dispatch::tasks_list_serves_on_era`); its
2004                    // `client_capabilities` are read by the v2
2005                    // extension-declaration gate (TASK-05). Passing the whole
2006                    // context is what keeps this dispatcher from ever re-reading
2007                    // `params._meta` for a second answer. Every gate lives in
2008                    // `task_dispatch`, never here.
2009                    protocol_context.as_ref(),
2010                )
2011                .await;
2012            // The claim travels WITH the response: a v2 `tasks/get` on an
2013            // `input_required` task owns the top-level `inputRequests` the
2014            // reserved-field registry would otherwise strip (114-10 row 23).
2015            *dispatch_claim = claim;
2016            return response;
2017        }
2018
2019        // ADAPTER (b) — `logging/setLevel`, era-branched (Phase 118.2-08, D-13).
2020        //
2021        // Intercepted HERE, above `process_client_request`, for the same
2022        // structural reason ADAPTER (a) above intercepts `tasks/*`: this
2023        // method's v2 answer is a JSON-RPC ERROR with a SPECIFIC code, and
2024        // `Self::create_response` flattens EVERY `Err` returned by
2025        // `process_client_request` to `-32603 INTERNAL_ERROR`. A `-32601`
2026        // therefore cannot travel through that function's
2027        // `Result<serde_json::Value>` return type at all — the same
2028        // `JSONRPCResponse -> Result<Value>` round-trip the `tasks/*` adapter
2029        // exists to avoid. Making `create_response` code-aware instead would
2030        // silently change the wire code of every other handler that returns a
2031        // `Error::Protocol`, which is not this plan's change to make.
2032        //
2033        // The ANSWER itself is not computed here: it comes from the single
2034        // shared unit in `server/core.rs`, the very same one `ServerCore`'s
2035        // dispatch arm calls. One era branch, two roots — that is D-13.
2036        if matches!(request, ClientRequest::SetLoggingLevel { .. }) {
2037            return crate::server::core::set_logging_level_response(
2038                id,
2039                protocol_context.as_ref().map(|ctx| ctx.era),
2040            );
2041        }
2042
2043        let result = self
2044            .process_client_request(
2045                id.clone(),
2046                request,
2047                auth_context,
2048                protocol_context,
2049                dispatch_claim,
2050            )
2051            .await;
2052        Self::create_response(id, result)
2053    }
2054
2055    /// Process a client request and return the result.
2056    ///
2057    /// `dispatch_claim` is the out-param the `tools/call` create path writes its
2058    /// v2 envelope claim into (Phase 114 plan 11); every other arm leaves it as
2059    /// the caller set it.
2060    async fn process_client_request(
2061        &self,
2062        request_id: RequestId,
2063        request: ClientRequest,
2064        auth_context: Option<auth::AuthContext>,
2065        protocol_context: Option<crate::types::protocol::ProtocolContext>,
2066        dispatch_claim: &mut crate::server::core::DispatchEnvelopeClaim,
2067    ) -> Result<serde_json::Value> {
2068        match request {
2069            ClientRequest::Initialize(_) => {
2070                // Already handled above
2071                unreachable!("Initialize should be handled separately")
2072            },
2073            ClientRequest::ListTools(req) => self.handle_list_tools(req),
2074            ClientRequest::CallTool(req) => {
2075                self.handle_call_tool(
2076                    request_id,
2077                    req,
2078                    auth_context,
2079                    protocol_context,
2080                    dispatch_claim,
2081                )
2082                .await
2083            },
2084            ClientRequest::ListPrompts(req) => self.handle_list_prompts(req),
2085            ClientRequest::GetPrompt(req) => {
2086                self.handle_get_prompt(request_id, req, auth_context, protocol_context)
2087                    .await
2088            },
2089            ClientRequest::ListResources(req) => {
2090                self.handle_list_resources(request_id, req, auth_context, protocol_context)
2091                    .await
2092            },
2093            ClientRequest::ReadResource(req) => {
2094                self.handle_read_resource(request_id, req, auth_context, protocol_context)
2095                    .await
2096            },
2097            ClientRequest::ListResourceTemplates(req) => {
2098                Self::handle_list_resource_templates(self, req)
2099            },
2100            // `completion/complete` (Phase 118.1-04, CONF-05 / G-4) — its OWN
2101            // arm, no longer inside the catch-all below. The shared unit is
2102            // DEFINED in `server/core.rs` and merely CALLED here, per the
2103            // twin-site parity rule stated at `src/server/core.rs`'s MRTR
2104            // section: `mod.rs` calls these helpers, it never defines its own.
2105            ClientRequest::Complete(req) => {
2106                crate::server::core::complete_completion(self.completions.as_ref(), &req)
2107                    .await
2108                    .and_then(|result| serde_json::to_value(result).map_err(Into::into))
2109            },
2110            // `logging/setLevel` (Phase 118.2-08, CONF-10 / D-13) — its OWN
2111            // arm, no longer inside the residual below, and answering from the
2112            // SAME shared unit in `server/core.rs` that `ServerCore`'s dispatch
2113            // arm calls.
2114            //
2115            // Reached only when a caller drives this function DIRECTLY. On the
2116            // production path `handle_client_request`'s ADAPTER (b) has already
2117            // answered — it must, because the v2 half of the era branch is a
2118            // `-32601` and `Self::create_response` flattens every `Err` from
2119            // this function to `-32603`. The v1 half is the whole of what this
2120            // arm can express, and it is spelled by calling the shared unit
2121            // rather than by re-typing `json!({})`, so a future change to the
2122            // measured shape (Pitfall 8) lands in exactly one place.
2123            ClientRequest::SetLoggingLevel { level: _ } => {
2124                Ok(crate::server::core::set_logging_level_v1_result())
2125            },
2126            // RESIDUAL, recorded rather than silently unified (Phase 118.1-04,
2127            // RESEARCH Open Question 4): these THREE methods STILL diverge
2128            // between the two native dispatchers. Here they answer
2129            // `json!({})`; `ServerCore`'s `_ =>` arm
2130            // (`src/server/core.rs`, the arm immediately after the
2131            // `ClientRequest::SetLoggingLevel` one) answers `-32601 Method not
2132            // supported`. Only this dispatcher is on the HTTP path, so only
2133            // this side is measured by the official conformance suite. G-5
2134            // (`resources/subscribe`, `resources/unsubscribe`,
2135            // `logging/setLevel`, `ping` retirement on v2) is the requirement
2136            // that owns them; unifying them here would smuggle a behaviour
2137            // change in behind a conformance fix.
2138            //
2139            // HISTORY — a residual is recorded, never silently unified, and
2140            // never silently SHRUNK either: `logging/setLevel` was the FOURTH
2141            // method on this arm and left it in Phase 118.2-08 under D-13,
2142            // because the official suite measures that method and the two roots
2143            // disagreed about it. `ping` in particular must stay here: it
2144            // already carries a recorded 118.1 v2 behaviour change (HTTP 404 /
2145            // `-32601` at the transport gate) and a second, differently-shaped
2146            // retirement at this layer would be a new divergence, not a fix.
2147            ClientRequest::Subscribe(_) | ClientRequest::Unsubscribe(_) | ClientRequest::Ping => {
2148                Ok(serde_json::json!({}))
2149            },
2150            ClientRequest::CreateMessage(req) => {
2151                self.handle_create_message(request_id, *req, protocol_context)
2152                    .await
2153            },
2154            // Note: Elicitation responses are now handled as the response to
2155            // ServerRequest::ElicitationCreate in the JSON-RPC response flow,
2156            // not as a separate client request variant.
2157            // Task requests (experimental MCP Tasks). On non-wasm these are
2158            // intercepted UPSTREAM in `handle_client_request` (adapter (a)) and
2159            // served by the shared `task_dispatch` unit, so they never reach
2160            // here. This arm is the wasm32 fall-through (the task lifecycle is
2161            // non-wasm-gated): the endpoints are genuinely unsupported there.
2162            ClientRequest::TasksGet(_)
2163            | ClientRequest::TasksResult(_)
2164            | ClientRequest::TasksList(_)
2165            | ClientRequest::TasksCancel(_) => Err(crate::Error::protocol(
2166                crate::ErrorCode::METHOD_NOT_FOUND,
2167                "Tasks not supported on this build",
2168            )),
2169        }
2170    }
2171
2172    /// Borrow this server's task backends as a [`TaskDispatch`] for the shared
2173    /// task-lifecycle unit. Single construction point so the `tasks/*` and
2174    /// create-path call sites don't re-inline the struct literal.
2175    #[cfg(not(target_arch = "wasm32"))]
2176    fn task_dispatch(&self) -> crate::server::task_dispatch::TaskDispatch<'_> {
2177        crate::server::task_dispatch::TaskDispatch {
2178            task_store: &self.task_store,
2179            task_router: &self.task_router,
2180            // The EXISTING public accessor, not a new field and not a widened
2181            // one — the same read `listen_server_view` makes for
2182            // `subscriptions/listen` (D-113-N), now feeding the SAME identity
2183            // table for `tasks/*` (TASK-05).
2184            has_auth_provider: self.get_auth_provider().is_some(),
2185        }
2186    }
2187
2188    /// Create a JSON-RPC response from a result.
2189    fn create_response(id: RequestId, result: Result<serde_json::Value>) -> JSONRPCResponse {
2190        match result {
2191            Ok(value) => JSONRPCResponse {
2192                jsonrpc: "2.0".to_string(),
2193                id,
2194                payload: crate::types::jsonrpc::ResponsePayload::Result(value),
2195            },
2196            Err(e) => JSONRPCResponse {
2197                jsonrpc: "2.0".to_string(),
2198                id,
2199                payload: crate::types::jsonrpc::ResponsePayload::Error(
2200                    crate::types::jsonrpc::JSONRPCError {
2201                        code: crate::types::protocol::error_codes::INTERNAL_ERROR,
2202                        message: e.to_string(),
2203                        data: None,
2204                    },
2205                ),
2206            },
2207        }
2208    }
2209
2210    fn handle_list_tools(&self, _req: ListToolsRequest) -> Result<Value> {
2211        let tools: Vec<ToolInfo> = self.tool_infos.values().cloned().collect();
2212
2213        Ok(serde_json::to_value(ListToolsResult {
2214            tools,
2215            next_cursor: None,
2216            ttl_ms: None,
2217            cache_scope: None,
2218        })?)
2219    }
2220
2221    async fn handle_call_tool(
2222        &self,
2223        request_id: RequestId,
2224        req: CallToolRequest,
2225        auth_context: Option<auth::AuthContext>,
2226        protocol_context: Option<crate::types::protocol::ProtocolContext>,
2227        dispatch_claim: &mut crate::server::core::DispatchEnvelopeClaim,
2228    ) -> Result<Value> {
2229        let handler = self
2230            .tools
2231            .get(&req.name)
2232            .ok_or_else(|| Error::not_found(format!("Tool '{}' not found", req.name)))?;
2233
2234        // Capture the create-path inputs BEFORE `req` is partially moved
2235        // (arguments are consumed by the middleware/handler below). The
2236        // create-path gate reads:
2237        //   - the ERA's create trigger — `req.task` on v1, the client's
2238        //     per-request tasks-extension declaration on v2 (plan 114-12), and
2239        //   - the tool's declared `TaskSupport` (from the cached `tool_infos`).
2240        // The SHARED `maybe_build_task_created` enforces the FULL gate
2241        // internally — we pass these RAW facts, never a pre-filtered precondition.
2242        // `CreateTrigger::resolve` is the ONE place the era picks a trigger, so
2243        // this dispatcher cannot implement a trigger `ServerCore` misses.
2244        #[cfg(not(target_arch = "wasm32"))]
2245        let create_trigger = crate::server::task_dispatch::CreateTrigger::resolve(
2246            protocol_context.as_ref().map(|ctx| ctx.era),
2247            req.task.is_some(),
2248            protocol_context.as_ref(),
2249        );
2250        #[cfg(not(target_arch = "wasm32"))]
2251        let tool_task_support = self
2252            .tool_infos
2253            .get(&req.name)
2254            .and_then(|info| info.execution.as_ref())
2255            .and_then(|exec| exec.task_support);
2256        #[cfg(not(target_arch = "wasm32"))]
2257        let create_path_id = request_id.clone();
2258
2259        let request_id_str = request_id.to_string();
2260        let cancellation_token = self
2261            .cancellation_manager
2262            .create_token(request_id_str.clone())
2263            .await;
2264
2265        // Auth context now comes from the transport layer
2266        // Validate authentication if auth provider is configured
2267        let validated_auth_context = if let Some(auth_provider) = &self.auth_provider {
2268            // If auth_context was provided by transport, use it; otherwise validate
2269            if auth_context.is_some() {
2270                auth_context
2271            } else {
2272                // Fallback: try to validate without headers (for backward compatibility)
2273                auth_provider.validate_request(None).await?
2274            }
2275        } else {
2276            auth_context // No auth provider, just use what was provided
2277        };
2278
2279        // Check tool authorization if tool authorizer is configured
2280        if let (Some(auth_ctx), Some(authorizer)) = (&validated_auth_context, &self.tool_authorizer)
2281        {
2282            if !authorizer.can_access_tool(auth_ctx, &req.name).await? {
2283                return Err(Error::protocol(
2284                    crate::error::ErrorCode::AUTHENTICATION_REQUIRED,
2285                    format!("Access denied for tool '{}'", req.name),
2286                ));
2287            }
2288        }
2289
2290        // The request-scoped progress reporter — the channel
2291        // `extra.report_progress(..)` actually reads. Resolved BEFORE
2292        // `protocol_context` is moved into `extra` below, because the transport's
2293        // session-bound sink rides on it (Phase 118.1 plan 11).
2294        #[allow(clippy::used_underscore_binding)] // _meta is part of MCP protocol spec
2295        let progress_reporter =
2296            self.progress_reporter_for(req._meta.as_ref(), protocol_context.as_ref());
2297
2298        // Clone the validated auth context for the create-path owner resolution
2299        // (the original is moved into `extra` below). This guarantees the
2300        // create-path scopes the minted task to the SAME owner the tool ran as.
2301        #[cfg(not(target_arch = "wasm32"))]
2302        let create_path_auth = validated_auth_context.clone();
2303
2304        // Capture the ALREADY-RESOLVED era before `protocol_context` is moved into
2305        // `extra` below, so the create-path owner binding reads the SAME ingress
2306        // value the handler does and never re-parses `params._meta` (Phase 112).
2307        // The twin of the `ServerCore` capture on its own `CallTool` arm.
2308        #[cfg(not(target_arch = "wasm32"))]
2309        let create_path_era = protocol_context.as_ref().map(|ctx| ctx.era);
2310
2311        // Same capture-before-move reason: the emit-time outputSchema validator
2312        // is era-branched (Phase 115 D-01) and `protocol_context` is moved into
2313        // `extra` below. UN-cfg'd — unlike `create_path_era` — because the
2314        // validation call site compiles on wasm32 too. The twin of the
2315        // `ServerCore` capture on its own `CallTool` arm.
2316        let validation_era = protocol_context.as_ref().map(|ctx| ctx.era);
2317
2318        // Propagate the request's `_meta` object (raw JSON incl. namespaced
2319        // `other` keys) so handlers can read it via `extra.request_meta` in the
2320        // high-level `Server` path too (ServerCore already wires this at core.rs).
2321        #[allow(clippy::used_underscore_binding)] // _meta is part of MCP protocol spec
2322        let request_meta_value = crate::server::core::request_meta_to_value(req._meta.as_ref());
2323
2324        let mut extra = self.attach_peer(
2325            crate::server::cancellation::RequestHandlerExtra::new(
2326                request_id.to_string(),
2327                cancellation_token,
2328            )
2329            .with_auth_context(validated_auth_context)
2330            .with_progress_reporter(progress_reporter)
2331            // Surface whether the client requested task augmentation so handlers
2332            // can branch on `extra.is_task_request()` in the high-level `Server`
2333            // path too (ServerCore already wires this at core.rs). Additive: the
2334            // dispatcher's own task-creation decision still reads `req.task`.
2335            .with_task_request(req.task.clone())
2336            .with_request_meta(request_meta_value)
2337            // Thread the once-at-ingress resolved protocol context (Phase 112) —
2338            // the twin of the ServerCore wiring so handlers read the SAME
2339            // era/identity on both dispatch sites.
2340            .with_protocol_context(protocol_context),
2341        );
2342
2343        // D-03.3 (TOUT-01): clone the interior-mutable result-`_meta` slot BEFORE
2344        // `extra` is moved into `handle_output`, so any `extra.set_result_meta(..)`
2345        // the handler performs can be drained back onto the Payload-path result.
2346        #[cfg(not(target_arch = "wasm32"))]
2347        let result_meta_handle = extra.result_meta_handle();
2348
2349        // Execute tool with middleware (native-only)
2350        #[cfg(not(target_arch = "wasm32"))]
2351        let dispatch_output = {
2352            // Create tool context for middleware
2353            let context = tool_middleware::ToolContext::new(&req.name, &request_id_str);
2354
2355            // Clone arguments for middleware processing
2356            let mut args = req.arguments;
2357
2358            // Process request through tool middleware chain.
2359            // Middleware rejection short-circuits tool execution. REQUEST
2360            // middleware runs BEFORE the handler for EVERY tool, regardless of the
2361            // ToolOutput variant it will return (kept here so it fires on both the
2362            // Payload and the verbatim Result path).
2363            self.tool_middleware_chain
2364                .read()
2365                .await
2366                .process_request(&req.name, &mut args, &mut extra, &context)
2367                .await?;
2368
2369            // Execute the tool. `handle_output` returns `Result<ToolOutput>`; the
2370            // SHARED `resolve_tool_output` (D-05) is the SINGLE place that decides
2371            // Payload-vs-Result and encodes the response-middleware-bypass rule, so
2372            // this dispatcher and `ServerCore` can never drift on it.
2373            let output = handler.handle_output(args, extra).await;
2374            let mut resolved = task_dispatch::resolve_tool_output(output);
2375
2376            // Why: `ToolOutput::Result` deliberately BYPASSES RESPONSE middleware
2377            // (D-04 + D-04a — USER-APPROVED and LOCKED: "keep the bypass, harden
2378            // it"). The handler owns the full envelope, including its own
2379            // redaction/sanitization, at the same trust level as returning a raw
2380            // Value today. RESPONSE middleware (redaction/sanitization/audit) +
2381            // `handle_tool_error` therefore run ONLY for the Payload/error arm
2382            // below; REQUEST middleware already fired above for EVERY tool, and a
2383            // handler `Err(_)` still routes through `handle_tool_error` via this
2384            // arm (the bypass is scoped to the successful `Verbatim` arm only).
2385            if let task_dispatch::DispatchOutput::Middleware(ref mut result) = resolved {
2386                // Process response through tool middleware chain
2387                if let Err(e) = self
2388                    .tool_middleware_chain
2389                    .read()
2390                    .await
2391                    .process_response(&req.name, result, &context)
2392                    .await
2393                {
2394                    // Log error but continue with original result
2395                    tracing::warn!("Tool response middleware processing failed: {}", e);
2396                }
2397
2398                // If tool execution failed, call handle_tool_error
2399                if let Err(ref e) = result {
2400                    self.tool_middleware_chain
2401                        .read()
2402                        .await
2403                        .handle_tool_error(&req.name, e, &context)
2404                        .await;
2405                }
2406            }
2407
2408            resolved
2409        };
2410
2411        // On WASM, execute tool directly without middleware
2412        #[cfg(target_arch = "wasm32")]
2413        let result = handler.handle(req.arguments, extra).await;
2414
2415        // Token cleanup is unconditional (success or failure) and does not
2416        // touch the outcome, so it runs once before the value/error split —
2417        // preserving cleanup parity for the verbatim `ToolOutput::Result` arm.
2418        self.cancellation_manager
2419            .remove_token(&request_id_str)
2420            .await;
2421        let result = match dispatch_output {
2422            // VERBATIM (D-04 + D-04a): the handler owns the full `CallToolResult`
2423            // envelope — emit it to the wire as-is. RESPONSE middleware, the
2424            // create-path gate, text-wrap, and widget enrichment are ALL bypassed
2425            // (mirrors the `ToolRejected` verbatim early-return below, which also
2426            // returns after the unconditional token cleanup).
2427            //
2428            // D-06 (Phase 118.1) RECLASSIFIES exactly one clause of D-04a: the
2429            // bypass covers the response PIPELINE, not the handler's own
2430            // `extra.set_result_meta(..)`. Those keys come from the same handler
2431            // that authored this envelope, at the same trust level, so draining
2432            // them here merges a handler's two `_meta` sources rather than
2433            // reintroducing server-side rewriting. Handler-key-wins precedence,
2434            // never a whole-map replace. Twin of the `ServerCore` arm.
2435            task_dispatch::DispatchOutput::Verbatim(call_result) => {
2436                #[cfg(not(target_arch = "wasm32"))]
2437                let call_result = {
2438                    let mut call_result = call_result;
2439                    if let Some(handler_meta) = result_meta_handle.take_result_meta() {
2440                        crate::server::cancellation::merge_result_meta(
2441                            &mut call_result,
2442                            handler_meta,
2443                        );
2444                    }
2445                    call_result
2446                };
2447                return Ok(serde_json::to_value(call_result)?);
2448            },
2449            task_dispatch::DispatchOutput::Middleware(result) => match result {
2450                Ok(v) => v,
2451                // `Error::ToolRejected` is an APPLICATION-level rejection (e.g.
2452                // Code Mode policy: a SELECT missing its LIMIT), not a protocol
2453                // fault. Map it to a successful `CallToolResult { isError: true }`
2454                // (message → content, details → structuredContent) so the model
2455                // reads the reason and retries with corrected input, instead of
2456                // `?`-propagating a JSON-RPC error that reads as a server crash.
2457                // All other errors keep propagating as protocol errors.
2458                Err(Error::ToolRejected { message, details }) => {
2459                    return Ok(serde_json::to_value(CallToolResult::rejected(
2460                        message, details,
2461                    ))?);
2462                },
2463                Err(e) => return Err(e),
2464            },
2465        };
2466
2467        // CREATE-PATH (Phase 102, HTASK-02; era-aware trigger from plan 114-12):
2468        // a `tools/call` whose era trigger fired over the high-level `Server`
2469        // mints a store task and returns a `CreateTaskResult` envelope. The
2470        // SHARED `maybe_build_task_created` gate is the SINGLE source of truth:
2471        // it returns `Some` ONLY when the era's trigger fired (v1: the `task`
2472        // field; v2: the client's tasks-extension declaration) AND a store
2473        // backend exists AND the tool's `TaskSupport ∈ {Required, Optional}` AND
2474        // the produced value is task-shaped (`taskId` + `status`); otherwise
2475        // `None` (fall through to a normal `CallToolResult`, no leakage — incl.
2476        // `Forbidden`/`None`).
2477        //
2478        // The store mints the canonical id (D-STORE-MINTS-ID); the tool's
2479        // fabricated `taskId` is never trusted on the wire. We pass the RAW
2480        // facts (`create_trigger`, `tool_task_support`) — the gate enforces the
2481        // complete precondition internally. The gate returns a full
2482        // `JSONRPCResponse`; we decompose it back into this fn's `Result<Value>`
2483        // contract (the caller re-wraps with the SAME request id via
2484        // `create_response`, so the id is preserved and `-32603` store errors
2485        // surface as JSON-RPC errors).
2486        #[cfg(not(target_arch = "wasm32"))]
2487        {
2488            if let Some((response, claim)) = self
2489                .task_dispatch()
2490                .maybe_build_task_created(
2491                    create_path_id,
2492                    &result,
2493                    tool_task_support,
2494                    create_trigger,
2495                    create_path_auth.as_ref(),
2496                    create_path_era,
2497                )
2498                .await
2499            {
2500                // On v2 this is the ONE response in the whole surface that earns
2501                // `resultType: "task"`; the claim is what carries that fact past
2502                // the `Result<Value>` contract this fn is bound to.
2503                *dispatch_claim = claim;
2504                return match response.payload {
2505                    crate::types::jsonrpc::ResponsePayload::Result(value) => Ok(value),
2506                    // The create-path only emits `-32603` store errors here; the
2507                    // caller's `create_response` re-wraps `Err` as `-32603`, so
2508                    // the code is preserved. Surface the store's message.
2509                    crate::types::jsonrpc::ResponsePayload::Error(err) => {
2510                        Err(crate::Error::Protocol {
2511                            code: crate::error::ErrorCode(err.code),
2512                            message: err.message,
2513                            data: err.data,
2514                        })
2515                    },
2516                };
2517            }
2518        }
2519
2520        // TOUT-02 double-wrap tripwire: BEFORE stringifying `result` into text
2521        // content, WARN (+ debug_assert in debug/CI) if it structurally resembles
2522        // an already-built `CallToolResult` — the silent double-wrap bug. Honors
2523        // the per-tool `suppress_double_wrap_check` opt-out (D-08). Non-wasm only
2524        // (the `task_dispatch` unit is non-wasm, matching the create-path above).
2525        #[cfg(not(target_arch = "wasm32"))]
2526        task_dispatch::double_wrap_tripwire(
2527            &req.name,
2528            &result,
2529            self.suppress_double_wrap.contains(req.name.as_str()),
2530        );
2531
2532        // Build CallToolResult, adding structured_content for widget tools and
2533        // for tools with a declared outputSchema (MCP spec: a tool that
2534        // declares an outputSchema SHOULD return structuredContent conforming
2535        // to it). The text voice always carries the serialized value so
2536        // text-only clients keep working.
2537        let text = result.to_string();
2538        let mut call_result = CallToolResult::new(vec![crate::types::Content::text(text)]);
2539
2540        if let Some(info) = self.tool_infos.get(&req.name) {
2541            // A declared outputSchema means structuredContent is emitted below
2542            // (via widget enrichment or the schema bridge) — validate the value
2543            // against it regardless of which branch does the emitting.
2544            if let Some(schema) = &info.output_schema {
2545                output_validation::warn_on_schema_mismatch(
2546                    &req.name,
2547                    schema,
2548                    &result,
2549                    validation_era,
2550                );
2551            }
2552            if info.widget_meta().is_some() {
2553                call_result = call_result.with_widget_enrichment(info, result);
2554            } else if info.output_schema.is_some() {
2555                call_result = call_result.with_structured_content(result);
2556            }
2557        }
2558
2559        // D-03.3: drain any handler-set result `_meta` (via extra.set_result_meta)
2560        // and merge it onto the Payload-built envelope with handler-key-wins
2561        // precedence (unrelated widget/native keys preserved). The verbatim
2562        // `ToolOutput::Result` arm above returns earlier and still owns its
2563        // content, its redaction and its bypass of the response pipeline — but
2564        // since D-06 (Phase 118.1) it performs this SAME drain against its own
2565        // envelope before returning, so `set_result_meta` is no longer silently
2566        // dropped there. By the time control reaches this line the slot has
2567        // therefore only ever been filled by a Payload-path handler.
2568        #[cfg(not(target_arch = "wasm32"))]
2569        if let Some(handler_meta) = result_meta_handle.take_result_meta() {
2570            crate::server::cancellation::merge_result_meta(&mut call_result, handler_meta);
2571        }
2572
2573        Ok(serde_json::to_value(call_result)?)
2574    }
2575
2576    fn handle_list_prompts(&self, _req: ListPromptsRequest) -> Result<Value> {
2577        let prompts = self
2578            .prompts
2579            .iter()
2580            .map(|(name, handler)| {
2581                // Use prompt metadata if provided, otherwise use defaults
2582                if let Some(mut info) = handler.metadata() {
2583                    // Ensure the name matches the registered name
2584                    info.name.clone_from(name);
2585                    info
2586                } else {
2587                    crate::types::PromptInfo::new(name)
2588                }
2589            })
2590            .collect::<Vec<_>>();
2591
2592        Ok(serde_json::to_value(ListPromptsResult {
2593            prompts,
2594            next_cursor: None,
2595            ttl_ms: None,
2596            cache_scope: None,
2597        })?)
2598    }
2599
2600    async fn handle_get_prompt(
2601        &self,
2602        request_id: RequestId,
2603        req: GetPromptRequest,
2604        auth_context: Option<auth::AuthContext>,
2605        protocol_context: Option<crate::types::protocol::ProtocolContext>,
2606    ) -> Result<Value> {
2607        let handler = self
2608            .prompts
2609            .get(&req.name)
2610            .ok_or_else(|| Error::not_found(format!("Prompt '{}' not found", req.name)))?;
2611
2612        let request_id_str = request_id.to_string();
2613        let cancellation_token = self
2614            .cancellation_manager
2615            .create_token(request_id_str.clone())
2616            .await;
2617
2618        // The request-scoped progress reporter — the SAME resolution the
2619        // tools/call dispatcher makes, so a prompt handler over v1 HTTP emits on
2620        // the session stream too (Phase 118.1 plan 11).
2621        #[allow(clippy::used_underscore_binding)] // _meta is part of MCP protocol spec
2622        let progress_reporter =
2623            self.progress_reporter_for(req._meta.as_ref(), protocol_context.as_ref());
2624
2625        // Propagate the request `_meta` (raw JSON) and the once-at-ingress
2626        // resolved protocol context so prompt handlers read
2627        // era/client_info/trace_context via `extra` on the high-level `Server`
2628        // path too — the twin of the ServerCore wiring (Phase 112, mirrors the
2629        // handle_call_tool twin).
2630        #[allow(clippy::used_underscore_binding)] // _meta is part of MCP protocol spec
2631        let request_meta_value = crate::server::core::request_meta_to_value(req._meta.as_ref());
2632
2633        let extra = self.attach_peer(
2634            crate::server::cancellation::RequestHandlerExtra::new(
2635                request_id_str.clone(),
2636                cancellation_token,
2637            )
2638            .with_auth_context(auth_context)
2639            .with_progress_reporter(progress_reporter)
2640            .with_request_meta(request_meta_value)
2641            .with_protocol_context(protocol_context),
2642        );
2643        let result = match handler.handle(req.arguments, extra).await {
2644            Ok(v) => {
2645                self.cancellation_manager
2646                    .remove_token(&request_id_str)
2647                    .await;
2648                Ok(v)
2649            },
2650            Err(e) => {
2651                self.cancellation_manager
2652                    .remove_token(&request_id_str)
2653                    .await;
2654                Err(e)
2655            },
2656        }?;
2657        Ok(serde_json::to_value(result)?)
2658    }
2659
2660    async fn handle_list_resources(
2661        &self,
2662        request_id: RequestId,
2663        req: ListResourcesRequest,
2664        auth_context: Option<auth::AuthContext>,
2665        // THREADED, not resolved here (Phase 118.1-08, G-9) — the twin of the
2666        // `ServerCore` site. `ListResourcesRequest` carries no `_meta`, so the
2667        // context can only arrive from the caller.
2668        protocol_context: Option<crate::types::protocol::ProtocolContext>,
2669    ) -> Result<Value> {
2670        if let Some(handler) = &self.resources {
2671            let request_id_str = request_id.to_string();
2672            let cancellation_token = self
2673                .cancellation_manager
2674                .create_token(request_id_str.clone())
2675                .await;
2676            let extra = self.attach_peer(
2677                crate::server::cancellation::RequestHandlerExtra::new(
2678                    request_id_str.clone(),
2679                    cancellation_token,
2680                )
2681                .with_auth_context(auth_context)
2682                .with_protocol_context(protocol_context),
2683            );
2684            let mut result = match handler.list(req.cursor, extra).await {
2685                Ok(v) => {
2686                    self.cancellation_manager
2687                        .remove_token(&request_id_str)
2688                        .await;
2689                    Ok(v)
2690                },
2691                Err(e) => {
2692                    self.cancellation_manager
2693                        .remove_token(&request_id_str)
2694                        .await;
2695                    Err(e)
2696                },
2697            }?;
2698            // Enrich ResourceInfo with tool _meta for widget resources
2699            if !self.uri_to_tool_meta.is_empty() {
2700                for resource in &mut result.resources {
2701                    if let Some(tool_meta) = self.uri_to_tool_meta.get(&resource.uri) {
2702                        let meta = resource.meta.get_or_insert_with(serde_json::Map::new);
2703                        crate::types::ui::deep_merge(meta, tool_meta.clone());
2704                    }
2705                }
2706            }
2707            Ok(serde_json::to_value(result)?)
2708        } else {
2709            Ok(serde_json::to_value(ListResourcesResult {
2710                resources: vec![],
2711                next_cursor: None,
2712                ttl_ms: None,
2713                cache_scope: None,
2714            })?)
2715        }
2716    }
2717
2718    async fn handle_read_resource(
2719        &self,
2720        request_id: RequestId,
2721        req: ReadResourceRequest,
2722        auth_context: Option<auth::AuthContext>,
2723        protocol_context: Option<crate::types::protocol::ProtocolContext>,
2724    ) -> Result<Value> {
2725        let handler = self
2726            .resources
2727            .as_ref()
2728            .ok_or_else(|| Error::not_found("No resource handler configured".to_string()))?;
2729
2730        let request_id_str = request_id.to_string();
2731        let cancellation_token = self
2732            .cancellation_manager
2733            .create_token(request_id_str.clone())
2734            .await;
2735
2736        // The request-scoped progress reporter — the SAME resolution the
2737        // tools/call dispatcher makes, so a resource read over v1 HTTP emits on
2738        // the session stream too (Phase 118.1 plan 11).
2739        #[allow(clippy::used_underscore_binding)] // _meta is part of MCP protocol spec
2740        let progress_reporter =
2741            self.progress_reporter_for(req._meta.as_ref(), protocol_context.as_ref());
2742
2743        // Propagate the request `_meta` (raw JSON) and the once-at-ingress
2744        // resolved protocol context so resource handlers read
2745        // era/client_info/trace_context via `extra` on the high-level `Server`
2746        // path too — the twin of the ServerCore wiring (Phase 112).
2747        #[allow(clippy::used_underscore_binding)] // _meta is part of MCP protocol spec
2748        let request_meta_value = crate::server::core::request_meta_to_value(req._meta.as_ref());
2749
2750        let extra = self.attach_peer(
2751            crate::server::cancellation::RequestHandlerExtra::new(
2752                request_id_str.clone(),
2753                cancellation_token,
2754            )
2755            .with_auth_context(auth_context)
2756            .with_progress_reporter(progress_reporter)
2757            .with_request_meta(request_meta_value)
2758            .with_protocol_context(protocol_context),
2759        );
2760        let mut result = match handler.read(&req.uri, extra).await {
2761            Ok(v) => {
2762                self.cancellation_manager
2763                    .remove_token(&request_id_str)
2764                    .await;
2765                Ok(v)
2766            },
2767            Err(e) => {
2768                self.cancellation_manager
2769                    .remove_token(&request_id_str)
2770                    .await;
2771                Err(e)
2772            },
2773        }?;
2774        // Merge tool descriptor keys into content _meta for widget resources
2775        if !self.uri_to_tool_meta.is_empty() {
2776            for content in &mut result.contents {
2777                if let crate::types::Content::Resource { uri, meta, .. } = content {
2778                    if let Some(tool_meta) = self.uri_to_tool_meta.get(uri.as_str()) {
2779                        let content_meta = meta.get_or_insert_with(serde_json::Map::new);
2780                        crate::types::ui::deep_merge(content_meta, tool_meta.clone());
2781                    }
2782                }
2783            }
2784        }
2785        Ok(serde_json::to_value(result)?)
2786    }
2787
2788    #[allow(clippy::unused_self)]
2789    fn handle_list_resource_templates(&self, _req: ListResourceTemplatesRequest) -> Result<Value> {
2790        Ok(serde_json::to_value(ListResourceTemplatesResult {
2791            resource_templates: vec![],
2792            next_cursor: None,
2793            ttl_ms: None,
2794            cache_scope: None,
2795        })?)
2796    }
2797
2798    async fn handle_create_message(
2799        &self,
2800        request_id: RequestId,
2801        req: crate::types::CreateMessageParams,
2802        // THREADED, not resolved here (Phase 118.1-08, G-9). This arm serves an
2803        // INBOUND `sampling/createMessage` — a `ClientRequest` variant, so a
2804        // client handshake absolutely does have meaning here and the site is
2805        // THREAD-THEN-FOLD, not a NO-OP. (The server-to-client direction is a
2806        // `ServerRequest` handled by the peer dispatcher, which builds no
2807        // `RequestHandlerExtra` at all.) `CreateMessageParams` carries no
2808        // `_meta`, so the context can only arrive from the caller.
2809        protocol_context: Option<crate::types::protocol::ProtocolContext>,
2810    ) -> Result<Value> {
2811        let handler = self
2812            .sampling
2813            .as_ref()
2814            .ok_or_else(|| Error::not_found("No sampling handler configured".to_string()))?;
2815
2816        let request_id_str = request_id.to_string();
2817        let cancellation_token = self
2818            .cancellation_manager
2819            .create_token(request_id_str.clone())
2820            .await;
2821        let extra = self.attach_peer(
2822            crate::server::cancellation::RequestHandlerExtra::new(
2823                request_id_str.clone(),
2824                cancellation_token,
2825            )
2826            .with_protocol_context(protocol_context),
2827        );
2828        let result = match handler.create_message(req, extra).await {
2829            Ok(v) => {
2830                self.cancellation_manager
2831                    .remove_token(&request_id_str)
2832                    .await;
2833                Ok(v)
2834            },
2835            Err(e) => {
2836                self.cancellation_manager
2837                    .remove_token(&request_id_str)
2838                    .await;
2839                Err(e)
2840            },
2841        }?;
2842        Ok(serde_json::to_value(result)?)
2843    }
2844
2845    /// Register a root directory or URI that the server has access to.
2846    ///
2847    /// This method allows the server to announce to clients that it has
2848    /// access to specific file system roots or URIs. This is useful for
2849    /// resource handlers that need to expose filesystem access or other
2850    /// URI-based resources.
2851    ///
2852    /// # Arguments
2853    ///
2854    /// * `uri` - The root URI to register (e.g., `file:///home/user/project`)
2855    /// * `name` - Optional human-readable name for the root
2856    ///
2857    /// # Returns
2858    ///
2859    /// An unregister function that can be called to remove the root registration.
2860    ///
2861    /// # Examples
2862    ///
2863    /// ```rust,no_run
2864    /// use pmcp::Server;
2865    ///
2866    /// # async fn example() -> pmcp::Result<()> {
2867    /// let server = Server::builder()
2868    ///     .name("file-server")
2869    ///     .version("1.0.0")
2870    ///     .build()?;
2871    ///
2872    /// // Register a project root
2873    /// let unregister = server.register_root(
2874    ///     "file:///home/user/project",
2875    ///     Some("My Project".to_string())
2876    /// ).await?;
2877    ///
2878    /// // Later, unregister the root
2879    /// unregister();
2880    /// # Ok(())
2881    /// # }
2882    /// ```
2883    pub async fn register_root(
2884        &self,
2885        uri: impl Into<String>,
2886        name: Option<String>,
2887    ) -> Result<impl FnOnce() + Send + 'static> {
2888        let mut roots_manager = self.roots_manager.write().await;
2889        if let Some(tx) = &self.notification_tx {
2890            roots_manager.set_notification_sender({
2891                let tx = tx.clone();
2892                move |server_notification| {
2893                    let _ = tx.try_send(Notification::Server(server_notification));
2894                }
2895            });
2896        }
2897        roots_manager.register_root(uri.into(), name).await
2898    }
2899
2900    /// Get the list of registered roots.
2901    ///
2902    /// Returns a list of all currently registered root URIs and their
2903    /// associated names. Roots are directories or URIs that the server
2904    /// has announced access to.
2905    ///
2906    /// # Returns
2907    ///
2908    /// A vector of `Root` objects containing URI and optional name.
2909    ///
2910    /// # Examples
2911    ///
2912    /// ```rust,no_run
2913    /// use pmcp::Server;
2914    ///
2915    /// # async fn example() -> pmcp::Result<()> {
2916    /// let server = Server::builder()
2917    ///     .name("file-server")
2918    ///     .version("1.0.0")
2919    ///     .build()?;
2920    ///
2921    /// // Register some roots
2922    /// server.register_root("file:///home/user/project1", Some("Project 1".to_string())).await?;
2923    /// server.register_root("file:///home/user/project2", None).await?;
2924    ///
2925    /// // Get the list of roots
2926    /// let roots = server.get_roots().await;
2927    /// println!("Registered {} roots", roots.len());
2928    /// # Ok(())
2929    /// # }
2930    /// ```
2931    pub async fn get_roots(&self) -> Vec<roots::Root> {
2932        let roots_manager = self.roots_manager.read().await;
2933        roots_manager.get_roots().await
2934    }
2935
2936    /// Subscribe a client to resource updates.
2937    ///
2938    /// This method allows the server to track which clients are interested
2939    /// in updates to specific resources. When a resource changes, the server
2940    /// can notify all subscribed clients.
2941    ///
2942    /// # Arguments
2943    ///
2944    /// * `uri` - The resource URI to subscribe to
2945    /// * `client_id` - Identifier for the subscribing client
2946    ///
2947    /// # Examples
2948    ///
2949    /// ```rust,no_run
2950    /// use pmcp::Server;
2951    ///
2952    /// # async fn example() -> pmcp::Result<()> {
2953    /// let server = Server::builder()
2954    ///     .name("file-server")
2955    ///     .version("1.0.0")
2956    ///     .build()?;
2957    ///
2958    /// // Subscribe client to resource updates
2959    /// server.subscribe_resource(
2960    ///     "file:///project/file.txt".to_string(),
2961    ///     "client-123".to_string()
2962    /// ).await?;
2963    /// # Ok(())
2964    /// # }
2965    /// ```
2966    pub async fn subscribe_resource(&self, uri: String, client_id: String) -> Result<()> {
2967        if uri.is_empty() || client_id.is_empty() {
2968            return Err(Error::invalid_params("URI and client_id must not be empty"));
2969        }
2970
2971        let mut subscription_manager = self.subscription_manager.write().await;
2972        if let Some(tx) = &self.notification_tx {
2973            subscription_manager.set_notification_sender({
2974                let tx = tx.clone();
2975                move |notification| {
2976                    let _ = tx.try_send(Notification::Server(notification));
2977                }
2978            });
2979        }
2980
2981        subscription_manager.subscribe(uri, client_id).await
2982    }
2983
2984    /// Cancel a request that is currently being processed.
2985    ///
2986    /// This method allows the server to cancel ongoing requests, which is
2987    /// useful for implementing request timeouts or client-requested cancellations.
2988    ///
2989    /// # Arguments
2990    ///
2991    /// * `request_id` - The ID of the request to cancel
2992    /// * `reason` - Optional reason for cancellation
2993    ///
2994    /// # Examples
2995    ///
2996    /// ```rust,no_run
2997    /// use pmcp::Server;
2998    ///
2999    /// # async fn example() -> pmcp::Result<()> {
3000    /// let server = Server::builder()
3001    ///     .name("cancel-server")
3002    ///     .version("1.0.0")
3003    ///     .build()?;
3004    ///
3005    /// // Cancel a request
3006    /// server.cancel_request(
3007    ///     "request-123".to_string(),
3008    ///     Some("User requested cancellation".to_string())
3009    /// ).await?;
3010    /// # Ok(())
3011    /// # }
3012    /// ```
3013    pub async fn cancel_request(&self, request_id: String, reason: Option<String>) -> Result<()> {
3014        if request_id.is_empty() {
3015            return Err(Error::invalid_params("Request ID must not be empty"));
3016        }
3017
3018        self.cancellation_manager
3019            .cancel_request(request_id, reason)
3020            .await
3021    }
3022
3023    /// Unsubscribe a client from resource updates.
3024    ///
3025    /// This method removes a client's subscription to a specific resource,
3026    /// so they will no longer receive notifications when that resource changes.
3027    ///
3028    /// # Arguments
3029    ///
3030    /// * `uri` - The resource URI to unsubscribe from
3031    /// * `client_id` - Identifier for the client to unsubscribe
3032    ///
3033    /// # Examples
3034    ///
3035    /// ```rust,no_run
3036    /// use pmcp::Server;
3037    ///
3038    /// # async fn example() -> pmcp::Result<()> {
3039    /// let server = Server::builder()
3040    ///     .name("file-server")
3041    ///     .version("1.0.0")
3042    ///     .build()?;
3043    ///
3044    /// // Unsubscribe client from resource updates
3045    /// server.unsubscribe_resource(
3046    ///     "file:///project/file.txt".to_string(),
3047    ///     "client-123".to_string()
3048    /// ).await?;
3049    /// # Ok(())
3050    /// # }
3051    /// ```
3052    pub async fn unsubscribe_resource(&self, uri: String, client_id: String) -> Result<()> {
3053        if uri.is_empty() || client_id.is_empty() {
3054            return Err(Error::invalid_params("URI and client_id must not be empty"));
3055        }
3056
3057        let subscription_manager = self.subscription_manager.read().await;
3058        subscription_manager.unsubscribe(uri, client_id).await
3059    }
3060
3061    /// Notify subscribers that a resource has been updated.
3062    ///
3063    /// # Arguments
3064    ///
3065    /// * `uri` - The URI of the resource that was updated
3066    ///
3067    /// # Returns
3068    ///
3069    /// The number of subscribers that were notified.
3070    pub async fn notify_resource_updated(&self, uri: String) -> Result<usize> {
3071        let mut subscription_manager = self.subscription_manager.write().await;
3072        if let Some(tx) = &self.notification_tx {
3073            subscription_manager.set_notification_sender({
3074                let tx = tx.clone();
3075                move |notification| {
3076                    let _ = tx.try_send(Notification::Server(notification));
3077                }
3078            });
3079        }
3080        subscription_manager.notify_resource_updated(uri).await
3081    }
3082}
3083
3084/// Trait for types annotated with `#[mcp_server]`.
3085///
3086/// Generated by the `#[mcp_server]` proc macro. Provides bulk registration of
3087/// tools and prompts via `register()`. Users should call `.mcp_server(instance)`
3088/// on the builder instead of implementing this trait manually.
3089///
3090/// # Examples
3091///
3092/// ```rust,ignore
3093/// use pmcp::ServerBuilder;
3094///
3095/// #[mcp_server]
3096/// impl MyServer {
3097///     #[mcp_tool(description = "Query data")]
3098///     async fn query(&self, args: QueryArgs) -> Result<Value> { /* ... */ }
3099///
3100///     #[mcp_prompt(description = "Generate query")]
3101///     async fn query_prompt(&self, args: PromptArgs) -> Result<GetPromptResult> { /* ... */ }
3102/// }
3103///
3104/// let server = MyServer { db };
3105/// let builder = ServerBuilder::new()
3106///     .mcp_server(server);
3107/// ```
3108#[cfg(not(target_arch = "wasm32"))]
3109pub trait McpServer {
3110    /// Register all tools and prompts from this server on the builder.
3111    fn register(self, builder: ServerBuilder) -> ServerBuilder;
3112}
3113
3114/// Builder for creating servers.
3115#[cfg(not(target_arch = "wasm32"))]
3116pub struct ServerBuilder {
3117    name: Option<String>,
3118    version: Option<String>,
3119    capabilities: ServerCapabilities,
3120    tools: HashMap<String, Arc<dyn ToolHandler>>,
3121    prompts: HashMap<String, Arc<dyn PromptHandler>>,
3122    resources: Option<Arc<dyn ResourceHandler>>,
3123    /// Completion provider backing `completion/complete` (Phase 118.1-04,
3124    /// CONF-05), set via [`Self::completions`]. The twin of
3125    /// `ServerCoreBuilder`'s slot of the same name: a provider registered
3126    /// through EITHER builder family reaches its own dispatcher.
3127    completions: Option<Arc<dyn crate::types::completable::CompletionProviderTrait>>,
3128    sampling: Option<Arc<dyn SamplingHandler>>,
3129    /// Cancellation manager for request cancellation
3130    cancellation_manager: cancellation::CancellationManager,
3131    /// Roots manager for directory/URI registration
3132    roots_manager: roots::RootsManager,
3133    /// Authentication provider for validating requests
3134    auth_provider: Option<Arc<dyn auth::AuthProvider>>,
3135    /// Tool authorizer for fine-grained access control
3136    tool_authorizer: Option<Arc<dyn auth::ToolAuthorizer>>,
3137    /// Tool protection requirements to be applied at build time
3138    tool_protections: HashMap<String, Vec<String>>,
3139    /// Tool middleware chain for cross-cutting concerns
3140    #[cfg(not(target_arch = "wasm32"))]
3141    tool_middlewares: Vec<Arc<dyn tool_middleware::ToolMiddleware>>,
3142    /// HTTP middleware chain for `StreamableHttpServer`
3143    #[cfg(feature = "streamable-http")]
3144    http_middleware: Option<Arc<http_middleware::ServerHttpMiddlewareChain>>,
3145    /// Host layers for MCP Apps metadata enrichment (e.g., `ChatGPT`)
3146    #[cfg(feature = "mcp-apps")]
3147    host_layers: Vec<crate::types::mcp_apps::HostType>,
3148    /// Optional website URL for the server implementation (MCP 2025-11-25)
3149    website_url: Option<String>,
3150    /// Optional icons for the server implementation (MCP 2025-11-25)
3151    icons: Option<Vec<crate::types::protocol::IconInfo>>,
3152    /// Accumulated SEP-2640 Agent Skills. The registry is finalized into a
3153    /// single `SkillsHandler` exactly once at `.build()` time so chained
3154    /// `.skill(...)` / `.skills(...)` calls never produce nested wrappers.
3155    #[cfg(feature = "skills")]
3156    pending_skills: Option<skills::Skills>,
3157    /// Legacy experimental task router backend (set via [`Self::with_task_store`]).
3158    #[cfg(not(target_arch = "wasm32"))]
3159    task_router: Option<Arc<dyn crate::server::tasks::TaskRouter>>,
3160    /// Standard task store backend (set via [`Self::task_store`]). Presence
3161    /// auto-advertises the `tasks` capability at `build()`.
3162    #[cfg(not(target_arch = "wasm32"))]
3163    task_store: Option<Arc<dyn crate::server::task_store::TaskStore>>,
3164    /// Tool names opting out of the TOUT-02 double-wrap tripwire (D-08), set via
3165    /// [`Self::suppress_double_wrap_check`]. Carried into the built `Server` and
3166    /// consulted at the Payload wrap site.
3167    #[cfg(not(target_arch = "wasm32"))]
3168    suppress_double_wrap: HashSet<String>,
3169    /// Configured protocol-version accept-list (Phase 112, VERS-01/02). Defaults
3170    /// to the v1-only legacy set (excludes `2026-07-28`); overridden via
3171    /// [`Self::with_supported_protocol_versions`].
3172    supported_protocol_versions: Vec<ProtocolVersion>,
3173    /// Explicit `requestState` minting key (Phase 113, HTTP-02), set via
3174    /// [`Self::with_request_state_key`]. When present it overrides
3175    /// `PMCP_REQUEST_STATE_KEY` entirely.
3176    ///
3177    /// Copy 1 of 3 (D-113-P): held as a
3178    /// [`SecretKey`](crate::server::request_state::SecretKey), never as bare
3179    /// `[u8; 32]`, so the destructor rides on the value and scrubs on drop —
3180    /// including on every early-`?` path out of [`Self::build`]. Reverting this
3181    /// to bare bytes is caught at COMPILE time by
3182    /// `server_builder_request_state_key_field_is_the_zeroizing_type`.
3183    #[cfg(feature = "streamable-http")]
3184    request_state_key: Option<request_state::SecretKey>,
3185    /// Rotated-out `requestState` keys accepted for VERIFICATION only, set via
3186    /// [`Self::with_request_state_previous_keys`].
3187    ///
3188    /// Copy 1 of 3 (D-113-P), the rotated-out half: each element scrubs itself
3189    /// when the `Vec` drops.
3190    #[cfg(feature = "streamable-http")]
3191    request_state_previous_keys: Vec<request_state::SecretKey>,
3192    /// Explicit continuation lifetime, set via [`Self::with_request_state_ttl`].
3193    /// Beats both the 300-second default and `PMCP_REQUEST_STATE_TTL_SECS` (D-05).
3194    #[cfg(feature = "streamable-http")]
3195    request_state_ttl: Option<std::time::Duration>,
3196}
3197
3198#[cfg(not(target_arch = "wasm32"))]
3199impl std::fmt::Debug for ServerBuilder {
3200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3201        f.debug_struct("ServerBuilder")
3202            .field("name", &self.name)
3203            .field("version", &self.version)
3204            .field("capabilities", &self.capabilities)
3205            .field("tools", &self.tools.keys().collect::<Vec<_>>())
3206            .field("prompts", &self.prompts.keys().collect::<Vec<_>>())
3207            .field("resources", &self.resources.is_some())
3208            .field("sampling", &self.sampling.is_some())
3209            .finish()
3210    }
3211}
3212
3213#[cfg(not(target_arch = "wasm32"))]
3214impl ServerBuilder {
3215    /// Create a new server builder.
3216    ///
3217    /// Creates a new `ServerBuilder` with default capabilities and no handlers.
3218    /// Use the builder methods to configure the server before calling `build()`.
3219    ///
3220    /// # Examples
3221    ///
3222    /// ```rust,no_run
3223    /// use pmcp::ServerBuilder;
3224    ///
3225    /// let builder = ServerBuilder::new();
3226    /// ```
3227    ///
3228    /// This is equivalent to using the default implementation:
3229    ///
3230    /// ```rust,no_run
3231    /// use pmcp::ServerBuilder;
3232    ///
3233    /// let builder = ServerBuilder::default();
3234    /// ```
3235    pub fn new() -> Self {
3236        Self {
3237            name: None,
3238            version: None,
3239            capabilities: ServerCapabilities::default(),
3240            tools: HashMap::new(),
3241            prompts: HashMap::new(),
3242            resources: None,
3243            completions: None,
3244            sampling: None,
3245            cancellation_manager: cancellation::CancellationManager::new(),
3246            roots_manager: roots::RootsManager::new(),
3247            auth_provider: None,
3248            tool_authorizer: None,
3249            tool_protections: HashMap::new(),
3250            #[cfg(not(target_arch = "wasm32"))]
3251            tool_middlewares: Vec::new(),
3252            #[cfg(feature = "streamable-http")]
3253            http_middleware: None,
3254            #[cfg(feature = "mcp-apps")]
3255            host_layers: Vec::new(),
3256            website_url: None,
3257            icons: None,
3258            #[cfg(feature = "skills")]
3259            pending_skills: None,
3260            #[cfg(not(target_arch = "wasm32"))]
3261            task_router: None,
3262            #[cfg(not(target_arch = "wasm32"))]
3263            task_store: None,
3264            #[cfg(not(target_arch = "wasm32"))]
3265            suppress_double_wrap: HashSet::new(),
3266            supported_protocol_versions: crate::types::protocol::context::default_accept_list(),
3267            #[cfg(feature = "streamable-http")]
3268            request_state_key: None,
3269            #[cfg(feature = "streamable-http")]
3270            request_state_previous_keys: Vec::new(),
3271            #[cfg(feature = "streamable-http")]
3272            request_state_ttl: None,
3273        }
3274    }
3275
3276    /// Configure the shared `requestState` minting key (Phase 113, HTTP-02, D-03).
3277    ///
3278    /// With no call, the key is resolved from `PMCP_REQUEST_STATE_KEY`; when that
3279    /// variable is unset the server generates a per-process key and WARNs at build
3280    /// time (D-04). Calling this overrides the environment entirely, which is what
3281    /// makes deterministic integration tests and multiple differently-configured
3282    /// servers in one process possible.
3283    ///
3284    /// The key must be shared byte-for-byte by every instance behind a load
3285    /// balancer that should be able to resume each other's multi-round-trip
3286    /// requests.
3287    ///
3288    /// Has no effect on a server that did not opt into the v2 (`2026-07-28`) era.
3289    ///
3290    /// The parameter type is deliberately still `[u8; 32]`: the SDK owns the
3291    /// copy it takes, not the caller's (D-113-P, T-113-121).
3292    #[cfg(feature = "streamable-http")]
3293    #[must_use]
3294    pub fn with_request_state_key(mut self, mut key: [u8; 32]) -> Self {
3295        // Closes copy 1 of 3 (D-113-P): the FIELD now scrubs on drop.
3296        self.request_state_key = Some(request_state::SecretKey::new(key));
3297        // Closes copy 2 of 3 (D-113-P): this by-value parameter's OWN stack
3298        // slot. `[u8; 32]` is `Copy`, so the line above copied out of it and
3299        // left the caller's key bytes sitting here.
3300        key.zeroize();
3301        self
3302    }
3303
3304    /// Accept rotated-out `requestState` keys for VERIFICATION only.
3305    ///
3306    /// Tokens minted under a listed key still verify, but new tokens are always
3307    /// minted under the current key — so a rotation does not strand in-flight
3308    /// continuations. With no call, only the current key is accepted.
3309    ///
3310    /// Has no effect on a server that did not opt into the v2 (`2026-07-28`) era.
3311    #[cfg(feature = "streamable-http")]
3312    #[must_use]
3313    pub fn with_request_state_previous_keys(mut self, mut keys: Vec<[u8; 32]>) -> Self {
3314        // Closes copy 1 of 3 (D-113-P), rotated-out half.
3315        self.request_state_previous_keys = keys
3316            .iter()
3317            .copied()
3318            .map(request_state::SecretKey::new)
3319            .collect();
3320        // Closes copy 2 of 3 (D-113-P): the by-value `Vec`'s own heap buffer,
3321        // which the copy above read out of and would otherwise return to the
3322        // allocator holding every rotated-out key in the clear. `Vec::zeroize`
3323        // scrubs the initialized elements AND the spare capacity.
3324        keys.zeroize();
3325        self
3326    }
3327
3328    /// Configure the `requestState` continuation lifetime (D-05).
3329    ///
3330    /// With no call, the lifetime is `PMCP_REQUEST_STATE_TTL_SECS` if parseable,
3331    /// else 300 seconds. A builder value beats both.
3332    ///
3333    /// Has no effect on a server that did not opt into the v2 (`2026-07-28`) era.
3334    #[cfg(feature = "streamable-http")]
3335    #[must_use]
3336    pub fn with_request_state_ttl(mut self, ttl: std::time::Duration) -> Self {
3337        self.request_state_ttl = Some(ttl);
3338        self
3339    }
3340
3341    /// Opt into a protocol-version accept-list (Phase 112, VERS-01/02; D-02/D-04).
3342    ///
3343    /// The high-level `Server` twin of
3344    /// [`ServerCoreBuilder::with_supported_protocol_versions`](crate::server::builder::ServerCoreBuilder::with_supported_protocol_versions).
3345    /// With no call, the server is v1-only and behaves exactly as today. An empty
3346    /// accept-list falls back to the v1-only default (never all-reject).
3347    #[must_use]
3348    pub fn with_supported_protocol_versions(
3349        mut self,
3350        versions: impl IntoIterator<Item = ProtocolVersion>,
3351    ) -> Self {
3352        self.supported_protocol_versions =
3353            crate::types::protocol::context::normalize_accept_list(versions);
3354        self
3355    }
3356
3357    /// Set the server name.
3358    ///
3359    /// The server name identifies this MCP server implementation.
3360    /// This is required and will be sent to clients during initialization.
3361    ///
3362    /// # Arguments
3363    ///
3364    /// * `name` - The name of the server
3365    ///
3366    /// # Examples
3367    ///
3368    /// ```rust,no_run
3369    /// use pmcp::Server;
3370    ///
3371    /// let server = Server::builder()
3372    ///     .name("file-manager")
3373    ///     .version("1.0.0")
3374    ///     .build()?;
3375    /// # Ok::<(), pmcp::Error>(())
3376    /// ```
3377    pub fn name(mut self, name: impl Into<String>) -> Self {
3378        self.name = Some(name.into());
3379        self
3380    }
3381
3382    /// Set the server version.
3383    ///
3384    /// The server version identifies this specific version of the MCP server.
3385    /// This is required and will be sent to clients during initialization.
3386    ///
3387    /// # Arguments
3388    ///
3389    /// * `version` - The version string (e.g., "1.0.0", "2.1.3-beta")
3390    ///
3391    /// # Examples
3392    ///
3393    /// ```rust,no_run
3394    /// use pmcp::Server;
3395    ///
3396    /// let server = Server::builder()
3397    ///     .name("data-processor")
3398    ///     .version("2.1.0")
3399    ///     .build()?;
3400    /// # Ok::<(), pmcp::Error>(())
3401    /// ```
3402    pub fn version(mut self, version: impl Into<String>) -> Self {
3403        self.version = Some(version.into());
3404        self
3405    }
3406
3407    /// Set the website URL for the server implementation (MCP 2025-11-25).
3408    pub fn website_url(mut self, url: impl Into<String>) -> Self {
3409        self.website_url = Some(url.into());
3410        self
3411    }
3412
3413    /// Set icons for the server implementation (MCP 2025-11-25).
3414    pub fn with_icons(mut self, icons: Vec<crate::types::protocol::IconInfo>) -> Self {
3415        self.icons = Some(icons);
3416        self
3417    }
3418
3419    /// Set server capabilities.
3420    ///
3421    /// Configures the capabilities that this server supports.
3422    /// Capabilities inform clients about which MCP features are available.
3423    ///
3424    /// # Arguments
3425    ///
3426    /// * `capabilities` - The server capabilities to advertise
3427    ///
3428    /// # Examples
3429    ///
3430    /// ```rust,no_run
3431    /// use pmcp::{Server, ServerCapabilities, ToolCapabilities};
3432    ///
3433    /// let mut capabilities = ServerCapabilities::default();
3434    /// capabilities.tools = Some(ToolCapabilities {
3435    ///     list_changed: Some(true),
3436    /// });
3437    ///
3438    /// let server = Server::builder()
3439    ///     .name("advanced-server")
3440    ///     .version("1.0.0")
3441    ///     .capabilities(capabilities)
3442    ///     .build()?;
3443    /// # Ok::<(), pmcp::Error>(())
3444    /// ```
3445    pub fn capabilities(mut self, capabilities: ServerCapabilities) -> Self {
3446        self.capabilities = capabilities;
3447        self
3448    }
3449
3450    /// Add a tool handler.
3451    ///
3452    /// Registers a tool that clients can call via the tools/call method.
3453    /// Tools are the primary way servers provide functionality to clients.
3454    ///
3455    /// # Arguments
3456    ///
3457    /// * `name` - The name of the tool (used by clients to call it)
3458    /// * `handler` - The handler implementation for this tool
3459    ///
3460    /// # Examples
3461    ///
3462    /// ```rust,no_run
3463    /// use pmcp::{Server, ToolHandler};
3464    /// use async_trait::async_trait;
3465    /// use serde_json::Value;
3466    ///
3467    /// struct FileListTool;
3468    ///
3469    /// #[async_trait]
3470    /// impl ToolHandler for FileListTool {
3471    ///     async fn handle(&self, args: Value, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<Value> {
3472    ///         let path = args["path"].as_str().unwrap_or(".");
3473    ///         // List files in path...
3474    ///         Ok(serde_json::json!({"files": ["file1.txt", "file2.txt"]}))
3475    ///     }
3476    /// }
3477    ///
3478    /// let server = Server::builder()
3479    ///     .name("file-server")
3480    ///     .version("1.0.0")
3481    ///     .tool("list_files", FileListTool{})
3482    ///     .build()?;
3483    /// # Ok::<(), pmcp::Error>(())
3484    /// ```
3485    pub fn tool(mut self, name: impl Into<String>, handler: impl ToolHandler + 'static) -> Self {
3486        self.tools.insert(name.into(), Arc::new(handler));
3487
3488        // Update capabilities to include tools
3489        // Use Some(false) instead of None to ensure the field serializes properly
3490        if self.capabilities.tools.is_none() {
3491            self.capabilities.tools = Some(crate::types::ToolCapabilities {
3492                list_changed: Some(false),
3493            });
3494        }
3495
3496        self
3497    }
3498
3499    /// Add a tool handler with an Arc.
3500    ///
3501    /// This variant lets the caller share the handler `Arc` between the
3502    /// builder and an external in-process handler map (e.g., a downstream
3503    /// toolkit's handler registry) without writing a delegating wrapper
3504    /// shim. Behavior is otherwise identical to [`Self::tool`]: the first
3505    /// registration auto-enables `capabilities.tools`.
3506    pub fn tool_arc(mut self, name: impl Into<String>, handler: Arc<dyn ToolHandler>) -> Self {
3507        let name = name.into();
3508        self.tools.insert(name, handler);
3509
3510        // Update capabilities to include tools
3511        // Use Some(false) instead of None to ensure the field serializes properly
3512        if self.capabilities.tools.is_none() {
3513            self.capabilities.tools = Some(crate::types::ToolCapabilities {
3514                list_changed: Some(false),
3515            });
3516        }
3517
3518        self
3519    }
3520
3521    /// Register all tools and prompts from an `#[mcp_server]` annotated type.
3522    ///
3523    /// This is the ergonomic counterpart to individually registering tools and
3524    /// prompts. The server instance provides shared state via `&self` to all
3525    /// tool and prompt methods.
3526    ///
3527    /// # Examples
3528    ///
3529    /// ```rust,ignore
3530    /// use pmcp::ServerBuilder;
3531    ///
3532    /// #[mcp_server]
3533    /// impl MyServer {
3534    ///     #[mcp_tool(description = "Query data")]
3535    ///     async fn query(&self, args: QueryArgs) -> Result<Value> { /* ... */ }
3536    ///
3537    ///     #[mcp_prompt(description = "Generate query")]
3538    ///     async fn query_prompt(&self, args: PromptArgs) -> Result<GetPromptResult> { /* ... */ }
3539    /// }
3540    ///
3541    /// let server = MyServer { db };
3542    /// let builder = ServerBuilder::new()
3543    ///     .name("my-server")
3544    ///     .mcp_server(server);
3545    /// ```
3546    pub fn mcp_server<T: McpServer>(self, server: T) -> Self {
3547        server.register(self)
3548    }
3549
3550    /// Add a type-safe tool handler with automatic schema generation.
3551    ///
3552    /// This method provides first-class support for creating tools with:
3553    /// - Automatic JSON schema generation from Rust types
3554    /// - Compile-time type safety
3555    /// - Runtime validation
3556    /// - Field descriptions from doc comments
3557    ///
3558    /// # Example
3559    /// ```no_run
3560    /// # #[cfg(feature = "schema-generation")]
3561    /// # {
3562    /// use pmcp::ServerBuilder;
3563    /// use schemars::JsonSchema;
3564    /// use serde::{Deserialize, Serialize};
3565    ///
3566    /// #[derive(Debug, Deserialize, Serialize, JsonSchema)]
3567    /// struct EchoArgs {
3568    ///     /// The message to echo
3569    ///     message: String,
3570    ///     /// Optional prefix
3571    ///     prefix: Option<String>,
3572    /// }
3573    ///
3574    /// # #[tokio::main]
3575    /// # async fn main() -> Result<(), pmcp::Error> {
3576    /// let server = ServerBuilder::new()
3577    ///     .name("example")
3578    ///     .tool_typed("echo", |args: EchoArgs, _| {
3579    ///         Box::pin(async move {
3580    ///             let message = match args.prefix {
3581    ///                 Some(p) => format!("{}: {}", p, args.message),
3582    ///                 None => args.message,
3583    ///             };
3584    ///             Ok(serde_json::json!({ "message": message }))
3585    ///         })
3586    ///     })
3587    ///     .build();
3588    /// # Ok::<(), pmcp::Error>(())
3589    /// # }
3590    /// # }
3591    /// ```
3592    #[cfg(feature = "schema-generation")]
3593    pub fn tool_typed<T, F, Fut>(mut self, name: impl Into<String>, handler: F) -> Self
3594    where
3595        T: serde::de::DeserializeOwned + schemars::JsonSchema + Send + Sync + 'static,
3596        F: Fn(T, crate::RequestHandlerExtra) -> Fut + Send + Sync + 'static,
3597        Fut: std::future::Future<Output = crate::Result<serde_json::Value>> + Send + 'static,
3598    {
3599        use crate::server::typed_tool::TypedTool;
3600        use std::pin::Pin;
3601
3602        let name_str = name.into();
3603
3604        // Wrap the handler to return Pin<Box<dyn Future>>
3605        let wrapped_handler = move |args: T,
3606                                    extra: crate::RequestHandlerExtra|
3607              -> Pin<
3608            Box<dyn std::future::Future<Output = crate::Result<serde_json::Value>> + Send>,
3609        > { Box::pin(handler(args, extra)) };
3610
3611        let tool = TypedTool::new(name_str.clone(), wrapped_handler);
3612        self.tools.insert(name_str, Arc::new(tool));
3613
3614        // Update capabilities to include tools
3615        if self.capabilities.tools.is_none() {
3616            self.capabilities.tools = Some(crate::types::ToolCapabilities {
3617                list_changed: Some(false),
3618            });
3619        }
3620
3621        self
3622    }
3623
3624    /// Add a type-safe tool handler with automatic schema generation and description.
3625    ///
3626    /// This is a convenience overload that allows setting a description directly
3627    /// without needing to chain `.with_description()`.
3628    ///
3629    /// # Example
3630    /// ```no_run
3631    /// # #[cfg(feature = "schema-generation")]
3632    /// # {
3633    /// use pmcp::ServerBuilder;
3634    /// use schemars::JsonSchema;
3635    /// use serde::{Deserialize, Serialize};
3636    ///
3637    /// #[derive(Debug, Deserialize, Serialize, JsonSchema)]
3638    /// struct EchoArgs {
3639    ///     /// The message to echo
3640    ///     message: String,
3641    ///     /// Optional prefix
3642    ///     prefix: Option<String>,
3643    /// }
3644    ///
3645    /// let server = ServerBuilder::new()
3646    ///     .name("example")
3647    ///     .tool_typed_with_description(
3648    ///         "echo",
3649    ///         "Echoes back a message with an optional prefix",
3650    ///         |args: EchoArgs, _| {
3651    ///             Box::pin(async move {
3652    ///                 let message = match args.prefix {
3653    ///                     Some(p) => format!("{}: {}", p, args.message),
3654    ///                     None => args.message,
3655    ///                 };
3656    ///                 Ok(serde_json::json!({ "message": message }))
3657    ///             })
3658    ///         }
3659    ///     );
3660    /// # }
3661    /// ```
3662    #[cfg(feature = "schema-generation")]
3663    pub fn tool_typed_with_description<T, F, Fut>(
3664        mut self,
3665        name: impl Into<String>,
3666        description: impl Into<String>,
3667        handler: F,
3668    ) -> Self
3669    where
3670        T: serde::de::DeserializeOwned + schemars::JsonSchema + Send + Sync + 'static,
3671        F: Fn(T, crate::RequestHandlerExtra) -> Fut + Send + Sync + 'static,
3672        Fut: std::future::Future<Output = crate::Result<serde_json::Value>> + Send + 'static,
3673    {
3674        use crate::server::typed_tool::TypedTool;
3675        use std::pin::Pin;
3676
3677        let name_str = name.into();
3678
3679        // Wrap the handler to return Pin<Box<dyn Future>>
3680        let wrapped_handler = move |args: T,
3681                                    extra: crate::RequestHandlerExtra|
3682              -> Pin<
3683            Box<dyn std::future::Future<Output = crate::Result<serde_json::Value>> + Send>,
3684        > { Box::pin(handler(args, extra)) };
3685
3686        let tool = TypedTool::new(name_str.clone(), wrapped_handler).with_description(description);
3687        self.tools.insert(name_str, Arc::new(tool));
3688
3689        // Update capabilities to include tools
3690        if self.capabilities.tools.is_none() {
3691            self.capabilities.tools = Some(crate::types::ToolCapabilities {
3692                list_changed: Some(false),
3693            });
3694        }
3695
3696        self
3697    }
3698
3699    /// Add a synchronous type-safe tool handler with automatic schema generation.
3700    ///
3701    /// Similar to `tool_typed` but for synchronous handlers.
3702    ///
3703    /// # Example
3704    /// ```no_run
3705    /// # #[cfg(feature = "schema-generation")]
3706    /// # {
3707    /// use pmcp::ServerBuilder;
3708    /// use schemars::JsonSchema;
3709    /// use serde::{Deserialize, Serialize};
3710    ///
3711    /// #[derive(Debug, Deserialize, Serialize, JsonSchema)]
3712    /// struct MathArgs {
3713    ///     /// First number
3714    ///     a: f64,
3715    ///     /// Second number
3716    ///     b: f64,
3717    ///     /// Operation to perform
3718    ///     op: String,
3719    /// }
3720    ///
3721    /// # fn main() -> Result<(), pmcp::Error> {
3722    /// let server = ServerBuilder::new()
3723    ///     .name("example")
3724    ///     .tool_typed_sync("calculator", |args: MathArgs, _| {
3725    ///         let result = match args.op.as_str() {
3726    ///             "add" => args.a + args.b,
3727    ///             "subtract" => args.a - args.b,
3728    ///             "multiply" => args.a * args.b,
3729    ///             "divide" => args.a / args.b,
3730    ///             _ => return Err(pmcp::Error::Validation("Unknown operation".into())),
3731    ///         };
3732    ///         Ok(serde_json::json!({ "result": result }))
3733    ///     })
3734    ///     .build();
3735    /// # Ok::<(), pmcp::Error>(())
3736    /// # }
3737    /// # }
3738    /// ```
3739    #[cfg(feature = "schema-generation")]
3740    pub fn tool_typed_sync<T, F>(mut self, name: impl Into<String>, handler: F) -> Self
3741    where
3742        T: serde::de::DeserializeOwned + schemars::JsonSchema + Send + Sync + 'static,
3743        F: Fn(T, crate::RequestHandlerExtra) -> crate::Result<serde_json::Value>
3744            + Send
3745            + Sync
3746            + 'static,
3747    {
3748        use crate::server::typed_tool::TypedSyncTool;
3749        let name_str = name.into();
3750        let tool = TypedSyncTool::new(name_str.clone(), handler);
3751        self.tools.insert(name_str, Arc::new(tool));
3752
3753        // Update capabilities to include tools
3754        if self.capabilities.tools.is_none() {
3755            self.capabilities.tools = Some(crate::types::ToolCapabilities {
3756                list_changed: Some(false),
3757            });
3758        }
3759
3760        self
3761    }
3762
3763    /// Add a synchronous type-safe tool handler with automatic schema generation and description.
3764    ///
3765    /// This is a convenience overload that allows setting a description directly
3766    /// without needing to chain `.with_description()`.
3767    ///
3768    /// # Example
3769    /// ```no_run
3770    /// # #[cfg(feature = "schema-generation")]
3771    /// # {
3772    /// use pmcp::ServerBuilder;
3773    /// use schemars::JsonSchema;
3774    /// use serde::{Deserialize, Serialize};
3775    ///
3776    /// #[derive(Debug, Deserialize, Serialize, JsonSchema)]
3777    /// struct MathArgs {
3778    ///     /// First number
3779    ///     a: f64,
3780    ///     /// Second number
3781    ///     b: f64,
3782    ///     /// Operation to perform
3783    ///     op: String,
3784    /// }
3785    ///
3786    /// let server = ServerBuilder::new()
3787    ///     .name("example")
3788    ///     .tool_typed_sync_with_description(
3789    ///         "calculator",
3790    ///         "Performs synchronous mathematical operations",
3791    ///         |args: MathArgs, _| {
3792    ///             let result = match args.op.as_str() {
3793    ///                 "add" => args.a + args.b,
3794    ///                 "subtract" => args.a - args.b,
3795    ///                 "multiply" => args.a * args.b,
3796    ///                 "divide" => args.a / args.b,
3797    ///                 _ => return Err(pmcp::Error::Validation("Unknown operation".into())),
3798    ///             };
3799    ///             Ok(serde_json::json!({ "result": result }))
3800    ///         }
3801    ///     );
3802    /// # }
3803    /// ```
3804    #[cfg(feature = "schema-generation")]
3805    pub fn tool_typed_sync_with_description<T, F>(
3806        mut self,
3807        name: impl Into<String>,
3808        description: impl Into<String>,
3809        handler: F,
3810    ) -> Self
3811    where
3812        T: serde::de::DeserializeOwned + schemars::JsonSchema + Send + Sync + 'static,
3813        F: Fn(T, crate::RequestHandlerExtra) -> crate::Result<serde_json::Value>
3814            + Send
3815            + Sync
3816            + 'static,
3817    {
3818        use crate::server::typed_tool::TypedSyncTool;
3819        let name_str = name.into();
3820        let tool = TypedSyncTool::new(name_str.clone(), handler).with_description(description);
3821        self.tools.insert(name_str, Arc::new(tool));
3822
3823        // Update capabilities to include tools
3824        if self.capabilities.tools.is_none() {
3825            self.capabilities.tools = Some(crate::types::ToolCapabilities {
3826                list_changed: Some(false),
3827            });
3828        }
3829
3830        self
3831    }
3832
3833    /// Add a type-safe tool handler with both input and output typing.
3834    ///
3835    /// This method provides full type safety for both input and output types,
3836    /// which is useful for testing, documentation, and API contracts.
3837    /// Note that output schemas are not part of the MCP protocol but can be
3838    /// valuable for development and integration testing.
3839    ///
3840    /// # Type Parameters
3841    ///
3842    /// * `TIn` - Input type that implements `JsonSchema`, `Deserialize`, `Send`, `Sync`
3843    /// * `TOut` - Output type that implements `JsonSchema`, `Serialize`, `Send`, `Sync`
3844    ///
3845    /// # Example
3846    /// ```no_run
3847    /// # #[cfg(feature = "schema-generation")]
3848    /// # {
3849    /// use pmcp::{ServerBuilder, TypedToolWithOutput};
3850    /// use schemars::JsonSchema;
3851    /// use serde::{Deserialize, Serialize};
3852    ///
3853    /// #[derive(JsonSchema, Deserialize)]
3854    /// struct MathInput { a: f64, b: f64, op: String }
3855    ///
3856    /// #[derive(JsonSchema, Serialize)]
3857    /// struct MathOutput { result: f64, operation: String }
3858    ///
3859    /// let server = ServerBuilder::new()
3860    ///     .name("example")
3861    ///     .tool_typed_with_output::<MathInput, MathOutput>("math", |args, _| {
3862    ///         Box::pin(async move {
3863    ///             let result = match args.op.as_str() {
3864    ///                 "add" => args.a + args.b,
3865    ///                 "subtract" => args.a - args.b,
3866    ///                 _ => return Err(pmcp::Error::Validation("Unknown operation".into())),
3867    ///             };
3868    ///             Ok(MathOutput {
3869    ///                 result,
3870    ///                 operation: args.op,
3871    ///             })
3872    ///         })
3873    ///     });
3874    /// # }
3875    /// ```
3876    #[cfg(feature = "schema-generation")]
3877    pub fn tool_typed_with_output<TIn, TOut>(
3878        mut self,
3879        name: impl Into<String>,
3880        handler: impl Fn(
3881                TIn,
3882                crate::RequestHandlerExtra,
3883            )
3884                -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<TOut>> + Send>>
3885            + Send
3886            + Sync
3887            + 'static,
3888    ) -> Self
3889    where
3890        TIn: serde::de::DeserializeOwned + schemars::JsonSchema + Send + Sync + 'static,
3891        TOut: serde::Serialize + schemars::JsonSchema + Send + Sync + 'static,
3892    {
3893        use crate::server::typed_tool::TypedToolWithOutput;
3894
3895        let name_str = name.into();
3896        let tool = TypedToolWithOutput::new(name_str.clone(), handler);
3897        self.tools.insert(name_str, Arc::new(tool));
3898
3899        // Update capabilities to include tools
3900        if self.capabilities.tools.is_none() {
3901            self.capabilities.tools = Some(crate::types::ToolCapabilities {
3902                list_changed: Some(false),
3903            });
3904        }
3905
3906        self
3907    }
3908
3909    /// Register a tool whose async closure returns a full [`CallToolResult`] the
3910    /// handler owns end-to-end, emitted to the wire **VERBATIM**.
3911    ///
3912    /// This mirrors [`tool_typed_with_output`](Self::tool_typed_with_output) but
3913    /// fixes the return type to
3914    /// [`CallToolResult`], so a handler can attach
3915    /// task augmentation (`CallToolResult::with_related_task(...)`), custom
3916    /// `_meta`, structured content, or an error envelope in ONE call — no
3917    /// hand-written [`ToolHandler`] `impl` required. The input
3918    /// arg type `TIn` deserializes from the tool arguments exactly as with
3919    /// [`tool_typed`](Self::tool_typed).
3920    ///
3921    /// # ⚠️ BYPASS WARNING — the returned result is sent to the wire VERBATIM
3922    ///
3923    /// The closure's [`CallToolResult`] is routed
3924    /// through [`ToolOutput::Result`] and
3925    /// therefore **BYPASSES response middleware** — redaction, sanitization, and
3926    /// audit hooks (`ToolMiddleware::on_response`) DO NOT run — as well as
3927    /// text-wrapping and widget enrichment. The handler owns its OWN redaction
3928    /// and sanitization of both `content` and `_meta`, at the same trust level as
3929    /// returning a raw `Value` today (D-04a). **Request** middleware still runs
3930    /// before the handler, and handler errors still route through the normal
3931    /// error path.
3932    ///
3933    /// To advertise a human-readable tool description in `tools/list`, use
3934    /// [`tool_with_result_and_description`](Self::tool_with_result_and_description).
3935    ///
3936    /// # Example
3937    /// ```no_run
3938    /// # #[cfg(feature = "schema-generation")]
3939    /// # {
3940    /// use pmcp::ServerBuilder;
3941    /// use pmcp::types::CallToolResult;
3942    /// use pmcp::types::tasks::TaskMetadata;
3943    /// use pmcp::types::Content;
3944    /// use schemars::JsonSchema;
3945    /// use serde::Deserialize;
3946    ///
3947    /// #[derive(JsonSchema, Deserialize)]
3948    /// struct RunArgs { job: String }
3949    ///
3950    /// let server = ServerBuilder::new()
3951    ///     .name("example")
3952    ///     .tool_with_result("start_job", |args: RunArgs, _extra| {
3953    ///         Box::pin(async move {
3954    ///             Ok(CallToolResult::new(vec![Content::text(
3955    ///                 format!("started {}", args.job),
3956    ///             )])
3957    ///             .with_related_task(TaskMetadata::new("t1")))
3958    ///         })
3959    ///     })
3960    ///     .build();
3961    /// # }
3962    /// ```
3963    #[cfg(feature = "schema-generation")]
3964    pub fn tool_with_result<TIn>(
3965        mut self,
3966        name: impl Into<String>,
3967        handler: impl Fn(
3968                TIn,
3969                crate::RequestHandlerExtra,
3970            ) -> std::pin::Pin<
3971                Box<
3972                    dyn std::future::Future<Output = crate::Result<crate::types::CallToolResult>>
3973                        + Send,
3974                >,
3975            > + Send
3976            + Sync
3977            + 'static,
3978    ) -> Self
3979    where
3980        TIn: serde::de::DeserializeOwned + schemars::JsonSchema + Send + Sync + 'static,
3981    {
3982        use crate::server::typed_tool::TypedToolWithResult;
3983
3984        let name_str = name.into();
3985        let tool = TypedToolWithResult::new(name_str.clone(), handler);
3986        self.tools.insert(name_str, Arc::new(tool));
3987
3988        // Update capabilities to include tools
3989        if self.capabilities.tools.is_none() {
3990            self.capabilities.tools = Some(crate::types::ToolCapabilities {
3991                list_changed: Some(false),
3992            });
3993        }
3994
3995        self
3996    }
3997
3998    /// [`tool_with_result`](Self::tool_with_result) WITH a human-readable
3999    /// description advertised in `tools/list`.
4000    ///
4001    /// Identical to [`tool_with_result`](Self::tool_with_result) — including
4002    /// the **BYPASS WARNING** documented there (the returned
4003    /// [`CallToolResult`] goes to the wire
4004    /// VERBATIM and skips response middleware) — but also sets the tool
4005    /// description, mirroring
4006    /// [`tool_typed_with_description`](Self::tool_typed_with_description).
4007    /// A description materially improves LLM tool selection; prefer this
4008    /// overload over the description-less
4009    /// [`tool_with_result`](Self::tool_with_result).
4010    ///
4011    /// # Example
4012    /// ```no_run
4013    /// # #[cfg(feature = "schema-generation")]
4014    /// # {
4015    /// use pmcp::ServerBuilder;
4016    /// use pmcp::types::CallToolResult;
4017    /// use pmcp::types::tasks::TaskMetadata;
4018    /// use pmcp::types::Content;
4019    /// use schemars::JsonSchema;
4020    /// use serde::Deserialize;
4021    ///
4022    /// #[derive(JsonSchema, Deserialize)]
4023    /// struct RunArgs { job: String }
4024    ///
4025    /// let server = ServerBuilder::new()
4026    ///     .name("example")
4027    ///     .tool_with_result_and_description(
4028    ///         "start_job",
4029    ///         "Start a background export job and return its task handle",
4030    ///         |args: RunArgs, _extra| {
4031    ///             Box::pin(async move {
4032    ///                 Ok(CallToolResult::new(vec![Content::text(
4033    ///                     format!("started {}", args.job),
4034    ///                 )])
4035    ///                 .with_related_task(TaskMetadata::new("t1")))
4036    ///             })
4037    ///         },
4038    ///     )
4039    ///     .build();
4040    /// # }
4041    /// ```
4042    #[cfg(feature = "schema-generation")]
4043    pub fn tool_with_result_and_description<TIn>(
4044        mut self,
4045        name: impl Into<String>,
4046        description: impl Into<String>,
4047        handler: impl Fn(
4048                TIn,
4049                crate::RequestHandlerExtra,
4050            ) -> std::pin::Pin<
4051                Box<
4052                    dyn std::future::Future<Output = crate::Result<crate::types::CallToolResult>>
4053                        + Send,
4054                >,
4055            > + Send
4056            + Sync
4057            + 'static,
4058    ) -> Self
4059    where
4060        TIn: serde::de::DeserializeOwned + schemars::JsonSchema + Send + Sync + 'static,
4061    {
4062        use crate::server::typed_tool::TypedToolWithResult;
4063
4064        let name_str = name.into();
4065        let tool =
4066            TypedToolWithResult::new(name_str.clone(), handler).with_description(description);
4067        self.tools.insert(name_str, Arc::new(tool));
4068
4069        // Update capabilities to include tools
4070        if self.capabilities.tools.is_none() {
4071            self.capabilities.tools = Some(crate::types::ToolCapabilities {
4072                list_changed: Some(false),
4073            });
4074        }
4075
4076        self
4077    }
4078
4079    /// Add a type-safe tool handler with both input and output typing and description.
4080    ///
4081    /// This is a convenience overload that allows setting a description directly
4082    /// without needing to chain `.with_description()`.
4083    ///
4084    /// # Example
4085    /// ```no_run
4086    /// # #[cfg(feature = "schema-generation")]
4087    /// # {
4088    /// use pmcp::ServerBuilder;
4089    /// use schemars::JsonSchema;
4090    /// use serde::{Deserialize, Serialize};
4091    ///
4092    /// #[derive(JsonSchema, Deserialize)]
4093    /// struct MathInput { a: f64, b: f64, op: String }
4094    ///
4095    /// #[derive(JsonSchema, Serialize)]
4096    /// struct MathOutput { result: f64, operation: String }
4097    ///
4098    /// let server = ServerBuilder::new()
4099    ///     .name("example")
4100    ///     .tool_typed_with_output_and_description::<MathInput, MathOutput>(
4101    ///         "math",
4102    ///         "Performs basic mathematical operations on two numbers",
4103    ///         |args, _| {
4104    ///             Box::pin(async move {
4105    ///                 let result = match args.op.as_str() {
4106    ///                     "add" => args.a + args.b,
4107    ///                     "subtract" => args.a - args.b,
4108    ///                     _ => return Err(pmcp::Error::Validation("Unknown operation".into())),
4109    ///                 };
4110    ///                 Ok(MathOutput { result, operation: args.op })
4111    ///             })
4112    ///         }
4113    ///     );
4114    /// # }
4115    /// ```
4116    #[cfg(feature = "schema-generation")]
4117    pub fn tool_typed_with_output_and_description<TIn, TOut>(
4118        mut self,
4119        name: impl Into<String>,
4120        description: impl Into<String>,
4121        handler: impl Fn(
4122                TIn,
4123                crate::RequestHandlerExtra,
4124            )
4125                -> std::pin::Pin<Box<dyn std::future::Future<Output = crate::Result<TOut>> + Send>>
4126            + Send
4127            + Sync
4128            + 'static,
4129    ) -> Self
4130    where
4131        TIn: serde::de::DeserializeOwned + schemars::JsonSchema + Send + Sync + 'static,
4132        TOut: serde::Serialize + schemars::JsonSchema + Send + Sync + 'static,
4133    {
4134        use crate::server::typed_tool::TypedToolWithOutput;
4135
4136        let name_str = name.into();
4137        let tool =
4138            TypedToolWithOutput::new(name_str.clone(), handler).with_description(description);
4139        self.tools.insert(name_str, Arc::new(tool));
4140
4141        // Update capabilities to include tools
4142        if self.capabilities.tools.is_none() {
4143            self.capabilities.tools = Some(crate::types::ToolCapabilities {
4144                list_changed: Some(false),
4145            });
4146        }
4147
4148        self
4149    }
4150
4151    /// Add a prompt handler.
4152    ///
4153    /// Registers a prompt that clients can retrieve via the prompts/get method.
4154    /// Prompts provide templates that clients can use for various tasks.
4155    ///
4156    /// # Arguments
4157    ///
4158    /// * `name` - The name of the prompt (used by clients to retrieve it)
4159    /// * `handler` - The handler implementation for this prompt
4160    ///
4161    /// # Examples
4162    ///
4163    /// ```rust,no_run
4164    /// use pmcp::{Server, PromptHandler, GetPromptResult, PromptMessage, Content};
4165    /// use async_trait::async_trait;
4166    /// use std::collections::HashMap;
4167    ///
4168    /// struct CodeReviewPrompt;
4169    ///
4170    /// #[async_trait]
4171    /// impl PromptHandler for CodeReviewPrompt {
4172    ///     async fn handle(&self, args: HashMap<String, String>, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<GetPromptResult> {
4173    ///         let language = args.get("language").map(|s| s.as_str()).unwrap_or("unknown");
4174    ///         Ok(GetPromptResult::new(
4175    ///             vec![PromptMessage::user(pmcp::Content::text(format!(
4176    ///                 "Please review this {} code:",
4177    ///                 language
4178    ///             )))],
4179    ///             Some(format!("Code review prompt for {}", language)),
4180    ///         ))
4181    ///     }
4182    /// }
4183    ///
4184    /// let server = Server::builder()
4185    ///     .name("code-server")
4186    ///     .version("1.0.0")
4187    ///     .prompt("code_review", CodeReviewPrompt{})
4188    ///     .build()?;
4189    /// # Ok::<(), pmcp::Error>(())
4190    /// ```
4191    pub fn prompt(
4192        mut self,
4193        name: impl Into<String>,
4194        handler: impl PromptHandler + 'static,
4195    ) -> Self {
4196        self.prompts.insert(name.into(), Arc::new(handler));
4197
4198        // Update capabilities to include prompts
4199        // Use Some(false) instead of None to ensure the field serializes properly
4200        if self.capabilities.prompts.is_none() {
4201            self.capabilities.prompts = Some(crate::types::PromptCapabilities {
4202                list_changed: Some(false),
4203            });
4204        }
4205
4206        self
4207    }
4208
4209    /// Add a prompt handler with an Arc.
4210    ///
4211    /// This variant lets the caller share the handler `Arc` between the
4212    /// builder and an external in-process handler map (e.g., a downstream
4213    /// toolkit's handler registry) without writing a delegating wrapper
4214    /// shim. Behavior is otherwise identical to [`Self::prompt`]: the first
4215    /// registration auto-enables `capabilities.prompts`.
4216    pub fn prompt_arc(mut self, name: impl Into<String>, handler: Arc<dyn PromptHandler>) -> Self {
4217        let name = name.into();
4218        self.prompts.insert(name, handler);
4219
4220        // Update capabilities to include prompts
4221        // Use Some(false) instead of None to ensure the field serializes properly
4222        if self.capabilities.prompts.is_none() {
4223            self.capabilities.prompts = Some(crate::types::PromptCapabilities {
4224                list_changed: Some(false),
4225            });
4226        }
4227
4228        self
4229    }
4230
4231    /// Register a workflow-based prompt with automatic validation.
4232    ///
4233    /// This method validates the workflow before registration and converts it
4234    /// to a prompt handler. The workflow's instructions become the prompt messages,
4235    /// and the workflow's arguments become the prompt arguments.
4236    ///
4237    /// # Arguments
4238    ///
4239    /// * `workflow` - The workflow definition to register as a prompt
4240    ///
4241    /// # Errors
4242    ///
4243    /// Returns an error if the workflow validation fails (e.g., undefined bindings,
4244    /// undefined prompt arguments, etc.).
4245    ///
4246    /// # Examples
4247    ///
4248    /// ```rust,no_run
4249    /// use pmcp::{Server, ServerBuilder};
4250    /// use pmcp::server::workflow::{SequentialWorkflow, InternalPromptMessage};
4251    /// use pmcp::types::Role;
4252    ///
4253    /// # fn main() -> pmcp::Result<()> {
4254    /// let workflow = SequentialWorkflow::new(
4255    ///     "code_review_workflow",
4256    ///     "Review code with multiple steps"
4257    /// )
4258    /// .argument("code", "Code to review", true)
4259    /// .instruction(InternalPromptMessage::new(
4260    ///     Role::System,
4261    ///     "You are a code reviewer. Review the provided code carefully."
4262    /// ));
4263    ///
4264    /// let server = Server::builder()
4265    ///     .name("code-server")
4266    ///     .version("1.0.0")
4267    ///     .prompt_workflow(workflow)?
4268    ///     .build()?;
4269    /// # Ok(())
4270    /// # }
4271    /// ```
4272    pub fn prompt_workflow(mut self, workflow: workflow::SequentialWorkflow) -> Result<Self> {
4273        // Validate the workflow before registration
4274        workflow
4275            .validate()
4276            .map_err(|e| Error::Validation(format!("Workflow validation failed: {}", e)))?;
4277
4278        // Build tool and resource registries from currently registered handlers
4279        // Note: This captures the current state of registered tools/resources
4280        let mut tools = std::collections::HashMap::new();
4281        for (name, handler) in &self.tools {
4282            if let Some(metadata) = handler.metadata() {
4283                tools.insert(
4284                    Arc::from(name.as_str()),
4285                    workflow::conversion::ToolInfo {
4286                        name: metadata.name,
4287                        description: metadata.description.unwrap_or_default(),
4288                        input_schema: metadata.input_schema,
4289                    },
4290                );
4291            }
4292        }
4293
4294        // Build tool handlers map for workflow execution
4295        // Clone Arc references for shared ownership
4296        let mut tool_handlers: std::collections::HashMap<Arc<str>, Arc<dyn ToolHandler>> =
4297            std::collections::HashMap::new();
4298        for (name, handler) in &self.tools {
4299            tool_handlers.insert(Arc::from(name.as_str()), Arc::clone(handler));
4300        }
4301
4302        // Get the workflow name before moving it
4303        let name = workflow.name().to_string();
4304
4305        // Create workflow prompt handler with tool execution and resource fetching capability
4306        // Note: Workflow prompts in ServerBuilder do not currently execute tool middleware.
4307        // For middleware support in workflow tool execution, use ServerCoreBuilder.
4308        let handler = workflow::WorkflowPromptHandler::new(
4309            workflow,
4310            tools,
4311            tool_handlers,
4312            self.resources.clone(),
4313        );
4314
4315        // Register as a prompt
4316        self.prompts.insert(name, Arc::new(handler));
4317
4318        // Update capabilities to include prompts
4319        // This ensures prompts/list returns the workflow prompts
4320        if self.capabilities.prompts.is_none() {
4321            self.capabilities.prompts = Some(crate::types::PromptCapabilities {
4322                list_changed: Some(false),
4323            });
4324        }
4325
4326        Ok(self)
4327    }
4328
4329    /// Set the resource handler.
4330    ///
4331    /// Registers a resource handler that provides access to server resources.
4332    /// Resources allow clients to read files, configurations, or other data.
4333    ///
4334    /// # Arguments
4335    ///
4336    /// * `handler` - The resource handler implementation
4337    ///
4338    /// # Examples
4339    ///
4340    /// ```rust,no_run
4341    /// use pmcp::{Server, ResourceHandler, ReadResourceResult, ListResourcesResult, ResourceInfo};
4342    /// use async_trait::async_trait;
4343    ///
4344    /// struct FileResourceHandler;
4345    ///
4346    /// #[async_trait]
4347    /// impl ResourceHandler for FileResourceHandler {
4348    ///     async fn read(&self, uri: &str, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<ReadResourceResult> {
4349    ///         // Read file content...
4350    ///         Ok(ReadResourceResult::new(vec![pmcp::Content::text("File content here")]))
4351    ///     }
4352    ///
4353    ///     async fn list(&self, _cursor: Option<String>, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<ListResourcesResult> {
4354    ///         Ok(ListResourcesResult::new(vec![
4355    ///             pmcp::ResourceInfo::new("file://example.txt", "example.txt")
4356    ///                 .with_description("Example file")
4357    ///                 .with_mime_type("text/plain"),
4358    ///         ]))
4359    ///     }
4360    /// }
4361    ///
4362    /// let server = Server::builder()
4363    ///     .name("file-server")
4364    ///     .version("1.0.0")
4365    ///     .resources(FileResourceHandler{})
4366    ///     .build()?;
4367    /// # Ok::<(), pmcp::Error>(())
4368    /// ```
4369    pub fn resources(mut self, handler: impl ResourceHandler + 'static) -> Self {
4370        self.resources = Some(Arc::new(handler));
4371
4372        // Update capabilities to include resources
4373        // Use Some(false) instead of None to ensure fields serialize properly
4374        if self.capabilities.resources.is_none() {
4375            self.capabilities.resources = Some(crate::types::ResourceCapabilities {
4376                subscribe: Some(false),
4377                list_changed: Some(false),
4378            });
4379        }
4380
4381        self
4382    }
4383
4384    /// Set the resource handler with an Arc.
4385    ///
4386    /// This variant lets the caller share the handler `Arc` between the
4387    /// builder and an external in-process handler map without writing a
4388    /// delegating wrapper. Behavior is otherwise identical to
4389    /// [`Self::resources`]: the first registration auto-enables
4390    /// `capabilities.resources`.
4391    pub fn resources_arc(mut self, handler: Arc<dyn ResourceHandler>) -> Self {
4392        self.resources = Some(handler);
4393
4394        // Update capabilities to include resources
4395        // Use Some(false) instead of None to ensure fields serialize properly
4396        if self.capabilities.resources.is_none() {
4397            self.capabilities.resources = Some(crate::types::ResourceCapabilities {
4398                subscribe: Some(false),
4399                list_changed: Some(false),
4400            });
4401        }
4402
4403        self
4404    }
4405
4406    /// Set the completion provider backing `completion/complete`.
4407    ///
4408    /// The twin of
4409    /// [`ServerCoreBuilder::completions`](crate::server::builder::ServerCoreBuilder::completions) —
4410    /// same name, same signature, same single-provider shape — so a provider
4411    /// registered through EITHER builder family reaches its own dispatcher. A
4412    /// slot on one family with the dispatch arm on the other's server would be
4413    /// an unreachable seam that still answered the spec shape, which is exactly
4414    /// the false green this pair exists to prevent.
4415    ///
4416    /// A SINGLE, server-wide provider (the [`Self::resources`] shape, not the
4417    /// name-keyed [`Self::prompt`] shape): the spec routes every
4418    /// `completion/complete` to one seam and passes the `ref` as data. The
4419    /// reference reaches the provider through
4420    /// [`CompletionRequest::context`](crate::types::completable::CompletionRequest::context)
4421    /// under the key `ref/prompt` or `ref/resource`.
4422    ///
4423    /// Registering a provider auto-advertises `capabilities.completions`.
4424    /// Not registering one is NOT an error: `completion/complete` still answers
4425    /// `{"completion": {"values": []}}`.
4426    ///
4427    /// # Examples
4428    ///
4429    /// ```rust
4430    /// use pmcp::Server;
4431    /// use pmcp::types::completable::StaticCompletionProvider;
4432    ///
4433    /// let server = Server::builder()
4434    ///     .name("completion-server")
4435    ///     .version("1.0.0")
4436    ///     .completions(StaticCompletionProvider::from_strings(vec![
4437    ///         "alpha".to_string(),
4438    ///         "beta".to_string(),
4439    ///     ]))
4440    ///     .build()?;
4441    /// # Ok::<(), pmcp::Error>(())
4442    /// ```
4443    #[must_use]
4444    pub fn completions(
4445        self,
4446        provider: impl crate::types::completable::CompletionProviderTrait + 'static,
4447    ) -> Self {
4448        self.completions_arc(Arc::new(provider))
4449    }
4450
4451    /// Set the completion provider with an Arc.
4452    ///
4453    /// This variant lets the caller share the provider `Arc` with something
4454    /// outside the builder. Behavior is otherwise identical to
4455    /// [`Self::completions`].
4456    #[must_use]
4457    pub fn completions_arc(
4458        mut self,
4459        provider: Arc<dyn crate::types::completable::CompletionProviderTrait>,
4460    ) -> Self {
4461        self.completions = Some(provider);
4462
4463        // Update capabilities to include completions.
4464        // Use Some(default) instead of None to ensure the field serializes.
4465        if self.capabilities.completions.is_none() {
4466            self.capabilities.completions = Some(crate::types::CompletionCapabilities::default());
4467        }
4468
4469        self
4470    }
4471
4472    /// Register a single SEP-2640 Agent Skill.
4473    ///
4474    /// Convenience over [`Self::skills`] for the single-skill case. The skill
4475    /// is accumulated and finalized into a `SkillsHandler` exactly once at
4476    /// [`Self::build`] time, then composed with any `.resources(...)`
4477    /// handler set on this builder.
4478    ///
4479    /// # Panics
4480    ///
4481    /// Panics at `.build()` time if multiple registered skills resolve to
4482    /// the same `skill://` URI. Use [`Self::try_skills`] with a pre-built
4483    /// [`skills::Skills`] registry to surface duplicates as a `Result`.
4484    ///
4485    /// # Examples
4486    ///
4487    /// ```rust,no_run
4488    /// # #[cfg(feature = "skills")] {
4489    /// use pmcp::{Server, server::skills::Skill};
4490    ///
4491    /// # fn example() -> pmcp::Result<()> {
4492    /// let server = Server::builder()
4493    ///     .name("my-server")
4494    ///     .version("1.0.0")
4495    ///     .skill(Skill::new("hello", "# Hello skill"))
4496    ///     .build()?;
4497    /// # Ok(())
4498    /// # }
4499    /// # }
4500    /// ```
4501    #[cfg(feature = "skills")]
4502    #[must_use]
4503    pub fn skill(self, skill: skills::Skill) -> Self {
4504        self.skills(skills::Skills::new().add(skill))
4505    }
4506
4507    /// Register a registry of SEP-2640 Agent Skills.
4508    ///
4509    /// Merges into any prior accumulated skills (a previous `.skill(...)` or
4510    /// `.skills(...)` call). The accumulated registry is finalized into a
4511    /// single `SkillsHandler` exactly once at [`Self::build`] time, then
4512    /// composed at most once with any `.resources(...)` handler.
4513    ///
4514    /// # Panics
4515    ///
4516    /// Panics at `.build()` if two registered skills resolve to the same
4517    /// `skill://` URI. Use [`Self::try_skills`] for fallible registration.
4518    #[cfg(feature = "skills")]
4519    #[must_use]
4520    pub fn skills(mut self, skills_registry: skills::Skills) -> Self {
4521        let merged = match self.pending_skills.take() {
4522            Some(prior) => prior.merge(skills_registry),
4523            None => skills_registry,
4524        };
4525        self.pending_skills = Some(merged);
4526        skills::set_skills_capabilities(&mut self.capabilities);
4527        self
4528    }
4529
4530    /// Fallible variant of [`Self::skills`] — returns `Err` immediately if
4531    /// the merged registry would contain duplicate URIs. Useful for
4532    /// runtime-dynamic registration where panicking is unacceptable.
4533    ///
4534    /// # Errors
4535    ///
4536    /// Returns `Err(pmcp::Error::Validation)` if the merged registry would
4537    /// produce duplicate `skill://` URIs.
4538    #[cfg(feature = "skills")]
4539    pub fn try_skills(mut self, skills_registry: skills::Skills) -> Result<Self> {
4540        let merged = match self.pending_skills.take() {
4541            Some(prior) => prior.merge(skills_registry),
4542            None => skills_registry,
4543        };
4544        // Probe by cloning + into_handler; discard the handler. The real
4545        // construction happens in `.build()` once everything is settled.
4546        merged.clone().into_handler()?;
4547        self.pending_skills = Some(merged);
4548        skills::set_skills_capabilities(&mut self.capabilities);
4549        Ok(self)
4550    }
4551
4552    /// Register a skill AND a parallel prompt that returns the same content.
4553    ///
4554    /// The dual-surface bootstrap: both surfaces are derived from one
4555    /// [`skills::Skill`] value so they cannot drift. The byte-equality
4556    /// between surfaces is asserted by the skills integration test.
4557    #[cfg(feature = "skills")]
4558    #[must_use]
4559    pub fn bootstrap_skill_and_prompt(
4560        self,
4561        skill: skills::Skill,
4562        prompt_name: impl Into<String>,
4563    ) -> Self {
4564        let prompt_handler = skills::SkillPromptHandler::new(skill.clone());
4565        self.skill(skill).prompt(prompt_name, prompt_handler)
4566    }
4567
4568    /// Set the sampling handler.
4569    ///
4570    /// Registers a sampling handler that provides LLM functionality.
4571    /// This allows the server to act as a language model provider.
4572    ///
4573    /// # Arguments
4574    ///
4575    /// * `handler` - The sampling handler implementation
4576    ///
4577    /// # Examples
4578    ///
4579    /// ```rust,no_run
4580    /// use pmcp::{Server, SamplingHandler, CreateMessageParams, CreateMessageResult};
4581    /// use async_trait::async_trait;
4582    ///
4583    /// struct MockLLM;
4584    ///
4585    /// #[async_trait]
4586    /// impl SamplingHandler for MockLLM {
4587    ///     async fn create_message(&self, params: CreateMessageParams, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<CreateMessageResult> {
4588    ///         // Process the messages and generate a response
4589    ///         Ok(CreateMessageResult::new(pmcp::Content::text("Generated response"), "mock-llm-v1")
4590    ///             .with_usage(pmcp::TokenUsage::new(10, 5, 15))
4591    ///             .with_stop_reason("end_of_text"))
4592    ///     }
4593    /// }
4594    ///
4595    /// let server = Server::builder()
4596    ///     .name("llm-server")
4597    ///     .version("1.0.0")
4598    ///     .sampling(MockLLM{})
4599    ///     .build()?;
4600    /// # Ok::<(), pmcp::Error>(())
4601    /// ```
4602    pub fn sampling(mut self, handler: impl SamplingHandler + 'static) -> Self {
4603        self.sampling = Some(Arc::new(handler));
4604        // Enable sampling capability
4605        self.capabilities.sampling = Some(crate::types::SamplingCapabilities::default());
4606        self
4607    }
4608
4609    /// Set the sampling handler with an Arc.
4610    ///
4611    /// This variant lets the caller share the handler `Arc` between the
4612    /// builder and an external in-process handler map without writing a
4613    /// delegating wrapper. Uses the donor's `if is_none` capability
4614    /// auto-enable so an explicit prior `.capabilities(custom)` is not
4615    /// clobbered by a later `_arc` registration.
4616    pub fn sampling_arc(mut self, handler: Arc<dyn SamplingHandler>) -> Self {
4617        self.sampling = Some(handler);
4618
4619        // Update capabilities to include sampling
4620        if self.capabilities.sampling.is_none() {
4621            self.capabilities.sampling = Some(crate::types::SamplingCapabilities::default());
4622        }
4623
4624        self
4625    }
4626
4627    /// Build the server.
4628    ///
4629    /// Constructs the final Server instance from the configured builder.
4630    /// This validates that required fields (name and version) are set.
4631    ///
4632    /// # Examples
4633    ///
4634    /// ```rust,no_run
4635    /// use pmcp::{Server, ToolHandler};
4636    /// use async_trait::async_trait;
4637    /// use serde_json::Value;
4638    ///
4639    /// struct PingTool;
4640    ///
4641    /// #[async_trait]
4642    /// impl ToolHandler for PingTool {
4643    ///     async fn handle(&self, _args: Value, _extra: pmcp::RequestHandlerExtra) -> pmcp::Result<Value> {
4644    ///         Ok(serde_json::json!({"response": "pong"}))
4645    ///     }
4646    /// }
4647    ///
4648    /// let server = Server::builder()
4649    ///     .name("ping-server")
4650    ///     .version("1.0.0")
4651    ///     .tool("ping", PingTool{})
4652    ///     .build()?;
4653    ///
4654    /// // Server is now ready to run
4655    /// // server.run_stdio().await?;
4656    /// # Ok::<(), pmcp::Error>(())
4657    /// ```
4658    /// Set the authentication provider.
4659    ///
4660    /// Configures an authentication provider that will validate incoming requests.
4661    /// When set, the server will use this provider to authenticate requests before
4662    /// processing them.
4663    ///
4664    /// # Arguments
4665    ///
4666    /// * `provider` - The authentication provider implementation
4667    ///
4668    /// # Examples
4669    ///
4670    /// ```rust,no_run
4671    /// use pmcp::{Server, auth::ProxyProvider};
4672    ///
4673    /// let auth_provider = ProxyProvider::with_upstream("https://oauth.example.com");
4674    ///
4675    /// let server = Server::builder()
4676    ///     .name("secure-server")
4677    ///     .version("1.0.0")
4678    ///     .auth_provider(auth_provider)
4679    ///     .build()?;
4680    /// # Ok::<(), pmcp::Error>(())
4681    /// ```
4682    pub fn auth_provider(mut self, provider: impl auth::AuthProvider + 'static) -> Self {
4683        self.auth_provider = Some(Arc::new(provider));
4684        self
4685    }
4686
4687    /// Set the authentication provider with an Arc.
4688    ///
4689    /// This variant lets the caller share the provider `Arc` between the
4690    /// builder and an external in-process registry without writing a
4691    /// delegating wrapper. Behavior is otherwise identical to
4692    /// [`Self::auth_provider`].
4693    pub fn auth_provider_arc(mut self, provider: Arc<dyn auth::AuthProvider>) -> Self {
4694        self.auth_provider = Some(provider);
4695        self
4696    }
4697
4698    /// Set the tool authorizer.
4699    ///
4700    /// Configures a tool authorizer for fine-grained access control.
4701    /// The authorizer determines which tools authenticated users can access
4702    /// based on their authentication context.
4703    ///
4704    /// # Arguments
4705    ///
4706    /// * `authorizer` - The tool authorization implementation
4707    ///
4708    /// # Examples
4709    ///
4710    /// ```rust,no_run
4711    /// use pmcp::{Server, auth::ScopeBasedAuthorizer};
4712    ///
4713    /// let authorizer = ScopeBasedAuthorizer::new()
4714    ///     .require_scopes("sensitive_tool", vec!["admin".to_string()])
4715    ///     .default_scopes(vec!["read".to_string()]);
4716    ///
4717    /// let server = Server::builder()
4718    ///     .name("secure-server")
4719    ///     .version("1.0.0")
4720    ///     .tool_authorizer(authorizer)
4721    ///     .build()?;
4722    /// # Ok::<(), pmcp::Error>(())
4723    /// ```
4724    pub fn tool_authorizer(mut self, authorizer: impl auth::ToolAuthorizer + 'static) -> Self {
4725        if !self.tool_protections.is_empty() {
4726            // Log a warning - custom authorizer supersedes protect_tool() configurations
4727            tracing::warn!(
4728                target: "mcp.auth",
4729                "Setting a custom tool_authorizer clears any previous protect_tool() configurations"
4730            );
4731            self.tool_protections.clear();
4732        }
4733        self.tool_authorizer = Some(Arc::new(authorizer));
4734        self
4735    }
4736
4737    /// Set the tool authorizer with an Arc.
4738    ///
4739    /// This variant lets the caller share the authorizer `Arc` between
4740    /// the builder and an external in-process registry without writing a
4741    /// delegating wrapper. Mirrors [`Self::tool_authorizer`]'s
4742    /// protection-clearing semantics: if any prior `protect_tool()`
4743    /// configurations exist, they are cleared and a `tracing::warn!` is
4744    /// emitted under target `"mcp.auth"`, since a custom authorizer
4745    /// supersedes scope-based tool protections.
4746    pub fn tool_authorizer_arc(mut self, authorizer: Arc<dyn auth::ToolAuthorizer>) -> Self {
4747        if !self.tool_protections.is_empty() {
4748            // Log a warning - custom authorizer supersedes protect_tool() configurations
4749            tracing::warn!(
4750                target: "mcp.auth",
4751                "Setting a custom tool_authorizer clears any previous protect_tool() configurations"
4752            );
4753            self.tool_protections.clear();
4754        }
4755        self.tool_authorizer = Some(authorizer);
4756        self
4757    }
4758
4759    /// Protect a specific tool with required scopes.
4760    ///
4761    /// This is a convenience method that creates or updates a scope-based authorizer
4762    /// to require specific scopes for accessing the named tool.
4763    ///
4764    /// # Arguments
4765    ///
4766    /// * `tool_name` - The name of the tool to protect
4767    /// * `scopes` - The required scopes for accessing this tool
4768    ///
4769    /// # Examples
4770    ///
4771    /// ```rust,no_run
4772    /// use pmcp::Server;
4773    ///
4774    /// let server = Server::builder()
4775    ///     .name("secure-server")
4776    ///     .version("1.0.0")
4777    ///     .protect_tool("delete_data", vec!["admin".to_string(), "write".to_string()])
4778    ///     .protect_tool("read_data", vec!["read".to_string()])
4779    ///     .build()?;
4780    /// # Ok::<(), pmcp::Error>(())
4781    /// ```
4782    pub fn protect_tool(mut self, tool_name: impl Into<String>, scopes: Vec<String>) -> Self {
4783        // Store the tool protection requirements to be applied at build time
4784        self.tool_protections.insert(tool_name.into(), scopes);
4785        self
4786    }
4787
4788    /// Add tool middleware for cross-cutting concerns.
4789    ///
4790    /// Tool middleware allows you to inject cross-cutting concerns into tool execution,
4791    /// such as OAuth token injection, logging, metrics, or request transformation.
4792    /// Middleware is executed in the order it's added, both for request processing
4793    /// (before tool execution) and response processing (after tool execution).
4794    ///
4795    /// This method brings middleware support to the high-level `ServerBuilder` API,
4796    /// enabling developers to use both typed tool registration AND middleware without
4797    /// dropping down to the lower-level `ServerCoreBuilder` API.
4798    ///
4799    /// # Arguments
4800    ///
4801    /// * `middleware` - The middleware implementation to add to the chain
4802    ///
4803    /// # Examples
4804    ///
4805    /// ## OAuth Token Injection Middleware
4806    ///
4807    /// ```rust,no_run
4808    /// use pmcp::server::tool_middleware::{ToolMiddleware, ToolContext};
4809    /// use pmcp::server::cancellation::RequestHandlerExtra;
4810    /// use pmcp::Server;
4811    /// use std::sync::Arc;
4812    /// use async_trait::async_trait;
4813    /// use serde_json::Value;
4814    ///
4815    /// struct OAuthInjectionMiddleware;
4816    ///
4817    /// #[async_trait]
4818    /// impl ToolMiddleware for OAuthInjectionMiddleware {
4819    ///     async fn on_request(
4820    ///         &self,
4821    ///         _tool_name: &str,
4822    ///         _args: &mut Value,
4823    ///         extra: &mut RequestHandlerExtra,
4824    ///         _context: &ToolContext,
4825    ///     ) -> pmcp::Result<()> {
4826    ///         // Extract OAuth token from auth_context and inject into metadata
4827    ///         if let Some(auth_ctx) = extra.auth_context() {
4828    ///             if let Some(token) = &auth_ctx.token {
4829    ///                 extra.set_metadata("oauth_token".to_string(), token.clone());
4830    ///             }
4831    ///         }
4832    ///         Ok(())
4833    ///     }
4834    /// }
4835    ///
4836    /// let server = Server::builder()
4837    ///     .name("oauth-server")
4838    ///     .version("1.0.0")
4839    ///     .tool_middleware(Arc::new(OAuthInjectionMiddleware))
4840    ///     .build()?;
4841    /// # Ok::<(), pmcp::Error>(())
4842    /// ```
4843    ///
4844    /// ## Combining with Typed Tools
4845    ///
4846    /// ```rust,no_run
4847    /// # #[cfg(feature = "schema-generation")]
4848    /// # {
4849    /// use pmcp::Server;
4850    /// use schemars::JsonSchema;
4851    /// use serde::{Deserialize, Serialize};
4852    ///
4853    /// #[derive(Debug, Deserialize, Serialize, JsonSchema)]
4854    /// struct ListGamesArgs {
4855    ///     filter: Option<String>,
4856    /// }
4857    ///
4858    /// let server = Server::builder()
4859    ///     .name("game-server")
4860    ///     .version("1.0.0")
4861    ///     .tool_typed_with_description(
4862    ///         "list_games",
4863    ///         "List all available games",
4864    ///         |args: ListGamesArgs, extra| {
4865    ///             Box::pin(async move {
4866    ///                 // Access OAuth token injected by middleware
4867    ///                 let _token = extra.get_metadata("oauth_token");
4868    ///                 Ok(serde_json::json!({"games": []}))
4869    ///             })
4870    ///         }
4871    ///     )
4872    ///     // .tool_middleware(Arc::new(oauth_middleware))  // Works with typed tools!
4873    ///     .build()?;
4874    /// # }
4875    /// # Ok::<(), pmcp::Error>(())
4876    /// ```
4877    ///
4878    /// # Middleware Execution Order
4879    ///
4880    /// Multiple middleware are executed in FIFO order for requests and FIFO for responses:
4881    ///
4882    /// ```text
4883    /// Request:  Middleware1 → Middleware2 → Tool Handler
4884    /// Response: Tool Handler → Middleware1 → Middleware2
4885    /// ```
4886    #[cfg(not(target_arch = "wasm32"))]
4887    pub fn tool_middleware(mut self, middleware: Arc<dyn tool_middleware::ToolMiddleware>) -> Self {
4888        self.tool_middlewares.push(middleware);
4889        self
4890    }
4891
4892    /// Enable observability for this server.
4893    ///
4894    /// This adds observability middleware that provides:
4895    /// - Distributed tracing with trace/span IDs
4896    /// - Request/response event logging
4897    /// - Metrics emission (duration, count, errors)
4898    ///
4899    /// The backend is automatically selected based on the configuration:
4900    /// - "console" - Pretty or JSON output to stdout (development)
4901    /// - "cloudwatch" - AWS `CloudWatch` EMF format (production)
4902    /// - "null" - Discards all events (testing)
4903    ///
4904    /// # Examples
4905    ///
4906    /// ```rust,no_run
4907    /// use pmcp::Server;
4908    /// use pmcp::server::observability::ObservabilityConfig;
4909    ///
4910    /// // Development: console output with pretty printing
4911    /// let server = Server::builder()
4912    ///     .name("my-server")
4913    ///     .version("1.0.0")
4914    ///     .with_observability(ObservabilityConfig::development())
4915    ///     .build()?;
4916    ///
4917    /// // Production: CloudWatch with EMF metrics
4918    /// let server = Server::builder()
4919    ///     .name("my-server")
4920    ///     .version("1.0.0")
4921    ///     .with_observability(ObservabilityConfig::production())
4922    ///     .build()?;
4923    ///
4924    /// // Auto-detect environment (Lambda vs local)
4925    /// let config = if std::env::var("AWS_LAMBDA_FUNCTION_NAME").is_ok() {
4926    ///     ObservabilityConfig::production()
4927    /// } else {
4928    ///     ObservabilityConfig::development()
4929    /// };
4930    /// let server = Server::builder()
4931    ///     .name("my-server")
4932    ///     .version("1.0.0")
4933    ///     .with_observability(config)
4934    ///     .build()?;
4935    /// # Ok::<(), pmcp::Error>(())
4936    /// ```
4937    #[cfg(not(target_arch = "wasm32"))]
4938    pub fn with_observability(mut self, config: observability::ObservabilityConfig) -> Self {
4939        if !config.enabled {
4940            return self;
4941        }
4942
4943        // Create backend based on configuration
4944        let backend: Arc<dyn observability::ObservabilityBackend> = match config.backend.as_str() {
4945            "cloudwatch" => Arc::new(observability::CloudWatchBackend::new(
4946                config.cloudwatch.clone(),
4947            )),
4948            "null" => Arc::new(observability::NullBackend),
4949            _ => Arc::new(observability::ConsoleBackend::new(config.console.pretty)),
4950        };
4951
4952        // Get server name for middleware (use placeholder if not yet set)
4953        let server_name = self.name.clone().unwrap_or_else(|| "unknown".to_string());
4954
4955        // Create and add the observability middleware
4956        let middleware =
4957            observability::McpObservabilityMiddleware::new(server_name, config, backend);
4958        self.tool_middlewares.push(Arc::new(middleware));
4959
4960        self
4961    }
4962
4963    /// Enable observability with a custom backend.
4964    ///
4965    /// Use this when you need a custom backend implementation (e.g., Datadog, custom metrics).
4966    ///
4967    /// # Examples
4968    ///
4969    /// ```rust,ignore
4970    /// use pmcp::Server;
4971    /// use pmcp::server::observability::{ObservabilityConfig, ObservabilityBackend};
4972    /// use std::sync::Arc;
4973    ///
4974    /// struct MyCustomBackend;
4975    ///
4976    /// #[async_trait]
4977    /// impl ObservabilityBackend for MyCustomBackend {
4978    ///     // ... custom implementation
4979    /// }
4980    ///
4981    /// let server = Server::builder()
4982    ///     .name("my-server")
4983    ///     .version("1.0.0")
4984    ///     .with_observability_backend(
4985    ///         ObservabilityConfig::development(),
4986    ///         Arc::new(MyCustomBackend),
4987    ///     )
4988    ///     .build()?;
4989    /// ```
4990    #[cfg(not(target_arch = "wasm32"))]
4991    pub fn with_observability_backend(
4992        mut self,
4993        config: observability::ObservabilityConfig,
4994        backend: Arc<dyn observability::ObservabilityBackend>,
4995    ) -> Self {
4996        if !config.enabled {
4997            return self;
4998        }
4999
5000        // Get server name for middleware (use placeholder if not yet set)
5001        let server_name = self.name.clone().unwrap_or_else(|| "unknown".to_string());
5002
5003        // Create and add the observability middleware
5004        let middleware =
5005            observability::McpObservabilityMiddleware::new(server_name, config, backend);
5006        self.tool_middlewares.push(Arc::new(middleware));
5007
5008        self
5009    }
5010
5011    /// Add a description to a tool (Note: Limited support).
5012    ///
5013    /// **Important**: Due to the immutable design of tool handlers, this method
5014    /// cannot retroactively add descriptions to already-registered tools.
5015    ///
5016    /// **Recommended**: Use the `*_with_description` variants instead:
5017    /// - `.tool_typed_with_description()`
5018    /// - `.tool_typed_sync_with_description()`
5019    /// - `.tool_typed_with_output_and_description()`
5020    ///
5021    /// This method is provided for API completeness but will log warnings
5022    /// when used, encouraging migration to the preferred approaches.
5023    ///
5024    /// # Preferred Examples
5025    ///
5026    /// ```rust,no_run
5027    /// # #[cfg(feature = "schema-generation")]
5028    /// # {
5029    /// use pmcp::ServerBuilder;
5030    /// use schemars::JsonSchema;
5031    /// use serde::{Deserialize, Serialize};
5032    ///
5033    /// #[derive(Debug, Deserialize, Serialize, JsonSchema)]
5034    /// struct MathArgs { a: f64, b: f64 }
5035    ///
5036    /// // Preferred: Use the direct description variants
5037    /// let server = ServerBuilder::new()
5038    ///     .name("example")
5039    ///     .tool_typed_with_description(
5040    ///         "add",
5041    ///         "Adds two numbers together",
5042    ///         |args: MathArgs, _| {
5043    ///             Box::pin(async move {
5044    ///                 Ok(serde_json::json!({ "result": args.a + args.b }))
5045    ///             })
5046    ///         }
5047    ///     )
5048    ///     .build();
5049    /// # }
5050    /// ```
5051    #[deprecated(
5052        since = "1.6.0",
5053        note = "Use tool_typed_with_description() and similar variants instead"
5054    )]
5055    pub fn with_tool_description(
5056        self,
5057        tool_name: impl Into<String>,
5058        description: impl Into<String>,
5059    ) -> Self {
5060        let tool_name = tool_name.into();
5061        let _description = description.into();
5062
5063        tracing::warn!(
5064            "with_tool_description('{}') called but cannot modify immutable tools. \
5065            Use tool_typed_with_description() variants instead.",
5066            tool_name
5067        );
5068
5069        self
5070    }
5071
5072    /// Configure HTTP middleware chain for `StreamableHttpServer`.
5073    ///
5074    /// This is a convenience method that stores the HTTP middleware chain
5075    /// so it can be retrieved later when creating a `StreamableHttpServer`.
5076    ///
5077    /// # Arguments
5078    ///
5079    /// * `middleware` - The HTTP middleware chain
5080    ///
5081    /// # Examples
5082    ///
5083    /// ```rust,no_run
5084    /// # #[cfg(feature = "streamable-http")]
5085    /// # fn example() -> Result<(), pmcp::Error> {
5086    /// use pmcp::Server;
5087    /// use pmcp::server::http_middleware::{ServerHttpLoggingMiddleware, ServerHttpMiddlewareChain};
5088    /// use std::sync::Arc;
5089    ///
5090    /// let mut http_chain = ServerHttpMiddlewareChain::new();
5091    /// http_chain.add(Arc::new(ServerHttpLoggingMiddleware::new()));
5092    ///
5093    /// let server = Server::builder()
5094    ///     .name("my-server")
5095    ///     .version("1.0.0")
5096    ///     .with_http_middleware(Arc::new(http_chain))
5097    ///     .build()?;
5098    ///
5099    /// // Later when creating StreamableHttpServer:
5100    /// // let config = StreamableHttpServerConfig {
5101    /// //     http_middleware: server.http_middleware(),
5102    /// //     ..Default::default()
5103    /// // };
5104    /// # Ok(())
5105    /// # }
5106    /// ```
5107    #[cfg(feature = "streamable-http")]
5108    pub fn with_http_middleware(
5109        mut self,
5110        middleware: Arc<http_middleware::ServerHttpMiddlewareChain>,
5111    ) -> Self {
5112        self.http_middleware = Some(middleware);
5113        self
5114    }
5115
5116    /// Add a host layer for MCP Apps metadata enrichment.
5117    ///
5118    /// Host layers enrich tool `_meta` at build time with host-specific keys.
5119    /// For example, `HostType::ChatGpt` adds `openai/outputTemplate` and
5120    /// `openai/widgetAccessible` derived from the standard `ui.resourceUri`.
5121    ///
5122    /// This is opt-in — standard MCP Apps hosts (Claude Desktop, etc.) work
5123    /// without any host layer. Duplicates are ignored.
5124    #[cfg(feature = "mcp-apps")]
5125    pub fn with_host_layer(mut self, host: crate::types::mcp_apps::HostType) -> Self {
5126        if !self.host_layers.contains(&host) {
5127            self.host_layers.push(host);
5128        }
5129        self
5130    }
5131
5132    /// Build the server.
5133    ///
5134    /// Constructs the final Server instance from the configured builder.
5135    /// This validates that required fields (name and version) are set.
5136    ///
5137    /// # Errors
5138    ///
5139    /// Register a [`TaskStore`](crate::server::task_store::TaskStore) for MCP
5140    /// Tasks on the high-level HTTP-facing `Server` (RECOMMENDED tools-as-Tasks
5141    /// path).
5142    ///
5143    /// This is the recommended, all-typed path for exposing a tool as an async
5144    /// MCP Task over the `Server` / `StreamableHttpServer` path: pair a
5145    /// task-capable [`TypedTool`](crate::server::typed_tool::TypedTool) (marked
5146    /// [`with_task_support(TaskSupport::Required)`](crate::types::ToolExecution::with_task_support))
5147    /// with a store here, and the SDK serves `tasks/*` typed from the store —
5148    /// you never hand-write `tasks/*` wire JSON, and the store mints the task id.
5149    /// For the legacy experimental router path, use [`Self::with_task_store`]
5150    /// (which takes a [`TaskRouter`](crate::server::tasks::TaskRouter), NOT a
5151    /// `TaskStore`).
5152    ///
5153    /// When a task store is registered, the server:
5154    /// - **Auto-advertises** `ServerCapabilities.tasks` (with list and cancel
5155    ///   support) in `initialize` — the mere presence of a store flips the
5156    ///   capability on, unless an explicit `tasks` capability was already
5157    ///   configured (additive-only; an explicit value is preserved verbatim).
5158    /// - Handles the `tasks/*` surface via the store. The method set is
5159    ///   ERA-DEPENDENT (Phase 114): v1 (2025-11-25) serves `tasks/get`,
5160    ///   `tasks/result`, `tasks/list` and `tasks/cancel`; v2 (2026-07-28)
5161    ///   serves `tasks/get`, `tasks/update` and `tasks/cancel`, and answers
5162    ///   `-32601` for the two retired methods
5163    /// - Resolves task owner from auth context. **v1** falls back through OAuth
5164    ///   subject → client ID → session ID; **v2** has no session to fall back
5165    ///   to and binds fail-closed on an auth-configured server (TASK-05, D-07)
5166    ///
5167    /// A tool declaring
5168    /// [`TaskSupport::Required`](crate::types::tools::TaskSupport::Required)
5169    /// with NO store (or router) makes [`Self::build`] return an `Err`, rather
5170    /// than advertising a hollow `tasks` capability whose endpoints cannot work.
5171    ///
5172    /// # Examples
5173    ///
5174    /// ```no_run
5175    /// use std::sync::Arc;
5176    /// use pmcp::Server;
5177    /// use pmcp::server::task_store::{InMemoryTaskStore, TaskStore};
5178    /// use pmcp::server::typed_tool::TypedTool;
5179    /// use pmcp::types::{TaskSupport, ToolExecution};
5180    ///
5181    /// # fn build() -> pmcp::Result<()> {
5182    /// let task_tool = TypedTool::new_with_schema(
5183    ///     "summarize",
5184    ///     serde_json::json!({ "type": "object" }),
5185    ///     |_args: serde_json::Value, _extra| {
5186    ///         Box::pin(async { Ok(serde_json::json!({ "status": "completed" })) })
5187    ///     },
5188    /// )
5189    /// .with_description("Summarize asynchronously as an MCP Task")
5190    /// .with_execution(ToolExecution::new().with_task_support(TaskSupport::Required));
5191    ///
5192    /// let store = Arc::new(InMemoryTaskStore::new()) as Arc<dyn TaskStore>;
5193    /// let server = Server::builder()
5194    ///     .name("my-server")
5195    ///     .version("1.0.0")
5196    ///     .tool("summarize", task_tool)
5197    ///     .task_store(store) // presence of a store auto-advertises the `tasks` capability
5198    ///     .build()?;
5199    /// # let _ = server;
5200    /// # Ok(())
5201    /// # }
5202    /// ```
5203    #[cfg(not(target_arch = "wasm32"))]
5204    pub fn task_store(mut self, store: Arc<dyn crate::server::task_store::TaskStore>) -> Self {
5205        // Capability advertisement is centralized in `build()` (see
5206        // `task_dispatch::apply_tasks_capability_rule`). Registering a store
5207        // records the backend; it does NOT itself set `capabilities.tasks`, so
5208        // an explicitly-configured capability is never clobbered (additive-only,
5209        // per D-CAPABILITY-ENDPOINT-BACKED).
5210        self.task_store = Some(store);
5211        self
5212    }
5213
5214    /// Register a legacy experimental
5215    /// [`TaskRouter`](crate::server::tasks::TaskRouter) for MCP Tasks on the
5216    /// high-level `Server`.
5217    ///
5218    /// NAMING NOTE: despite the `with_task_store` name, this setter accepts a
5219    /// **[`TaskRouter`](crate::server::tasks::TaskRouter)** (the legacy,
5220    /// experimental router-backed path), NOT a
5221    /// [`TaskStore`](crate::server::task_store::TaskStore). The setter for an
5222    /// actual `TaskStore` (the RECOMMENDED polling path) is
5223    /// [`Self::task_store`]. This carried-over naming mirrors
5224    /// `ServerCoreBuilder::with_task_store`; the API is additive-only, so the
5225    /// confusing pair is documented here rather than renamed.
5226    ///
5227    /// Registering a router auto-configures the `experimental.tasks` capability
5228    /// from the router's `task_capabilities()`.
5229    ///
5230    /// **That advertisement is v1-only (Phase 114).** `experimental.tasks` is
5231    /// the 2025-11-25 spelling; a v2 (2026-07-28) client never sees it, because
5232    /// `project_capabilities_for_v2` strips both `experimental` and
5233    /// `capabilities.tasks` and v2 declares tasks through the `extensions` map
5234    /// key `io.modelcontextprotocol/tasks` instead (plan 114-05).
5235    #[cfg(not(target_arch = "wasm32"))]
5236    pub fn with_task_store(mut self, router: Arc<dyn crate::server::tasks::TaskRouter>) -> Self {
5237        // Auto-configure experimental.tasks capability from the router.
5238        let experimental = self
5239            .capabilities
5240            .experimental
5241            .get_or_insert_with(HashMap::new);
5242        experimental.insert("tasks".to_string(), router.task_capabilities());
5243
5244        self.task_router = Some(router);
5245        self
5246    }
5247
5248    /// Opt a tool OUT of the TOUT-02 double-wrap tripwire (D-08).
5249    ///
5250    /// The tripwire WARNs (every build) and `debug_assert!`-fails (debug/CI)
5251    /// when a tool returns a `ToolOutput::Payload` `Value` that STRUCTURALLY
5252    /// resembles an already-built `CallToolResult` (a non-empty `content` array
5253    /// of `Content`, or a `_meta` related-task envelope) — the silent
5254    /// double-wrap bug. Naming a tool here suppresses that check for it.
5255    ///
5256    /// SUPPRESSION SHOULD BE RARE AND REVIEWED: it disables a safety tripwire for
5257    /// one tool whose LEGITIMATE payload happens to trip the heuristic. Prefer
5258    /// returning [`ToolOutput::Result`] so the
5259    /// handler owns the full envelope verbatim, rather than suppressing. Reach
5260    /// for this only when a tool genuinely produces a plain `Value` that mimics a
5261    /// result shape and cannot be restructured.
5262    ///
5263    /// The same suppression set is carried into `ServerCore`, so both native
5264    /// dispatchers honor the opt-out identically (no drift).
5265    #[cfg(not(target_arch = "wasm32"))]
5266    #[must_use]
5267    pub fn suppress_double_wrap_check(mut self, name: impl Into<String>) -> Self {
5268        self.suppress_double_wrap.insert(name.into());
5269        self
5270    }
5271
5272    /// Returns an error if:
5273    /// - The server name is not set
5274    /// - The server version is not set
5275    /// - A tool declares `TaskSupport::Required` but no `TaskStore`/`TaskRouter`
5276    ///   backend is configured (see [`Self::task_store`])
5277    #[allow(unused_mut)] // `self` is mutated only on non-wasm (capability rule).
5278    pub fn build(mut self) -> Result<Server> {
5279        let name = self
5280            .name
5281            .ok_or_else(|| crate::Error::validation("Server name is required"))?;
5282        let version = self
5283            .version
5284            .ok_or_else(|| crate::Error::validation("Server version is required"))?;
5285
5286        // Apply tool protections
5287        let tool_authorizer = if !self.tool_protections.is_empty() {
5288            if self.tool_authorizer.is_some() {
5289                // If there's an existing authorizer and tool protections are specified,
5290                // this is a configuration error
5291                return Err(crate::Error::validation(
5292                    "Cannot use protect_tool() with a custom tool_authorizer. \
5293                     Either use protect_tool() to configure scope-based authorization, \
5294                     or provide a custom ToolAuthorizer implementation, but not both.",
5295                ));
5296            }
5297            // Create a ScopeBasedAuthorizer with all the tool protections
5298            let mut authorizer = auth::ScopeBasedAuthorizer::new();
5299            for (tool_name, scopes) in self.tool_protections {
5300                authorizer = authorizer.require_scopes(tool_name, scopes);
5301            }
5302            Some(Arc::new(authorizer) as Arc<dyn auth::ToolAuthorizer>)
5303        } else {
5304            self.tool_authorizer
5305        };
5306
5307        // Initialize tool middleware chain
5308        #[cfg(not(target_arch = "wasm32"))]
5309        let tool_middleware_chain = {
5310            let mut chain = tool_middleware::ToolMiddlewareChain::new();
5311            for middleware in self.tool_middlewares {
5312                chain.add(middleware);
5313            }
5314            Arc::new(RwLock::new(chain))
5315        };
5316
5317        // Build tool_infos cache at construction time (mirrors ServerCore pattern)
5318        let tool_infos: HashMap<String, ToolInfo> = self
5319            .tools
5320            .iter()
5321            .map(|(name, handler)| {
5322                let info = handler.metadata().unwrap_or_else(|| {
5323                    ToolInfo::new(
5324                        name.clone(),
5325                        None,
5326                        serde_json::json!({"type": "object", "properties": {}}),
5327                    )
5328                });
5329                (name.clone(), info)
5330            })
5331            .collect();
5332
5333        // Apply host layer enrichment to tool _meta (e.g., ChatGPT openai/* keys)
5334        #[cfg(feature = "mcp-apps")]
5335        let tool_infos = {
5336            let mut infos = tool_infos;
5337            for host in &self.host_layers {
5338                for info in infos.values_mut() {
5339                    if let Some(meta) = info._meta.as_mut() {
5340                        core::enrich_meta_for_host(meta, *host);
5341                    }
5342                }
5343            }
5344            infos
5345        };
5346
5347        // Build URI-to-tool-meta index for widget resource _meta propagation
5348        let uri_to_tool_meta = core::build_uri_to_tool_meta(&tool_infos);
5349
5350        // Apply the SHARED endpoint-backed `tasks`-capability rule (the SAME
5351        // free fn `ServerCoreBuilder::build` uses) now that `tool_infos` is
5352        // finalized: a store-backed `Server` auto-advertises `tasks`, and a
5353        // `TaskSupport::Required` tool with no backend is a build-time error.
5354        // Runs BEFORE `self.capabilities` is moved into the `Server` literal.
5355        #[cfg(not(target_arch = "wasm32"))]
5356        {
5357            let has_backend = self.task_store.is_some() || self.task_router.is_some();
5358            crate::server::task_dispatch::apply_tasks_capability_rule(
5359                &mut self.capabilities,
5360                &tool_infos,
5361                has_backend,
5362            )?;
5363        }
5364
5365        // Finalize accumulated skills exactly once and compose with the
5366        // user's `.resources(...)` slot if both are set. `.resources(...)`
5367        // itself stays "last write wins" — composition lives here so the
5368        // setter's semantics are unchanged for callers that don't use
5369        // skills.
5370        #[cfg(feature = "skills")]
5371        let final_resources: Option<Arc<dyn ResourceHandler>> =
5372            builder::finalize_skills_resources(self.pending_skills, self.resources);
5373        #[cfg(not(feature = "skills"))]
5374        let final_resources = self.resources;
5375
5376        // HTTP-04: advertising ANY subscription-delivered capability opts this
5377        // server into serving `subscriptions/listen`, whose registry is
5378        // INSTANCE-LOCAL. Warn at BUILD time — this is startup, and a silent
5379        // under-delivery behind a load balancer surfaces no error at runtime
5380        // (T-113-64).
5381        //
5382        // Gated on the v2 opt-in as well as the capability: `subscriptions/listen`
5383        // is a 2026-07-28-only route, so a v1-only server can never serve it and
5384        // the warning would be FALSE. It is not a rare corner either —
5385        // `ServerCapabilities::tools_only()` sets `tools.listChanged = true`, so
5386        // without this gate essentially every existing pmcp server would print a
5387        // warning about a stream it does not implement (D-04: zero era behaviour
5388        // on a non-opted-in server).
5389        if crate::types::protocol::context::is_v2_opted_in(&self.supported_protocol_versions)
5390            && crate::types::subscriptions::advertises_subscriptions(&self.capabilities)
5391        {
5392            tracing::warn!(
5393                target: "mcp.subscriptions",
5394                "a subscription-delivered capability is advertised, so subscriptions/listen \
5395                 will be SERVED; its registry is INSTANCE-LOCAL, so notifications generated on \
5396                 another instance are not delivered — supported for single-instance or \
5397                 sticky-routed deployments only. Polling over Tasks remains the recommended \
5398                 pmcp enterprise mechanism (D-11)."
5399            );
5400        }
5401
5402        // Resolve the server-owned `requestState` codec EXACTLY ONCE, here at
5403        // BUILD time (Phase 113, HTTP-02). A malformed CONFIGURED key fails the
5404        // build; an UNSET key falls back to a per-process key with a WARN emitted
5405        // from inside `from_env`, which is a genuine STARTUP warning because this
5406        // is startup. A v1-only server gets `None` and reads no env var at all.
5407        //
5408        // Both key arguments go BY REFERENCE, which closes copy 3 of 3
5409        // (D-113-P): the by-value form manufactured an unscrubbed stack copy on
5410        // every call. Because they are borrowed rather than moved, the two
5411        // fields are still owned by `self` here and drop through the zeroizing
5412        // destructor — on this path AND on every early `?` above, none of which
5413        // moves the key material anywhere.
5414        #[cfg(feature = "streamable-http")]
5415        let request_state_codec = request_state::resolve_codec_at_build(
5416            &self.supported_protocol_versions,
5417            self.request_state_key.as_ref(),
5418            &self.request_state_previous_keys,
5419            self.request_state_ttl,
5420        )?;
5421
5422        Ok(Server {
5423            info: {
5424                let mut info = Implementation::new(&name, &version);
5425                if let Some(url) = self.website_url {
5426                    info = info.with_website_url(url);
5427                }
5428                if let Some(icons) = self.icons {
5429                    info = info.with_icons(icons);
5430                }
5431                info
5432            },
5433            capabilities: self.capabilities,
5434            tools: self.tools,
5435            tool_infos,
5436            uri_to_tool_meta,
5437            prompts: self.prompts,
5438            resources: final_resources,
5439            completions: self.completions,
5440            sampling: self.sampling,
5441            client_capabilities: Arc::new(RwLock::new(None)),
5442            initialized: Arc::new(RwLock::new(false)),
5443            notification_tx: None,
5444            cancellation_manager: self.cancellation_manager,
5445            roots_manager: Arc::new(RwLock::new(self.roots_manager)),
5446            subscription_manager: Arc::new(RwLock::new(subscriptions::SubscriptionManager::new())),
5447            listen_registry: Arc::new(subscriptions::ListenRegistry::new()),
5448            elicitation_manager: None,
5449            server_request_dispatcher: None,
5450            peer_handle: None,
5451            auth_provider: self.auth_provider,
5452            tool_authorizer,
5453            #[cfg(not(target_arch = "wasm32"))]
5454            tool_middleware_chain,
5455            #[cfg(feature = "streamable-http")]
5456            http_middleware: self.http_middleware,
5457            #[cfg(not(target_arch = "wasm32"))]
5458            task_router: self.task_router,
5459            #[cfg(not(target_arch = "wasm32"))]
5460            task_store: self.task_store,
5461            #[cfg(not(target_arch = "wasm32"))]
5462            suppress_double_wrap: self.suppress_double_wrap,
5463            supported_protocol_versions: self.supported_protocol_versions,
5464            #[cfg(feature = "streamable-http")]
5465            request_state_codec,
5466        })
5467    }
5468}
5469
5470#[cfg(not(target_arch = "wasm32"))]
5471impl Default for ServerBuilder {
5472    fn default() -> Self {
5473        Self::new()
5474    }
5475}
5476
5477#[cfg(test)]
5478mod tests {
5479    use super::*;
5480    use crate::shared::Transport;
5481    use crate::types::{
5482        jsonrpc::ResponsePayload, ClientCapabilities, InitializeRequest, ServerCapabilities,
5483        TransportMessage,
5484    };
5485    use async_trait::async_trait;
5486    use serde_json::json;
5487    use std::sync::{Arc, Mutex};
5488    use tokio::time::timeout;
5489
5490    // -- requestState key material (D-113-P) --------------------------------
5491
5492    /// COMPILE-LEVEL guard on the FIELD TYPES, not on behaviour.
5493    ///
5494    /// The twin of `builder.rs`'s `request_state_key_field_is_the_zeroizing_type`.
5495    /// D-113-P named only `ServerCoreBuilder`; `ServerBuilder` carried the
5496    /// identical defect on the path most users actually take, so both need the
5497    /// guard. Reverting either field to bare `[u8; 32]` fails to compile here.
5498    #[cfg(feature = "streamable-http")]
5499    #[test]
5500    fn server_builder_request_state_key_field_is_the_zeroizing_type() {
5501        use crate::server::request_state::SecretKey;
5502        let builder = ServerBuilder::new()
5503            .with_request_state_key([0x11; 32])
5504            .with_request_state_previous_keys(vec![[0x22; 32]]);
5505
5506        let key: &Option<SecretKey> = &builder.request_state_key;
5507        let previous: &Vec<SecretKey> = &builder.request_state_previous_keys;
5508
5509        assert_eq!(key.as_deref(), Some(&[0x11u8; 32]));
5510        assert_eq!(previous.len(), 1);
5511        assert_eq!(**previous.first().expect("one previous key"), [0x22u8; 32]);
5512    }
5513
5514    /// The plumbing regression guard for `ServerBuilder`: a server configured
5515    /// with a key plus a rotated-out key must still mint under the current key
5516    /// and verify.
5517    #[cfg(feature = "streamable-http")]
5518    #[test]
5519    fn a_server_with_zeroizing_key_fields_still_mints_and_verifies() {
5520        use crate::server::request_state::{key_id_of, RequestBinding, Verdict};
5521
5522        const CURRENT: [u8; 32] = [0x11; 32];
5523        const ROTATED: [u8; 32] = [0x22; 32];
5524
5525        let server = Server::builder()
5526            .name("t")
5527            .version("1")
5528            .with_supported_protocol_versions([
5529                ProtocolVersion("2026-07-28".to_string()),
5530                ProtocolVersion("2025-11-25".to_string()),
5531            ])
5532            .with_request_state_key(CURRENT)
5533            .with_request_state_previous_keys(vec![ROTATED])
5534            .build()
5535            .expect("server builds");
5536
5537        let codec = server
5538            .request_state_codec()
5539            .expect("a v2 server has a codec");
5540        let params = json!({ "name": "t", "arguments": { "a": 1 } });
5541        let binding = RequestBinding::from_request("alice", "tools/call", &params)
5542            .expect("a two-level fixture is far inside the canonical depth cap");
5543        let token = codec
5544            .mint(&json!({ "step": 1 }), &binding, 0, None)
5545            .expect("mint");
5546        assert!(
5547            matches!(codec.verify(&token, &binding), Verdict::Ok(_)),
5548            "the zeroizing field type must not disturb the key plumbing"
5549        );
5550        assert!(codec.accepting_key_ids().contains(&key_id_of(&ROTATED)));
5551    }
5552
5553    /// Mock transport for testing
5554    #[derive(Debug)]
5555    struct MockTransport {
5556        messages: Arc<Mutex<Vec<TransportMessage>>>,
5557        responses: Arc<Mutex<Vec<TransportMessage>>>,
5558    }
5559
5560    impl MockTransport {
5561        #[allow(dead_code)]
5562        fn new() -> Self {
5563            Self {
5564                messages: Arc::new(Mutex::new(Vec::new())),
5565                responses: Arc::new(Mutex::new(Vec::new())),
5566            }
5567        }
5568
5569        fn with_requests(requests: Vec<TransportMessage>) -> Self {
5570            Self {
5571                messages: Arc::new(Mutex::new(requests)),
5572                responses: Arc::new(Mutex::new(Vec::new())),
5573            }
5574        }
5575
5576        #[allow(dead_code)]
5577        fn add_request(&self, request: TransportMessage) {
5578            self.messages.lock().unwrap().push(request);
5579        }
5580
5581        #[allow(dead_code)]
5582        fn get_sent_responses(&self) -> Vec<TransportMessage> {
5583            self.responses.lock().unwrap().clone()
5584        }
5585    }
5586
5587    #[async_trait]
5588    impl Transport for MockTransport {
5589        async fn send(&mut self, message: TransportMessage) -> Result<()> {
5590            self.responses.lock().unwrap().push(message);
5591            Ok(())
5592        }
5593
5594        async fn receive(&mut self) -> Result<TransportMessage> {
5595            let mut messages = self.messages.lock().unwrap();
5596            messages
5597                .pop()
5598                .map_or_else(|| Err(Error::protocol_msg("No more messages")), Ok)
5599        }
5600
5601        async fn close(&mut self) -> Result<()> {
5602            Ok(())
5603        }
5604
5605        fn is_connected(&self) -> bool {
5606            !self.messages.lock().unwrap().is_empty()
5607        }
5608
5609        fn transport_type(&self) -> &'static str {
5610            "mock"
5611        }
5612    }
5613
5614    /// Mock tool handler for testing
5615    struct MockTool {
5616        result: Value,
5617    }
5618
5619    impl MockTool {
5620        fn new(result: Value) -> Self {
5621            Self { result }
5622        }
5623    }
5624
5625    #[async_trait]
5626    impl ToolHandler for MockTool {
5627        async fn handle(
5628            &self,
5629            _args: Value,
5630            _extra: crate::server::cancellation::RequestHandlerExtra,
5631        ) -> Result<Value> {
5632            Ok(self.result.clone())
5633        }
5634    }
5635
5636    /// Mock prompt handler for testing
5637    struct MockPrompt {
5638        result: crate::types::GetPromptResult,
5639    }
5640
5641    impl MockPrompt {
5642        fn new(result: crate::types::GetPromptResult) -> Self {
5643            Self { result }
5644        }
5645    }
5646
5647    #[async_trait]
5648    impl PromptHandler for MockPrompt {
5649        async fn handle(
5650            &self,
5651            _args: HashMap<String, String>,
5652            _extra: crate::server::cancellation::RequestHandlerExtra,
5653        ) -> Result<crate::types::GetPromptResult> {
5654            Ok(self.result.clone())
5655        }
5656    }
5657
5658    /// Mock resource handler for testing
5659    struct MockResource {
5660        resources: Vec<crate::types::ResourceInfo>,
5661        contents: HashMap<String, crate::types::ReadResourceResult>,
5662    }
5663
5664    impl MockResource {
5665        fn new() -> Self {
5666            Self {
5667                resources: Vec::new(),
5668                contents: HashMap::new(),
5669            }
5670        }
5671
5672        fn with_resource(mut self, uri: String, content: crate::types::ReadResourceResult) -> Self {
5673            self.contents.insert(uri, content);
5674            self
5675        }
5676    }
5677
5678    #[async_trait]
5679    impl ResourceHandler for MockResource {
5680        async fn read(
5681            &self,
5682            uri: &str,
5683            _extra: crate::server::cancellation::RequestHandlerExtra,
5684        ) -> Result<crate::types::ReadResourceResult> {
5685            self.contents
5686                .get(uri)
5687                .cloned()
5688                .ok_or_else(|| Error::not_found(format!("Resource '{}' not found", uri)))
5689        }
5690
5691        async fn list(
5692            &self,
5693            _cursor: Option<String>,
5694            _extra: crate::server::cancellation::RequestHandlerExtra,
5695        ) -> Result<crate::types::ListResourcesResult> {
5696            Ok(crate::types::ListResourcesResult {
5697                resources: self.resources.clone(),
5698                next_cursor: None,
5699                ttl_ms: None,
5700                cache_scope: None,
5701            })
5702        }
5703    }
5704
5705    #[test]
5706    fn test_server_builder() {
5707        let server = Server::builder()
5708            .name("test-server")
5709            .version("1.0.0")
5710            .capabilities(ServerCapabilities::tools_only())
5711            .tool("test-tool", MockTool::new(json!({"result": "success"})))
5712            .build()
5713            .unwrap();
5714
5715        assert_eq!(server.info.name, "test-server");
5716        assert_eq!(server.info.version, "1.0.0");
5717        assert!(server.tools.contains_key("test-tool"));
5718    }
5719
5720    #[test]
5721    fn test_server_builder_validation() {
5722        // Missing name
5723        let result = Server::builder().version("1.0.0").build();
5724        assert!(result.is_err());
5725
5726        // Missing version
5727        let result = Server::builder().name("test-server").build();
5728        assert!(result.is_err());
5729    }
5730
5731    #[tokio::test]
5732    async fn test_server_initialization() {
5733        let init_request = TransportMessage::Request {
5734            id: RequestId::from(1i64),
5735            request: Request::Client(Box::new(ClientRequest::Initialize(InitializeRequest {
5736                protocol_version: "2024-11-05".to_string(),
5737                capabilities: ClientCapabilities::minimal(),
5738                client_info: Implementation::new("test-client", "1.0.0"),
5739            }))),
5740        };
5741
5742        let transport = MockTransport::with_requests(vec![init_request]);
5743        let server = Server::builder()
5744            .name("test-server")
5745            .version("1.0.0")
5746            .capabilities(ServerCapabilities::tools_only())
5747            .build()
5748            .unwrap();
5749
5750        // Test server run for a short time
5751        let server_handle = tokio::spawn(async move {
5752            let _ = timeout(std::time::Duration::from_millis(100), server.run(transport)).await;
5753        });
5754
5755        // Wait for server to process
5756        let _ = timeout(std::time::Duration::from_millis(200), server_handle).await;
5757    }
5758
5759    #[tokio::test]
5760    async fn test_server_capabilities() {
5761        let server = Server::builder()
5762            .name("test-server")
5763            .version("1.0.0")
5764            .capabilities(ServerCapabilities::tools_only())
5765            .build()
5766            .unwrap();
5767
5768        assert!(!server.is_initialized().await);
5769        assert!(server.get_client_capabilities().await.is_none());
5770    }
5771
5772    #[tokio::test]
5773    async fn test_server_notifications() {
5774        let server = Server::builder()
5775            .name("test-server")
5776            .version("1.0.0")
5777            .build()
5778            .unwrap();
5779
5780        // Send notification (should not panic even without transport)
5781        server
5782            .send_notification(ServerNotification::ToolsChanged)
5783            .await;
5784    }
5785
5786    #[test]
5787    fn test_server_builder_with_all_handlers() {
5788        let prompt_result = crate::types::GetPromptResult {
5789            description: Some("Test prompt".to_string()),
5790            messages: vec![],
5791            _meta: None,
5792        };
5793
5794        let resource_content =
5795            crate::types::ReadResourceResult::new(vec![crate::types::Content::text(
5796                "Hello, world!",
5797            )]);
5798
5799        let server = Server::builder()
5800            .name("test-server")
5801            .version("1.0.0")
5802            .tool("test-tool", MockTool::new(json!({"result": "success"})))
5803            .prompt("test-prompt", MockPrompt::new(prompt_result))
5804            .resources(
5805                MockResource::new().with_resource("test://uri".to_string(), resource_content),
5806            )
5807            .build()
5808            .unwrap();
5809
5810        assert!(server.tools.contains_key("test-tool"));
5811        assert!(server.prompts.contains_key("test-prompt"));
5812        assert!(server.resources.is_some());
5813    }
5814
5815    #[tokio::test]
5816    async fn test_handle_request_initialize() {
5817        let server = Server::builder()
5818            .name("test-server")
5819            .version("1.0.0")
5820            .capabilities(ServerCapabilities::tools_only())
5821            .build()
5822            .unwrap();
5823
5824        let request = Request::Client(Box::new(ClientRequest::Initialize(InitializeRequest {
5825            protocol_version: "2024-11-05".to_string(),
5826            capabilities: ClientCapabilities::default(),
5827            client_info: Implementation::new("test-client", "1.0.0"),
5828        })));
5829
5830        let response = server
5831            .handle_request(RequestId::from(1i64), request, None)
5832            .await;
5833
5834        assert_eq!(response.id, RequestId::from(1i64));
5835        match response.payload {
5836            ResponsePayload::Result(_) => {
5837                assert!(server.is_initialized().await);
5838            },
5839            ResponsePayload::Error(_) => panic!("Expected success response"),
5840        }
5841    }
5842
5843    #[tokio::test]
5844    async fn test_handle_list_tools() {
5845        let server = Server::builder()
5846            .name("test-server")
5847            .version("1.0.0")
5848            .tool("test-tool", MockTool::new(json!({"result": "success"})))
5849            .build()
5850            .unwrap();
5851
5852        let request = Request::Client(Box::new(ClientRequest::ListTools(ListToolsRequest {
5853            cursor: None,
5854        })));
5855        let response = server
5856            .handle_request(RequestId::from(1i64), request, None)
5857            .await;
5858
5859        match response.payload {
5860            ResponsePayload::Result(result) => {
5861                let tools_result: ListToolsResult = serde_json::from_value(result).unwrap();
5862                assert_eq!(tools_result.tools.len(), 1);
5863                assert_eq!(tools_result.tools[0].name, "test-tool");
5864            },
5865            ResponsePayload::Error(_) => panic!("Expected success response"),
5866        }
5867    }
5868
5869    #[tokio::test]
5870    async fn test_handle_call_tool() {
5871        let server = Server::builder()
5872            .name("test-server")
5873            .version("1.0.0")
5874            .tool("test-tool", MockTool::new(json!({"result": "success"})))
5875            .build()
5876            .unwrap();
5877
5878        let request = Request::Client(Box::new(ClientRequest::CallTool(CallToolRequest {
5879            name: "test-tool".to_string(),
5880            arguments: json!({"input": "test"}),
5881            _meta: None,
5882            task: None,
5883        })));
5884
5885        let response = server
5886            .handle_request(RequestId::from(1i64), request, None)
5887            .await;
5888
5889        match response.payload {
5890            ResponsePayload::Result(result) => {
5891                let call_result: CallToolResult = serde_json::from_value(result).unwrap();
5892                assert!(!call_result.is_error);
5893                assert_eq!(call_result.content.len(), 1);
5894            },
5895            ResponsePayload::Error(_) => panic!("Expected success response"),
5896        }
5897    }
5898
5899    /// Tool that reports the ingress-resolved era back through its result so a
5900    /// dispatch test can prove ingress→handler protocol-context threading on the
5901    /// high-level `Server` dispatch site.
5902    struct EraProbeServerTool;
5903
5904    #[async_trait]
5905    impl ToolHandler for EraProbeServerTool {
5906        async fn handle(
5907            &self,
5908            _args: Value,
5909            extra: crate::server::cancellation::RequestHandlerExtra,
5910        ) -> Result<Value> {
5911            Ok(json!({ "era": extra.era().map(|e| format!("{e:?}")) }))
5912        }
5913    }
5914
5915    fn probe_server_era(result: &Value) -> Value {
5916        let text = result["content"][0]["text"]
5917            .as_str()
5918            .expect("probe result carries text content");
5919        serde_json::from_str::<Value>(text).expect("probe text is JSON")["era"].clone()
5920    }
5921
5922    fn v2_probe_call() -> Request {
5923        let meta = crate::types::protocol::RequestMeta::new().with_meta(
5924            "io.modelcontextprotocol/protocolVersion",
5925            json!("2026-07-28"),
5926        );
5927        Request::Client(Box::new(ClientRequest::CallTool(CallToolRequest {
5928            name: "probe".to_string(),
5929            arguments: json!({}),
5930            _meta: Some(meta),
5931            task: None,
5932        })))
5933    }
5934
5935    /// Cross-site parity: the high-level `Server` dispatch site resolves the SAME
5936    /// v2 era as `ServerCore` for identical `_meta` (both use the one shared
5937    /// resolver), visible in the handler (Pitfall 3, twin wiring).
5938    #[tokio::test]
5939    async fn test_server_dispatch_resolves_v2_era_parity() {
5940        use crate::types::protocol::PROTOCOL_VERSION_2026_07_28;
5941        use crate::types::ProtocolVersion;
5942
5943        let server = Server::builder()
5944            .name("probe-server")
5945            .version("1.0.0")
5946            .tool("probe", EraProbeServerTool)
5947            .with_supported_protocol_versions([
5948                ProtocolVersion("2025-11-25".to_string()),
5949                ProtocolVersion(PROTOCOL_VERSION_2026_07_28.to_string()),
5950            ])
5951            .build()
5952            .unwrap();
5953
5954        let response = server
5955            .handle_request(RequestId::from(1i64), v2_probe_call(), None)
5956            .await;
5957        match response.payload {
5958            ResponsePayload::Result(result) => {
5959                assert_eq!(probe_server_era(&result), json!("V2"));
5960            },
5961            ResponsePayload::Error(e) => panic!("probe call failed: {}", e.message),
5962        }
5963    }
5964
5965    /// Twin-site envelope parity (VERS-07): the high-level `Server` dispatch
5966    /// site injects the SAME v2 `resultType`/`serverInfo` envelope `ServerCore`
5967    /// does — via the ONE shared `core::inject_v2_result_envelope` helper — on a
5968    /// v2 object result.
5969    #[tokio::test]
5970    async fn test_server_dispatch_injects_v2_result_envelope_parity() {
5971        use crate::types::protocol::PROTOCOL_VERSION_2026_07_28;
5972        use crate::types::ProtocolVersion;
5973
5974        let server = Server::builder()
5975            .name("envelope-server")
5976            .version("3.2.1")
5977            .tool("probe", EraProbeServerTool)
5978            .with_supported_protocol_versions([
5979                ProtocolVersion("2025-11-25".to_string()),
5980                ProtocolVersion(PROTOCOL_VERSION_2026_07_28.to_string()),
5981            ])
5982            .build()
5983            .unwrap();
5984
5985        // v2 request → envelope injected.
5986        let response = server
5987            .handle_request(RequestId::from(1i64), v2_probe_call(), None)
5988            .await;
5989        let ResponsePayload::Result(v) = response.payload else {
5990            panic!("expected result");
5991        };
5992        assert_eq!(v["resultType"], "complete");
5993        // Plan 113-09 Task 3: the schema places server identity INSIDE
5994        // `result._meta`, not at the top level.
5995        let server_info = &v["_meta"][crate::server::core::RESERVED_SERVER_INFO_KEY];
5996        assert_eq!(server_info["name"], "envelope-server");
5997        assert_eq!(server_info["version"], "3.2.1");
5998        assert!(
5999            v.get("serverInfo").is_none(),
6000            "the envelope must not write a top-level serverInfo: {v}"
6001        );
6002    }
6003
6004    /// v1 byte-identity at the twin site: a non-opted-in `Server` gains NO
6005    /// `resultType`/`serverInfo` even with a v2 `_meta` signal (D-07).
6006    #[tokio::test]
6007    async fn test_server_dispatch_v1_no_envelope() {
6008        let server = Server::builder()
6009            .name("v1-envelope-server")
6010            .version("1.0.0")
6011            .tool("probe", EraProbeServerTool)
6012            .build()
6013            .unwrap();
6014
6015        let response = server
6016            .handle_request(RequestId::from(1i64), v2_probe_call(), None)
6017            .await;
6018        let ResponsePayload::Result(v) = response.payload else {
6019            panic!("expected result");
6020        };
6021        assert!(v.get("resultType").is_none(), "v1 must not gain resultType");
6022        assert!(v.get("serverInfo").is_none(), "v1 must not gain serverInfo");
6023        assert!(
6024            v.get("_meta").is_none(),
6025            "v1 must not gain the _meta the v2 envelope creates: {v}"
6026        );
6027    }
6028
6029    /// A non-opted-in high-level `Server` runs zero era-detection: the handler
6030    /// reads `era()==None` even with a v2 `_meta` signal (D-04 parity).
6031    #[tokio::test]
6032    async fn test_server_dispatch_non_opted_in_yields_none() {
6033        let server = Server::builder()
6034            .name("v1-server")
6035            .version("1.0.0")
6036            .tool("probe", EraProbeServerTool)
6037            .build()
6038            .unwrap();
6039
6040        let response = server
6041            .handle_request(RequestId::from(1i64), v2_probe_call(), None)
6042            .await;
6043        match response.payload {
6044            ResponsePayload::Result(result) => {
6045                assert_eq!(probe_server_era(&result), Value::Null);
6046            },
6047            ResponsePayload::Error(e) => panic!("probe call failed: {}", e.message),
6048        }
6049    }
6050
6051    #[tokio::test]
6052    async fn test_handle_call_tool_rejected_is_iserror_not_protocol_error() {
6053        // A handler returning `Error::tool_rejected` must surface through the
6054        // streamable-HTTP `Server` path as a SUCCESSFUL `CallToolResult`
6055        // with `isError: true` (message → content, details → structuredContent),
6056        // NOT a JSON-RPC protocol error. This is the Code Mode policy-rejection
6057        // envelope (e.g. "SELECT missing LIMIT") observed by `pmcp-sql-server`.
6058        struct RejectingTool;
6059        #[async_trait]
6060        impl ToolHandler for RejectingTool {
6061            async fn handle(
6062                &self,
6063                _args: Value,
6064                _extra: crate::server::cancellation::RequestHandlerExtra,
6065            ) -> Result<Value> {
6066                Err(Error::tool_rejected(
6067                    "SELECT statements must declare a LIMIT",
6068                    Some(json!({ "violations": [{ "rule": "missing_limit" }] })),
6069                ))
6070            }
6071        }
6072
6073        let server = Server::builder()
6074            .name("test-server")
6075            .version("1.0.0")
6076            .tool("reject", RejectingTool)
6077            .build()
6078            .unwrap();
6079
6080        let request = Request::Client(Box::new(ClientRequest::CallTool(CallToolRequest {
6081            name: "reject".to_string(),
6082            arguments: json!({}),
6083            _meta: None,
6084            task: None,
6085        })));
6086
6087        let response = server
6088            .handle_request(RequestId::from(1i64), request, None)
6089            .await;
6090
6091        match response.payload {
6092            ResponsePayload::Result(result) => {
6093                let call_result: CallToolResult = serde_json::from_value(result).unwrap();
6094                assert!(call_result.is_error, "tool_rejected must set isError: true");
6095                let text = call_result
6096                    .content
6097                    .iter()
6098                    .find_map(|c| match c {
6099                        crate::types::Content::Text { text } => Some(text.clone()),
6100                        _ => None,
6101                    })
6102                    .unwrap_or_default();
6103                assert!(
6104                    text.contains("must declare a LIMIT"),
6105                    "content must carry the rejection message, got: {text}"
6106                );
6107                let sc = call_result
6108                    .structured_content
6109                    .expect("structuredContent must carry the violation detail");
6110                assert_eq!(sc["violations"][0]["rule"], "missing_limit");
6111            },
6112            ResponsePayload::Error(e) => panic!(
6113                "tool_rejected must NOT be a protocol error, got {}: {}",
6114                e.code, e.message
6115            ),
6116        }
6117    }
6118
6119    #[tokio::test]
6120    async fn test_handle_call_tool_not_found() {
6121        let server = Server::builder()
6122            .name("test-server")
6123            .version("1.0.0")
6124            .build()
6125            .unwrap();
6126
6127        let request = Request::Client(Box::new(ClientRequest::CallTool(CallToolRequest {
6128            name: "nonexistent-tool".to_string(),
6129            arguments: json!({}),
6130            _meta: None,
6131            task: None,
6132        })));
6133
6134        let response = server
6135            .handle_request(RequestId::from(1i64), request, None)
6136            .await;
6137
6138        match response.payload {
6139            ResponsePayload::Error(error) => {
6140                assert!(error.message.contains("not found"));
6141            },
6142            ResponsePayload::Result(_) => panic!("Expected error response"),
6143        }
6144    }
6145
6146    #[tokio::test]
6147    async fn test_handle_list_prompts() {
6148        let prompt_result = crate::types::GetPromptResult {
6149            description: Some("Test prompt".to_string()),
6150            messages: vec![],
6151            _meta: None,
6152        };
6153
6154        let server = Server::builder()
6155            .name("test-server")
6156            .version("1.0.0")
6157            .prompt("test-prompt", MockPrompt::new(prompt_result))
6158            .build()
6159            .unwrap();
6160
6161        let request = Request::Client(Box::new(ClientRequest::ListPrompts(ListPromptsRequest {
6162            cursor: None,
6163        })));
6164        let response = server
6165            .handle_request(RequestId::from(1i64), request, None)
6166            .await;
6167
6168        match response.payload {
6169            ResponsePayload::Result(result) => {
6170                let list_result: ListPromptsResult = serde_json::from_value(result).unwrap();
6171                assert_eq!(list_result.prompts.len(), 1);
6172                assert_eq!(list_result.prompts[0].name, "test-prompt");
6173            },
6174            ResponsePayload::Error(_) => panic!("Expected success response"),
6175        }
6176    }
6177
6178    #[tokio::test]
6179    async fn test_handle_get_prompt() {
6180        let prompt_result = crate::types::GetPromptResult {
6181            description: Some("Test prompt".to_string()),
6182            messages: vec![],
6183            _meta: None,
6184        };
6185
6186        let server = Server::builder()
6187            .name("test-server")
6188            .version("1.0.0")
6189            .prompt("test-prompt", MockPrompt::new(prompt_result.clone()))
6190            .build()
6191            .unwrap();
6192
6193        let request = Request::Client(Box::new(ClientRequest::GetPrompt(GetPromptRequest {
6194            name: "test-prompt".to_string(),
6195            arguments: HashMap::new(),
6196            _meta: None,
6197        })));
6198
6199        let response = server
6200            .handle_request(RequestId::from(1i64), request, None)
6201            .await;
6202
6203        match response.payload {
6204            ResponsePayload::Result(result) => {
6205                let get_result: crate::types::GetPromptResult =
6206                    serde_json::from_value(result).unwrap();
6207                assert_eq!(get_result.description, prompt_result.description);
6208            },
6209            ResponsePayload::Error(_) => panic!("Expected success response"),
6210        }
6211    }
6212
6213    // -----------------------------------------------------------------------
6214    // Phase 112-09 (Gap B): the high-level `Server` twin threads protocol_context
6215    // + request_meta into prompt/resource handlers. Enters through the REAL
6216    // dispatch entrypoint (`process_client_request`), NOT the leaf handlers.
6217    // -----------------------------------------------------------------------
6218    #[derive(Clone, Debug, Default, PartialEq)]
6219    struct DispatchCaptured {
6220        era: Option<crate::types::protocol::Era>,
6221        has_client_info: bool,
6222        traceparent: Option<String>,
6223    }
6224
6225    struct DispatchCapturingPrompt(Arc<Mutex<Option<DispatchCaptured>>>);
6226
6227    #[async_trait]
6228    impl PromptHandler for DispatchCapturingPrompt {
6229        async fn handle(
6230            &self,
6231            _args: HashMap<String, String>,
6232            extra: crate::server::cancellation::RequestHandlerExtra,
6233        ) -> Result<crate::types::GetPromptResult> {
6234            *self.0.lock().unwrap() = Some(DispatchCaptured {
6235                era: extra.era(),
6236                has_client_info: extra.client_info().is_some(),
6237                traceparent: extra.trace_context().map(|t| t.traceparent),
6238            });
6239            Ok(crate::types::GetPromptResult::new(vec![], None))
6240        }
6241    }
6242
6243    struct DispatchCapturingResource(Arc<Mutex<Option<DispatchCaptured>>>);
6244
6245    #[async_trait]
6246    impl ResourceHandler for DispatchCapturingResource {
6247        async fn read(
6248            &self,
6249            _uri: &str,
6250            extra: crate::server::cancellation::RequestHandlerExtra,
6251        ) -> Result<crate::types::ReadResourceResult> {
6252            *self.0.lock().unwrap() = Some(DispatchCaptured {
6253                era: extra.era(),
6254                has_client_info: extra.client_info().is_some(),
6255                traceparent: extra.trace_context().map(|t| t.traceparent),
6256            });
6257            Ok(crate::types::ReadResourceResult::new(vec![
6258                crate::types::Content::text("ok"),
6259            ]))
6260        }
6261
6262        async fn list(
6263            &self,
6264            _cursor: Option<String>,
6265            _extra: crate::server::cancellation::RequestHandlerExtra,
6266        ) -> Result<crate::types::ListResourcesResult> {
6267            Ok(crate::types::ListResourcesResult {
6268                resources: vec![],
6269                next_cursor: None,
6270                ttl_ms: None,
6271                cache_scope: None,
6272            })
6273        }
6274    }
6275
6276    fn dispatch_v2_meta() -> crate::types::protocol::RequestMeta {
6277        crate::types::protocol::RequestMeta::new().with_meta(
6278            "traceparent",
6279            serde_json::json!("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"),
6280        )
6281    }
6282
6283    fn dispatch_v2_context() -> crate::types::protocol::ProtocolContext {
6284        crate::types::protocol::ProtocolContext::new(
6285            crate::types::protocol::Era::V2,
6286            ProtocolVersion(crate::types::protocol::PROTOCOL_VERSION_2026_07_28.to_string()),
6287        )
6288        .with_client_info(crate::types::Implementation::new("test-client", "9.9.9"))
6289    }
6290
6291    #[tokio::test]
6292    async fn prompt_resource_protocol_context_via_dispatch_server() {
6293        use crate::types::protocol::Era;
6294
6295        let pcap = Arc::new(Mutex::new(None));
6296        let rcap = Arc::new(Mutex::new(None));
6297        let server = Server::builder()
6298            .name("dispatch-server")
6299            .version("1.0.0")
6300            .prompt("greeting", DispatchCapturingPrompt(pcap.clone()))
6301            .resources(DispatchCapturingResource(rcap.clone()))
6302            .with_supported_protocol_versions([
6303                ProtocolVersion("2025-11-25".to_string()),
6304                ProtocolVersion(crate::types::protocol::PROTOCOL_VERSION_2026_07_28.to_string()),
6305            ])
6306            .build()
6307            .unwrap();
6308
6309        // --- v2 dispatch through process_client_request: era==V2, client_info,
6310        // and a populated trace_context (proves .with_request_meta threading).
6311        server
6312            .process_client_request(
6313                RequestId::from(1i64),
6314                ClientRequest::GetPrompt(GetPromptRequest {
6315                    name: "greeting".to_string(),
6316                    arguments: HashMap::new(),
6317                    _meta: Some(dispatch_v2_meta()),
6318                }),
6319                None,
6320                Some(dispatch_v2_context()),
6321                &mut crate::server::core::DispatchEnvelopeClaim::default(),
6322            )
6323            .await
6324            .unwrap();
6325        server
6326            .process_client_request(
6327                RequestId::from(2i64),
6328                ClientRequest::ReadResource(ReadResourceRequest {
6329                    uri: "mem://greeting".to_string(),
6330                    _meta: Some(dispatch_v2_meta()),
6331                }),
6332                None,
6333                Some(dispatch_v2_context()),
6334                &mut crate::server::core::DispatchEnvelopeClaim::default(),
6335            )
6336            .await
6337            .unwrap();
6338
6339        for cap in [&pcap, &rcap] {
6340            let c = cap.lock().unwrap().clone().expect("handler ran");
6341            assert_eq!(c.era, Some(Era::V2));
6342            assert!(c.has_client_info);
6343            assert_eq!(
6344                c.traceparent.as_deref(),
6345                Some("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01")
6346            );
6347        }
6348
6349        // --- opted-in v1 fallback: era==Some(V1) (distinct from None).
6350        let pcap = Arc::new(Mutex::new(None));
6351        let rcap = Arc::new(Mutex::new(None));
6352        let server = Server::builder()
6353            .name("dispatch-server")
6354            .version("1.0.0")
6355            .prompt("greeting", DispatchCapturingPrompt(pcap.clone()))
6356            .resources(DispatchCapturingResource(rcap.clone()))
6357            .with_supported_protocol_versions([
6358                ProtocolVersion("2025-11-25".to_string()),
6359                ProtocolVersion(crate::types::protocol::PROTOCOL_VERSION_2026_07_28.to_string()),
6360            ])
6361            .build()
6362            .unwrap();
6363        let v1 = crate::types::protocol::ProtocolContext::new(
6364            Era::V1,
6365            ProtocolVersion("2025-11-25".to_string()),
6366        );
6367        server
6368            .process_client_request(
6369                RequestId::from(3i64),
6370                ClientRequest::GetPrompt(GetPromptRequest {
6371                    name: "greeting".to_string(),
6372                    arguments: HashMap::new(),
6373                    _meta: None,
6374                }),
6375                None,
6376                Some(v1.clone()),
6377                &mut crate::server::core::DispatchEnvelopeClaim::default(),
6378            )
6379            .await
6380            .unwrap();
6381        server
6382            .process_client_request(
6383                RequestId::from(4i64),
6384                ClientRequest::ReadResource(ReadResourceRequest {
6385                    uri: "mem://greeting".to_string(),
6386                    _meta: None,
6387                }),
6388                None,
6389                Some(v1),
6390                &mut crate::server::core::DispatchEnvelopeClaim::default(),
6391            )
6392            .await
6393            .unwrap();
6394        assert_eq!(pcap.lock().unwrap().clone().unwrap().era, Some(Era::V1));
6395        assert_eq!(rcap.lock().unwrap().clone().unwrap().era, Some(Era::V1));
6396
6397        // --- non-opted-in (protocol_context == None): era==None.
6398        let pcap = Arc::new(Mutex::new(None));
6399        let rcap = Arc::new(Mutex::new(None));
6400        let server = Server::builder()
6401            .name("dispatch-server")
6402            .version("1.0.0")
6403            .prompt("greeting", DispatchCapturingPrompt(pcap.clone()))
6404            .resources(DispatchCapturingResource(rcap.clone()))
6405            .build()
6406            .unwrap();
6407        server
6408            .process_client_request(
6409                RequestId::from(5i64),
6410                ClientRequest::GetPrompt(GetPromptRequest {
6411                    name: "greeting".to_string(),
6412                    arguments: HashMap::new(),
6413                    _meta: None,
6414                }),
6415                None,
6416                None,
6417                &mut crate::server::core::DispatchEnvelopeClaim::default(),
6418            )
6419            .await
6420            .unwrap();
6421        server
6422            .process_client_request(
6423                RequestId::from(6i64),
6424                ClientRequest::ReadResource(ReadResourceRequest {
6425                    uri: "mem://greeting".to_string(),
6426                    _meta: None,
6427                }),
6428                None,
6429                None,
6430                &mut crate::server::core::DispatchEnvelopeClaim::default(),
6431            )
6432            .await
6433            .unwrap();
6434        assert_eq!(pcap.lock().unwrap().clone().unwrap().era, None);
6435        assert_eq!(rcap.lock().unwrap().clone().unwrap().era, None);
6436    }
6437
6438    #[tokio::test]
6439    async fn test_handle_list_resources() {
6440        let resource_content =
6441            crate::types::ReadResourceResult::new(vec![crate::types::Content::text(
6442                "Hello, world!",
6443            )]);
6444
6445        let server = Server::builder()
6446            .name("test-server")
6447            .version("1.0.0")
6448            .resources(
6449                MockResource::new().with_resource("test://uri".to_string(), resource_content),
6450            )
6451            .build()
6452            .unwrap();
6453
6454        let request = Request::Client(Box::new(ClientRequest::ListResources(
6455            ListResourcesRequest { cursor: None },
6456        )));
6457        let response = server
6458            .handle_request(RequestId::from(1i64), request, None)
6459            .await;
6460
6461        match response.payload {
6462            ResponsePayload::Result(result) => {
6463                let resources_result: ListResourcesResult = serde_json::from_value(result).unwrap();
6464                assert_eq!(resources_result.resources.len(), 0); // MockResource has empty list by default
6465            },
6466            ResponsePayload::Error(_) => panic!("Expected success response"),
6467        }
6468    }
6469
6470    #[tokio::test]
6471    async fn test_handle_read_resource() {
6472        let resource_content =
6473            crate::types::ReadResourceResult::new(vec![crate::types::Content::text(
6474                "Hello, world!",
6475            )]);
6476
6477        let server = Server::builder()
6478            .name("test-server")
6479            .version("1.0.0")
6480            .resources(
6481                MockResource::new()
6482                    .with_resource("test://uri".to_string(), resource_content.clone()),
6483            )
6484            .build()
6485            .unwrap();
6486
6487        let request = Request::Client(Box::new(ClientRequest::ReadResource(ReadResourceRequest {
6488            uri: "test://uri".to_string(),
6489            _meta: None,
6490        })));
6491
6492        let response = server
6493            .handle_request(RequestId::from(1i64), request, None)
6494            .await;
6495
6496        match response.payload {
6497            ResponsePayload::Result(result) => {
6498                let read_result: crate::types::ReadResourceResult =
6499                    serde_json::from_value(result).unwrap();
6500                assert_eq!(read_result.contents.len(), 1);
6501            },
6502            ResponsePayload::Error(_) => panic!("Expected success response"),
6503        }
6504    }
6505
6506    #[tokio::test]
6507    async fn test_handle_read_resource_not_found() {
6508        let server = Server::builder()
6509            .name("test-server")
6510            .version("1.0.0")
6511            .resources(MockResource::new())
6512            .build()
6513            .unwrap();
6514
6515        let request = Request::Client(Box::new(ClientRequest::ReadResource(ReadResourceRequest {
6516            uri: "nonexistent://uri".to_string(),
6517            _meta: None,
6518        })));
6519
6520        let response = server
6521            .handle_request(RequestId::from(1i64), request, None)
6522            .await;
6523
6524        match response.payload {
6525            ResponsePayload::Error(error) => {
6526                assert!(error.message.contains("not found"));
6527            },
6528            ResponsePayload::Result(_) => panic!("Expected error response"),
6529        }
6530    }
6531
6532    #[tokio::test]
6533    async fn test_handle_ping() {
6534        let server = Server::builder()
6535            .name("test-server")
6536            .version("1.0.0")
6537            .build()
6538            .unwrap();
6539
6540        let request = Request::Client(Box::new(ClientRequest::Ping));
6541        let response = server
6542            .handle_request(RequestId::from(1i64), request, None)
6543            .await;
6544
6545        match response.payload {
6546            ResponsePayload::Result(_) => {
6547                // Success
6548            },
6549            ResponsePayload::Error(_) => panic!("Expected success response"),
6550        }
6551    }
6552
6553    #[tokio::test]
6554    async fn test_handle_server_request() {
6555        let server = Server::builder()
6556            .name("test-server")
6557            .version("1.0.0")
6558            .build()
6559            .unwrap();
6560
6561        let request = Request::Server(Box::new(crate::types::ServerRequest::CreateMessage(
6562            Box::new(crate::types::CreateMessageParams {
6563                messages: vec![],
6564                model_preferences: None,
6565                system_prompt: None,
6566                include_context: crate::types::IncludeContext::None,
6567                temperature: None,
6568                max_tokens: None,
6569                stop_sequences: None,
6570                metadata: None,
6571                tools: None,
6572                tool_choice: None,
6573            }),
6574        )));
6575        let response = server
6576            .handle_request(RequestId::from(1i64), request, None)
6577            .await;
6578
6579        match response.payload {
6580            ResponsePayload::Error(error) => {
6581                assert_eq!(error.code, -32601);
6582                assert!(error.message.contains("not supported"));
6583            },
6584            ResponsePayload::Result(_) => panic!("Expected error response"),
6585        }
6586    }
6587
6588    // Tests for tool middleware support in ServerBuilder
6589    #[tokio::test]
6590    async fn test_server_builder_with_tool_middleware() {
6591        use crate::server::tool_middleware::{ToolContext, ToolMiddleware};
6592        use std::sync::atomic::{AtomicBool, Ordering};
6593
6594        // Create a simple middleware that sets a flag when called
6595        struct TestMiddleware {
6596            called: Arc<AtomicBool>,
6597        }
6598
6599        #[async_trait]
6600        impl ToolMiddleware for TestMiddleware {
6601            async fn on_request(
6602                &self,
6603                _tool_name: &str,
6604                _args: &mut Value,
6605                extra: &mut crate::server::cancellation::RequestHandlerExtra,
6606                _context: &ToolContext,
6607            ) -> Result<()> {
6608                self.called.store(true, Ordering::SeqCst);
6609                extra.set_metadata("middleware_executed".to_string(), "true".to_string());
6610                Ok(())
6611            }
6612        }
6613
6614        let middleware_called = Arc::new(AtomicBool::new(false));
6615        let middleware = Arc::new(TestMiddleware {
6616            called: Arc::clone(&middleware_called),
6617        });
6618
6619        // Build server with middleware
6620        let server = Server::builder()
6621            .name("test-server")
6622            .version("1.0.0")
6623            .tool("test_tool", MockTool::new(json!({"result": "success"})))
6624            .tool_middleware(middleware)
6625            .build()
6626            .unwrap();
6627
6628        // Call the tool
6629        let request = Request::Client(Box::new(ClientRequest::CallTool(CallToolRequest {
6630            name: "test_tool".to_string(),
6631            arguments: json!({}),
6632            _meta: None,
6633            task: None,
6634        })));
6635
6636        let response = server
6637            .handle_request(RequestId::from(1i64), request, None)
6638            .await;
6639
6640        // Verify middleware was called
6641        assert!(middleware_called.load(Ordering::SeqCst));
6642
6643        // Verify tool executed successfully
6644        match response.payload {
6645            ResponsePayload::Result(_) => {}, // Success
6646            ResponsePayload::Error(e) => panic!("Expected success, got error: {:?}", e),
6647        }
6648    }
6649
6650    #[tokio::test]
6651    async fn test_server_builder_multiple_middlewares() {
6652        use crate::server::tool_middleware::{ToolContext, ToolMiddleware};
6653        use std::sync::atomic::{AtomicUsize, Ordering};
6654
6655        // Middleware that increments a counter
6656        struct CounterMiddleware {
6657            counter: Arc<AtomicUsize>,
6658            id: usize,
6659        }
6660
6661        #[async_trait]
6662        impl ToolMiddleware for CounterMiddleware {
6663            async fn on_request(
6664                &self,
6665                _tool_name: &str,
6666                _args: &mut Value,
6667                extra: &mut crate::server::cancellation::RequestHandlerExtra,
6668                _context: &ToolContext,
6669            ) -> Result<()> {
6670                let count = self.counter.fetch_add(1, Ordering::SeqCst);
6671                extra.set_metadata(format!("middleware_{}_order", self.id), count.to_string());
6672                Ok(())
6673            }
6674        }
6675
6676        let counter = Arc::new(AtomicUsize::new(0));
6677        let middleware1 = Arc::new(CounterMiddleware {
6678            counter: Arc::clone(&counter),
6679            id: 1,
6680        });
6681        let middleware2 = Arc::new(CounterMiddleware {
6682            counter: Arc::clone(&counter),
6683            id: 2,
6684        });
6685
6686        // Build server with multiple middlewares
6687        let server = Server::builder()
6688            .name("test-server")
6689            .version("1.0.0")
6690            .tool("test_tool", MockTool::new(json!({"result": "success"})))
6691            .tool_middleware(middleware1)
6692            .tool_middleware(middleware2)
6693            .build()
6694            .unwrap();
6695
6696        // Call the tool
6697        let request = Request::Client(Box::new(ClientRequest::CallTool(CallToolRequest {
6698            name: "test_tool".to_string(),
6699            arguments: json!({}),
6700            _meta: None,
6701            task: None,
6702        })));
6703
6704        let _response = server
6705            .handle_request(RequestId::from(1i64), request, None)
6706            .await;
6707
6708        // Verify both middlewares were called in order
6709        assert_eq!(counter.load(Ordering::SeqCst), 2);
6710    }
6711
6712    #[tokio::test]
6713    async fn test_server_builder_middleware_with_typed_tools() {
6714        use crate::server::tool_middleware::{ToolContext, ToolMiddleware};
6715        use std::sync::atomic::{AtomicBool, Ordering};
6716
6717        // Middleware that injects OAuth token
6718        struct OAuthMiddleware {
6719            called: Arc<AtomicBool>,
6720        }
6721
6722        #[async_trait]
6723        impl ToolMiddleware for OAuthMiddleware {
6724            async fn on_request(
6725                &self,
6726                _tool_name: &str,
6727                _args: &mut Value,
6728                extra: &mut crate::server::cancellation::RequestHandlerExtra,
6729                _context: &ToolContext,
6730            ) -> Result<()> {
6731                self.called.store(true, Ordering::SeqCst);
6732                extra.set_metadata("oauth_token".to_string(), "test-token-123".to_string());
6733                Ok(())
6734            }
6735        }
6736
6737        // Tool that verifies OAuth token was injected
6738        struct OAuthVerifyTool;
6739
6740        #[async_trait]
6741        impl ToolHandler for OAuthVerifyTool {
6742            async fn handle(
6743                &self,
6744                _args: Value,
6745                extra: crate::server::cancellation::RequestHandlerExtra,
6746            ) -> Result<Value> {
6747                // Verify OAuth token was injected by middleware
6748                let token = extra.get_metadata("oauth_token");
6749                assert!(token.is_some());
6750                assert_eq!(token.unwrap(), "test-token-123");
6751                Ok(json!({"success": true}))
6752            }
6753        }
6754
6755        let middleware_called = Arc::new(AtomicBool::new(false));
6756        let middleware = Arc::new(OAuthMiddleware {
6757            called: Arc::clone(&middleware_called),
6758        });
6759
6760        let server = Server::builder()
6761            .name("test-server")
6762            .version("1.0.0")
6763            .tool("typed_tool", OAuthVerifyTool)
6764            .tool_middleware(middleware)
6765            .build()
6766            .unwrap();
6767
6768        // Call the typed tool
6769        let request = Request::Client(Box::new(ClientRequest::CallTool(CallToolRequest {
6770            name: "typed_tool".to_string(),
6771            arguments: json!({}),
6772            _meta: None,
6773            task: None,
6774        })));
6775
6776        let response = server
6777            .handle_request(RequestId::from(1i64), request, None)
6778            .await;
6779
6780        // Verify middleware was called
6781        assert!(middleware_called.load(Ordering::SeqCst));
6782
6783        // Verify tool executed successfully
6784        match response.payload {
6785            ResponsePayload::Result(_) => {}, // Success
6786            ResponsePayload::Error(e) => panic!("Expected success, got error: {:?}", e),
6787        }
6788    }
6789
6790    #[tokio::test]
6791    async fn test_server_builder_middleware_error_handling() {
6792        use crate::server::tool_middleware::{ToolContext, ToolMiddleware};
6793
6794        // Middleware that rejects requests
6795        struct RejectMiddleware;
6796
6797        #[async_trait]
6798        impl ToolMiddleware for RejectMiddleware {
6799            async fn on_request(
6800                &self,
6801                _tool_name: &str,
6802                _args: &mut Value,
6803                _extra: &mut crate::server::cancellation::RequestHandlerExtra,
6804                _context: &ToolContext,
6805            ) -> Result<()> {
6806                Err(Error::validation("Middleware rejected request"))
6807            }
6808        }
6809
6810        // Build server with rejecting middleware
6811        let server = Server::builder()
6812            .name("test-server")
6813            .version("1.0.0")
6814            .tool("test_tool", MockTool::new(json!({"result": "success"})))
6815            .tool_middleware(Arc::new(RejectMiddleware))
6816            .build()
6817            .unwrap();
6818
6819        // Call the tool
6820        let request = Request::Client(Box::new(ClientRequest::CallTool(CallToolRequest {
6821            name: "test_tool".to_string(),
6822            arguments: json!({}),
6823            _meta: None,
6824            task: None,
6825        })));
6826
6827        let response = server
6828            .handle_request(RequestId::from(1i64), request, None)
6829            .await;
6830
6831        // Verify request was rejected by middleware
6832        match response.payload {
6833            ResponsePayload::Error(e) => {
6834                assert!(e.message.contains("Middleware rejected request"));
6835            },
6836            ResponsePayload::Result(_) => panic!("Expected error from middleware"),
6837        }
6838    }
6839
6840    #[tokio::test]
6841    async fn test_server_builder_auto_capabilities_serialization() {
6842        // Test that ServerBuilder (used by Server::builder()) auto-sets capabilities
6843        // with proper serialization values
6844        let server = Server::builder()
6845            .name("test")
6846            .version("1.0.0")
6847            .tool("test-tool", MockTool::new(json!({"result": "ok"})))
6848            .prompt(
6849                "test-prompt",
6850                MockPrompt::new(crate::types::GetPromptResult {
6851                    description: None,
6852                    messages: vec![],
6853                    _meta: None,
6854                }),
6855            )
6856            .resources(MockResource::new())
6857            .build()
6858            .unwrap();
6859
6860        let caps = &server.capabilities;
6861        let json = serde_json::to_value(caps).unwrap();
6862
6863        // Verify tools capability is present and properly structured
6864        let tools = json.get("tools").expect("tools should be present in JSON");
6865        assert!(tools.is_object(), "tools should be an object");
6866        let list_changed = tools.get("listChanged");
6867        assert!(
6868            list_changed.is_some(),
6869            "listChanged should be present in tools"
6870        );
6871        assert_eq!(
6872            list_changed.unwrap(),
6873            &serde_json::json!(false),
6874            "listChanged should be false"
6875        );
6876
6877        // Verify prompts capability
6878        let prompts = json
6879            .get("prompts")
6880            .expect("prompts should be present in JSON");
6881        assert!(prompts.is_object(), "prompts should be an object");
6882        assert!(
6883            prompts.get("listChanged").is_some(),
6884            "listChanged should be present in prompts"
6885        );
6886
6887        // Verify resources capability
6888        let resources = json
6889            .get("resources")
6890            .expect("resources should be present in JSON");
6891        assert!(resources.is_object(), "resources should be an object");
6892        assert!(
6893            resources.get("listChanged").is_some() || resources.get("subscribe").is_some(),
6894            "resources should have fields"
6895        );
6896
6897        println!(
6898            "Serialized capabilities: {}",
6899            serde_json::to_string_pretty(&json).unwrap()
6900        );
6901    }
6902
6903    /// Behavioral test for `ServerBuilder::tool_authorizer_arc` — the only
6904    /// non-mechanical lift among Phase 82's six `_arc` lifts.
6905    ///
6906    /// `tool_authorizer_arc` mirrors `tool_authorizer()`'s protection-clearing
6907    /// semantics: chaining `.protect_tool(...)` BEFORE `.tool_authorizer_arc(...)`
6908    /// must clear `tool_protections` so that `.build()` does NOT hit the
6909    /// mixed-config rejection branch at mod.rs `build()` (which fires if
6910    /// `tool_protections` is non-empty AND `tool_authorizer` is set).
6911    /// This test fills the verification gap source-greps cannot — proving
6912    /// the `.clear()` call actually fires.
6913    #[tokio::test]
6914    async fn tool_authorizer_arc_clears_tool_protections_and_allows_build() {
6915        // Define a no-op custom ToolAuthorizer for the test.
6916        struct NoopAuthorizer;
6917        #[async_trait]
6918        impl crate::server::auth::ToolAuthorizer for NoopAuthorizer {
6919            async fn can_access_tool(
6920                &self,
6921                _auth: &crate::server::auth::AuthContext,
6922                _tool_name: &str,
6923            ) -> crate::Result<bool> {
6924                Ok(true)
6925            }
6926            async fn required_scopes_for_tool(
6927                &self,
6928                _tool_name: &str,
6929            ) -> crate::Result<Vec<String>> {
6930                Ok(vec![])
6931            }
6932        }
6933
6934        // Build a server with BOTH protect_tool AND tool_authorizer_arc.
6935        // Without the clearing semantic, build() would return the
6936        // "Cannot use protect_tool() with a custom tool_authorizer" error.
6937        let builder = ServerBuilder::new()
6938            .name("test")
6939            .version("1")
6940            .protect_tool("delete", vec!["admin".to_string()])
6941            .tool_authorizer_arc(Arc::new(NoopAuthorizer));
6942
6943        // ASSERT 1: tool_protections was cleared by tool_authorizer_arc(),
6944        // visible because we are inside the same module and have access
6945        // to the private field.
6946        assert!(
6947            builder.tool_protections.is_empty(),
6948            "tool_authorizer_arc() must clear tool_protections to mirror tool_authorizer()"
6949        );
6950
6951        // ASSERT 2: build() succeeds — the mixed-config rejection branch
6952        // does NOT fire because protections was cleared.
6953        let build_result = builder.build();
6954        assert!(
6955            build_result.is_ok(),
6956            "build() should succeed after tool_authorizer_arc() clears protections; got Err({:?})",
6957            build_result.err()
6958        );
6959    }
6960}
6961
6962#[cfg(test)]
6963#[cfg(all(feature = "skills", not(target_arch = "wasm32")))]
6964mod skills_builder_tests {
6965    use super::*;
6966    use crate::server::cancellation::RequestHandlerExtra;
6967    use crate::server::skills::{Skill, SkillReference, Skills};
6968    use crate::types::Content;
6969    use async_trait::async_trait;
6970
6971    // ── Test 2.1a: single skill via ServerBuilder (public path) ──────
6972    #[test]
6973    fn test_2_1a_skill_method_single_skill_via_server_builder() {
6974        let server = Server::builder()
6975            .name("test")
6976            .version("1.0")
6977            .skill(Skill::new("foo", "body"))
6978            .build()
6979            .unwrap();
6980        assert!(server.capabilities.resources.is_some());
6981    }
6982
6983    // ── Test 2.2 (ServerBuilder): extensions capability ──────────────
6984    #[test]
6985    fn test_2_2_server_builder_skills_sets_extensions_capability() {
6986        let server = Server::builder()
6987            .name("test")
6988            .version("1.0")
6989            .skills(Skills::new().add(Skill::new("a", "")))
6990            .build()
6991            .unwrap();
6992        let ext = server
6993            .capabilities
6994            .extensions
6995            .as_ref()
6996            .expect("extensions should be set");
6997        assert_eq!(
6998            ext.get("io.modelcontextprotocol/skills"),
6999            Some(&serde_json::json!({}))
7000        );
7001    }
7002
7003    // ── Test 2.3 (ServerBuilder): resources capability ───────────────
7004    #[test]
7005    fn test_2_3_server_builder_skills_sets_resources_capability() {
7006        let server = Server::builder()
7007            .name("test")
7008            .version("1.0")
7009            .skills(Skills::new().add(Skill::new("a", "")))
7010            .build()
7011            .unwrap();
7012        let r = server
7013            .capabilities
7014            .resources
7015            .as_ref()
7016            .expect("resources should be set");
7017        assert_eq!(r.subscribe, Some(false));
7018        assert_eq!(r.list_changed, Some(false));
7019    }
7020
7021    // ── Test 2.4a: skills compose with existing resources (ServerBuilder) ─
7022    struct DocsHandler;
7023    #[async_trait]
7024    impl ResourceHandler for DocsHandler {
7025        async fn read(
7026            &self,
7027            uri: &str,
7028            _extra: RequestHandlerExtra,
7029        ) -> Result<crate::types::ReadResourceResult> {
7030            Ok(crate::types::ReadResourceResult::new(vec![Content::text(
7031                format!("DOCS:{uri}"),
7032            )]))
7033        }
7034        async fn list(
7035            &self,
7036            _cursor: Option<String>,
7037            _extra: RequestHandlerExtra,
7038        ) -> Result<crate::types::ListResourcesResult> {
7039            Ok(crate::types::ListResourcesResult::new(vec![
7040                crate::types::ResourceInfo::new("docs://handbook", "handbook"),
7041            ]))
7042        }
7043    }
7044
7045    #[test]
7046    fn test_2_4a_server_builder_skills_compose_with_existing_resources() {
7047        let server = Server::builder()
7048            .name("t")
7049            .version("1.0")
7050            .resources(DocsHandler)
7051            .skill(Skill::new("a", "skill-a"))
7052            .build()
7053            .unwrap();
7054        // Capability state must reflect both surfaces.
7055        assert!(server.capabilities.resources.is_some());
7056        let ext = server.capabilities.extensions.as_ref().unwrap();
7057        assert!(ext.contains_key("io.modelcontextprotocol/skills"));
7058    }
7059
7060    // ── Test 2.7 (ServerBuilder): bootstrap_skill_and_prompt ──────────
7061    #[test]
7062    fn test_2_7_server_builder_bootstrap_skill_and_prompt() {
7063        let server = Server::builder()
7064            .name("t")
7065            .version("1.0")
7066            .bootstrap_skill_and_prompt(Skill::new("c", "body-c"), "my_prompt")
7067            .build()
7068            .unwrap();
7069        assert!(server.has_prompt("my_prompt"));
7070        assert!(server.capabilities.prompts.is_some());
7071        let ext = server
7072            .capabilities
7073            .extensions
7074            .as_ref()
7075            .expect("extensions should be set");
7076        assert!(ext.contains_key("io.modelcontextprotocol/skills"));
7077        assert!(server.capabilities.resources.is_some());
7078    }
7079
7080    // ── Test 2.8: wire-level dual-surface invariant via ServerBuilder ─
7081    #[tokio::test]
7082    async fn test_2_8_bootstrap_skill_and_prompt_byte_equal_invariant() {
7083        let skill = Skill::new("x", "A").with_reference(SkillReference::new(
7084            "ref1.md",
7085            "text/markdown",
7086            "refbody",
7087        ));
7088        let expected_text = skill.as_prompt_text();
7089
7090        let server = Server::builder()
7091            .name("t")
7092            .version("1.0")
7093            .bootstrap_skill_and_prompt(skill, "x")
7094            .build()
7095            .unwrap();
7096
7097        let prompt = server
7098            .get_prompt("x")
7099            .expect("prompt 'x' must be registered");
7100        let result = prompt
7101            .handle(HashMap::new(), RequestHandlerExtra::default())
7102            .await
7103            .unwrap();
7104        assert_eq!(result.messages.len(), 1);
7105        match &result.messages[0].content {
7106            Content::Text { text } => assert_eq!(text, &expected_text),
7107            other => panic!("expected Content::Text, got {other:?}"),
7108        }
7109    }
7110
7111    // ── Test 2.9 (ServerBuilder): duplicate URI panics at .build() ───
7112    #[test]
7113    #[should_panic(expected = "duplicate")]
7114    fn test_2_9_server_builder_skills_panics_on_duplicate_uri_at_build() {
7115        let _ = Server::builder()
7116            .name("t")
7117            .version("1.0")
7118            .skill(Skill::new("x", "a"))
7119            .skill(Skill::new("x", "b"))
7120            .build()
7121            .unwrap();
7122    }
7123
7124    // ── Test 2.9a (ServerBuilder): try_skills returns Err on duplicate ─
7125    #[test]
7126    fn test_2_9a_server_builder_try_skills_returns_err_on_duplicate() {
7127        let res = Server::builder().name("t").version("1.0").try_skills(
7128            Skills::new()
7129                .add(Skill::new("x", "a"))
7130                .add(Skill::new("x", "b")),
7131        );
7132        assert!(res.is_err());
7133        match res {
7134            Err(crate::Error::Validation(_)) => {},
7135            Err(other) => panic!("expected Validation, got {other:?}"),
7136            Ok(_) => panic!("expected Err for duplicate"),
7137        }
7138    }
7139
7140    // ── Test 2.10 (ServerBuilder): capability merge preserves extensions ─
7141    #[test]
7142    fn test_2_10_server_builder_capability_merge_preserves_pre_existing_extensions() {
7143        let mut caps = crate::types::ServerCapabilities::default();
7144        let mut ext = HashMap::new();
7145        ext.insert("some.other/ext".to_string(), serde_json::json!({"foo": 1}));
7146        caps.extensions = Some(ext);
7147
7148        let server = Server::builder()
7149            .name("t")
7150            .version("1.0")
7151            .capabilities(caps)
7152            .skill(Skill::new("a", ""))
7153            .build()
7154            .unwrap();
7155        let ext = server.capabilities.extensions.as_ref().unwrap();
7156        assert!(ext.contains_key("some.other/ext"));
7157        assert!(ext.contains_key("io.modelcontextprotocol/skills"));
7158    }
7159
7160    // ── Test 2.11 (ServerBuilder): accumulator — all skills reachable ─
7161    #[test]
7162    fn test_2_11_server_builder_accumulator_repeated_skill_calls_all_reachable() {
7163        // Build a server with three .skill calls and confirm prompts/caps wire up.
7164        let server = Server::builder()
7165            .name("t")
7166            .version("1.0")
7167            .skill(Skill::new("a", "body-a"))
7168            .skill(Skill::new("b", "body-b"))
7169            .bootstrap_skill_and_prompt(Skill::new("c", "body-c"), "c_prompt")
7170            .build()
7171            .unwrap();
7172        assert!(server.has_prompt("c_prompt"));
7173        assert!(server.capabilities.resources.is_some());
7174    }
7175
7176    // ── Test 2.5a (ServerBuilder): .resources() semantics unchanged ──
7177    #[test]
7178    fn test_2_5a_server_builder_resources_replace_unchanged_no_skills() {
7179        struct A;
7180        #[async_trait]
7181        impl ResourceHandler for A {
7182            async fn read(
7183                &self,
7184                _uri: &str,
7185                _extra: RequestHandlerExtra,
7186            ) -> Result<crate::types::ReadResourceResult> {
7187                Ok(crate::types::ReadResourceResult::new(vec![Content::text(
7188                    "A",
7189                )]))
7190            }
7191            async fn list(
7192                &self,
7193                _cursor: Option<String>,
7194                _extra: RequestHandlerExtra,
7195            ) -> Result<crate::types::ListResourcesResult> {
7196                Ok(crate::types::ListResourcesResult::new(vec![]))
7197            }
7198        }
7199        struct B;
7200        #[async_trait]
7201        impl ResourceHandler for B {
7202            async fn read(
7203                &self,
7204                _uri: &str,
7205                _extra: RequestHandlerExtra,
7206            ) -> Result<crate::types::ReadResourceResult> {
7207                Ok(crate::types::ReadResourceResult::new(vec![Content::text(
7208                    "B",
7209                )]))
7210            }
7211            async fn list(
7212                &self,
7213                _cursor: Option<String>,
7214                _extra: RequestHandlerExtra,
7215            ) -> Result<crate::types::ListResourcesResult> {
7216                Ok(crate::types::ListResourcesResult::new(vec![]))
7217            }
7218        }
7219
7220        let server = Server::builder()
7221            .name("t")
7222            .version("1.0")
7223            .resources(A)
7224            .resources(B)
7225            .build()
7226            .unwrap();
7227        // No skills registered → no composition. Capabilities reflect resources only.
7228        assert!(server.capabilities.resources.is_some());
7229        // Skills extension should NOT have been auto-set.
7230        let ext = server.capabilities.extensions.as_ref();
7231        if let Some(ext_map) = ext {
7232            assert!(!ext_map.contains_key("io.modelcontextprotocol/skills"));
7233        }
7234    }
7235}
7236
7237#[cfg(test)]
7238#[cfg(not(target_arch = "wasm32"))]
7239mod tool_output_tests {
7240    use super::*;
7241    use crate::server::cancellation::RequestHandlerExtra;
7242    use async_trait::async_trait;
7243
7244    /// A handler that implements ONLY `handle` (no `handle_output` override) —
7245    /// the common case. It must route through the default `handle_output` and
7246    /// come back as `ToolOutput::Payload` equal to what `handle` returned.
7247    struct PlainHandler;
7248
7249    #[async_trait]
7250    impl ToolHandler for PlainHandler {
7251        async fn handle(&self, args: Value, _extra: RequestHandlerExtra) -> Result<Value> {
7252            Ok(serde_json::json!({ "echo": args }))
7253        }
7254    }
7255
7256    #[tokio::test]
7257    async fn default_handle_output_delegates_to_handle_as_payload() {
7258        let handler = PlainHandler;
7259        let extra = RequestHandlerExtra::new("req-1".to_string(), Default::default());
7260        let args = serde_json::json!({ "n": 42 });
7261
7262        let value_via_handle = handler.handle(args.clone(), extra.clone()).await.unwrap();
7263        let output = handler.handle_output(args, extra).await.unwrap();
7264
7265        match output {
7266            ToolOutput::Payload(v) => assert_eq!(
7267                v, value_via_handle,
7268                "default handle_output must wrap handle()'s value as Payload"
7269            ),
7270            other => panic!("expected ToolOutput::Payload, got {other:?}"),
7271        }
7272    }
7273}
7274
7275// ===========================================================================
7276// `attach_peer` precedence and authorization ordering.
7277//
7278// Phase 118.1 plan 11. Two claims, both of which need crate-internal access —
7279// `attach_peer` is private and `Server::peer_handle` is only ever set by
7280// `Server::run()` — so they live here rather than in an integration test:
7281//
7282//   * T-118.1-11-04: when BOTH a global `Server::peer_handle` and a
7283//     request-scoped `TransportBackchannel` peer are configured, the
7284//     request-scoped one wins. A global handle cannot express WHICH session
7285//     issued the request, so on a multiplexed transport it is the wrong answer.
7286//   * `src/shared/peer.rs`'s authorization invariant: tool-level authz runs
7287//     BEFORE the peer is wired, so a refused caller never reaches a handler body
7288//     and therefore never sees `extra.peer()`.
7289// ===========================================================================
7290#[cfg(all(test, not(target_arch = "wasm32")))]
7291mod peer_precedence_tests {
7292    use super::*;
7293    use crate::server::auth::AuthContext;
7294    use crate::shared::peer::PeerHandle;
7295    use crate::types::protocol::context::TransportBackchannel;
7296    use crate::types::protocol::{Era, ProtocolContext, ProtocolVersion};
7297    use crate::types::roots::{ListRootsResult, Root};
7298    use crate::types::sampling::{CreateMessageParams, CreateMessageResult};
7299    use crate::types::ProgressToken;
7300    use crate::RequestHandlerExtra;
7301    use async_trait::async_trait;
7302    use std::sync::atomic::{AtomicBool, Ordering};
7303
7304    /// A peer that reports WHICH source supplied it, through the one method
7305    /// with an observable, source-specific answer.
7306    struct NamedPeer(&'static str);
7307
7308    #[async_trait]
7309    impl PeerHandle for NamedPeer {
7310        async fn sample(&self, _params: CreateMessageParams) -> Result<CreateMessageResult> {
7311            Err(Error::protocol(
7312                crate::ErrorCode::METHOD_NOT_FOUND,
7313                "not the method under test",
7314            ))
7315        }
7316
7317        async fn list_roots(&self) -> Result<ListRootsResult> {
7318            Ok(ListRootsResult {
7319                roots: vec![Root {
7320                    uri: format!("file:///{}", self.0),
7321                    name: Some(self.0.to_string()),
7322                }],
7323            })
7324        }
7325
7326        async fn progress_notify(
7327            &self,
7328            _token: ProgressToken,
7329            _progress: f64,
7330            _total: Option<f64>,
7331            _message: Option<String>,
7332        ) -> Result<()> {
7333            Ok(())
7334        }
7335    }
7336
7337    /// The name the attached peer answers with, or `None` if none was attached.
7338    async fn attached_peer_name(
7339        extra: &crate::server::cancellation::RequestHandlerExtra,
7340    ) -> Option<String> {
7341        let peer = extra.peer()?;
7342        let roots = peer.list_roots().await.expect("the fixture peer answers");
7343        roots.roots.first().and_then(|r| r.name.clone())
7344    }
7345
7346    /// A `ProtocolContext` carrying a request-scoped peer named `name`.
7347    fn context_with_peer(name: &'static str) -> ProtocolContext {
7348        let peer: Arc<dyn PeerHandle> = Arc::new(NamedPeer(name));
7349        ProtocolContext::new(
7350            Era::V1,
7351            ProtocolVersion(crate::types::protocol::LATEST_PROTOCOL_VERSION.to_string()),
7352        )
7353        .with_transport_backchannel(TransportBackchannel::new().with_peer(peer))
7354    }
7355
7356    fn bare_server() -> Server {
7357        Server::builder()
7358            .name("attach-peer-precedence")
7359            .version("1.0.0")
7360            .build()
7361            .expect("server builds")
7362    }
7363
7364    fn extra_with_context(
7365        context: Option<ProtocolContext>,
7366    ) -> crate::server::cancellation::RequestHandlerExtra {
7367        crate::server::cancellation::RequestHandlerExtra::new(
7368            "req-attach-peer".to_string(),
7369            crate::server::cancellation::RequestHandlerExtra::default().cancellation_token,
7370        )
7371        .with_protocol_context(context)
7372    }
7373
7374    /// THE precedence claim (T-118.1-11-04). Both sources configured at once.
7375    #[tokio::test]
7376    async fn the_request_scoped_peer_wins_over_the_global_peer_handle() {
7377        let mut server = bare_server();
7378        server.peer_handle = Some(Arc::new(NamedPeer("global")));
7379
7380        let extra = server.attach_peer(extra_with_context(Some(context_with_peer(
7381            "request-scoped",
7382        ))));
7383
7384        assert_eq!(
7385            attached_peer_name(&extra).await.as_deref(),
7386            Some("request-scoped"),
7387            "a request-scoped transport handle must win: the global `peer_handle` is a SINGLE \
7388             field and cannot express which session issued this request (T-118.1-11-04)"
7389        );
7390    }
7391
7392    /// The fallback is untouched: the in-process `Server::run` path attaches no
7393    /// backchannel, so it must still see the global handle.
7394    #[tokio::test]
7395    async fn the_global_handle_still_applies_when_no_backchannel_rides_the_context() {
7396        let mut server = bare_server();
7397        server.peer_handle = Some(Arc::new(NamedPeer("global")));
7398
7399        let with_no_context = server.attach_peer(extra_with_context(None));
7400        assert_eq!(
7401            attached_peer_name(&with_no_context).await.as_deref(),
7402            Some("global"),
7403            "with no protocol context at all the global handle must still apply"
7404        );
7405
7406        let bare_context = ProtocolContext::new(
7407            Era::V1,
7408            ProtocolVersion(crate::types::protocol::LATEST_PROTOCOL_VERSION.to_string()),
7409        );
7410        let with_peerless_context = server.attach_peer(extra_with_context(Some(bare_context)));
7411        assert_eq!(
7412            attached_peer_name(&with_peerless_context).await.as_deref(),
7413            Some("global"),
7414            "a context with no backchannel must fall through to the global handle"
7415        );
7416    }
7417
7418    /// Neither source configured: still a no-op, exactly as before.
7419    #[tokio::test]
7420    async fn attach_peer_is_a_no_op_when_neither_source_is_configured() {
7421        let server = bare_server();
7422        let extra = server.attach_peer(extra_with_context(None));
7423        assert!(
7424            extra.peer().is_none(),
7425            "with no global handle and no backchannel, `extra.peer()` stays None"
7426        );
7427    }
7428
7429    /// A request-scoped peer applies even with NO global handle — the
7430    /// `StreamableHTTP` case, where `Server::run()` never ran.
7431    #[tokio::test]
7432    async fn the_request_scoped_peer_applies_with_no_global_handle_at_all() {
7433        let server = bare_server();
7434        let extra = server.attach_peer(extra_with_context(Some(context_with_peer("transport"))));
7435        assert_eq!(
7436            attached_peer_name(&extra).await.as_deref(),
7437            Some("transport"),
7438            "the HTTP transport never calls `Server::run()`, so the request-scoped handle is \
7439             the ONLY source there"
7440        );
7441    }
7442
7443    // -----------------------------------------------------------------------
7444    // Authorization ordering.
7445    // -----------------------------------------------------------------------
7446
7447    /// Records whether its body ever ran, and reports the peer it saw.
7448    struct EntryRecordingTool(Arc<AtomicBool>);
7449
7450    #[async_trait]
7451    impl ToolHandler for EntryRecordingTool {
7452        async fn handle(&self, _args: Value, extra: RequestHandlerExtra) -> Result<Value> {
7453            self.0.store(true, Ordering::SeqCst);
7454            Ok(serde_json::json!({ "saw_peer": extra.peer().is_some() }))
7455        }
7456    }
7457
7458    /// Refuses every tool.
7459    struct DenyAll;
7460
7461    #[async_trait]
7462    impl crate::server::auth::ToolAuthorizer for DenyAll {
7463        async fn can_access_tool(&self, _auth: &AuthContext, _tool: &str) -> Result<bool> {
7464            Ok(false)
7465        }
7466
7467        async fn required_scopes_for_tool(&self, _tool_name: &str) -> Result<Vec<String>> {
7468            Ok(Vec::new())
7469        }
7470    }
7471
7472    /// An unauthorized caller never reaches the handler BODY, so it can never
7473    /// observe `extra.peer()` — regardless of which peer source is configured.
7474    ///
7475    /// The ordering this measures is structural: `handle_call_tool` runs the
7476    /// `tool_authorizer` check (`src/server/mod.rs`, immediately after the
7477    /// auth-context resolution) and only afterwards calls `attach_peer`.
7478    #[tokio::test]
7479    async fn an_unauthorized_caller_never_reaches_the_handler_body() {
7480        let entered = Arc::new(AtomicBool::new(false));
7481        let mut server = Server::builder()
7482            .name("authz-before-peer")
7483            .version("1.0.0")
7484            .tool("guarded", EntryRecordingTool(entered.clone()))
7485            .tool_authorizer(DenyAll)
7486            .build()
7487            .expect("server builds");
7488        // BOTH peer sources configured, so a leak through either would show up.
7489        server.peer_handle = Some(Arc::new(NamedPeer("global")));
7490
7491        let mut claim = crate::server::core::DispatchEnvelopeClaim::default();
7492        let result = server
7493            .handle_call_tool(
7494                RequestId::from(1i64),
7495                CallToolRequest {
7496                    name: "guarded".to_string(),
7497                    arguments: serde_json::json!({}),
7498                    task: None,
7499                    _meta: None,
7500                },
7501                Some(AuthContext::new("someone")),
7502                Some(context_with_peer("request-scoped")),
7503                &mut claim,
7504            )
7505            .await;
7506
7507        assert!(result.is_err(), "a denied tool call must return an error");
7508        assert!(
7509            !entered.load(Ordering::SeqCst),
7510            "the handler body must never run for an unauthorized caller — authz runs BEFORE \
7511             `attach_peer`, so a refused caller never sees `extra.peer()`"
7512        );
7513    }
7514}
7515
7516// ===========================================================================
7517// `attach_request_log_sink` at the `Server` root — the TWIN of
7518// `core_log_sink_tests` in `src/server/core.rs` (Phase 118.2 plan 06, CONF-10 /
7519// D-07).
7520//
7521// Same crate-internal-access reason as `peer_precedence_tests` above:
7522// `attach_peer`, `notification_tx_sink`, `progress_reporter_for` and
7523// `Server::notification_tx` are all private, and `TransportBackchannel` /
7524// `ProtocolContext::with_resolved_log_level` are `pub(crate)`. An integration
7525// test can construct none of them.
7526//
7527// The claims measured here that the `ServerCore` side CANNOT measure:
7528//
7529//   * the `notification_tx`-derived fallback exists on this root and nowhere
7530//     else, and the request-scoped sink still beats it;
7531//   * D-07: the progress-token gate moved OFF the log sink and STAYED on the
7532//     progress reporter. One request, both answers, in one test.
7533// ===========================================================================
7534#[cfg(all(test, not(target_arch = "wasm32")))]
7535mod log_sink_precedence_tests {
7536    use super::*;
7537    use crate::types::protocol::context::TransportBackchannel;
7538    use crate::types::protocol::{Era, ProtocolContext, ProtocolVersion};
7539    use crate::types::LoggingLevel;
7540    use std::sync::Mutex;
7541
7542    /// A sink that records every notification handed to it.
7543    #[derive(Clone, Default)]
7544    struct Capture(Arc<Mutex<Vec<Notification>>>);
7545
7546    impl Capture {
7547        fn sink(&self) -> Arc<dyn Fn(Notification) + Send + Sync> {
7548            let slot = Arc::clone(&self.0);
7549            Arc::new(move |notification| {
7550                slot.lock()
7551                    .unwrap_or_else(std::sync::PoisonError::into_inner)
7552                    .push(notification);
7553            })
7554        }
7555
7556        fn len(&self) -> usize {
7557            self.0
7558                .lock()
7559                .unwrap_or_else(std::sync::PoisonError::into_inner)
7560                .len()
7561        }
7562    }
7563
7564    fn bare_context() -> ProtocolContext {
7565        ProtocolContext::new(
7566            Era::V1,
7567            ProtocolVersion(crate::types::protocol::LATEST_PROTOCOL_VERSION.to_string()),
7568        )
7569    }
7570
7571    fn context_with_sink(capture: &Capture) -> ProtocolContext {
7572        bare_context().with_transport_backchannel(
7573            TransportBackchannel::new().with_notification_sink(capture.sink()),
7574        )
7575    }
7576
7577    fn extra_with_context(
7578        context: Option<ProtocolContext>,
7579    ) -> crate::server::cancellation::RequestHandlerExtra {
7580        crate::server::cancellation::RequestHandlerExtra::new(
7581            "req-attach-log-sink".to_string(),
7582            crate::server::cancellation::RequestHandlerExtra::default().cancellation_token,
7583        )
7584        .with_protocol_context(context)
7585    }
7586
7587    fn bare_server() -> Server {
7588        Server::builder()
7589            .name("attach-log-sink-precedence")
7590            .version("1.0.0")
7591            .build()
7592            .expect("server builds")
7593    }
7594
7595    /// The ROOT claim on this side: `Server::attach_peer` wires the log sink from
7596    /// the server-wide `notification_tx` when the request carries no
7597    /// back-channel of its own — the in-process `Server::run` path.
7598    #[tokio::test]
7599    async fn the_server_root_attaches_its_notification_tx_derived_fallback_log_sink() {
7600        let mut server = bare_server();
7601        let (tx, mut rx) = mpsc::channel(4);
7602        server.notification_tx = Some(tx);
7603
7604        let extra = server.attach_peer(extra_with_context(Some(bare_context())));
7605        extra
7606            .log(
7607                LoggingLevel::Warning,
7608                "through the notification_tx fallback",
7609            )
7610            .expect("the emitter always returns Ok");
7611
7612        let received = rx.try_recv().expect("the fallback sink must deliver");
7613        match received {
7614            Notification::Server(crate::types::ServerNotification::LogMessage(params)) => {
7615                assert_eq!(params.message, "through the notification_tx fallback");
7616                assert_eq!(params.level, LoggingLevel::Warning);
7617            },
7618            other => panic!("expected a LogMessage notification, got {other:?}"),
7619        }
7620    }
7621
7622    /// The precedence rule measured on THIS root too, with both sources live at
7623    /// once — the `ServerCore` twin cannot run this, because it has no
7624    /// `notification_tx` to lose to.
7625    #[tokio::test]
7626    async fn the_request_scoped_sink_wins_over_the_notification_tx_fallback() {
7627        let mut server = bare_server();
7628        let (tx, mut rx) = mpsc::channel(4);
7629        server.notification_tx = Some(tx);
7630        let request_scoped = Capture::default();
7631
7632        let extra =
7633            server.attach_peer(extra_with_context(Some(context_with_sink(&request_scoped))));
7634        extra
7635            .log(LoggingLevel::Warning, "which sink received me?")
7636            .expect("the emitter always returns Ok");
7637
7638        assert_eq!(
7639            request_scoped.len(),
7640            1,
7641            "the session-bound transport sink must win at the `Server` root exactly as it does at \
7642             the `ServerCore` root (T-118.2-06-02/03)"
7643        );
7644        assert!(
7645            rx.try_recv().is_err(),
7646            "the server-wide channel must NOT also receive one session's record"
7647        );
7648    }
7649
7650    /// D-07, stated as a test: ONE request with NO `progressToken` gets a LIVE
7651    /// log sink and a `None` progress reporter.
7652    ///
7653    /// The gate moved off the sink and stayed on the reporter. Unifying the two
7654    /// would either silence logs for every client that never asked for progress,
7655    /// or make progress notifications unconditional — a client that sent no token
7656    /// has nothing to correlate them with (T-118.2-06-04).
7657    #[tokio::test]
7658    async fn the_progress_token_gate_still_applies_to_progress_only() {
7659        let mut server = bare_server();
7660        let (tx, _rx) = mpsc::channel(4);
7661        server.notification_tx = Some(tx);
7662        let capture = Capture::default();
7663        let context = context_with_sink(&capture);
7664
7665        assert!(
7666            server.progress_reporter_for(None, Some(&context)).is_none(),
7667            "no `params._meta.progressToken` must still mean no progress reporter"
7668        );
7669
7670        let extra = server.attach_peer(extra_with_context(Some(context)));
7671        assert!(
7672            extra.log_sink.is_some(),
7673            "the log sink is UNGATED by the progress token — a client that never asked for \
7674             progress must still receive `notifications/message` (D-07)"
7675        );
7676        extra
7677            .log(LoggingLevel::Info, "no progress token on this request")
7678            .expect("the emitter always returns Ok");
7679        assert_eq!(
7680            capture.len(),
7681            1,
7682            "the record must reach the client even though this request has no progress reporter"
7683        );
7684    }
7685
7686    /// There is exactly ONE `notification_tx`-to-sink conversion, and both
7687    /// consumers read it. Measured behaviourally rather than by grep: the
7688    /// progress path and the log path must produce sinks that reach the SAME
7689    /// channel with the SAME non-blocking discipline.
7690    #[tokio::test]
7691    async fn the_progress_and_log_paths_share_one_notification_tx_sink() {
7692        let mut server = bare_server();
7693        let (tx, mut rx) = mpsc::channel(4);
7694        server.notification_tx = Some(tx);
7695
7696        let via_progress = server
7697            .progress_notification_sink(None)
7698            .expect("a server with a notification_tx has a sink");
7699        let via_log = server
7700            .notification_tx_sink()
7701            .expect("the same server has the same sink");
7702
7703        via_progress(Notification::Server(
7704            crate::types::ServerNotification::LogMessage(crate::types::LogMessageParams::new(
7705                LoggingLevel::Info,
7706                "via progress".to_string(),
7707            )),
7708        ));
7709        via_log(Notification::Server(
7710            crate::types::ServerNotification::LogMessage(crate::types::LogMessageParams::new(
7711                LoggingLevel::Info,
7712                "via log".to_string(),
7713            )),
7714        ));
7715
7716        assert!(rx.try_recv().is_ok(), "the progress-derived sink delivers");
7717        assert!(rx.try_recv().is_ok(), "the log-derived sink delivers too");
7718    }
7719}