Skip to main content

mcpkit_server/
server.rs

1//! Server runtime for MCP servers.
2//!
3//! This module provides the runtime that executes an MCP server over
4//! a transport, handling message routing, request correlation, and
5//! the connection lifecycle.
6//!
7//! # Overview
8//!
9//! The server runtime:
10//! 1. Accepts a transport for communication
11//! 2. Handles the initialize/initialized handshake
12//! 3. Routes incoming requests to the appropriate handlers
13//! 4. Manages the connection lifecycle
14//!
15//! # Example
16//!
17//! ```rust
18//! use mcpkit_server::{ServerBuilder, ServerHandler, ServerState};
19//! use mcpkit_core::capability::{ServerInfo, ServerCapabilities};
20//!
21//! struct MyHandler;
22//! impl ServerHandler for MyHandler {
23//!     fn server_info(&self) -> ServerInfo {
24//!         ServerInfo::new("my-server", "1.0.0")
25//!     }
26//! }
27//!
28//! // Build a server and create server state
29//! let server = ServerBuilder::new(MyHandler).build();
30//! let state = ServerState::new(server.capabilities().clone());
31//!
32//! assert!(!state.is_initialized());
33//! ```
34
35use crate::builder::Server;
36use crate::context::{CancellationToken, Context, ContextData, Peer};
37use crate::dispatch::{PromptSlot, ResourceSlot, TaskSlot, ToolSlot};
38use crate::handler::ServerHandler;
39use crate::router::{route_prompts, route_resources, route_tasks, route_tools};
40use futures::channel::{mpsc, oneshot};
41use mcpkit_core::capability::{ClientCapabilities, ServerCapabilities};
42use mcpkit_core::error::McpError;
43use mcpkit_core::protocol::{Message, Notification, ProgressToken, Request, RequestId, Response};
44use mcpkit_core::protocol_version::ProtocolVersion;
45use mcpkit_transport::Transport;
46use std::collections::HashMap;
47use std::sync::Arc;
48use std::sync::RwLock;
49use std::sync::atomic::{AtomicBool, Ordering};
50use std::time::Duration;
51
52/// State for a running server.
53pub struct ServerState {
54    /// Client capabilities negotiated during initialization.
55    pub client_caps: RwLock<ClientCapabilities>,
56    /// Server capabilities advertised during initialization.
57    pub server_caps: ServerCapabilities,
58    /// Whether the server has been initialized.
59    pub initialized: AtomicBool,
60    /// Active cancellation tokens by request ID.
61    pub cancellations: RwLock<HashMap<String, CancellationToken>>,
62    /// The protocol version negotiated during initialization.
63    ///
64    /// This is stored as a `ProtocolVersion` enum for type-safe feature detection.
65    /// Use methods like `protocol_version().supports_tasks()` to check capabilities.
66    pub negotiated_version: RwLock<Option<ProtocolVersion>>,
67    /// Correlation registry for server-initiated (outbound) requests — the
68    /// same implementation the adapter session peer uses (#153), so a
69    /// correlation bug can only exist in one place.
70    outbound: crate::adapter_peer::SessionOutbound,
71    /// Publish end of the ambient-notification queue (see
72    /// [`publish_notification`](Self::publish_notification)).
73    ambient_tx: mpsc::UnboundedSender<Notification>,
74    /// Drain end, taken once by the run loop.
75    ambient_rx: std::sync::Mutex<Option<mpsc::UnboundedReceiver<Notification>>>,
76}
77
78impl ServerState {
79    /// Create a new server state.
80    #[must_use]
81    pub fn new(server_caps: ServerCapabilities) -> Self {
82        let (ambient_tx, ambient_rx) = mpsc::unbounded();
83        Self {
84            client_caps: RwLock::new(ClientCapabilities::default()),
85            server_caps,
86            initialized: AtomicBool::new(false),
87            cancellations: RwLock::new(HashMap::new()),
88            negotiated_version: RwLock::new(None),
89            outbound: crate::adapter_peer::SessionOutbound::new(),
90            ambient_tx,
91            ambient_rx: std::sync::Mutex::new(Some(ambient_rx)),
92        }
93    }
94
95    /// Queue a notification produced by an *ambient* source — a state change
96    /// that no inbound request triggered, and which therefore has no
97    /// request-scoped [`Peer`] to send on.
98    ///
99    /// The run loop drains this queue and writes to the transport. Publishing is
100    /// synchronous and non-blocking, so it is safe to call from a lock-free
101    /// callback (such as a `TaskObserver`).
102    ///
103    /// Delivery is best-effort by design, matching the spec's treatment of
104    /// notifications: a failed write is logged, never fatal.
105    ///
106    /// The queue is unbounded, which is safe because the only producers are
107    /// in-process and the run loop drains continuously. The HTTP adapters do not
108    /// use this path at all — they never construct a `ServerState`, and reach
109    /// their client through the session's `StreamRegistry` instead.
110    pub fn publish_notification(&self, notification: Notification) {
111        // Fails only if the receiver was dropped, i.e. the session is gone.
112        let _ = self.ambient_tx.unbounded_send(notification);
113    }
114
115    /// Take the drain end of the ambient queue. Returns `None` on any call
116    /// after the first, so two concurrent run loops cannot split the stream.
117    fn take_ambient_receiver(&self) -> Option<mpsc::UnboundedReceiver<Notification>> {
118        self.ambient_rx.lock().ok()?.take()
119    }
120
121    /// Allocate a unique id for a server-initiated (outbound) request.
122    pub(crate) fn next_outbound_id(&self) -> RequestId {
123        self.outbound.next_id()
124    }
125
126    /// Register a pending outbound request, returning the receiver that resolves
127    /// when the matching response arrives.
128    pub(crate) fn register_outbound(&self, id: RequestId) -> oneshot::Receiver<Response> {
129        self.outbound.register(id)
130    }
131
132    /// Drop a pending outbound request (e.g. on timeout or cancellation).
133    pub(crate) fn remove_outbound(&self, id: &RequestId) {
134        self.outbound.remove(id);
135    }
136
137    /// Route an inbound response to the outbound request that is waiting for it.
138    pub(crate) fn route_response(&self, response: Response) {
139        let id = response.id.clone();
140        if !self.outbound.resolve(response) {
141            tracing::debug!(id = %id, "response did not match a pending request");
142        }
143    }
144
145    /// Fail every pending outbound request (e.g. the connection closed). Dropping
146    /// the senders makes the waiting receivers resolve with an error.
147    pub(crate) fn fail_pending_requests(&self) {
148        self.outbound.fail_all();
149    }
150
151    /// Get the negotiated protocol version.
152    ///
153    /// Returns `None` if not yet initialized.
154    ///
155    /// # Example
156    ///
157    /// ```rust,ignore
158    /// if let Some(version) = state.protocol_version() {
159    ///     if version.supports_tasks() {
160    ///         // Tasks are available in this session
161    ///     }
162    /// }
163    /// ```
164    pub fn protocol_version(&self) -> Option<ProtocolVersion> {
165        self.negotiated_version.read().ok().and_then(|guard| *guard)
166    }
167
168    /// Set the negotiated protocol version.
169    ///
170    /// Silently fails if the lock is poisoned.
171    pub fn set_protocol_version(&self, version: ProtocolVersion) {
172        if let Ok(mut guard) = self.negotiated_version.write() {
173            *guard = Some(version);
174        }
175    }
176
177    /// Get a snapshot of client capabilities.
178    ///
179    /// Returns default capabilities if the lock is poisoned.
180    pub fn client_caps(&self) -> ClientCapabilities {
181        self.client_caps
182            .read()
183            .map(|guard| guard.clone())
184            .unwrap_or_default()
185    }
186
187    /// Update client capabilities.
188    ///
189    /// Silently fails if the lock is poisoned.
190    pub fn set_client_caps(&self, caps: ClientCapabilities) {
191        if let Ok(mut guard) = self.client_caps.write() {
192            *guard = caps;
193        }
194    }
195
196    /// Check if the server is initialized.
197    pub fn is_initialized(&self) -> bool {
198        self.initialized.load(Ordering::Acquire)
199    }
200
201    /// Mark the server as initialized.
202    pub fn set_initialized(&self) {
203        self.initialized.store(true, Ordering::Release);
204    }
205
206    /// Register a cancellation token for a request.
207    pub fn register_cancellation(&self, request_id: &str, token: CancellationToken) {
208        if let Ok(mut cancellations) = self.cancellations.write() {
209            cancellations.insert(request_id.to_string(), token);
210        }
211    }
212
213    /// Cancel a request by ID.
214    pub fn cancel_request(&self, request_id: &str) {
215        if let Ok(cancellations) = self.cancellations.read() {
216            if let Some(token) = cancellations.get(request_id) {
217                token.cancel();
218            }
219        }
220    }
221
222    /// Remove a cancellation token after request completion.
223    pub fn remove_cancellation(&self, request_id: &str) {
224        if let Ok(mut cancellations) = self.cancellations.write() {
225            cancellations.remove(request_id);
226        }
227    }
228}
229
230/// Shared state a [`TransportPeer`] needs to make server-initiated requests:
231/// the pending-request registry (on [`ServerState`]) and the outbound timeout.
232#[derive(Clone)]
233struct OutboundCtx {
234    state: Arc<ServerState>,
235    timeout: Duration,
236}
237
238/// A peer implementation that sends notifications over a transport.
239///
240/// Constructed with [`new`](Self::new) it can only send notifications. The
241/// runtime builds request-capable peers (with a pending-request registry) for
242/// handler contexts via `with_outbound`.
243pub struct TransportPeer<T: Transport> {
244    transport: Arc<T>,
245    outbound: Option<OutboundCtx>,
246}
247
248impl<T: Transport> TransportPeer<T> {
249    /// Create a new notification-only transport peer.
250    pub const fn new(transport: Arc<T>) -> Self {
251        Self {
252            transport,
253            outbound: None,
254        }
255    }
256
257    /// Create a request-capable transport peer that correlates responses through
258    /// the given server state.
259    pub(crate) fn with_outbound(
260        transport: Arc<T>,
261        state: Arc<ServerState>,
262        timeout: Duration,
263    ) -> Self {
264        Self {
265            transport,
266            outbound: Some(OutboundCtx { state, timeout }),
267        }
268    }
269}
270
271impl<T: Transport + 'static> Peer for TransportPeer<T>
272where
273    T::Error: Into<McpError>,
274{
275    fn notify(
276        &self,
277        notification: Notification,
278    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), McpError>> + Send + '_>>
279    {
280        let transport = self.transport.clone();
281        Box::pin(async move {
282            transport
283                .send(Message::Notification(notification))
284                .await
285                .map_err(std::convert::Into::into)
286        })
287    }
288
289    fn request(
290        &self,
291        method: std::borrow::Cow<'static, str>,
292        params: Option<serde_json::Value>,
293    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Response, McpError>> + Send + '_>>
294    {
295        let Some(outbound) = self.outbound.clone() else {
296            return Box::pin(async {
297                Err(McpError::internal(
298                    "this peer does not support server-initiated requests",
299                ))
300            });
301        };
302        let transport = self.transport.clone();
303        Box::pin(async move {
304            use futures::future::{Either, select};
305
306            let id = outbound.state.next_outbound_id();
307            let rx = outbound.state.register_outbound(id.clone());
308            let request = match params {
309                Some(p) => Request::with_params(method, id.clone(), p),
310                None => Request::new(method, id.clone()),
311            };
312            transport
313                .send(Message::Request(request))
314                .await
315                .map_err(std::convert::Into::into)?;
316
317            let sleep = mcpkit_transport::runtime::sleep(outbound.timeout);
318            futures::pin_mut!(sleep);
319            match select(rx, sleep).await {
320                Either::Left((Ok(response), _)) => Ok(response),
321                Either::Left((Err(_canceled), _)) => {
322                    outbound.state.remove_outbound(&id);
323                    Err(McpError::internal(
324                        "response channel closed before a reply arrived",
325                    ))
326                }
327                Either::Right(((), _)) => {
328                    outbound.state.remove_outbound(&id);
329                    Err(McpError::internal(format!(
330                        "server-initiated request timed out after {:?}",
331                        outbound.timeout
332                    )))
333                }
334            }
335        })
336    }
337}
338
339/// A cloneable handle for sending server-initiated notifications from outside a
340/// request context.
341///
342/// A handler's [`Context`] can only send notifications while a request is being
343/// served. When the server's own state changes between requests — for example
344/// its tool set changes — use a `ServerNotifier` to push the corresponding
345/// notification (`tools/list_changed`, `resources/list_changed`, etc.) to the
346/// client.
347///
348/// Obtain one from [`ServerRuntime::notifier`] before spawning the runtime:
349///
350/// ```rust,ignore
351/// let runtime = ServerRuntime::new(server, transport);
352/// let notifier = runtime.notifier();
353/// tokio::spawn(async move { runtime.run().await });
354///
355/// // later, from anywhere:
356/// notifier.tools_list_changed().await?;
357/// ```
358#[derive(Clone)]
359pub struct ServerNotifier {
360    peer: Arc<dyn Peer>,
361}
362
363impl ServerNotifier {
364    /// Send a notification with the given method and optional params.
365    ///
366    /// # Errors
367    ///
368    /// Returns an error if the notification could not be sent over the transport.
369    pub async fn notify(
370        &self,
371        method: impl Into<std::borrow::Cow<'static, str>>,
372        params: Option<serde_json::Value>,
373    ) -> Result<(), McpError> {
374        let notification = match params {
375            Some(p) => Notification::with_params(method, p),
376            None => Notification::new(method),
377        };
378        self.peer.notify(notification).await
379    }
380
381    /// Emit a `notifications/message` log to the client at `level`, optionally
382    /// tagged with a `logger` name and carrying arbitrary JSON `data`.
383    ///
384    /// # Errors
385    ///
386    /// Returns an error if the notification could not be sent.
387    pub async fn log(
388        &self,
389        level: mcpkit_core::types::LoggingLevel,
390        logger: Option<&str>,
391        data: serde_json::Value,
392    ) -> Result<(), McpError> {
393        let params = mcpkit_core::types::LoggingMessageNotificationParams {
394            logger: logger.map(String::from),
395            ..mcpkit_core::types::LoggingMessageNotificationParams::new(level, data)
396        };
397        self.notify(
398            crate::router::notifications::MESSAGE,
399            Some(serde_json::to_value(params)?),
400        )
401        .await
402    }
403
404    /// Notify the client that the available tool list has changed.
405    ///
406    /// # Errors
407    ///
408    /// Returns an error if the notification could not be sent.
409    pub async fn tools_list_changed(&self) -> Result<(), McpError> {
410        self.notify(crate::router::notifications::TOOLS_LIST_CHANGED, None)
411            .await
412    }
413
414    /// Notify the client that the available resource list has changed.
415    ///
416    /// # Errors
417    ///
418    /// Returns an error if the notification could not be sent.
419    pub async fn resources_list_changed(&self) -> Result<(), McpError> {
420        self.notify(crate::router::notifications::RESOURCES_LIST_CHANGED, None)
421            .await
422    }
423
424    /// Notify the client that the available prompt list has changed.
425    ///
426    /// # Errors
427    ///
428    /// Returns an error if the notification could not be sent.
429    pub async fn prompts_list_changed(&self) -> Result<(), McpError> {
430        self.notify(crate::router::notifications::PROMPTS_LIST_CHANGED, None)
431            .await
432    }
433
434    /// Notify the client that a subscribed resource was updated.
435    ///
436    /// # Errors
437    ///
438    /// Returns an error if the notification could not be sent.
439    pub async fn resource_updated(&self, uri: impl Into<String>) -> Result<(), McpError> {
440        self.notify(
441            crate::router::notifications::RESOURCES_UPDATED,
442            Some(serde_json::json!({ "uri": uri.into() })),
443        )
444        .await
445    }
446
447    /// Notify the client that a URL-mode elicitation's out-of-band interaction
448    /// has completed (`notifications/elicitation/complete`).
449    ///
450    /// # Errors
451    ///
452    /// Returns an error if the notification could not be sent.
453    pub async fn elicitation_complete(
454        &self,
455        elicitation_id: impl Into<String>,
456    ) -> Result<(), McpError> {
457        self.notify(
458            crate::router::notifications::ELICITATION_COMPLETE,
459            Some(serde_json::json!({ "elicitationId": elicitation_id.into() })),
460        )
461        .await
462    }
463}
464
465/// Server runtime configuration.
466///
467/// Marked `#[non_exhaustive]`: the runtime gains settings over time, and with an
468/// exhaustive struct every one of those additions is a breaking change for any
469/// downstream struct-literal construction. Build one with
470/// [`new`](Self::new)/[`default`](Default::default) and the setters below, which
471/// keeps later additions compatible. (Doing this before 1.0 is the whole point —
472/// after 1.0 the type would be stuck exhaustive.)
473///
474/// ```
475/// use mcpkit_server::RuntimeConfig;
476/// let config = RuntimeConfig::new()
477///     .max_concurrent_requests(32)
478///     .task_status_notifications(false);
479/// ```
480#[derive(Debug, Clone)]
481#[non_exhaustive]
482pub struct RuntimeConfig {
483    /// Whether to automatically send initialized notification.
484    pub auto_initialized: bool,
485    /// Maximum concurrent requests to process.
486    pub max_concurrent_requests: usize,
487    /// How long a server-initiated request (e.g. elicitation, sampling) waits
488    /// for the client's response before failing.
489    pub outbound_request_timeout: Duration,
490    /// Retention (milliseconds) applied to a task whose `tools/call` omits a
491    /// `ttl`. `None` means unlimited (such tasks are never TTL-evicted).
492    pub default_task_ttl_ms: Option<u64>,
493    /// Suggested polling interval (milliseconds) stamped on tasks the runtime
494    /// creates, surfaced to the client as `pollInterval`. `None` (the default)
495    /// leaves it absent, which is legal — the field is a hint, so a requestor
496    /// that receives none picks its own rate.
497    pub default_task_poll_interval_ms: Option<u64>,
498    /// Whether to publish `notifications/tasks/status` when a task changes
499    /// status. Optional per spec ("Receivers MAY send"), so this can be turned
500    /// off for a chattier-than-wanted session without affecting conformance;
501    /// the requesting peer must not rely on receiving it either way.
502    pub task_status_notifications: bool,
503}
504
505impl Default for RuntimeConfig {
506    fn default() -> Self {
507        Self {
508            auto_initialized: true,
509            max_concurrent_requests: 100,
510            outbound_request_timeout: Duration::from_secs(60),
511            default_task_ttl_ms: Some(crate::capability::tasks::DEFAULT_TASK_TTL_MS),
512            default_task_poll_interval_ms: None,
513            task_status_notifications: true,
514        }
515    }
516}
517
518impl RuntimeConfig {
519    /// Create a runtime configuration with default values.
520    #[must_use]
521    pub fn new() -> Self {
522        Self::default()
523    }
524
525    /// Whether to automatically send the `initialized` notification.
526    #[must_use]
527    pub const fn auto_initialized(mut self, yes: bool) -> Self {
528        self.auto_initialized = yes;
529        self
530    }
531
532    /// Maximum number of requests processed concurrently.
533    #[must_use]
534    pub const fn max_concurrent_requests(mut self, max: usize) -> Self {
535        self.max_concurrent_requests = max;
536        self
537    }
538
539    /// How long a server-initiated request waits for the client's response.
540    #[must_use]
541    pub const fn outbound_request_timeout(mut self, timeout: Duration) -> Self {
542        self.outbound_request_timeout = timeout;
543        self
544    }
545
546    /// Retention applied to a task whose `tools/call` omits a `ttl`.
547    /// `None` means unlimited.
548    #[must_use]
549    pub const fn default_task_ttl_ms(mut self, ttl_ms: Option<u64>) -> Self {
550        self.default_task_ttl_ms = ttl_ms;
551        self
552    }
553
554    /// Suggested polling interval (milliseconds) for tasks the runtime creates.
555    #[must_use]
556    pub const fn default_task_poll_interval_ms(mut self, poll_interval_ms: Option<u64>) -> Self {
557        self.default_task_poll_interval_ms = poll_interval_ms;
558        self
559    }
560
561    /// Whether to publish `notifications/tasks/status` on task transitions.
562    #[must_use]
563    pub const fn task_status_notifications(mut self, yes: bool) -> Self {
564        self.task_status_notifications = yes;
565        self
566    }
567}
568
569/// Server runtime that handles the message loop.
570///
571/// This runtime manages the connection lifecycle, routes requests to
572/// handlers, and coordinates response delivery.
573pub struct ServerRuntime<S, Tr>
574where
575    Tr: Transport,
576{
577    server: S,
578    transport: Arc<Tr>,
579    state: Arc<ServerState>,
580    /// Built-in store for task-augmented execution (the runtime creates tasks
581    /// here and serves `tasks/*` from it).
582    task_store: Arc<crate::capability::tasks::TaskManager>,
583    /// Runtime configuration (concurrency limit, etc.).
584    config: RuntimeConfig,
585}
586
587/// A task-augmented `tools/call` whose tool runs in the background after the
588/// `CreateTaskResult` reply has been sent.
589struct BackgroundExec {
590    handle: crate::capability::tasks::TaskHandle,
591    name: String,
592    args: mcpkit_core::types::Object,
593    ctx_data: ContextData,
594    cancel: CancellationToken,
595}
596
597/// Outcome of inspecting a request for task augmentation.
598enum TaskBegin {
599    /// Not a task-augmented `tools/call`; handle it normally.
600    NotApplicable,
601    /// The augmentation was rejected and an error response was already sent.
602    Rejected,
603    /// A task was created and `CreateTaskResult` sent; run this in the background.
604    Deferred(Box<BackgroundExec>),
605}
606
607/// Make progress on in-flight requests and background task executions, returning
608/// any new background work an in-flight request just produced. Background tasks
609/// are polled here but do not count against the request concurrency limit.
610async fn drive_sets<F1, F2, F3>(
611    in_flight: &mut futures::stream::FuturesUnordered<F1>,
612    background: &mut futures::stream::FuturesUnordered<F2>,
613    notifications: &mut futures::stream::FuturesUnordered<F3>,
614) -> Option<BackgroundExec>
615where
616    F1: std::future::Future<Output = Option<BackgroundExec>>,
617    F2: std::future::Future<Output = ()>,
618    F3: std::future::Future<Output = Result<(), McpError>>,
619{
620    use futures::future::{Either, select};
621    use futures::stream::StreamExt;
622    use std::future::pending;
623    use std::pin::pin;
624
625    // Each set is polled only when non-empty; an empty set parks forever
626    // (`pending`) so it never wins the race or busy-loops. Only `in_flight` can
627    // surface a `BackgroundExec` (a request that spun off task-augmented work).
628    let requests = pin!(async {
629        if in_flight.is_empty() {
630            pending::<Option<BackgroundExec>>().await
631        } else {
632            in_flight.next().await.flatten()
633        }
634    });
635    let tasks = pin!(async {
636        if background.is_empty() {
637            pending::<()>().await;
638        } else {
639            background.next().await;
640        }
641    });
642    let notifs = pin!(async {
643        if notifications.is_empty() {
644            pending::<()>().await;
645        } else if let Some(Err(e)) = notifications.next().await {
646            tracing::error!(error = %e, "Error handling notification");
647        }
648    });
649    let unit = pin!(async {
650        let _ = select(tasks, notifs).await;
651    });
652    match select(requests, unit).await {
653        Either::Left((res, _)) => res,
654        Either::Right(((), _)) => None,
655    }
656}
657
658impl<S, Tr> ServerRuntime<S, Tr>
659where
660    S: RequestRouter + Send + Sync,
661    Tr: Transport + 'static,
662    Tr::Error: Into<McpError>,
663{
664    /// Get the server state.
665    pub const fn state(&self) -> &Arc<ServerState> {
666        &self.state
667    }
668
669    /// Get a cloneable [`ServerNotifier`] for sending server-initiated
670    /// notifications (e.g. `tools/list_changed`) from outside a request context.
671    ///
672    /// Call this before spawning [`run`](Self::run); the returned handle shares
673    /// the runtime's transport and can be used from any task.
674    #[must_use]
675    pub fn notifier(&self) -> ServerNotifier {
676        ServerNotifier {
677            peer: Arc::new(TransportPeer::new(self.transport.clone())),
678        }
679    }
680
681    /// Run the server message loop.
682    ///
683    /// This method runs until the connection is closed or an error occurs.
684    ///
685    /// Requests are processed concurrently (interleaved on this task) up to
686    /// `config.max_concurrent_requests` in flight at once; once that limit is
687    /// reached, no new messages are accepted until an in-flight request
688    /// completes (backpressure). Each request runs with panic isolation, so a
689    /// panicking handler returns a JSON-RPC internal error instead of tearing
690    /// down the connection. Notification hooks run concurrently too, so a hook
691    /// that issues its own server-to-client request does not deadlock the loop.
692    pub async fn run(&self) -> Result<(), McpError> {
693        use futures::future::{Either, select};
694        use futures::stream::{FuturesUnordered, StreamExt};
695
696        // What the loop should do next, decided after borrows on the future sets
697        // are released so we can push new background work.
698        enum Step {
699            Message(Option<Message>),
700            // Boxed: `BackgroundExec` is large (it owns a `ContextData`), so an
701            // unboxed variant makes `Step` lopsided (`clippy::large_enum_variant`).
702            Progress(Option<Box<BackgroundExec>>),
703            /// A notification published by an ambient source, to be written out.
704            Ambient(Notification),
705        }
706
707        /// Yield the next ambient notification, parking forever once the queue
708        /// is gone so a closed channel cannot spin the loop.
709        async fn next_ambient(
710            slot: &mut Option<mpsc::UnboundedReceiver<Notification>>,
711        ) -> Notification {
712            loop {
713                match slot {
714                    Some(rx) => match rx.next().await {
715                        Some(notification) => return notification,
716                        None => *slot = None,
717                    },
718                    None => std::future::pending::<()>().await,
719                }
720            }
721        }
722
723        // Drain end of the ambient-notification queue. `None` if another run
724        // loop already took it.
725        let mut ambient = self.state.take_ambient_receiver();
726
727        let max = self.config.max_concurrent_requests.max(1);
728        let mut in_flight = FuturesUnordered::new();
729        // Task-augmented tool executions run here, off the request concurrency
730        // limit, so long-running tasks never starve normal request handling.
731        let mut background = FuturesUnordered::new();
732        // Notification hooks run here so a hook that makes its own server-to-client
733        // request (e.g. `on_roots_list_changed` calling `ctx.list_roots()`) does
734        // not block the loop from receiving that request's reply.
735        let mut notifications = FuturesUnordered::new();
736        // Requests received while at the concurrency limit. They run as soon as a
737        // slot frees. Crucially the loop keeps receiving in the meantime, so a
738        // handler parked on its own server-initiated request (which needs an
739        // inbound response to complete) cannot deadlock the loop.
740        let mut queued: std::collections::VecDeque<Request> = std::collections::VecDeque::new();
741
742        let outcome = loop {
743            // Dispatch queued requests while concurrency slots are free.
744            while in_flight.len() < max {
745                let Some(request) = queued.pop_front() else {
746                    break;
747                };
748                in_flight.push(self.handle_request_isolated(request));
749            }
750
751            // Always receive (so responses to our own outbound requests are
752            // routed even when every slot is parked) while making progress on
753            // in-flight requests and background tasks.
754            // Ambient notifications race every other source, so a state change
755            // reaches the client even while the loop is otherwise idle.
756            let recv = std::pin::pin!(self.transport.recv());
757            let published = std::pin::pin!(next_ambient(&mut ambient));
758            let idle = in_flight.is_empty() && background.is_empty() && notifications.is_empty();
759            let step = if idle {
760                match select(recv, published).await {
761                    Either::Left((Ok(opt), _)) => Step::Message(opt),
762                    Either::Left((Err(e), _)) => break Err(e.into()),
763                    Either::Right((notification, _)) => Step::Ambient(notification),
764                }
765            } else {
766                let progress = std::pin::pin!(drive_sets(
767                    &mut in_flight,
768                    &mut background,
769                    &mut notifications
770                ));
771                match select(select(recv, progress), published).await {
772                    Either::Left((Either::Left((Ok(opt), _)), _)) => Step::Message(opt),
773                    Either::Left((Either::Left((Err(e), _)), _)) => break Err(e.into()),
774                    Either::Left((Either::Right((maybe_exec, _)), _)) => {
775                        Step::Progress(maybe_exec.map(Box::new))
776                    }
777                    Either::Right((notification, _)) => Step::Ambient(notification),
778                }
779            };
780
781            // Borrows on the future sets are released here, so we may push work.
782            match step {
783                Step::Progress(Some(exec)) => {
784                    background.push(self.run_task(*exec));
785                }
786                Step::Progress(None) => {}
787                Step::Ambient(notification) => {
788                    // Written inline: a notification is a single small frame, so
789                    // this costs less than carrying another future set through
790                    // `drive_sets`. Delivery is best-effort — a failed write is
791                    // logged, never fatal to the session.
792                    if let Err(e) = self
793                        .transport
794                        .send(Message::Notification(notification))
795                        .await
796                    {
797                        tracing::warn!(error = ?e, "failed to send ambient notification");
798                    }
799                }
800                Step::Message(Some(Message::Request(request))) => {
801                    if in_flight.len() < max {
802                        in_flight.push(self.handle_request_isolated(request));
803                    } else {
804                        queued.push_back(request);
805                    }
806                }
807                Step::Message(Some(Message::Notification(notification))) => {
808                    // Handle concurrently so a hook doing a server-to-client
809                    // request does not deadlock the receive loop. Errors are
810                    // logged when the future completes in `drive_sets`.
811                    notifications.push(self.handle_notification(notification));
812                }
813                Step::Message(Some(Message::Response(response))) => {
814                    // A reply to a server-initiated request (elicitation, etc.).
815                    self.state.route_response(response);
816                }
817                Step::Message(None) => {
818                    tracing::info!("Connection closed");
819                    break Ok(());
820                }
821            }
822        };
823
824        // The connection is going away: fail any in-flight outbound requests so
825        // handlers parked on them unblock, then drain the handlers so their
826        // responses are delivered before we return. Background tasks are drained
827        // too so their results are stored before we exit.
828        self.state.fail_pending_requests();
829        while in_flight.next().await.is_some() {}
830        while background.next().await.is_some() {}
831        while notifications.next().await.is_some() {}
832
833        if let Err(ref err) = outcome {
834            tracing::error!(error = %err, "Transport error");
835        }
836        outcome
837    }
838
839    /// Compute the result for a request without sending it.
840    async fn compute_response(&self, request: &Request) -> Result<serde_json::Value, McpError> {
841        match request.method.as_ref() {
842            "initialize" => self.handle_initialize(request).await,
843            // `ping` is a liveness check and is valid at any time, including
844            // before the initialize handshake completes.
845            "ping" => self.route_request(request).await,
846            _ if !self.state.is_initialized() => {
847                Err(McpError::invalid_request("Server not initialized"))
848            }
849            _ => self.route_request(request).await,
850        }
851    }
852
853    /// Handle a request with panic isolation, sending the response when done.
854    ///
855    /// A panic in the handler is caught and converted into a JSON-RPC internal
856    /// error response so a single misbehaving handler cannot tear down the
857    /// whole connection.
858    async fn handle_request_isolated(&self, request: Request) -> Option<BackgroundExec> {
859        use futures::FutureExt;
860        use std::panic::AssertUnwindSafe;
861
862        let id = request.id.clone();
863        tracing::debug!(method = %request.method, id = %id, "Handling request");
864
865        // Task-augmented `tools/call`: reply with `CreateTaskResult` now and hand
866        // the tool execution back to the run loop to run in the background.
867        match self.try_begin_task(&request).await {
868            TaskBegin::Deferred(exec) => return Some(*exec),
869            TaskBegin::Rejected => return None,
870            TaskBegin::NotApplicable => {}
871        }
872
873        let computed = AssertUnwindSafe(self.compute_response(&request))
874            .catch_unwind()
875            .await;
876
877        let response_msg = match computed {
878            Ok(Ok(result)) => Response::success(id, result),
879            Ok(Err(e)) => Response::error(id, e.into()),
880            Err(panic) => {
881                let detail = panic_message(&*panic);
882                tracing::error!(method = %request.method, panic = %detail, "Handler panicked");
883                Response::error(
884                    id,
885                    McpError::internal(format!("handler panicked: {detail}")).into(),
886                )
887            }
888        };
889
890        if let Err(e) = self.transport.send(Message::Response(response_msg)).await {
891            let err: McpError = e.into();
892            tracing::error!(error = %err, "Failed to send response");
893        }
894        None
895    }
896
897    /// Inspect a request for task augmentation. For a task-augmented `tools/call`
898    /// on a tool that supports it, create the task, reply with `CreateTaskResult`
899    /// immediately, and return the background execution; otherwise leave it to the
900    /// normal request path.
901    async fn try_begin_task(&self, request: &Request) -> TaskBegin {
902        if request.method.as_ref() != "tools/call" {
903            return TaskBegin::NotApplicable;
904        }
905        let params = request.params.as_ref();
906        let Some(task_meta) = params.and_then(|p| p.get("task")) else {
907            return TaskBegin::NotApplicable;
908        };
909        if task_meta.is_null() {
910            return TaskBegin::NotApplicable;
911        }
912        // Before initialization, let the normal path emit the not-initialized error.
913        if !self.state.is_initialized() {
914            return TaskBegin::NotApplicable;
915        }
916        let Some(name) = params
917            .and_then(|p| p.get("name"))
918            .and_then(|v| v.as_str())
919            .map(str::to_string)
920        else {
921            // Malformed call; let the normal path report it.
922            return TaskBegin::NotApplicable;
923        };
924        let args = match params.and_then(|p| p.get("arguments")) {
925            None => mcpkit_core::types::Object::new(),
926            Some(serde_json::Value::Object(map)) => map.clone(),
927            // Malformed call; let the normal path report it.
928            Some(_) => return TaskBegin::NotApplicable,
929        };
930        let ttl = task_meta.get("ttl").and_then(serde_json::Value::as_u64);
931
932        let client_caps = self.state.client_caps();
933        let protocol_version = self
934            .state
935            .protocol_version()
936            .unwrap_or(ProtocolVersion::LATEST);
937
938        // Gate on the tool's declared task support (spec: a `forbidden` tool must
939        // not be task-augmented).
940        let support = {
941            let peer = TransportPeer::with_outbound(
942                self.transport.clone(),
943                self.state.clone(),
944                self.config.outbound_request_timeout,
945            );
946            let ctx = Context::new(
947                &request.id,
948                None,
949                &client_caps,
950                &self.state.server_caps,
951                protocol_version,
952                &peer,
953            );
954            self.server.tool_task_support(&name, &ctx).await
955        };
956        if support == mcpkit_core::types::TaskSupport::Forbidden {
957            // Spec: -32601 (Method not found) for task-augmenting a
958            // forbidden tool.
959            let err = McpError::JsonRpc(mcpkit_core::error::JsonRpcError::method_not_found(
960                format!("tool '{name}' does not support task-augmented execution"),
961            ));
962            let _ = self
963                .transport
964                .send(Message::Response(Response::error(
965                    request.id.clone(),
966                    err.into(),
967                )))
968                .await;
969            return TaskBegin::Rejected;
970        }
971
972        // Create the task and reply with `CreateTaskResult` immediately.
973        let handle = self.task_store.create(ttl);
974        let task = handle
975            .task()
976            .unwrap_or_else(|| mcpkit_core::types::Task::new(handle.id().clone()));
977        let create_result =
978            serde_json::to_value(mcpkit_core::types::CreateTaskResult { task, meta: None })
979                .unwrap_or_default();
980        if let Err(e) = self
981            .transport
982            .send(Message::Response(Response::success(
983                request.id.clone(),
984                create_result,
985            )))
986            .await
987        {
988            let err: McpError = e.into();
989            tracing::error!(error = %err, "Failed to send CreateTaskResult");
990        }
991
992        let cancel = handle.cancel_token().unwrap_or_else(CancellationToken::new);
993        let ctx_data = ContextData::new(
994            request.id.clone(),
995            client_caps,
996            self.state.server_caps.clone(),
997            protocol_version,
998        );
999        TaskBegin::Deferred(Box::new(BackgroundExec {
1000            handle,
1001            name,
1002            args,
1003            ctx_data,
1004            cancel,
1005        }))
1006    }
1007
1008    /// Run a task-augmented tool to completion in the background, storing the
1009    /// result (or failure) on the task.
1010    async fn run_task(&self, exec: BackgroundExec) {
1011        let BackgroundExec {
1012            handle,
1013            name,
1014            args,
1015            ctx_data,
1016            cancel,
1017        } = exec;
1018        let peer = TransportPeer::with_outbound(
1019            self.transport.clone(),
1020            self.state.clone(),
1021            self.config.outbound_request_timeout,
1022        );
1023        let ctx = Context::with_cancellation(
1024            &ctx_data.request_id,
1025            None,
1026            &ctx_data.client_caps,
1027            &ctx_data.server_caps,
1028            ctx_data.protocol_version,
1029            &peer,
1030            cancel,
1031        );
1032        match self.server.call_tool_json(&name, args, &ctx).await {
1033            // Per spec, a tool result with `isError: true` moves the task to
1034            // `failed`, while `tasks/result` still returns that result.
1035            Ok(payload)
1036                if payload
1037                    .get("isError")
1038                    .and_then(serde_json::Value::as_bool)
1039                    .unwrap_or(false) =>
1040            {
1041                let _ =
1042                    handle.fail_with_result(payload, Some("tool reported an error".to_string()));
1043            }
1044            Ok(payload) => {
1045                let _ = handle.complete(payload);
1046            }
1047            // `tasks/result` must reproduce the JSON-RPC error the request
1048            // would have returned.
1049            Err(e) => {
1050                let _ = handle.fail_with_error(e.into());
1051            }
1052        }
1053    }
1054
1055    /// Serve task queries from the built-in task store.
1056    ///
1057    /// Unlike the adapters, the runtime may have a custom `with_tasks` handler,
1058    /// so it matches on [`TaskRoute`] rather than folding an unowned id straight
1059    /// into an error: the custom handler gets its chance first.
1060    async fn route_runtime_tasks(
1061        &self,
1062        method: &str,
1063        params: Option<&serde_json::Value>,
1064    ) -> crate::capability::tasks::TaskRoute {
1065        crate::capability::tasks::route_task_store(&self.task_store, method, params).await
1066    }
1067
1068    /// Handle the initialize request.
1069    ///
1070    /// This performs protocol version negotiation according to the MCP specification:
1071    /// 1. Client sends its preferred protocol version
1072    /// 2. Server responds with the same version if supported, or its preferred version
1073    /// 3. Client must support the returned version or disconnect
1074    async fn handle_initialize(&self, request: &Request) -> Result<serde_json::Value, McpError> {
1075        if self.state.is_initialized() {
1076            return Err(McpError::invalid_request("Already initialized"));
1077        }
1078
1079        // Parse initialize params
1080        let params = request
1081            .params
1082            .as_ref()
1083            .ok_or_else(|| McpError::invalid_params("initialize", "missing params"))?;
1084
1085        // Extract and negotiate protocol version using type-safe enum
1086        let requested_version_str = params
1087            .get("protocolVersion")
1088            .and_then(|v| v.as_str())
1089            .unwrap_or("");
1090
1091        // Negotiate using the ProtocolVersion enum for type safety
1092        let negotiated_version =
1093            ProtocolVersion::negotiate(requested_version_str, ProtocolVersion::ALL)
1094                .unwrap_or(ProtocolVersion::LATEST);
1095
1096        // Log version negotiation details for debugging
1097        if requested_version_str == negotiated_version.as_str() {
1098            tracing::debug!(
1099                version = %negotiated_version,
1100                "Protocol version negotiated successfully"
1101            );
1102        } else {
1103            tracing::info!(
1104                requested = %requested_version_str,
1105                negotiated = %negotiated_version,
1106                supported = ?ProtocolVersion::ALL.iter().map(ProtocolVersion::as_str).collect::<Vec<_>>(),
1107                "Protocol version negotiation: client requested different version"
1108            );
1109        }
1110
1111        // Store the negotiated version (type-safe enum)
1112        self.state.set_protocol_version(negotiated_version);
1113
1114        // Extract client info and capabilities
1115        if let Some(caps) = params.get("capabilities") {
1116            if let Ok(client_caps) = serde_json::from_value::<ClientCapabilities>(caps.clone()) {
1117                self.state.set_client_caps(client_caps);
1118            }
1119        }
1120
1121        // Build response with negotiated version (serialized to string by serde)
1122        let result = serde_json::json!({
1123            "protocolVersion": negotiated_version.as_str(),
1124            "serverInfo": self.server.server_info(),
1125            "capabilities": self.state.server_caps
1126        });
1127
1128        self.state.set_initialized();
1129
1130        Ok(result)
1131    }
1132
1133    /// Route a request to the appropriate handler.
1134    async fn route_request(&self, request: &Request) -> Result<serde_json::Value, McpError> {
1135        let method = request.method.as_ref();
1136        let params = request.params.as_ref();
1137
1138        // Serve task queries from the built-in store first. An id the store does
1139        // not own falls through to a custom `with_tasks` handler; hold the
1140        // spec-correct error in case no such handler owns it either.
1141        let unowned_task: Option<McpError> = match self.route_runtime_tasks(method, params).await {
1142            crate::capability::tasks::TaskRoute::Handled(result) => return result,
1143            crate::capability::tasks::TaskRoute::NotTaskMethod => None,
1144            unowned => unowned.or_unknown_task().and_then(Result::err),
1145        };
1146
1147        // Extract progress token from params._meta.progressToken if present
1148        let progress_token = extract_progress_token(params);
1149
1150        // Create context for the handler. The peer is request-capable so handlers
1151        // can make server-initiated requests (e.g. elicitation) via `ctx.request`.
1152        let peer = TransportPeer::with_outbound(
1153            self.transport.clone(),
1154            self.state.clone(),
1155            self.config.outbound_request_timeout,
1156        );
1157        let client_caps = self.state.client_caps();
1158        let protocol_version = self
1159            .state
1160            .protocol_version()
1161            .unwrap_or(ProtocolVersion::LATEST);
1162
1163        // Register a cancellation token for this request so a matching
1164        // `notifications/cancelled` trips the handler's `ctx.cancel`. The token
1165        // is removed once the handler returns.
1166        let cancel = CancellationToken::new();
1167        let cancel_key = request.id.to_string();
1168        self.state
1169            .register_cancellation(&cancel_key, cancel.clone());
1170
1171        let ctx = Context::with_cancellation(
1172            &request.id,
1173            progress_token.as_ref(),
1174            &client_caps,
1175            &self.state.server_caps,
1176            protocol_version,
1177            &peer,
1178            cancel,
1179        );
1180
1181        // Delegate to the router, then drop the cancellation registration.
1182        let result = self.server.route(method, params, &ctx).await;
1183        self.state.remove_cancellation(&cancel_key);
1184
1185        // No custom handler owned the task either, so the router reported
1186        // *method not found* — which tells the client this server has no
1187        // `tasks/get` at all, when it answered `tasks/*` a moment ago. Report
1188        // the unowned id as the defect instead.
1189        // Not a `let`-chain: chained `let` in `if` is unstable before Rust 1.88
1190        // and this crate's MSRV is 1.85.
1191        if let Some(unowned) = unowned_task {
1192            if result.as_ref().err().map(McpError::code)
1193                == Some(mcpkit_core::error::codes::METHOD_NOT_FOUND)
1194            {
1195                return Err(unowned);
1196            }
1197        }
1198        result
1199    }
1200
1201    /// Handle a notification.
1202    async fn handle_notification(&self, notification: Notification) -> Result<(), McpError> {
1203        let method = notification.method.as_ref();
1204
1205        tracing::debug!(method = %method, "Handling notification");
1206
1207        // `notifications/cancelled` is a runtime concern — it trips the
1208        // cancellation registry for an in-flight request, not a handler hook.
1209        if method == crate::router::notifications::CANCELLED {
1210            if let Some(request_id) = notification
1211                .params
1212                .as_ref()
1213                .and_then(|p| {
1214                    serde_json::from_value::<mcpkit_core::types::CancelledNotificationParams>(
1215                        p.clone(),
1216                    )
1217                    .ok()
1218                })
1219                .and_then(|c| c.request_id)
1220            {
1221                // Match the canonical id form `route_request` registers with,
1222                // so numeric and string request ids both resolve.
1223                self.state.cancel_request(&request_id.to_string());
1224            }
1225            return Ok(());
1226        }
1227
1228        // Everything else is dispatched to the server's notification hooks with a
1229        // notification-scoped, outbound-capable context (so a hook may call e.g.
1230        // `ctx.list_roots()`). Unhandled methods are a no-op in `route_notification`.
1231        let client_caps = self.state.client_caps();
1232        let protocol_version = self
1233            .state
1234            .protocol_version()
1235            .unwrap_or(ProtocolVersion::LATEST);
1236        let peer = TransportPeer::with_outbound(
1237            self.transport.clone(),
1238            self.state.clone(),
1239            self.config.outbound_request_timeout,
1240        );
1241        let ctx = Context::for_notification(
1242            &client_caps,
1243            &self.state.server_caps,
1244            protocol_version,
1245            &peer,
1246        );
1247        self.server
1248            .route_notification(method, notification.params.as_ref(), &ctx)
1249            .await;
1250        Ok(())
1251    }
1252}
1253
1254// Constructor implementations for ServerRuntime with different server types
1255impl<H, T, R, P, K, Tr> ServerRuntime<Server<H, T, R, P, K>, Tr>
1256where
1257    H: ServerHandler + Send + Sync,
1258    T: Send + Sync,
1259    R: Send + Sync,
1260    P: Send + Sync,
1261    K: Send + Sync,
1262    Tr: Transport + 'static,
1263    Tr::Error: Into<McpError>,
1264{
1265    /// Create a new server runtime.
1266    pub fn new(server: Server<H, T, R, P, K>, transport: Tr) -> Self {
1267        Self::with_config(server, transport, RuntimeConfig::default())
1268    }
1269
1270    /// Create a new server runtime with custom configuration.
1271    pub fn with_config(
1272        server: Server<H, T, R, P, K>,
1273        transport: Tr,
1274        config: RuntimeConfig,
1275    ) -> Self {
1276        let caps = server.capabilities().clone();
1277        let task_store = Arc::new(
1278            crate::capability::tasks::TaskManager::with_default_ttl(config.default_task_ttl_ms)
1279                .with_poll_interval(config.default_task_poll_interval_ms),
1280        );
1281        let state = Arc::new(ServerState::new(caps));
1282        if config.task_status_notifications {
1283            // Transitions have no request-scoped peer, so they publish onto the
1284            // ambient queue the run loop drains.
1285            let _ = task_store.set_observer(Arc::new(
1286                crate::capability::tasks::TaskStatusNotifier::new(state.clone()),
1287            ));
1288        }
1289        Self {
1290            server,
1291            transport: Arc::new(transport),
1292            state,
1293            task_store,
1294            config,
1295        }
1296    }
1297}
1298
1299/// Trait for routing requests to handlers.
1300///
1301/// This trait is implemented by Server with different bounds depending on
1302/// which handlers are registered.
1303#[allow(async_fn_in_trait)]
1304pub trait RequestRouter: Send + Sync {
1305    /// Get the server info.
1306    fn server_info(&self) -> mcpkit_core::capability::ServerInfo;
1307
1308    /// Route a request and return the result.
1309    async fn route(
1310        &self,
1311        method: &str,
1312        params: Option<&serde_json::Value>,
1313        ctx: &Context<'_>,
1314    ) -> Result<serde_json::Value, McpError>;
1315
1316    /// Dispatch an inbound client notification (e.g. `notifications/initialized`
1317    /// or `notifications/roots/list_changed`) to the server's lifecycle hooks.
1318    /// Analogous to [`route`](Self::route) but for notifications — there is no
1319    /// reply. Defaults to a no-op.
1320    async fn route_notification(
1321        &self,
1322        _method: &str,
1323        _params: Option<&serde_json::Value>,
1324        _ctx: &Context<'_>,
1325    ) {
1326    }
1327
1328    /// The task-augmentation support a tool declares (`Tool.execution.taskSupport`),
1329    /// used to gate task-augmented `tools/call`. Defaults to `Forbidden`.
1330    async fn tool_task_support(
1331        &self,
1332        _name: &str,
1333        _ctx: &Context<'_>,
1334    ) -> mcpkit_core::types::TaskSupport {
1335        mcpkit_core::types::TaskSupport::Forbidden
1336    }
1337
1338    /// Run a tool to completion for task-augmented execution, returning its
1339    /// `CallToolResult` as JSON (the `tasks/result` payload). Defaults to
1340    /// method-not-found.
1341    async fn call_tool_json(
1342        &self,
1343        name: &str,
1344        _args: mcpkit_core::types::Object,
1345        _ctx: &Context<'_>,
1346    ) -> Result<serde_json::Value, McpError> {
1347        Err(McpError::method_not_found(name))
1348    }
1349}
1350
1351/// Extension methods for Server to run with a transport.
1352impl<H, T, R, P, K> Server<H, T, R, P, K>
1353where
1354    H: ServerHandler + Send + Sync + 'static,
1355    T: Send + Sync + 'static,
1356    R: Send + Sync + 'static,
1357    P: Send + Sync + 'static,
1358    K: Send + Sync + 'static,
1359    Self: RequestRouter,
1360{
1361    /// Run this server over the given transport.
1362    pub async fn serve<Tr>(self, transport: Tr) -> Result<(), McpError>
1363    where
1364        Tr: Transport + 'static,
1365        Tr::Error: Into<McpError>,
1366    {
1367        let runtime = ServerRuntime::new(self, transport);
1368        runtime.run().await
1369    }
1370}
1371
1372// ============================================================================
1373// Request routing
1374// ============================================================================
1375
1376/// Single [`RequestRouter`] implementation over the typestate handler slots.
1377///
1378/// Each capability is a slot (`Registered<H>` / `NotRegistered`) exposing an
1379/// optional object-safe handler; routing checks each in turn. Adding a
1380/// dispatched capability is one slot plus one arm here -- there is no
1381/// per-combination explosion. The shared per-method routing logic lives in
1382/// [`crate::router`].
1383impl<H, T, R, P, K> RequestRouter for Server<H, T, R, P, K>
1384where
1385    H: ServerHandler + Send + Sync,
1386    T: ToolSlot,
1387    R: ResourceSlot,
1388    P: PromptSlot,
1389    K: TaskSlot,
1390{
1391    fn server_info(&self) -> mcpkit_core::capability::ServerInfo {
1392        self.handler().server_info()
1393    }
1394
1395    async fn route_notification(
1396        &self,
1397        method: &str,
1398        _params: Option<&serde_json::Value>,
1399        ctx: &Context<'_>,
1400    ) {
1401        crate::router::dispatch_notification_hooks(self.handler(), method, ctx).await;
1402    }
1403
1404    async fn route(
1405        &self,
1406        method: &str,
1407        params: Option<&serde_json::Value>,
1408        ctx: &Context<'_>,
1409    ) -> Result<serde_json::Value, McpError> {
1410        if method == "ping" {
1411            return Ok(serde_json::json!({}));
1412        }
1413        let page_size = self.list_page_size;
1414        if let Some(handler) = self.tools.as_tool_handler() {
1415            if let Some(result) = route_tools(handler, method, params, ctx, page_size).await {
1416                return result;
1417            }
1418        }
1419        if let Some(handler) = self.resources.as_resource_handler() {
1420            if let Some(result) = route_resources(handler, method, params, ctx, page_size).await {
1421                return result;
1422            }
1423        }
1424        if let Some(handler) = self.prompts.as_prompt_handler() {
1425            if let Some(result) = route_prompts(handler, method, params, ctx, page_size).await {
1426                return result;
1427            }
1428        }
1429        if let Some(handler) = self.tasks.as_task_handler() {
1430            if let Some(result) = route_tasks(handler, method, params, ctx).await {
1431                return result;
1432            }
1433        }
1434        // `logging/setLevel` is handled by the base handler when the `logging`
1435        // capability is advertised (shared with the HTTP adapters).
1436        if let Some(result) =
1437            crate::router::route_logging(self.handler(), self.capabilities(), method, params, ctx)
1438                .await
1439        {
1440            return result;
1441        }
1442        // `completion/complete` is handled when a completion handler is
1443        // registered (shared with the HTTP adapters).
1444        if let Some(result) =
1445            crate::router::route_completion(self.completion.as_deref(), method, params, ctx).await
1446        {
1447            return result;
1448        }
1449        Err(McpError::method_not_found(method))
1450    }
1451
1452    async fn tool_task_support(
1453        &self,
1454        name: &str,
1455        ctx: &Context<'_>,
1456    ) -> mcpkit_core::types::TaskSupport {
1457        match self.tools.as_tool_handler() {
1458            Some(handler) => crate::router::tool_task_support(handler, name, ctx).await,
1459            None => mcpkit_core::types::TaskSupport::Forbidden,
1460        }
1461    }
1462
1463    async fn call_tool_json(
1464        &self,
1465        name: &str,
1466        args: mcpkit_core::types::Object,
1467        ctx: &Context<'_>,
1468    ) -> Result<serde_json::Value, McpError> {
1469        match self.tools.as_tool_handler() {
1470            Some(handler) => crate::router::call_tool_json(handler, name, args, ctx).await,
1471            None => Err(McpError::method_not_found(name)),
1472        }
1473    }
1474}
1475
1476// ============================================================================
1477// Helper functions
1478// ============================================================================
1479
1480/// Extract a progress token from request parameters.
1481///
1482/// Per the MCP specification, progress tokens are sent in the `_meta.progressToken`
1483/// field of request parameters. This function attempts to extract and parse that
1484/// field into a `ProgressToken`.
1485///
1486/// # Example JSON structure
1487/// ```json
1488/// {
1489///   "_meta": {
1490///     "progressToken": "token-123"
1491///   },
1492///   "name": "my-tool",
1493///   "arguments": {}
1494/// }
1495/// ```
1496/// Extract a human-readable message from a caught panic payload.
1497fn panic_message(panic: &(dyn std::any::Any + Send)) -> String {
1498    if let Some(s) = panic.downcast_ref::<&str>() {
1499        (*s).to_string()
1500    } else if let Some(s) = panic.downcast_ref::<String>() {
1501        s.clone()
1502    } else {
1503        "unknown panic".to_string()
1504    }
1505}
1506
1507fn extract_progress_token(params: Option<&serde_json::Value>) -> Option<ProgressToken> {
1508    params.and_then(mcpkit_core::types::Meta::progress_token_from_params)
1509}
1510
1511#[cfg(test)]
1512mod tests {
1513    use super::*;
1514
1515    use mcpkit_core::capability::{ClientCapabilities, ServerInfo};
1516    use mcpkit_core::protocol::RequestId;
1517    use mcpkit_core::types::content::Role;
1518    use mcpkit_core::types::elicitation::ElicitRequest;
1519    use mcpkit_core::types::sampling::{CreateMessageRequest, CreateMessageResult};
1520    use mcpkit_transport::MemoryTransport;
1521    use std::time::Duration;
1522    use tokio::sync::Notify;
1523    use tokio::time::timeout;
1524
1525    /// A minimal router whose `route` can panic, succeed, or 404.
1526    struct PanicRouter;
1527
1528    impl RequestRouter for PanicRouter {
1529        fn server_info(&self) -> ServerInfo {
1530            ServerInfo::new("panic-test", "0.0.0")
1531        }
1532        async fn route(
1533            &self,
1534            method: &str,
1535            _params: Option<&serde_json::Value>,
1536            _ctx: &Context<'_>,
1537        ) -> Result<serde_json::Value, McpError> {
1538            match method {
1539                "panic" => panic!("boom in handler"),
1540                "ok" => Ok(serde_json::json!("ok")),
1541                other => Err(McpError::method_not_found(other)),
1542            }
1543        }
1544    }
1545
1546    /// A router that parks the "blocker" request until released, to prove
1547    /// requests are processed concurrently rather than serially.
1548    struct CoordRouter {
1549        started: Arc<Notify>,
1550        release: Arc<Notify>,
1551    }
1552
1553    impl RequestRouter for CoordRouter {
1554        fn server_info(&self) -> ServerInfo {
1555            ServerInfo::new("coord-test", "0.0.0")
1556        }
1557        async fn route(
1558            &self,
1559            method: &str,
1560            _params: Option<&serde_json::Value>,
1561            _ctx: &Context<'_>,
1562        ) -> Result<serde_json::Value, McpError> {
1563            match method {
1564                "blocker" => {
1565                    self.started.notify_one();
1566                    self.release.notified().await;
1567                    Ok(serde_json::json!("blocked-done"))
1568                }
1569                "fast" => Ok(serde_json::json!("fast-done")),
1570                other => Err(McpError::method_not_found(other)),
1571            }
1572        }
1573    }
1574
1575    /// A router that answers `ping` and nothing else (like the macro-generated
1576    /// router's ping handling), for testing pre-initialize behavior.
1577    struct PingRouter;
1578
1579    impl RequestRouter for PingRouter {
1580        fn server_info(&self) -> ServerInfo {
1581            ServerInfo::new("ping-test", "0.0.0")
1582        }
1583        async fn route(
1584            &self,
1585            method: &str,
1586            _params: Option<&serde_json::Value>,
1587            _ctx: &Context<'_>,
1588        ) -> Result<serde_json::Value, McpError> {
1589            match method {
1590                "ping" => Ok(serde_json::json!({})),
1591                other => Err(McpError::method_not_found(other)),
1592            }
1593        }
1594    }
1595
1596    /// A router whose handler parks on `ctx.cancelled()` and reports whether the
1597    /// request was cancelled, for testing that `notifications/cancelled` trips
1598    /// the in-flight handler's context.
1599    struct CancelRouter {
1600        started: Arc<Notify>,
1601    }
1602
1603    impl RequestRouter for CancelRouter {
1604        fn server_info(&self) -> ServerInfo {
1605            ServerInfo::new("cancel-test", "0.0.0")
1606        }
1607        async fn route(
1608            &self,
1609            method: &str,
1610            _params: Option<&serde_json::Value>,
1611            ctx: &Context<'_>,
1612        ) -> Result<serde_json::Value, McpError> {
1613            match method {
1614                "wait_cancel" => {
1615                    self.started.notify_one();
1616                    ctx.cancelled().await;
1617                    Ok(serde_json::json!(ctx.is_cancelled()))
1618                }
1619                other => Err(McpError::method_not_found(other)),
1620            }
1621        }
1622    }
1623
1624    /// A router whose `ask` handler makes a server-initiated request back to the
1625    /// client (`ask/upstream`) and returns its result, for testing the reverse
1626    /// request/response path.
1627    struct OutboundRouter;
1628
1629    impl RequestRouter for OutboundRouter {
1630        fn server_info(&self) -> ServerInfo {
1631            ServerInfo::new("outbound-test", "0.0.0")
1632        }
1633        async fn route(
1634            &self,
1635            method: &str,
1636            _params: Option<&serde_json::Value>,
1637            ctx: &Context<'_>,
1638        ) -> Result<serde_json::Value, McpError> {
1639            match method {
1640                "ask" => ctx.request("ask/upstream", None).await,
1641                other => Err(McpError::method_not_found(other)),
1642            }
1643        }
1644    }
1645
1646    /// A router whose `ask_name` handler elicits a name from the user via the
1647    /// client and reports the outcome.
1648    struct ElicitRouter;
1649
1650    impl RequestRouter for ElicitRouter {
1651        fn server_info(&self) -> ServerInfo {
1652            ServerInfo::new("elicit-test", "0.0.0")
1653        }
1654        async fn route(
1655            &self,
1656            method: &str,
1657            _params: Option<&serde_json::Value>,
1658            ctx: &Context<'_>,
1659        ) -> Result<serde_json::Value, McpError> {
1660            match method {
1661                "ask_name" => {
1662                    let result = ctx
1663                        .elicit(ElicitRequest::text("Your name?", "name"))
1664                        .await?;
1665                    Ok(serde_json::json!({
1666                        "accepted": result.is_accepted(),
1667                        "name": result.get_string("name"),
1668                    }))
1669                }
1670                other => Err(McpError::method_not_found(other)),
1671            }
1672        }
1673    }
1674
1675    /// A router whose `summarize` handler asks the client to run an LLM
1676    /// completion (sampling) and returns the generated text.
1677    struct SampleRouter;
1678
1679    impl RequestRouter for SampleRouter {
1680        fn server_info(&self) -> ServerInfo {
1681            ServerInfo::new("sample-test", "0.0.0")
1682        }
1683        async fn route(
1684            &self,
1685            method: &str,
1686            _params: Option<&serde_json::Value>,
1687            ctx: &Context<'_>,
1688        ) -> Result<serde_json::Value, McpError> {
1689            match method {
1690                "summarize" => {
1691                    let result = ctx
1692                        .create_message(CreateMessageRequest::simple("hello", 100))
1693                        .await?;
1694                    Ok(serde_json::json!({ "text": result.as_text() }))
1695                }
1696                other => Err(McpError::method_not_found(other)),
1697            }
1698        }
1699    }
1700
1701    fn req(method: &'static str, id: u64) -> Message {
1702        Message::Request(Request::new(method, id))
1703    }
1704
1705    /// The next *response*, skipping any notifications the server publishes in
1706    /// the meantime. Ambient notifications (e.g. `notifications/tasks/status`)
1707    /// can legitimately interleave with responses, so a test that wants a
1708    /// response must not treat one as a failure.
1709    async fn next_response(transport: &MemoryTransport) -> Response {
1710        for _ in 0..16 {
1711            let msg = timeout(Duration::from_secs(2), transport.recv())
1712                .await
1713                .expect("no response (connection died?)")
1714                .expect("recv ok")
1715                .expect("some message");
1716            match msg {
1717                Message::Response(r) => return r,
1718                Message::Notification(_) => continue,
1719                other => panic!("expected response, got {other:?}"),
1720            }
1721        }
1722        panic!("no response after 16 messages");
1723    }
1724
1725    fn notif_msg(method: &str) -> Message {
1726        Message::Notification(Notification::with_params(
1727            method.to_string(),
1728            serde_json::json!({}),
1729        ))
1730    }
1731
1732    /// Records lifecycle-hook invocations; `on_roots_list_changed` exercises the
1733    /// notification-scoped context by calling `ctx.list_roots()`.
1734    struct RootsHookHandler {
1735        initialized: Arc<std::sync::atomic::AtomicBool>,
1736        roots_changed: Arc<std::sync::atomic::AtomicUsize>,
1737        seen_roots: Arc<std::sync::Mutex<Vec<mcpkit_core::types::Root>>>,
1738        done: Arc<Notify>,
1739    }
1740
1741    impl crate::handler::ServerHandler for RootsHookHandler {
1742        fn server_info(&self) -> ServerInfo {
1743            ServerInfo::new("roots-test", "0.0.0")
1744        }
1745        async fn on_initialized(&self, _ctx: &Context<'_>) {
1746            self.initialized
1747                .store(true, std::sync::atomic::Ordering::SeqCst);
1748            self.done.notify_one();
1749        }
1750        async fn on_roots_list_changed(&self, ctx: &Context<'_>) {
1751            self.roots_changed
1752                .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1753            if let Ok(roots) = ctx.list_roots().await {
1754                *self.seen_roots.lock().expect("lock") = roots;
1755            }
1756            self.done.notify_one();
1757        }
1758    }
1759
1760    #[tokio::test]
1761    async fn notification_hooks_fire_and_on_roots_list_changed_can_list_roots() {
1762        use crate::builder::ServerBuilder;
1763        use mcpkit_core::capability::ClientCapabilities;
1764        use mcpkit_core::types::{ListRootsResult, Root};
1765        use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1766
1767        let initialized = Arc::new(AtomicBool::new(false));
1768        let roots_changed = Arc::new(AtomicUsize::new(0));
1769        let seen = Arc::new(std::sync::Mutex::new(Vec::new()));
1770        let done = Arc::new(Notify::new());
1771        let handler = RootsHookHandler {
1772            initialized: initialized.clone(),
1773            roots_changed: roots_changed.clone(),
1774            seen_roots: seen.clone(),
1775            done: done.clone(),
1776        };
1777
1778        let (client, server_tr) = MemoryTransport::pair();
1779        let runtime = ServerRuntime::new(ServerBuilder::new(handler).build(), server_tr);
1780        runtime.state().set_initialized();
1781        runtime
1782            .state()
1783            .set_client_caps(ClientCapabilities::default().with_roots());
1784        let handle = tokio::spawn(async move { runtime.run().await });
1785
1786        // `on_initialized` fires on notifications/initialized.
1787        client
1788            .send(notif_msg("notifications/initialized"))
1789            .await
1790            .expect("send");
1791        timeout(Duration::from_secs(2), done.notified())
1792            .await
1793            .expect("on_initialized never ran");
1794        assert!(initialized.load(Ordering::SeqCst));
1795
1796        // `on_roots_list_changed` fires and calls `ctx.list_roots()`, which issues
1797        // a server->client roots/list request the loop must service concurrently.
1798        client
1799            .send(notif_msg("notifications/roots/list_changed"))
1800            .await
1801            .expect("send");
1802        let roots_req = match timeout(Duration::from_secs(2), client.recv())
1803            .await
1804            .expect("no roots/list request")
1805            .expect("recv ok")
1806            .expect("some message")
1807        {
1808            Message::Request(r) => r,
1809            other => panic!("expected roots/list, got {other:?}"),
1810        };
1811        assert_eq!(roots_req.method.as_ref(), "roots/list");
1812        let result = ListRootsResult {
1813            roots: vec![Root::new("file:///work")],
1814            meta: None,
1815        };
1816        client
1817            .send(Message::Response(Response::success(
1818                roots_req.id.clone(),
1819                serde_json::to_value(result).expect("serialize"),
1820            )))
1821            .await
1822            .expect("send");
1823
1824        timeout(Duration::from_secs(2), done.notified())
1825            .await
1826            .expect("on_roots_list_changed never finished");
1827        assert_eq!(roots_changed.load(Ordering::SeqCst), 1);
1828        assert_eq!(seen.lock().expect("lock")[0].uri, "file:///work");
1829
1830        drop(client);
1831        let _ = timeout(Duration::from_secs(2), handle).await;
1832    }
1833
1834    #[tokio::test]
1835    async fn roots_list_changed_is_ignored_without_roots_capability() {
1836        use crate::builder::ServerBuilder;
1837        use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
1838
1839        let roots_changed = Arc::new(AtomicUsize::new(0));
1840        let handler = RootsHookHandler {
1841            initialized: Arc::new(AtomicBool::new(false)),
1842            roots_changed: roots_changed.clone(),
1843            seen_roots: Arc::new(std::sync::Mutex::new(Vec::new())),
1844            done: Arc::new(Notify::new()),
1845        };
1846
1847        let (client, server_tr) = MemoryTransport::pair();
1848        // No `set_client_caps` with roots -> the client did not advertise roots.
1849        let runtime = ServerRuntime::new(ServerBuilder::new(handler).build(), server_tr);
1850        runtime.state().set_initialized();
1851        let handle = tokio::spawn(async move { runtime.run().await });
1852
1853        client
1854            .send(notif_msg("notifications/roots/list_changed"))
1855            .await
1856            .expect("send");
1857        // A ping round-trips; the reply must be the ping response, never a
1858        // roots/list request (which would mean the gated hook wrongly ran).
1859        client.send(req("ping", 1)).await.expect("send");
1860        let resp = next_response(&client).await;
1861        assert_eq!(resp.id, RequestId::Number(1));
1862        assert_eq!(
1863            roots_changed.load(Ordering::SeqCst),
1864            0,
1865            "on_roots_list_changed must not fire without the roots capability"
1866        );
1867
1868        drop(client);
1869        let _ = timeout(Duration::from_secs(2), handle).await;
1870    }
1871
1872    #[tokio::test]
1873    async fn task_transition_publishes_status_notification() {
1874        let (client, server) = MemoryTransport::pair();
1875        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
1876        state.set_initialized();
1877        let task_store = Arc::new(crate::capability::tasks::TaskManager::new());
1878        task_store
1879            .set_observer(Arc::new(crate::capability::tasks::TaskStatusNotifier::new(
1880                state.clone(),
1881            )))
1882            .expect("install observer");
1883        let runtime = ServerRuntime {
1884            server: PingRouter,
1885            transport: Arc::new(server),
1886            state,
1887            task_store: Arc::clone(&task_store),
1888            config: RuntimeConfig::default(),
1889        };
1890        let handle = tokio::spawn(async move { runtime.run().await });
1891
1892        // An ambient transition: no inbound request triggered it, so there is no
1893        // request-scoped peer. It must still reach the wire.
1894        let task = task_store.create(None);
1895        task.complete(serde_json::json!({"ok": true}))
1896            .expect("complete");
1897
1898        let msg = timeout(Duration::from_secs(2), client.recv())
1899            .await
1900            .expect("no notification (loop never drained the queue?)")
1901            .expect("recv ok")
1902            .expect("some message");
1903        let Message::Notification(notification) = msg else {
1904            panic!("expected notification, got {msg:?}");
1905        };
1906        assert_eq!(notification.method, "notifications/tasks/status");
1907
1908        let params = notification.params.expect("params");
1909        assert_eq!(params["taskId"], task.id().as_str());
1910        assert_eq!(params["status"], "completed");
1911        // Per spec the status notification must not be tagged with
1912        // `io.modelcontextprotocol/related-task`; the taskId is already here.
1913        assert!(
1914            params.get("_meta").is_none(),
1915            "status notification must not carry _meta: {params}"
1916        );
1917
1918        drop(client);
1919        let _ = timeout(Duration::from_secs(2), handle).await;
1920    }
1921
1922    #[tokio::test]
1923    async fn task_status_notifications_can_be_disabled() {
1924        let (client, server) = MemoryTransport::pair();
1925        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
1926        state.set_initialized();
1927        let config = RuntimeConfig::new().task_status_notifications(false);
1928        // Mirrors `with_config`: the observer is simply not installed.
1929        let task_store = Arc::new(crate::capability::tasks::TaskManager::new());
1930        let runtime = ServerRuntime {
1931            server: PingRouter,
1932            transport: Arc::new(server),
1933            state,
1934            task_store: Arc::clone(&task_store),
1935            config,
1936        };
1937        let handle = tokio::spawn(async move { runtime.run().await });
1938
1939        let task = task_store.create(None);
1940        task.complete(serde_json::json!({})).expect("complete");
1941
1942        // Nothing ambient should appear; a ping still answers, proving the loop
1943        // is alive rather than merely slow.
1944        client.send(req("ping", 1)).await.expect("send");
1945        let resp = next_response(&client).await;
1946        assert_eq!(resp.id, RequestId::Number(1));
1947
1948        drop(client);
1949        let _ = timeout(Duration::from_secs(2), handle).await;
1950    }
1951
1952    #[tokio::test]
1953    async fn panic_in_handler_returns_internal_error_and_keeps_connection() {
1954        let (client, server) = MemoryTransport::pair();
1955        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
1956        state.set_initialized();
1957        let runtime = ServerRuntime {
1958            server: PanicRouter,
1959            transport: Arc::new(server),
1960            state,
1961            task_store: Arc::new(crate::capability::tasks::TaskManager::new()),
1962            config: RuntimeConfig::default(),
1963        };
1964        let handle = tokio::spawn(async move { runtime.run().await });
1965
1966        // A panicking handler must yield a JSON-RPC error, not kill the loop.
1967        client.send(req("panic", 1)).await.expect("send");
1968        let resp = next_response(&client).await;
1969        assert_eq!(resp.id, RequestId::Number(1));
1970        let err = resp.error.expect("expected error response");
1971        assert!(
1972            err.message.contains("panicked"),
1973            "unexpected error message: {}",
1974            err.message
1975        );
1976
1977        // The connection must still be alive for subsequent requests.
1978        client.send(req("ok", 2)).await.expect("send");
1979        let resp = next_response(&client).await;
1980        assert_eq!(resp.id, RequestId::Number(2));
1981        assert!(
1982            resp.result.is_some(),
1983            "expected success after a prior panic"
1984        );
1985
1986        drop(client);
1987        let _ = timeout(Duration::from_secs(2), handle).await;
1988    }
1989
1990    #[tokio::test]
1991    async fn ping_is_answered_before_initialize() {
1992        let (client, server) = MemoryTransport::pair();
1993        // Deliberately NOT initialized: the server is mid-handshake.
1994        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
1995        let runtime = ServerRuntime {
1996            server: PingRouter,
1997            transport: Arc::new(server),
1998            state,
1999            task_store: Arc::new(crate::capability::tasks::TaskManager::new()),
2000            config: RuntimeConfig::default(),
2001        };
2002        let handle = tokio::spawn(async move { runtime.run().await });
2003
2004        // `ping` must be answered even before `initialize`.
2005        client.send(req("ping", 1)).await.expect("send");
2006        let resp = next_response(&client).await;
2007        assert_eq!(resp.id, RequestId::Number(1));
2008        assert!(
2009            resp.error.is_none(),
2010            "ping before initialize must not error: {:?}",
2011            resp.error
2012        );
2013        assert!(resp.result.is_some(), "ping should return a result");
2014
2015        // ...but other requests are still rejected until initialized.
2016        client.send(req("tools/list", 2)).await.expect("send");
2017        let resp = next_response(&client).await;
2018        assert_eq!(resp.id, RequestId::Number(2));
2019        assert!(
2020            resp.error.is_some(),
2021            "non-ping requests before initialize must still be rejected"
2022        );
2023
2024        drop(client);
2025        let _ = timeout(Duration::from_secs(2), handle).await;
2026    }
2027
2028    #[tokio::test]
2029    async fn requests_are_processed_concurrently() {
2030        let (client, server) = MemoryTransport::pair();
2031        let started = Arc::new(Notify::new());
2032        let release = Arc::new(Notify::new());
2033        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
2034        state.set_initialized();
2035        let runtime = ServerRuntime {
2036            server: CoordRouter {
2037                started: started.clone(),
2038                release: release.clone(),
2039            },
2040            transport: Arc::new(server),
2041            state,
2042            task_store: Arc::new(crate::capability::tasks::TaskManager::new()),
2043            config: RuntimeConfig::default(),
2044        };
2045        let handle = tokio::spawn(async move { runtime.run().await });
2046
2047        client.send(req("blocker", 1)).await.expect("send");
2048        client.send(req("fast", 2)).await.expect("send");
2049
2050        // Wait until the blocker is in-flight and parked.
2051        timeout(Duration::from_secs(2), started.notified())
2052            .await
2053            .expect("blocker never started");
2054
2055        // If processing were serial, the parked blocker would prevent the fast
2056        // request from completing. Concurrency means the fast response (id 2)
2057        // arrives while the blocker is still parked.
2058        let resp = next_response(&client).await;
2059        assert_eq!(
2060            resp.id,
2061            RequestId::Number(2),
2062            "fast request should finish first"
2063        );
2064
2065        // Release the blocker; its response should now arrive.
2066        release.notify_one();
2067        let resp = next_response(&client).await;
2068        assert_eq!(resp.id, RequestId::Number(1));
2069
2070        drop(client);
2071        let _ = timeout(Duration::from_secs(2), handle).await;
2072    }
2073
2074    #[tokio::test]
2075    async fn max_concurrent_requests_limits_in_flight() {
2076        // With a limit of 1, a parked blocker must prevent a second request
2077        // from being picked up until the blocker completes.
2078        let (client, server) = MemoryTransport::pair();
2079        let started = Arc::new(Notify::new());
2080        let release = Arc::new(Notify::new());
2081        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
2082        state.set_initialized();
2083        let runtime = ServerRuntime {
2084            server: CoordRouter {
2085                started: started.clone(),
2086                release: release.clone(),
2087            },
2088            transport: Arc::new(server),
2089            state,
2090            task_store: Arc::new(crate::capability::tasks::TaskManager::new()),
2091            config: RuntimeConfig {
2092                auto_initialized: true,
2093                max_concurrent_requests: 1,
2094                ..RuntimeConfig::default()
2095            },
2096        };
2097        let handle = tokio::spawn(async move { runtime.run().await });
2098
2099        client.send(req("blocker", 1)).await.expect("send");
2100        client.send(req("fast", 2)).await.expect("send");
2101
2102        timeout(Duration::from_secs(2), started.notified())
2103            .await
2104            .expect("blocker never started");
2105
2106        // The fast request must NOT be processed while the blocker holds the
2107        // single slot: no response should arrive yet.
2108        let early = timeout(Duration::from_millis(200), client.recv()).await;
2109        assert!(
2110            early.is_err(),
2111            "fast request was processed despite max_concurrent_requests = 1"
2112        );
2113
2114        // Release the blocker; both responses arrive, blocker first.
2115        release.notify_one();
2116        assert_eq!(next_response(&client).await.id, RequestId::Number(1));
2117        assert_eq!(next_response(&client).await.id, RequestId::Number(2));
2118
2119        drop(client);
2120        let _ = timeout(Duration::from_secs(2), handle).await;
2121    }
2122
2123    #[tokio::test]
2124    async fn cancelled_notification_trips_in_flight_handler() {
2125        let (client, server) = MemoryTransport::pair();
2126        let started = Arc::new(Notify::new());
2127        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
2128        state.set_initialized();
2129        let runtime = ServerRuntime {
2130            server: CancelRouter {
2131                started: started.clone(),
2132            },
2133            transport: Arc::new(server),
2134            state,
2135            task_store: Arc::new(crate::capability::tasks::TaskManager::new()),
2136            config: RuntimeConfig::default(),
2137        };
2138        let handle = tokio::spawn(async move { runtime.run().await });
2139
2140        // Start a request whose handler parks on `ctx.cancelled()`.
2141        client.send(req("wait_cancel", 1)).await.expect("send");
2142        timeout(Duration::from_secs(2), started.notified())
2143            .await
2144            .expect("handler never started");
2145
2146        // Cancel it by id. Before the fix this never reached the handler's token,
2147        // so the handler would park forever and `next_response` would time out.
2148        let cancel = Message::Notification(Notification::with_params(
2149            "notifications/cancelled".to_string(),
2150            serde_json::json!({ "requestId": 1 }),
2151        ));
2152        client.send(cancel).await.expect("send cancel");
2153
2154        let resp = next_response(&client).await;
2155        assert_eq!(resp.id, RequestId::Number(1));
2156        assert_eq!(
2157            resp.result,
2158            Some(serde_json::json!(true)),
2159            "ctx.is_cancelled() should be true after notifications/cancelled"
2160        );
2161
2162        drop(client);
2163        let _ = timeout(Duration::from_secs(2), handle).await;
2164    }
2165
2166    #[tokio::test]
2167    async fn notifier_sends_list_changed_outside_request() {
2168        let (client, server) = MemoryTransport::pair();
2169        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
2170        let runtime = ServerRuntime {
2171            server: PingRouter,
2172            transport: Arc::new(server),
2173            state,
2174            task_store: Arc::new(crate::capability::tasks::TaskManager::new()),
2175            config: RuntimeConfig::default(),
2176        };
2177
2178        // The notifier works without an active request and without running the
2179        // message loop — it sends straight over the shared transport.
2180        let notifier = runtime.notifier();
2181        notifier.tools_list_changed().await.expect("notify");
2182
2183        let msg = timeout(Duration::from_secs(2), client.recv())
2184            .await
2185            .expect("no notification (timed out)")
2186            .expect("recv ok")
2187            .expect("some message");
2188        match msg {
2189            Message::Notification(n) => {
2190                assert_eq!(n.method.as_ref(), "notifications/tools/list_changed");
2191            }
2192            other => panic!("expected a notification, got {other:?}"),
2193        }
2194    }
2195
2196    #[tokio::test]
2197    async fn server_initiated_request_roundtrips_at_concurrency_limit() {
2198        let (client, server) = MemoryTransport::pair();
2199        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
2200        state.set_initialized();
2201        let runtime = ServerRuntime {
2202            server: OutboundRouter,
2203            transport: Arc::new(server),
2204            state,
2205            task_store: Arc::new(crate::capability::tasks::TaskManager::new()),
2206            // max=1: the handler holds the only slot while parked on its outbound
2207            // request, so the loop MUST keep receiving to route the response.
2208            // The old "drain at max" loop would deadlock here.
2209            config: RuntimeConfig {
2210                auto_initialized: true,
2211                max_concurrent_requests: 1,
2212                ..RuntimeConfig::default()
2213            },
2214        };
2215        let handle = tokio::spawn(async move { runtime.run().await });
2216
2217        // Trigger a handler that issues a server-initiated request.
2218        client.send(req("ask", 1)).await.expect("send");
2219
2220        // The server sends us (the client) its outbound request.
2221        let outbound = match timeout(Duration::from_secs(2), client.recv())
2222            .await
2223            .expect("no outbound request (timed out)")
2224            .expect("recv ok")
2225            .expect("some message")
2226        {
2227            Message::Request(r) => r,
2228            other => panic!("expected a server-initiated request, got {other:?}"),
2229        };
2230        assert_eq!(outbound.method.as_ref(), "ask/upstream");
2231
2232        // Reply to it; the handler should resume and return the result.
2233        client
2234            .send(Message::Response(Response::success(
2235                outbound.id.clone(),
2236                serde_json::json!({ "answer": 42 }),
2237            )))
2238            .await
2239            .expect("send response");
2240
2241        let resp = next_response(&client).await;
2242        assert_eq!(resp.id, RequestId::Number(1));
2243        assert_eq!(resp.result, Some(serde_json::json!({ "answer": 42 })));
2244
2245        drop(client);
2246        let _ = timeout(Duration::from_secs(2), handle).await;
2247    }
2248
2249    #[tokio::test]
2250    async fn server_initiated_request_times_out() {
2251        let (client, server) = MemoryTransport::pair();
2252        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
2253        state.set_initialized();
2254        let runtime = ServerRuntime {
2255            server: OutboundRouter,
2256            transport: Arc::new(server),
2257            state,
2258            task_store: Arc::new(crate::capability::tasks::TaskManager::new()),
2259            config: RuntimeConfig {
2260                outbound_request_timeout: Duration::from_millis(100),
2261                ..RuntimeConfig::default()
2262            },
2263        };
2264        let handle = tokio::spawn(async move { runtime.run().await });
2265
2266        client.send(req("ask", 1)).await.expect("send");
2267
2268        // Receive the outbound request but never answer it.
2269        let _outbound = timeout(Duration::from_secs(2), client.recv())
2270            .await
2271            .expect("no outbound request")
2272            .expect("recv ok")
2273            .expect("some message");
2274
2275        // The handler's request times out, so its own response is an error.
2276        let resp = next_response(&client).await;
2277        assert_eq!(resp.id, RequestId::Number(1));
2278        assert!(resp.error.is_some(), "timed-out request should error");
2279
2280        drop(client);
2281        let _ = timeout(Duration::from_secs(2), handle).await;
2282    }
2283
2284    #[tokio::test]
2285    async fn ctx_elicit_roundtrips() {
2286        let (client, server) = MemoryTransport::pair();
2287        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
2288        state.set_initialized();
2289        state.set_client_caps(ClientCapabilities::default().with_elicitation());
2290        let runtime = ServerRuntime {
2291            server: ElicitRouter,
2292            transport: Arc::new(server),
2293            state,
2294            task_store: Arc::new(crate::capability::tasks::TaskManager::new()),
2295            config: RuntimeConfig::default(),
2296        };
2297        let handle = tokio::spawn(async move { runtime.run().await });
2298
2299        client.send(req("ask_name", 1)).await.expect("send");
2300
2301        // The server sends an `elicitation/create` request to the client.
2302        let elicit = match timeout(Duration::from_secs(2), client.recv())
2303            .await
2304            .expect("no elicitation request")
2305            .expect("recv ok")
2306            .expect("some message")
2307        {
2308            Message::Request(r) => r,
2309            other => panic!("expected elicitation/create, got {other:?}"),
2310        };
2311        assert_eq!(elicit.method.as_ref(), "elicitation/create");
2312        assert!(
2313            elicit
2314                .params
2315                .as_ref()
2316                .and_then(|p| p.get("requestedSchema"))
2317                .is_some(),
2318            "elicitation request should carry a requestedSchema"
2319        );
2320
2321        // Reply as the user accepting with a name.
2322        client
2323            .send(Message::Response(Response::success(
2324                elicit.id.clone(),
2325                serde_json::json!({ "action": "accept", "content": { "name": "Ada" } }),
2326            )))
2327            .await
2328            .expect("send response");
2329
2330        let resp = next_response(&client).await;
2331        assert_eq!(resp.id, RequestId::Number(1));
2332        assert_eq!(
2333            resp.result,
2334            Some(serde_json::json!({ "accepted": true, "name": "Ada" }))
2335        );
2336
2337        drop(client);
2338        let _ = timeout(Duration::from_secs(2), handle).await;
2339    }
2340
2341    #[tokio::test]
2342    async fn ctx_elicit_requires_client_capability() {
2343        let (client, server) = MemoryTransport::pair();
2344        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
2345        state.set_initialized();
2346        // The client did NOT declare the elicitation capability.
2347        let runtime = ServerRuntime {
2348            server: ElicitRouter,
2349            transport: Arc::new(server),
2350            state,
2351            task_store: Arc::new(crate::capability::tasks::TaskManager::new()),
2352            config: RuntimeConfig::default(),
2353        };
2354        let handle = tokio::spawn(async move { runtime.run().await });
2355
2356        client.send(req("ask_name", 1)).await.expect("send");
2357
2358        // No `elicitation/create` is sent; the handler errors straight away.
2359        // `next_response` panics on anything other than a Response, so reaching
2360        // an error response proves nothing was elicited.
2361        let resp = next_response(&client).await;
2362        assert_eq!(resp.id, RequestId::Number(1));
2363        assert!(
2364            resp.error.is_some(),
2365            "elicit without client capability should error"
2366        );
2367
2368        drop(client);
2369        let _ = timeout(Duration::from_secs(2), handle).await;
2370    }
2371
2372    #[tokio::test]
2373    async fn ctx_create_message_roundtrips() {
2374        let (client, server) = MemoryTransport::pair();
2375        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
2376        state.set_initialized();
2377        state.set_client_caps(ClientCapabilities::default().with_sampling());
2378        let runtime = ServerRuntime {
2379            server: SampleRouter,
2380            transport: Arc::new(server),
2381            state,
2382            task_store: Arc::new(crate::capability::tasks::TaskManager::new()),
2383            config: RuntimeConfig::default(),
2384        };
2385        let handle = tokio::spawn(async move { runtime.run().await });
2386
2387        client.send(req("summarize", 1)).await.expect("send");
2388
2389        let sampling = match timeout(Duration::from_secs(2), client.recv())
2390            .await
2391            .expect("no sampling request")
2392            .expect("recv ok")
2393            .expect("some message")
2394        {
2395            Message::Request(r) => r,
2396            other => panic!("expected sampling/createMessage, got {other:?}"),
2397        };
2398        assert_eq!(sampling.method.as_ref(), "sampling/createMessage");
2399
2400        // Reply as the client with a generated message.
2401        let result = CreateMessageResult {
2402            role: Role::Assistant,
2403            content: mcpkit_core::types::OneOrMany::One(mcpkit_core::types::SamplingContent::text(
2404                "a summary",
2405            )),
2406            model: "test-model".to_string(),
2407            stop_reason: None,
2408            meta: None,
2409        };
2410        client
2411            .send(Message::Response(Response::success(
2412                sampling.id.clone(),
2413                serde_json::to_value(result).expect("serialize result"),
2414            )))
2415            .await
2416            .expect("send response");
2417
2418        let resp = next_response(&client).await;
2419        assert_eq!(resp.id, RequestId::Number(1));
2420        assert_eq!(
2421            resp.result,
2422            Some(serde_json::json!({ "text": "a summary" }))
2423        );
2424
2425        drop(client);
2426        let _ = timeout(Duration::from_secs(2), handle).await;
2427    }
2428
2429    #[tokio::test]
2430    async fn ctx_create_message_requires_client_capability() {
2431        let (client, server) = MemoryTransport::pair();
2432        let state = Arc::new(ServerState::new(ServerCapabilities::default()));
2433        state.set_initialized();
2434        // The client did NOT declare the sampling capability.
2435        let runtime = ServerRuntime {
2436            server: SampleRouter,
2437            transport: Arc::new(server),
2438            state,
2439            task_store: Arc::new(crate::capability::tasks::TaskManager::new()),
2440            config: RuntimeConfig::default(),
2441        };
2442        let handle = tokio::spawn(async move { runtime.run().await });
2443
2444        client.send(req("summarize", 1)).await.expect("send");
2445
2446        // No `sampling/createMessage` is sent; the handler errors immediately.
2447        let resp = next_response(&client).await;
2448        assert_eq!(resp.id, RequestId::Number(1));
2449        assert!(
2450            resp.error.is_some(),
2451            "create_message without client capability should error"
2452        );
2453
2454        drop(client);
2455        let _ = timeout(Duration::from_secs(2), handle).await;
2456    }
2457
2458    #[test]
2459    fn test_server_state_initialization() {
2460        let state = ServerState::new(ServerCapabilities::default());
2461        assert!(!state.is_initialized());
2462
2463        state.set_initialized();
2464        assert!(state.is_initialized());
2465    }
2466
2467    #[test]
2468    fn test_cancellation_management() {
2469        let state = ServerState::new(ServerCapabilities::default());
2470        let token = CancellationToken::new();
2471
2472        state.register_cancellation("req-1", token.clone());
2473        assert!(!token.is_cancelled());
2474
2475        state.cancel_request("req-1");
2476        assert!(token.is_cancelled());
2477
2478        state.remove_cancellation("req-1");
2479    }
2480
2481    #[test]
2482    fn test_runtime_config_default() {
2483        let config = RuntimeConfig::default();
2484        assert!(config.auto_initialized);
2485        assert_eq!(config.max_concurrent_requests, 100);
2486    }
2487
2488    #[test]
2489    fn test_extract_progress_token_string() -> Result<(), Box<dyn std::error::Error>> {
2490        let params = serde_json::json!({
2491            "_meta": {
2492                "progressToken": "my-token-123"
2493            },
2494            "name": "test-tool"
2495        });
2496        let token = extract_progress_token(Some(&params));
2497        assert!(token.is_some());
2498        assert_eq!(
2499            token.ok_or("Token not found")?,
2500            ProgressToken::String("my-token-123".to_string())
2501        );
2502
2503        Ok(())
2504    }
2505
2506    #[test]
2507    fn test_extract_progress_token_number() -> Result<(), Box<dyn std::error::Error>> {
2508        let params = serde_json::json!({
2509            "_meta": {
2510                "progressToken": 42
2511            },
2512            "arguments": {}
2513        });
2514        let token = extract_progress_token(Some(&params));
2515        assert!(token.is_some());
2516        assert_eq!(token.ok_or("Token not found")?, ProgressToken::Number(42));
2517
2518        Ok(())
2519    }
2520
2521    #[test]
2522    fn test_extract_progress_token_missing_meta() {
2523        let params = serde_json::json!({
2524            "name": "test-tool",
2525            "arguments": {}
2526        });
2527        let token = extract_progress_token(Some(&params));
2528        assert!(token.is_none());
2529    }
2530
2531    #[test]
2532    fn test_extract_progress_token_missing_token() {
2533        let params = serde_json::json!({
2534            "_meta": {},
2535            "name": "test-tool"
2536        });
2537        let token = extract_progress_token(Some(&params));
2538        assert!(token.is_none());
2539    }
2540
2541    #[test]
2542    fn test_extract_progress_token_none_params() {
2543        let token = extract_progress_token(None);
2544        assert!(token.is_none());
2545    }
2546
2547    #[tokio::test]
2548    async fn task_augmented_tools_call_runs_in_background() {
2549        use crate::builder::ServerBuilder;
2550        use crate::handler::{ServerHandler, ToolHandler};
2551        use mcpkit_core::protocol::Request;
2552        use mcpkit_core::types::{TaskSupport, Tool, ToolOutput};
2553
2554        struct H;
2555        impl ServerHandler for H {
2556            fn server_info(&self) -> ServerInfo {
2557                ServerInfo::new("t", "1.0.0")
2558            }
2559        }
2560        impl ToolHandler for H {
2561            async fn list_tools(&self, _ctx: &Context<'_>) -> Result<Vec<Tool>, McpError> {
2562                Ok(vec![Tool::new("slow").task_support(TaskSupport::Optional)])
2563            }
2564            async fn call_tool(
2565                &self,
2566                name: &str,
2567                _args: serde_json::Map<String, serde_json::Value>,
2568                _ctx: &Context<'_>,
2569            ) -> Result<ToolOutput, McpError> {
2570                Ok(ToolOutput::text(format!("done:{name}")))
2571            }
2572        }
2573
2574        let request = |id: u64, method: &'static str, params: serde_json::Value| {
2575            Message::Request(Request {
2576                jsonrpc: "2.0".into(),
2577                id: RequestId::Number(id),
2578                method: method.into(),
2579                params: Some(params),
2580            })
2581        };
2582
2583        let (client, server_tr) = MemoryTransport::pair();
2584        let built = ServerBuilder::new(H).with_tools(H).build();
2585        let runtime = ServerRuntime::new(built, server_tr);
2586        runtime.state().set_initialized();
2587        let handle = tokio::spawn(async move { runtime.run().await });
2588
2589        // A task-augmented tools/call returns CreateTaskResult immediately.
2590        client
2591            .send(request(
2592                1,
2593                "tools/call",
2594                serde_json::json!({ "name": "slow", "arguments": {}, "task": {} }),
2595            ))
2596            .await
2597            .expect("send");
2598        let resp = next_response(&client).await;
2599        assert_eq!(resp.id, RequestId::Number(1));
2600        assert!(
2601            resp.error.is_none(),
2602            "augmented call errored: {:?}",
2603            resp.error
2604        );
2605        let result = resp.result.expect("create result");
2606        assert_eq!(result["task"]["status"], "working");
2607        let task_id = result["task"]["taskId"]
2608            .as_str()
2609            .expect("taskId")
2610            .to_string();
2611
2612        // The tool runs in the background; tasks/result yields its payload once done.
2613        let mut payload = None;
2614        for attempt in 0..100u64 {
2615            client
2616                .send(request(
2617                    100 + attempt,
2618                    "tasks/result",
2619                    serde_json::json!({ "taskId": task_id }),
2620                ))
2621                .await
2622                .expect("send");
2623            let r = next_response(&client).await;
2624            if r.error.is_none() {
2625                payload = r.result;
2626                break;
2627            }
2628            tokio::time::sleep(Duration::from_millis(10)).await;
2629        }
2630        let payload = payload.expect("task completed with a payload");
2631        assert!(
2632            payload["content"][0]["text"]
2633                .as_str()
2634                .unwrap_or_default()
2635                .contains("done:slow"),
2636            "unexpected task payload: {payload}"
2637        );
2638
2639        // tasks/get reports the terminal status.
2640        client
2641            .send(request(
2642                999,
2643                "tasks/get",
2644                serde_json::json!({ "taskId": task_id }),
2645            ))
2646            .await
2647            .expect("send");
2648        let got = next_response(&client).await;
2649        assert_eq!(got.result.expect("task")["status"], "completed");
2650
2651        drop(client);
2652        let _ = timeout(Duration::from_secs(2), handle).await;
2653    }
2654
2655    #[tokio::test]
2656    async fn tasks_result_blocks_while_loop_stays_live() {
2657        use crate::builder::ServerBuilder;
2658        use crate::handler::{ServerHandler, ToolHandler};
2659        use mcpkit_core::protocol::Request;
2660        use mcpkit_core::types::{TaskSupport, Tool, ToolOutput};
2661
2662        // A tool that finishes only when released, so tasks/result issued
2663        // mid-run must block (spec) — without stalling the cooperative loop:
2664        // a concurrent request is still answered while tasks/result waits.
2665        struct H(Arc<tokio::sync::Notify>);
2666        impl ServerHandler for H {
2667            fn server_info(&self) -> ServerInfo {
2668                ServerInfo::new("t", "1.0.0")
2669            }
2670        }
2671        impl ToolHandler for H {
2672            async fn list_tools(&self, _ctx: &Context<'_>) -> Result<Vec<Tool>, McpError> {
2673                Ok(vec![Tool::new("gated").task_support(TaskSupport::Optional)])
2674            }
2675            async fn call_tool(
2676                &self,
2677                _name: &str,
2678                _args: serde_json::Map<String, serde_json::Value>,
2679                _ctx: &Context<'_>,
2680            ) -> Result<ToolOutput, McpError> {
2681                self.0.notified().await;
2682                Ok(ToolOutput::text("released"))
2683            }
2684        }
2685
2686        let request = |id: u64, method: &'static str, params: serde_json::Value| {
2687            Message::Request(Request {
2688                jsonrpc: "2.0".into(),
2689                id: RequestId::Number(id),
2690                method: method.into(),
2691                params: Some(params),
2692            })
2693        };
2694
2695        let release = Arc::new(tokio::sync::Notify::new());
2696        let (client, server_tr) = MemoryTransport::pair();
2697        let built = ServerBuilder::new(H(release.clone()))
2698            .with_tools(H(release.clone()))
2699            .build();
2700        let runtime = ServerRuntime::new(built, server_tr);
2701        runtime.state().set_initialized();
2702        let handle = tokio::spawn(async move { runtime.run().await });
2703
2704        client
2705            .send(request(
2706                1,
2707                "tools/call",
2708                serde_json::json!({ "name": "gated", "arguments": {}, "task": {} }),
2709            ))
2710            .await
2711            .expect("send");
2712        let resp = next_response(&client).await;
2713        let task_id = resp.result.expect("create result")["task"]["taskId"]
2714            .as_str()
2715            .expect("taskId")
2716            .to_string();
2717
2718        // tasks/result while the tool is still gated: blocks, no response yet.
2719        client
2720            .send(request(
2721                2,
2722                "tasks/result",
2723                serde_json::json!({ "taskId": task_id }),
2724            ))
2725            .await
2726            .expect("send");
2727
2728        // The loop must stay live: an unrelated request is answered while
2729        // tasks/result waits.
2730        client
2731            .send(request(3, "tools/list", serde_json::json!({})))
2732            .await
2733            .expect("send");
2734        let live = timeout(Duration::from_secs(2), next_response(&client))
2735            .await
2736            .expect("loop stalled while tasks/result was blocking");
2737        assert_eq!(live.id, RequestId::Number(3), "expected tools/list reply");
2738
2739        // Release the tool; the blocked tasks/result now yields the payload.
2740        release.notify_one();
2741        let result = timeout(Duration::from_secs(2), next_response(&client))
2742            .await
2743            .expect("blocked tasks/result never completed");
2744        assert_eq!(result.id, RequestId::Number(2));
2745        let payload = result.result.expect("payload");
2746        assert!(
2747            payload["content"][0]["text"]
2748                .as_str()
2749                .unwrap_or_default()
2750                .contains("released"),
2751            "unexpected payload: {payload}"
2752        );
2753        // Spec MUST: the tasks/result response carries the related-task _meta.
2754        assert_eq!(
2755            payload["_meta"]["io.modelcontextprotocol/related-task"]["taskId"],
2756            task_id.as_str()
2757        );
2758
2759        drop(client);
2760        let _ = timeout(Duration::from_secs(2), handle).await;
2761    }
2762
2763    #[tokio::test]
2764    async fn task_augmented_call_on_forbidden_tool_is_rejected() {
2765        use crate::builder::ServerBuilder;
2766        use crate::handler::{ServerHandler, ToolHandler};
2767        use mcpkit_core::protocol::Request;
2768        use mcpkit_core::types::{Tool, ToolOutput};
2769
2770        struct H;
2771        impl ServerHandler for H {
2772            fn server_info(&self) -> ServerInfo {
2773                ServerInfo::new("t", "1.0.0")
2774            }
2775        }
2776        impl ToolHandler for H {
2777            async fn list_tools(&self, _ctx: &Context<'_>) -> Result<Vec<Tool>, McpError> {
2778                // No execution.taskSupport -> forbidden by default.
2779                Ok(vec![Tool::new("plain")])
2780            }
2781            async fn call_tool(
2782                &self,
2783                _name: &str,
2784                _args: serde_json::Map<String, serde_json::Value>,
2785                _ctx: &Context<'_>,
2786            ) -> Result<ToolOutput, McpError> {
2787                Ok(ToolOutput::text("ok"))
2788            }
2789        }
2790
2791        let (client, server_tr) = MemoryTransport::pair();
2792        let runtime = ServerRuntime::new(ServerBuilder::new(H).with_tools(H).build(), server_tr);
2793        runtime.state().set_initialized();
2794        let handle = tokio::spawn(async move { runtime.run().await });
2795
2796        client
2797            .send(Message::Request(Request {
2798                jsonrpc: "2.0".into(),
2799                id: RequestId::Number(1),
2800                method: "tools/call".into(),
2801                params: Some(serde_json::json!({ "name": "plain", "task": {} })),
2802            }))
2803            .await
2804            .expect("send");
2805        let resp = next_response(&client).await;
2806        let err = resp
2807            .error
2808            .expect("a forbidden tool must reject task augmentation");
2809        // Spec: -32601 (Method not found), not -32602.
2810        assert_eq!(err.code, -32601, "wrong rejection code: {err:?}");
2811
2812        drop(client);
2813        let _ = timeout(Duration::from_secs(2), handle).await;
2814    }
2815
2816    /// The validation decorator must also cover the *task* path: a
2817    /// task-augmented `tools/call` whose arguments violate the `inputSchema`
2818    /// must resolve to an `isError` result rather than running the tool body.
2819    /// This exercises `Server::call_tool_json` (background execution), a
2820    /// different call site than `route_tools`.
2821    #[cfg(feature = "schema-validation")]
2822    #[tokio::test]
2823    async fn task_path_validates_input_via_decorator() {
2824        use crate::builder::ServerBuilder;
2825        use crate::handler::{ServerHandler, ToolHandler};
2826        use mcpkit_core::protocol::Request;
2827        use mcpkit_core::types::{TaskSupport, Tool, ToolOutput};
2828
2829        struct H;
2830        impl ServerHandler for H {
2831            fn server_info(&self) -> ServerInfo {
2832                ServerInfo::new("t", "1.0.0")
2833            }
2834        }
2835        impl ToolHandler for H {
2836            async fn list_tools(&self, _ctx: &Context<'_>) -> Result<Vec<Tool>, McpError> {
2837                Ok(vec![
2838                    Tool::new("slow")
2839                        .task_support(TaskSupport::Optional)
2840                        .input_schema(serde_json::json!({
2841                            "type": "object",
2842                            "properties": { "n": { "type": "number" } },
2843                            "required": ["n"]
2844                        })),
2845                ])
2846            }
2847            async fn call_tool(
2848                &self,
2849                name: &str,
2850                _args: serde_json::Map<String, serde_json::Value>,
2851                _ctx: &Context<'_>,
2852            ) -> Result<ToolOutput, McpError> {
2853                Ok(ToolOutput::text(format!("done:{name}")))
2854            }
2855        }
2856
2857        let request = |id: u64, method: &'static str, params: serde_json::Value| {
2858            Message::Request(Request {
2859                jsonrpc: "2.0".into(),
2860                id: RequestId::Number(id),
2861                method: method.into(),
2862                params: Some(params),
2863            })
2864        };
2865
2866        let (client, server_tr) = MemoryTransport::pair();
2867        // `validate_tool_io()` wraps the tool handler; background task execution
2868        // must still route through it.
2869        let built = ServerBuilder::new(H)
2870            .with_tools(H)
2871            .validate_tool_io()
2872            .build();
2873        let runtime = ServerRuntime::new(built, server_tr);
2874        runtime.state().set_initialized();
2875        let handle = tokio::spawn(async move { runtime.run().await });
2876
2877        // Task-augmented call with input that violates the schema (missing "n").
2878        client
2879            .send(request(
2880                1,
2881                "tools/call",
2882                serde_json::json!({ "name": "slow", "arguments": {}, "task": {} }),
2883            ))
2884            .await
2885            .expect("send");
2886        let resp = next_response(&client).await;
2887        let task_id = resp.result.expect("create result")["task"]["taskId"]
2888            .as_str()
2889            .expect("taskId")
2890            .to_string();
2891
2892        let mut payload = None;
2893        for attempt in 0..100u64 {
2894            client
2895                .send(request(
2896                    100 + attempt,
2897                    "tasks/result",
2898                    serde_json::json!({ "taskId": task_id }),
2899                ))
2900                .await
2901                .expect("send");
2902            let r = next_response(&client).await;
2903            if r.error.is_none() {
2904                payload = r.result;
2905                break;
2906            }
2907            tokio::time::sleep(Duration::from_millis(10)).await;
2908        }
2909        let payload = payload.expect("task completed with a payload");
2910        assert_eq!(
2911            payload["isError"],
2912            serde_json::json!(true),
2913            "task path must validate input: {payload}"
2914        );
2915        assert!(
2916            !payload["content"][0]["text"]
2917                .as_str()
2918                .unwrap_or_default()
2919                .contains("done:slow"),
2920            "the tool body must not have run: {payload}"
2921        );
2922
2923        drop(client);
2924        let _ = timeout(Duration::from_secs(2), handle).await;
2925    }
2926
2927    #[tokio::test]
2928    async fn logging_set_level_dispatches_when_advertised_else_method_not_found() {
2929        use crate::builder::ServerBuilder;
2930        use crate::context::NoOpPeer;
2931        use crate::handler::ServerHandler;
2932        use mcpkit_core::capability::{ClientCapabilities, ServerCapabilities};
2933        use mcpkit_core::protocol::RequestId;
2934        use mcpkit_core::protocol_version::ProtocolVersion;
2935        use mcpkit_core::types::LoggingLevel;
2936        use std::sync::Mutex;
2937
2938        struct H(Arc<Mutex<Option<LoggingLevel>>>);
2939        impl ServerHandler for H {
2940            fn server_info(&self) -> ServerInfo {
2941                ServerInfo::new("t", "1.0.0")
2942            }
2943            async fn set_log_level(
2944                &self,
2945                level: LoggingLevel,
2946                _ctx: &Context<'_>,
2947            ) -> Result<(), McpError> {
2948                *self.0.lock().unwrap() = Some(level);
2949                Ok(())
2950            }
2951        }
2952
2953        let request_id = RequestId::Number(1);
2954        let client_caps = ClientCapabilities::default();
2955        let server_caps = ServerCapabilities::default();
2956        let peer = NoOpPeer;
2957        let ctx = Context::new(
2958            &request_id,
2959            None,
2960            &client_caps,
2961            &server_caps,
2962            ProtocolVersion::LATEST,
2963            &peer,
2964        );
2965
2966        // Advertised -> dispatched to the base handler, empty result.
2967        let seen = Arc::new(Mutex::new(None));
2968        let server = ServerBuilder::new(H(Arc::clone(&seen)))
2969            .capabilities(ServerCapabilities::new().with_logging())
2970            .build();
2971        let out = server
2972            .route(
2973                "logging/setLevel",
2974                Some(&serde_json::json!({ "level": "warning" })),
2975                &ctx,
2976            )
2977            .await
2978            .expect("setLevel dispatched");
2979        assert_eq!(out, serde_json::json!({}));
2980        assert_eq!(*seen.lock().unwrap(), Some(LoggingLevel::Warning));
2981
2982        // Invalid level -> invalid params.
2983        assert!(
2984            server
2985                .route(
2986                    "logging/setLevel",
2987                    Some(&serde_json::json!({ "level": "loud" })),
2988                    &ctx,
2989                )
2990                .await
2991                .is_err()
2992        );
2993
2994        // Not advertised -> method not found.
2995        let plain = ServerBuilder::new(H(Arc::new(Mutex::new(None)))).build();
2996        let err = plain
2997            .route(
2998                "logging/setLevel",
2999                Some(&serde_json::json!({ "level": "info" })),
3000                &ctx,
3001            )
3002            .await
3003            .expect_err("no logging capability -> method not found");
3004        assert!(matches!(err, McpError::MethodNotFound { .. }));
3005    }
3006
3007    #[tokio::test]
3008    async fn context_log_emits_message_notification() {
3009        use crate::context::Peer;
3010        use mcpkit_core::capability::{ClientCapabilities, ServerCapabilities};
3011        use mcpkit_core::protocol::RequestId;
3012        use mcpkit_core::protocol_version::ProtocolVersion;
3013        use mcpkit_core::types::LoggingLevel;
3014        use std::pin::Pin;
3015        use std::sync::Mutex;
3016
3017        struct RecPeer(Arc<Mutex<Vec<Notification>>>);
3018        impl Peer for RecPeer {
3019            fn notify(
3020                &self,
3021                notification: Notification,
3022            ) -> Pin<Box<dyn std::future::Future<Output = Result<(), McpError>> + Send + '_>>
3023            {
3024                self.0.lock().unwrap().push(notification);
3025                Box::pin(async { Ok(()) })
3026            }
3027        }
3028
3029        let seen = Arc::new(Mutex::new(Vec::new()));
3030        let peer = RecPeer(Arc::clone(&seen));
3031        let request_id = RequestId::Number(1);
3032        let client_caps = ClientCapabilities::default();
3033        let server_caps = ServerCapabilities::default();
3034        let ctx = Context::new(
3035            &request_id,
3036            None,
3037            &client_caps,
3038            &server_caps,
3039            ProtocolVersion::LATEST,
3040            &peer,
3041        );
3042
3043        ctx.log(LoggingLevel::Error, Some("db"), serde_json::json!("boom"))
3044            .await
3045            .expect("log sent");
3046
3047        let seen = seen.lock().unwrap();
3048        assert_eq!(seen.len(), 1);
3049        assert_eq!(seen[0].method.as_ref(), "notifications/message");
3050        let params = seen[0].params.as_ref().expect("params");
3051        assert_eq!(params["level"], serde_json::json!("error"));
3052        assert_eq!(params["logger"], serde_json::json!("db"));
3053        assert_eq!(params["data"], serde_json::json!("boom"));
3054    }
3055}