Skip to main content

mcpkit_server/
router.rs

1//! Request routing for MCP servers.
2//!
3//! This module provides the routing infrastructure that dispatches
4//! incoming requests to the appropriate handler methods.
5//!
6//! # MCP Method Categories
7//!
8//! - **Initialization**: `initialize`, `ping`
9//! - **Tools**: `tools/list`, `tools/call`
10//! - **Resources**: `resources/list`, `resources/read`, `resources/subscribe`
11//! - **Prompts**: `prompts/list`, `prompts/get`
12//! - **Tasks**: `tasks/list`, `tasks/get`, `tasks/cancel`
13//! - **Sampling**: `sampling/createMessage`
14//! - **Completions**: `completion/complete`
15
16use mcpkit_core::error::McpError;
17use mcpkit_core::protocol::Request;
18use mcpkit_core::types::Object;
19use serde_json::Value;
20
21/// Spec-defined MCP method names.
22///
23/// Re-exported from [`mcpkit_core::methods`], where they now live: they are
24/// protocol facts, and declaring them here left every other crate writing the
25/// names as literals.
26pub use mcpkit_core::methods;
27
28/// Spec-defined MCP notification names.
29pub use mcpkit_core::methods::notifications;
30
31/// Represents a parsed MCP request with typed parameters.
32///
33/// This enum provides a type-safe representation of all MCP request types,
34/// with parameters parsed into their appropriate structures.
35#[derive(Debug)]
36pub enum ParsedRequest {
37    /// Initialize request to establish connection.
38    Initialize(InitializeParams),
39    /// Ping request to check connection health.
40    Ping,
41
42    /// Request to list available tools.
43    ToolsList(ListParams),
44    /// Request to call a specific tool.
45    ToolsCall(ToolCallParams),
46
47    /// Request to list available resources.
48    ResourcesList(ListParams),
49    /// Request to read a resource's contents.
50    ResourcesRead(ResourceReadParams),
51    /// Request to list resource templates.
52    ResourcesTemplatesList(ListParams),
53    /// Request to subscribe to resource updates.
54    ResourcesSubscribe(ResourceSubscribeParams),
55    /// Request to unsubscribe from resource updates.
56    ResourcesUnsubscribe(ResourceUnsubscribeParams),
57
58    /// Request to list available prompts.
59    PromptsList(ListParams),
60    /// Request to get a specific prompt.
61    PromptsGet(PromptGetParams),
62
63    /// Request to list running tasks.
64    TasksList(ListParams),
65    /// Request to get a task's status.
66    TasksGet(TaskGetParams),
67    /// Request to cancel a running task.
68    TasksCancel(TaskCancelParams),
69
70    /// Request for the client to sample from a language model.
71    SamplingCreateMessage(SamplingParams),
72
73    /// Request for completion suggestions.
74    CompletionComplete(CompletionParams),
75
76    /// Request to set the logging level.
77    LoggingSetLevel(LogLevelParams),
78
79    /// An unrecognized method name.
80    Unknown(String),
81}
82
83/// Common list parameters with optional cursor for pagination.
84#[derive(Debug, Default)]
85pub struct ListParams {
86    /// Optional cursor for pagination.
87    pub cursor: Option<String>,
88}
89
90/// Initialize request parameters.
91#[derive(Debug)]
92pub struct InitializeParams {
93    /// The protocol version requested by the client.
94    pub protocol_version: String,
95    /// Information about the client.
96    pub client_info: ClientInfo,
97    /// Client capabilities.
98    pub capabilities: Value,
99}
100
101/// Client info from initialize request.
102#[derive(Debug)]
103pub struct ClientInfo {
104    /// The name of the client application.
105    pub name: String,
106    /// The version of the client application.
107    pub version: String,
108}
109
110/// Tool call parameters.
111#[derive(Debug)]
112pub struct ToolCallParams {
113    /// The name of the tool to call.
114    pub name: String,
115    /// Arguments to pass to the tool.
116    pub arguments: Object,
117}
118
119/// Resource read parameters.
120#[derive(Debug)]
121pub struct ResourceReadParams {
122    /// The URI of the resource to read.
123    pub uri: String,
124}
125
126/// Resource subscribe parameters.
127#[derive(Debug)]
128pub struct ResourceSubscribeParams {
129    /// The URI of the resource to subscribe to.
130    pub uri: String,
131}
132
133/// Resource unsubscribe parameters.
134#[derive(Debug)]
135pub struct ResourceUnsubscribeParams {
136    /// The URI of the resource to unsubscribe from.
137    pub uri: String,
138}
139
140/// Prompt get parameters.
141#[derive(Debug)]
142pub struct PromptGetParams {
143    /// The name of the prompt to get.
144    pub name: String,
145    /// Optional arguments to pass to the prompt.
146    pub arguments: Option<Object>,
147}
148
149/// Task get parameters.
150#[derive(Debug)]
151pub struct TaskGetParams {
152    /// The ID of the task to get.
153    pub task_id: String,
154}
155
156/// Task cancel parameters.
157#[derive(Debug)]
158pub struct TaskCancelParams {
159    /// The ID of the task to cancel.
160    pub task_id: String,
161}
162
163/// Sampling create message parameters.
164#[derive(Debug)]
165pub struct SamplingParams {
166    /// The messages to sample from.
167    pub messages: Vec<Value>,
168    /// Optional model preferences.
169    pub model_preferences: Option<Value>,
170    /// Optional system prompt.
171    pub system_prompt: Option<String>,
172    /// Optional maximum number of tokens.
173    pub max_tokens: Option<u32>,
174}
175
176/// Completion parameters.
177#[derive(Debug)]
178pub struct CompletionParams {
179    /// The type of reference (e.g., "ref/resource", "ref/prompt").
180    pub ref_type: String,
181    /// The value of the reference (URI or name).
182    pub ref_value: String,
183    /// Optional argument for completion context.
184    pub argument: Option<CompletionArgument>,
185}
186
187/// Completion argument providing context for completion.
188#[derive(Debug)]
189pub struct CompletionArgument {
190    /// The name of the argument.
191    pub name: String,
192    /// The current value being completed.
193    pub value: String,
194}
195
196/// Log level parameters.
197#[derive(Debug)]
198pub struct LogLevelParams {
199    /// The log level to set (e.g., "debug", "info", "warn", "error").
200    pub level: String,
201}
202
203/// Parse a request into a typed representation.
204pub fn parse_request(request: &Request) -> Result<ParsedRequest, McpError> {
205    let method = request.method.as_ref();
206    let params = request.params.as_ref();
207
208    match method {
209        methods::INITIALIZE => {
210            let params =
211                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
212
213            Ok(ParsedRequest::Initialize(InitializeParams {
214                protocol_version: params
215                    .get("protocolVersion")
216                    .and_then(|v| v.as_str())
217                    .unwrap_or("unknown")
218                    .to_string(),
219                client_info: ClientInfo {
220                    name: params
221                        .get("clientInfo")
222                        .and_then(|v| v.get("name"))
223                        .and_then(|v| v.as_str())
224                        .unwrap_or("unknown")
225                        .to_string(),
226                    version: params
227                        .get("clientInfo")
228                        .and_then(|v| v.get("version"))
229                        .and_then(|v| v.as_str())
230                        .unwrap_or("unknown")
231                        .to_string(),
232                },
233                capabilities: params
234                    .get("capabilities")
235                    .cloned()
236                    .unwrap_or_else(|| Value::Object(serde_json::Map::new())),
237            }))
238        }
239
240        methods::PING => Ok(ParsedRequest::Ping),
241
242        methods::TOOLS_LIST => Ok(ParsedRequest::ToolsList(parse_list_params(params))),
243
244        methods::TOOLS_CALL => {
245            let params =
246                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
247
248            let name = params
249                .get("name")
250                .and_then(|v| v.as_str())
251                .ok_or_else(|| McpError::invalid_params(method, "missing name"))?
252                .to_string();
253
254            let arguments = match params.get("arguments") {
255                None => Object::new(),
256                Some(Value::Object(map)) => map.clone(),
257                Some(_) => {
258                    return Err(McpError::invalid_params(
259                        method,
260                        "arguments must be an object",
261                    ));
262                }
263            };
264
265            Ok(ParsedRequest::ToolsCall(ToolCallParams { name, arguments }))
266        }
267
268        methods::RESOURCES_LIST => Ok(ParsedRequest::ResourcesList(parse_list_params(params))),
269
270        methods::RESOURCES_READ => {
271            let params =
272                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
273
274            let uri = params
275                .get("uri")
276                .and_then(|v| v.as_str())
277                .ok_or_else(|| McpError::invalid_params(method, "missing uri"))?
278                .to_string();
279
280            Ok(ParsedRequest::ResourcesRead(ResourceReadParams { uri }))
281        }
282
283        methods::RESOURCES_TEMPLATES_LIST => Ok(ParsedRequest::ResourcesTemplatesList(
284            parse_list_params(params),
285        )),
286
287        methods::RESOURCES_SUBSCRIBE => {
288            let params =
289                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
290
291            let uri = params
292                .get("uri")
293                .and_then(|v| v.as_str())
294                .ok_or_else(|| McpError::invalid_params(method, "missing uri"))?
295                .to_string();
296
297            Ok(ParsedRequest::ResourcesSubscribe(ResourceSubscribeParams {
298                uri,
299            }))
300        }
301
302        methods::RESOURCES_UNSUBSCRIBE => {
303            let params =
304                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
305
306            let uri = params
307                .get("uri")
308                .and_then(|v| v.as_str())
309                .ok_or_else(|| McpError::invalid_params(method, "missing uri"))?
310                .to_string();
311
312            Ok(ParsedRequest::ResourcesUnsubscribe(
313                ResourceUnsubscribeParams { uri },
314            ))
315        }
316
317        methods::PROMPTS_LIST => Ok(ParsedRequest::PromptsList(parse_list_params(params))),
318
319        methods::PROMPTS_GET => {
320            let params =
321                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
322
323            let name = params
324                .get("name")
325                .and_then(|v| v.as_str())
326                .ok_or_else(|| McpError::invalid_params(method, "missing name"))?
327                .to_string();
328
329            let arguments = match params.get("arguments") {
330                None => None,
331                Some(Value::Object(map)) => Some(map.clone()),
332                Some(_) => {
333                    return Err(McpError::invalid_params(
334                        method,
335                        "arguments must be an object",
336                    ));
337                }
338            };
339
340            Ok(ParsedRequest::PromptsGet(PromptGetParams {
341                name,
342                arguments,
343            }))
344        }
345
346        methods::TASKS_LIST => Ok(ParsedRequest::TasksList(parse_list_params(params))),
347
348        methods::TASKS_GET => {
349            let params =
350                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
351
352            let task_id = params
353                .get("taskId")
354                .and_then(|v| v.as_str())
355                .ok_or_else(|| McpError::invalid_params(method, "missing taskId"))?
356                .to_string();
357
358            Ok(ParsedRequest::TasksGet(TaskGetParams { task_id }))
359        }
360
361        methods::TASKS_CANCEL => {
362            let params =
363                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
364
365            let task_id = params
366                .get("taskId")
367                .and_then(|v| v.as_str())
368                .ok_or_else(|| McpError::invalid_params(method, "missing taskId"))?
369                .to_string();
370
371            Ok(ParsedRequest::TasksCancel(TaskCancelParams { task_id }))
372        }
373
374        methods::SAMPLING_CREATE_MESSAGE => {
375            let params =
376                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
377
378            let messages = params
379                .get("messages")
380                .and_then(|v| v.as_array())
381                .ok_or_else(|| McpError::invalid_params(method, "missing messages"))?
382                .clone();
383
384            Ok(ParsedRequest::SamplingCreateMessage(SamplingParams {
385                messages,
386                model_preferences: params.get("modelPreferences").cloned(),
387                system_prompt: params
388                    .get("systemPrompt")
389                    .and_then(|v| v.as_str())
390                    .map(String::from),
391                max_tokens: params
392                    .get("maxTokens")
393                    .and_then(serde_json::Value::as_u64)
394                    .map(|v| v as u32),
395            }))
396        }
397
398        methods::COMPLETION_COMPLETE => {
399            let params =
400                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
401
402            let ref_obj = params
403                .get("ref")
404                .ok_or_else(|| McpError::invalid_params(method, "missing ref"))?;
405
406            Ok(ParsedRequest::CompletionComplete(CompletionParams {
407                ref_type: ref_obj
408                    .get("type")
409                    .and_then(|v| v.as_str())
410                    .unwrap_or("")
411                    .to_string(),
412                ref_value: ref_obj
413                    .get("uri")
414                    .or_else(|| ref_obj.get("name"))
415                    .and_then(|v| v.as_str())
416                    .unwrap_or("")
417                    .to_string(),
418                argument: params.get("argument").map(|arg| CompletionArgument {
419                    name: arg
420                        .get("name")
421                        .and_then(|v| v.as_str())
422                        .unwrap_or("")
423                        .to_string(),
424                    value: arg
425                        .get("value")
426                        .and_then(|v| v.as_str())
427                        .unwrap_or("")
428                        .to_string(),
429                }),
430            }))
431        }
432
433        methods::LOGGING_SET_LEVEL => {
434            let params =
435                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
436
437            let level = params
438                .get("level")
439                .and_then(|v| v.as_str())
440                .ok_or_else(|| McpError::invalid_params(method, "missing level"))?
441                .to_string();
442
443            Ok(ParsedRequest::LoggingSetLevel(LogLevelParams { level }))
444        }
445
446        _ => Ok(ParsedRequest::Unknown(method.to_string())),
447    }
448}
449
450/// Parse common list parameters.
451fn parse_list_params(params: Option<&Value>) -> ListParams {
452    ListParams {
453        cursor: params
454            .and_then(|p| p.get("cursor"))
455            .and_then(|v| v.as_str())
456            .map(String::from),
457    }
458}
459
460// =============================================================================
461// Public routing functions for HTTP integrations
462//
463// These functions allow HTTP handlers (axum, actix, etc.) to properly route
464// requests to handler trait implementations.
465// =============================================================================
466
467use crate::context::Context;
468use crate::dispatch::{
469    DynCompletionHandler, DynPromptHandler, DynResourceHandler, DynTaskHandler, DynToolHandler,
470};
471use mcpkit_core::pagination::paginate;
472use mcpkit_core::types::{
473    CallToolResult, CompleteRequest, CompleteResult, SubscribeRequest, TaskId, UnsubscribeRequest,
474};
475
476/// Build a paginated list result: the items under `key` plus an optional
477/// `nextCursor`.
478fn list_result<T: serde::Serialize>(key: &str, items: Vec<T>, next: Option<String>) -> Value {
479    let mut obj = serde_json::Map::new();
480    obj.insert(
481        key.to_string(),
482        serde_json::to_value(items).unwrap_or_default(),
483    );
484    if let Some(cursor) = next {
485        obj.insert("nextCursor".to_string(), Value::String(cursor));
486    }
487    Value::Object(obj)
488}
489
490/// The `cursor` string from list-request params, if present.
491fn list_cursor(params: Option<&Value>) -> Option<&str> {
492    params.and_then(|p| p.get("cursor")).and_then(Value::as_str)
493}
494
495/// Route tool-related requests to a handler implementing
496/// [`ToolHandler`](crate::handler::ToolHandler).
497///
498/// This function handles `tools/list` and `tools/call` methods.
499/// Returns `None` if the method is not tool-related.
500///
501/// # Example
502///
503/// ```ignore
504/// if let Some(result) = route_tools(&handler, method, params, &ctx).await {
505///     return result;
506/// }
507/// ```
508pub async fn route_tools(
509    handler: &dyn DynToolHandler,
510    method: &str,
511    params: Option<&serde_json::Value>,
512    ctx: &Context<'_>,
513    page_size: Option<usize>,
514) -> Option<Result<serde_json::Value, McpError>> {
515    match method {
516        methods::TOOLS_LIST => {
517            tracing::debug!("Listing available tools");
518            let result = async {
519                let tools = handler.list_tools(ctx).await?;
520                let (page, next) =
521                    paginate(tools, list_cursor(params), page_size, methods::TOOLS_LIST)?;
522                tracing::debug!(count = page.len(), "Listed tools");
523                Ok(list_result("tools", page, next))
524            }
525            .await;
526            Some(result)
527        }
528        methods::TOOLS_CALL => {
529            let result = async {
530                let params = params.ok_or_else(|| {
531                    McpError::invalid_params(methods::TOOLS_CALL, "missing params")
532                })?;
533                let name = params.get("name").and_then(|v| v.as_str()).ok_or_else(|| {
534                    McpError::invalid_params(methods::TOOLS_CALL, "missing tool name")
535                })?;
536                let args = match params.get("arguments") {
537                    None => Object::new(),
538                    Some(Value::Object(map)) => map.clone(),
539                    Some(_) => {
540                        return Err(McpError::invalid_params(
541                            methods::TOOLS_CALL,
542                            "arguments must be an object",
543                        ));
544                    }
545                };
546
547                tracing::info!(tool = %name, "Calling tool");
548                let start = std::time::Instant::now();
549                let output = handler.call_tool(name, args, ctx).await;
550                let duration = start.elapsed();
551
552                match &output {
553                    Ok(_) => tracing::info!(
554                        tool = %name,
555                        duration_ms = duration.as_millis(),
556                        "Tool call completed"
557                    ),
558                    Err(e) => tracing::warn!(
559                        tool = %name,
560                        duration_ms = duration.as_millis(),
561                        error = %e,
562                        "Tool call failed"
563                    ),
564                }
565
566                let output = output?;
567                let result: CallToolResult = output.into();
568                Ok(serde_json::to_value(result).unwrap_or_else(|_| serde_json::json!({})))
569            }
570            .await;
571            Some(result)
572        }
573        _ => None,
574    }
575}
576
577/// The task-augmentation support a tool declares (`Tool.execution.taskSupport`).
578///
579/// Defaults to `Forbidden` when the tool is unknown or declares nothing. Used to
580/// gate a task-augmented `tools/call` before creating the task. Shared by the
581/// stdio runtime and the HTTP adapters.
582pub async fn tool_task_support(
583    handler: &dyn DynToolHandler,
584    name: &str,
585    ctx: &Context<'_>,
586) -> mcpkit_core::types::TaskSupport {
587    use mcpkit_core::types::TaskSupport;
588    let tools = handler.list_tools(ctx).await.unwrap_or_default();
589    tools
590        .iter()
591        .find(|t| t.name == name)
592        .and_then(|t| t.execution.as_ref())
593        .and_then(|e| e.task_support)
594        .unwrap_or(TaskSupport::Forbidden)
595}
596
597/// Dispatch an inbound client notification to the server's lifecycle hooks.
598///
599/// Covers `on_initialized` and `on_roots_list_changed`; unknown methods are
600/// a no-op. Shared by the stdio runtime's `RequestRouter::route_notification`
601/// and the HTTP adapters (#153), so the hook surface is wired identically
602/// everywhere.
603pub async fn dispatch_notification_hooks<H: crate::handler::ServerHandler>(
604    handler: &H,
605    method: &str,
606    ctx: &Context<'_>,
607) {
608    match method {
609        notifications::INITIALIZED => handler.on_initialized(ctx).await,
610        // Only meaningful from a client that advertised the `roots`
611        // capability; ignore it otherwise.
612        notifications::ROOTS_LIST_CHANGED if ctx.client_caps.has_roots() => {
613            handler.on_roots_list_changed(ctx).await;
614        }
615        _ => {}
616    }
617}
618
619/// Run a tool and return its `CallToolResult` as JSON (the `tasks/result`
620/// payload shape). Shared by the stdio runtime and the HTTP adapters.
621pub async fn call_tool_json(
622    handler: &dyn DynToolHandler,
623    name: &str,
624    args: Object,
625    ctx: &Context<'_>,
626) -> Result<serde_json::Value, McpError> {
627    let output = handler.call_tool(name, args, ctx).await?;
628    let result: CallToolResult = output.into();
629    Ok(serde_json::to_value(result).unwrap_or_default())
630}
631
632/// Run a task-augmented tool to completion, writing the result (or failure) back
633/// through the task-store `handle`.
634///
635/// Built for HTTP adapters, which spawn this onto their own executor after
636/// replying with the initial `CreateTaskResult`. The background context carries
637/// the session `peer` (#153), so a task-augmented tool can make
638/// server-to-client requests (elicitation/sampling/roots) and its notifications
639/// ride the session's SSE stream like any other outbound message (store-and-drop
640/// without a live stream — they never fail the tool). Cancellation works too —
641/// the handle's cancellation token is wired into the context, so a cooperative
642/// tool awaiting `ctx.cancelled()` observes `tasks/cancel`.
643#[allow(clippy::too_many_arguments)]
644pub async fn run_augmented_tool(
645    handler: std::sync::Arc<dyn DynToolHandler>,
646    handle: crate::capability::tasks::TaskHandle,
647    name: String,
648    args: Object,
649    client_caps: mcpkit_core::capability::ClientCapabilities,
650    server_caps: mcpkit_core::capability::ServerCapabilities,
651    protocol_version: mcpkit_core::protocol_version::ProtocolVersion,
652    peer: std::sync::Arc<dyn crate::context::Peer>,
653) {
654    use crate::context::Context;
655    let request_id = mcpkit_core::protocol::RequestId::String(handle.id().as_str().to_string());
656    let ctx = match handle.cancel_token() {
657        Some(token) => Context::with_cancellation(
658            &request_id,
659            None,
660            &client_caps,
661            &server_caps,
662            protocol_version,
663            peer.as_ref(),
664            token,
665        ),
666        None => Context::new(
667            &request_id,
668            None,
669            &client_caps,
670            &server_caps,
671            protocol_version,
672            peer.as_ref(),
673        ),
674    }
675    // Spec: every request this tool raises while it runs — an
676    // `elicitation/create`, a `sampling/createMessage` — MUST carry the same
677    // related task id. Set once here so all 5 dispatch paths inherit it.
678    .with_related_task(handle.id().clone());
679    match call_tool_json(handler.as_ref(), &name, args, &ctx).await {
680        // Per spec, a tool result with `isError: true` moves the task to
681        // `failed`, while `tasks/result` still returns that result.
682        Ok(payload)
683            if payload
684                .get("isError")
685                .and_then(serde_json::Value::as_bool)
686                .unwrap_or(false) =>
687        {
688            let _ = handle.fail_with_result(payload, Some("tool reported an error".to_string()));
689        }
690        Ok(payload) => {
691            let _ = handle.complete(payload);
692        }
693        // `tasks/result` must reproduce the JSON-RPC error the request would
694        // have returned.
695        Err(e) => {
696            let _ = handle.fail_with_error(e.into());
697        }
698    }
699}
700
701/// Outcome of [`begin_augmented_task`] — the adapter's decision for a
702/// (potentially) task-augmented `tools/call`.
703pub enum AugmentedTaskOutcome {
704    /// Not a task-augmented call (no non-null `task` field, or malformed) — the
705    /// caller should fall through to the normal synchronous `tools/call` path.
706    NotApplicable,
707    /// Rejected before any task was created (e.g. the tool forbids task
708    /// augmentation) — reply with this error.
709    Rejected(McpError),
710    /// A task was created. Reply immediately with the JSON `CreateTaskResult`
711    /// (`.0`), then run the background future (`.1`) on the caller's executor
712    /// (e.g. `tokio::spawn`); it writes the result back into the store.
713    Started(serde_json::Value, futures::future::BoxFuture<'static, ()>),
714}
715
716/// Begin a task-augmented `tools/call` against a per-session task `store`.
717///
718/// Mirrors the stdio runtime's `try_begin_task`: detect the `task` field, gate on
719/// the tool's declared `taskSupport`, create the task, and return the initial
720/// `CreateTaskResult` plus a background future the caller spawns. Only call this
721/// for the `tools/call` method. The `peer` is carried into the background
722/// context (#153), so the tool keeps its server-to-client request capability
723/// after the initial `CreateTaskResult` reply.
724pub async fn begin_augmented_task(
725    handler: std::sync::Arc<dyn DynToolHandler>,
726    store: &std::sync::Arc<crate::capability::tasks::TaskManager>,
727    params: Option<&serde_json::Value>,
728    client_caps: mcpkit_core::capability::ClientCapabilities,
729    server_caps: mcpkit_core::capability::ServerCapabilities,
730    protocol_version: mcpkit_core::protocol_version::ProtocolVersion,
731    peer: std::sync::Arc<dyn crate::context::Peer>,
732) -> AugmentedTaskOutcome {
733    use mcpkit_core::types::TaskSupport;
734
735    let Some(task_meta) = params.and_then(|p| p.get("task")) else {
736        return AugmentedTaskOutcome::NotApplicable;
737    };
738    if task_meta.is_null() {
739        return AugmentedTaskOutcome::NotApplicable;
740    }
741    let Some(name) = params
742        .and_then(|p| p.get("name"))
743        .and_then(|v| v.as_str())
744        .map(str::to_string)
745    else {
746        // Malformed; let the normal path report it.
747        return AugmentedTaskOutcome::NotApplicable;
748    };
749    let args = match params.and_then(|p| p.get("arguments")) {
750        None => Object::new(),
751        Some(Value::Object(map)) => map.clone(),
752        // Malformed; let the normal path report it.
753        Some(_) => return AugmentedTaskOutcome::NotApplicable,
754    };
755    let ttl = task_meta.get("ttl").and_then(serde_json::Value::as_u64);
756
757    // Gate on the tool's declared task support (a `forbidden` tool must not be
758    // task-augmented). The gating context needs no real peer.
759    let support = {
760        use crate::context::{Context, NoOpPeer};
761        let peer = NoOpPeer;
762        let gate_id = mcpkit_core::protocol::RequestId::String("tasks/gate".to_string());
763        let ctx = Context::new(
764            &gate_id,
765            None,
766            &client_caps,
767            &server_caps,
768            protocol_version,
769            &peer,
770        );
771        tool_task_support(handler.as_ref(), &name, &ctx).await
772    };
773    if support == TaskSupport::Forbidden {
774        // Spec: servers SHOULD return -32601 (Method not found) when a client
775        // task-augments a tool whose taskSupport is absent/forbidden.
776        return AugmentedTaskOutcome::Rejected(McpError::JsonRpc(
777            mcpkit_core::error::JsonRpcError::method_not_found(format!(
778                "tool '{name}' does not support task-augmented execution"
779            )),
780        ));
781    }
782
783    let handle = store.create(ttl);
784    let task = handle
785        .task()
786        .unwrap_or_else(|| mcpkit_core::types::Task::new(handle.id().clone()));
787    let create_result =
788        serde_json::to_value(mcpkit_core::types::CreateTaskResult { task, meta: None })
789            .unwrap_or_default();
790    let fut = run_augmented_tool(
791        handler,
792        handle,
793        name,
794        args,
795        client_caps,
796        server_caps,
797        protocol_version,
798        peer,
799    );
800    AugmentedTaskOutcome::Started(create_result, Box::pin(fut))
801}
802
803/// Route resource-related requests to a handler implementing
804/// [`ResourceHandler`](crate::handler::ResourceHandler).
805///
806/// This function handles `resources/list`, `resources/templates/list`, and `resources/read` methods.
807/// Returns `None` if the method is not resource-related.
808///
809/// # Example
810///
811/// ```ignore
812/// if let Some(result) = route_resources(&handler, method, params, &ctx).await {
813///     return result;
814/// }
815/// ```
816pub async fn route_resources(
817    handler: &dyn DynResourceHandler,
818    method: &str,
819    params: Option<&serde_json::Value>,
820    ctx: &Context<'_>,
821    page_size: Option<usize>,
822) -> Option<Result<serde_json::Value, McpError>> {
823    match method {
824        methods::RESOURCES_LIST => {
825            tracing::debug!("Listing available resources");
826            let result = async {
827                let resources = handler.list_resources(ctx).await?;
828                let (page, next) = paginate(
829                    resources,
830                    list_cursor(params),
831                    page_size,
832                    methods::RESOURCES_LIST,
833                )?;
834                tracing::debug!(count = page.len(), "Listed resources");
835                Ok(list_result("resources", page, next))
836            }
837            .await;
838            Some(result)
839        }
840        methods::RESOURCES_TEMPLATES_LIST => {
841            tracing::debug!("Listing available resource templates");
842            let result = async {
843                let templates = handler.list_resource_templates(ctx).await?;
844                let (page, next) = paginate(
845                    templates,
846                    list_cursor(params),
847                    page_size,
848                    methods::RESOURCES_TEMPLATES_LIST,
849                )?;
850                tracing::debug!(count = page.len(), "Listed resource templates");
851                Ok(list_result("resourceTemplates", page, next))
852            }
853            .await;
854            Some(result)
855        }
856        methods::RESOURCES_READ => {
857            let result = async {
858                let params = params.ok_or_else(|| {
859                    McpError::invalid_params(methods::RESOURCES_READ, "missing params")
860                })?;
861                let uri = params.get("uri").and_then(|v| v.as_str()).ok_or_else(|| {
862                    McpError::invalid_params(methods::RESOURCES_READ, "missing uri")
863                })?;
864
865                tracing::info!(uri = %uri, "Reading resource");
866                let start = std::time::Instant::now();
867                let contents = handler.read_resource(uri, ctx).await;
868                let duration = start.elapsed();
869
870                match &contents {
871                    Ok(_) => tracing::info!(
872                        uri = %uri,
873                        duration_ms = duration.as_millis(),
874                        "Resource read completed"
875                    ),
876                    Err(e) => tracing::warn!(
877                        uri = %uri,
878                        duration_ms = duration.as_millis(),
879                        error = %e,
880                        "Resource read failed"
881                    ),
882                }
883
884                let contents = contents?;
885                Ok(serde_json::json!({ "contents": contents }))
886            }
887            .await;
888            Some(result)
889        }
890        methods::RESOURCES_SUBSCRIBE => {
891            let result = async {
892                let params = params.ok_or_else(|| {
893                    McpError::invalid_params(methods::RESOURCES_SUBSCRIBE, "missing params")
894                })?;
895                let req: SubscribeRequest =
896                    serde_json::from_value(params.clone()).map_err(|_| {
897                        McpError::invalid_params(methods::RESOURCES_SUBSCRIBE, "missing uri")
898                    })?;
899                tracing::info!(uri = %req.uri, "Subscribing to resource");
900                if handler.subscribe(&req.uri, ctx).await? {
901                    Ok(serde_json::json!({}))
902                } else {
903                    Err(McpError::internal(format!(
904                        "subscription not established for {}",
905                        req.uri
906                    )))
907                }
908            }
909            .await;
910            Some(result)
911        }
912        methods::RESOURCES_UNSUBSCRIBE => {
913            let result = async {
914                let params = params.ok_or_else(|| {
915                    McpError::invalid_params(methods::RESOURCES_UNSUBSCRIBE, "missing params")
916                })?;
917                let req: UnsubscribeRequest =
918                    serde_json::from_value(params.clone()).map_err(|_| {
919                        McpError::invalid_params(methods::RESOURCES_UNSUBSCRIBE, "missing uri")
920                    })?;
921                tracing::info!(uri = %req.uri, "Unsubscribing from resource");
922                if handler.unsubscribe(&req.uri, ctx).await? {
923                    Ok(serde_json::json!({}))
924                } else {
925                    Err(McpError::internal(format!(
926                        "unsubscribe not honored for {}",
927                        req.uri
928                    )))
929                }
930            }
931            .await;
932            Some(result)
933        }
934        _ => None,
935    }
936}
937
938/// Route prompt-related requests to a handler implementing
939/// [`PromptHandler`](crate::handler::PromptHandler).
940///
941/// This function handles `prompts/list` and `prompts/get` methods.
942/// Returns `None` if the method is not prompt-related.
943///
944/// # Example
945///
946/// ```ignore
947/// if let Some(result) = route_prompts(&handler, method, params, &ctx).await {
948///     return result;
949/// }
950/// ```
951pub async fn route_prompts(
952    handler: &dyn DynPromptHandler,
953    method: &str,
954    params: Option<&serde_json::Value>,
955    ctx: &Context<'_>,
956    page_size: Option<usize>,
957) -> Option<Result<serde_json::Value, McpError>> {
958    match method {
959        methods::PROMPTS_LIST => {
960            tracing::debug!("Listing available prompts");
961            let result = async {
962                let prompts = handler.list_prompts(ctx).await?;
963                let (page, next) = paginate(
964                    prompts,
965                    list_cursor(params),
966                    page_size,
967                    methods::PROMPTS_LIST,
968                )?;
969                tracing::debug!(count = page.len(), "Listed prompts");
970                Ok(list_result("prompts", page, next))
971            }
972            .await;
973            Some(result)
974        }
975        methods::PROMPTS_GET => {
976            let result = async {
977                let params = params.ok_or_else(|| {
978                    McpError::invalid_params(methods::PROMPTS_GET, "missing params")
979                })?;
980                let name = params.get("name").and_then(|v| v.as_str()).ok_or_else(|| {
981                    McpError::invalid_params(methods::PROMPTS_GET, "missing prompt name")
982                })?;
983                let args = match params.get("arguments") {
984                    None => None,
985                    Some(Value::Object(map)) => Some(map.clone()),
986                    Some(_) => {
987                        return Err(McpError::invalid_params(
988                            methods::PROMPTS_GET,
989                            "arguments must be an object",
990                        ));
991                    }
992                };
993
994                tracing::info!(prompt = %name, "Getting prompt");
995                let start = std::time::Instant::now();
996                let prompt_result = handler.get_prompt(name, args, ctx).await;
997                let duration = start.elapsed();
998
999                match &prompt_result {
1000                    Ok(_) => tracing::info!(
1001                        prompt = %name,
1002                        duration_ms = duration.as_millis(),
1003                        "Prompt retrieval completed"
1004                    ),
1005                    Err(e) => tracing::warn!(
1006                        prompt = %name,
1007                        duration_ms = duration.as_millis(),
1008                        error = %e,
1009                        "Prompt retrieval failed"
1010                    ),
1011                }
1012
1013                let result = prompt_result?;
1014                Ok(serde_json::to_value(result).unwrap_or_else(|_| serde_json::json!({})))
1015            }
1016            .await;
1017            Some(result)
1018        }
1019        _ => None,
1020    }
1021}
1022
1023/// Route task-related requests to a handler implementing
1024/// [`TaskHandler`](crate::handler::TaskHandler).
1025///
1026/// Handles `tasks/list`, `tasks/get`, and `tasks/cancel`. Returns `None` if the
1027/// method is not task-related. (`tasks/result` is handled by the task-augmented
1028/// call flow, not here.)
1029pub async fn route_tasks(
1030    handler: &dyn DynTaskHandler,
1031    method: &str,
1032    params: Option<&serde_json::Value>,
1033    ctx: &Context<'_>,
1034) -> Option<Result<serde_json::Value, McpError>> {
1035    match method {
1036        methods::TASKS_LIST => {
1037            let result = handler.list_tasks(ctx).await;
1038            Some(result.map(|r| serde_json::to_value(r).unwrap_or_default()))
1039        }
1040        methods::TASKS_GET => {
1041            let result = async {
1042                let id = parse_task_id(params, methods::TASKS_GET)?;
1043                match handler.get_task(&id, ctx).await? {
1044                    Some(result) => Ok(serde_json::to_value(result).unwrap_or_default()),
1045                    None => Err(McpError::invalid_params(
1046                        methods::TASKS_GET,
1047                        format!("unknown task: {id}"),
1048                    )),
1049                }
1050            }
1051            .await;
1052            Some(result)
1053        }
1054        methods::TASKS_CANCEL => {
1055            let result = async {
1056                let id = parse_task_id(params, methods::TASKS_CANCEL)?;
1057                // `Ok(None)` is an unknown task; a real cancellation failure
1058                // propagates as `Err` from the handler.
1059                match handler.cancel_task(&id, ctx).await? {
1060                    Some(result) => Ok(serde_json::to_value(result).unwrap_or_default()),
1061                    None => Err(McpError::invalid_params(
1062                        methods::TASKS_CANCEL,
1063                        format!("unknown task: {id}"),
1064                    )),
1065                }
1066            }
1067            .await;
1068            Some(result)
1069        }
1070        _ => None,
1071    }
1072}
1073
1074/// Route `logging/setLevel` to the base handler's
1075/// [`set_log_level`](crate::handler::ServerHandler::set_log_level), but only when
1076/// the server advertises the `logging` capability.
1077///
1078/// Returns `None` for any other method (or when logging is not advertised) so the
1079/// caller falls through to its normal not-found handling. Shared by the runtime
1080/// router and the HTTP adapters so `logging/setLevel` behaves the same on every
1081/// surface.
1082pub async fn route_logging<H: crate::handler::ServerHandler>(
1083    handler: &H,
1084    server_caps: &mcpkit_core::capability::ServerCapabilities,
1085    method: &str,
1086    params: Option<&serde_json::Value>,
1087    ctx: &Context<'_>,
1088) -> Option<Result<serde_json::Value, McpError>> {
1089    if method != methods::LOGGING_SET_LEVEL || !server_caps.has_logging() {
1090        return None;
1091    }
1092    let result = async {
1093        let params = params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
1094        let req: mcpkit_core::types::SetLevelRequest = serde_json::from_value(params.clone())
1095            .map_err(|_| McpError::invalid_params(method, "invalid or missing level"))?;
1096        handler.set_log_level(req.level, ctx).await?;
1097        Ok(serde_json::json!({}))
1098    }
1099    .await;
1100    Some(result)
1101}
1102
1103/// Route `completion/complete` to a registered completion handler.
1104///
1105/// Returns `None` when the method is not `completion/complete` or no completion
1106/// handler is registered (so the caller falls through to its normal not-found
1107/// handling). Shared by the runtime and the framework adapters so completion is
1108/// dispatched consistently wherever the server routes requests. The response's
1109/// `values` are capped at [`MAX_COMPLETION_VALUES`](mcpkit_core::types::MAX_COMPLETION_VALUES).
1110pub async fn route_completion(
1111    handler: Option<&dyn DynCompletionHandler>,
1112    method: &str,
1113    params: Option<&serde_json::Value>,
1114    ctx: &Context<'_>,
1115) -> Option<Result<serde_json::Value, McpError>> {
1116    if method != methods::COMPLETION_COMPLETE {
1117        return None;
1118    }
1119    let handler = handler?;
1120    let result = async {
1121        let params = params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
1122        let req: CompleteRequest = serde_json::from_value(params.clone()).map_err(|e| {
1123            McpError::invalid_params(method, format!("invalid completion request: {e}"))
1124        })?;
1125        // Cap the values to the spec limit while preserving any result-level
1126        // `_meta` the handler attached.
1127        let CompleteResult { completion, meta } = handler.complete(&req, ctx).await?;
1128        let result = CompleteResult {
1129            completion: completion.capped(),
1130            meta,
1131        };
1132        Ok(serde_json::to_value(result).unwrap_or_default())
1133    }
1134    .await;
1135    Some(result)
1136}
1137
1138/// Extract a required `taskId` parameter.
1139fn parse_task_id(
1140    params: Option<&serde_json::Value>,
1141    method: &'static str,
1142) -> Result<TaskId, McpError> {
1143    let task_id = params
1144        .and_then(|p| p.get("taskId"))
1145        .and_then(|v| v.as_str())
1146        .ok_or_else(|| McpError::invalid_params(method, "missing taskId"))?;
1147    Ok(TaskId::new(task_id))
1148}
1149
1150#[cfg(test)]
1151mod tests {
1152    use super::*;
1153    use mcpkit_core::protocol::Request;
1154
1155    fn make_request(method: &'static str, params: Option<Value>) -> Request {
1156        if let Some(p) = params {
1157            Request::with_params(method, 1u64, p)
1158        } else {
1159            Request::new(method, 1u64)
1160        }
1161    }
1162
1163    #[test]
1164    fn test_parse_ping() -> Result<(), Box<dyn std::error::Error>> {
1165        let request = make_request("ping", None);
1166        let parsed = parse_request(&request)?;
1167        assert!(matches!(parsed, ParsedRequest::Ping));
1168
1169        Ok(())
1170    }
1171
1172    #[test]
1173    fn test_parse_tools_list() -> Result<(), Box<dyn std::error::Error>> {
1174        let request = make_request("tools/list", None);
1175        let parsed = parse_request(&request)?;
1176        assert!(matches!(parsed, ParsedRequest::ToolsList(_)));
1177
1178        Ok(())
1179    }
1180
1181    #[test]
1182    fn test_parse_tools_list_with_cursor() -> Result<(), Box<dyn std::error::Error>> {
1183        let request = make_request(
1184            "tools/list",
1185            Some(serde_json::json!({ "cursor": "abc123" })),
1186        );
1187        let parsed = parse_request(&request)?;
1188
1189        if let ParsedRequest::ToolsList(params) = parsed {
1190            assert_eq!(params.cursor, Some("abc123".to_string()));
1191        } else {
1192            panic!("Expected ToolsList");
1193        }
1194
1195        Ok(())
1196    }
1197
1198    #[test]
1199    fn test_parse_tools_call() -> Result<(), Box<dyn std::error::Error>> {
1200        let request = make_request(
1201            "tools/call",
1202            Some(serde_json::json!({
1203                "name": "search",
1204                "arguments": {"query": "test"}
1205            })),
1206        );
1207        let parsed = parse_request(&request)?;
1208
1209        if let ParsedRequest::ToolsCall(params) = parsed {
1210            assert_eq!(params.name, "search");
1211            assert_eq!(params.arguments["query"], "test");
1212        } else {
1213            panic!("Expected ToolsCall");
1214        }
1215
1216        Ok(())
1217    }
1218
1219    #[test]
1220    fn test_parse_tools_call_missing_params() {
1221        let request = make_request("tools/call", None);
1222        let result = parse_request(&request);
1223        assert!(result.is_err());
1224    }
1225
1226    #[test]
1227    fn test_parse_tools_call_missing_name() {
1228        let request = make_request("tools/call", Some(serde_json::json!({"arguments": {}})));
1229        let result = parse_request(&request);
1230        assert!(result.is_err());
1231    }
1232
1233    #[test]
1234    fn test_parse_tools_call_rejects_non_object_arguments() {
1235        let request = make_request(
1236            "tools/call",
1237            Some(serde_json::json!({ "name": "search", "arguments": 5 })),
1238        );
1239        let err = parse_request(&request).expect_err("non-object arguments must be rejected");
1240        assert!(
1241            err.to_string().contains("arguments must be an object"),
1242            "expected invalid-params on arguments, got: {err}"
1243        );
1244    }
1245
1246    #[test]
1247    fn test_parse_prompts_get_rejects_non_object_arguments() {
1248        let request = make_request(
1249            "prompts/get",
1250            Some(serde_json::json!({ "name": "code-review", "arguments": ["rust"] })),
1251        );
1252        let err = parse_request(&request).expect_err("non-object arguments must be rejected");
1253        assert!(
1254            err.to_string().contains("arguments must be an object"),
1255            "expected invalid-params on arguments, got: {err}"
1256        );
1257    }
1258
1259    #[test]
1260    fn test_parse_unknown_method() -> Result<(), Box<dyn std::error::Error>> {
1261        let request = make_request("unknown/method", None);
1262        let parsed = parse_request(&request)?;
1263
1264        if let ParsedRequest::Unknown(method) = parsed {
1265            assert_eq!(method, "unknown/method");
1266        } else {
1267            panic!("Expected Unknown");
1268        }
1269
1270        Ok(())
1271    }
1272
1273    #[test]
1274    fn test_parse_initialize() -> Result<(), Box<dyn std::error::Error>> {
1275        let request = make_request(
1276            "initialize",
1277            Some(serde_json::json!({
1278                "protocolVersion": "2025-11-25",
1279                "clientInfo": {
1280                    "name": "test-client",
1281                    "version": "1.0.0"
1282                },
1283                "capabilities": {}
1284            })),
1285        );
1286        let parsed = parse_request(&request)?;
1287
1288        if let ParsedRequest::Initialize(params) = parsed {
1289            assert_eq!(params.protocol_version, "2025-11-25");
1290            assert_eq!(params.client_info.name, "test-client");
1291            assert_eq!(params.client_info.version, "1.0.0");
1292        } else {
1293            panic!("Expected Initialize");
1294        }
1295
1296        Ok(())
1297    }
1298
1299    #[test]
1300    fn test_parse_initialize_missing_params() {
1301        let request = make_request("initialize", None);
1302        let result = parse_request(&request);
1303        assert!(result.is_err());
1304    }
1305
1306    // =========================================================================
1307    // Resource Methods
1308    // =========================================================================
1309
1310    #[test]
1311    fn test_parse_resources_list() -> Result<(), Box<dyn std::error::Error>> {
1312        let request = make_request("resources/list", None);
1313        let parsed = parse_request(&request)?;
1314        assert!(matches!(parsed, ParsedRequest::ResourcesList(_)));
1315        Ok(())
1316    }
1317
1318    #[test]
1319    fn test_parse_resources_list_with_cursor() -> Result<(), Box<dyn std::error::Error>> {
1320        let request = make_request(
1321            "resources/list",
1322            Some(serde_json::json!({ "cursor": "page2" })),
1323        );
1324        let parsed = parse_request(&request)?;
1325
1326        if let ParsedRequest::ResourcesList(params) = parsed {
1327            assert_eq!(params.cursor, Some("page2".to_string()));
1328        } else {
1329            panic!("Expected ResourcesList");
1330        }
1331        Ok(())
1332    }
1333
1334    #[test]
1335    fn test_parse_resources_read() -> Result<(), Box<dyn std::error::Error>> {
1336        let request = make_request(
1337            "resources/read",
1338            Some(serde_json::json!({ "uri": "file:///etc/hosts" })),
1339        );
1340        let parsed = parse_request(&request)?;
1341
1342        if let ParsedRequest::ResourcesRead(params) = parsed {
1343            assert_eq!(params.uri, "file:///etc/hosts");
1344        } else {
1345            panic!("Expected ResourcesRead");
1346        }
1347        Ok(())
1348    }
1349
1350    #[test]
1351    fn test_parse_resources_read_missing_uri() {
1352        let request = make_request("resources/read", Some(serde_json::json!({})));
1353        let result = parse_request(&request);
1354        assert!(result.is_err());
1355    }
1356
1357    #[test]
1358    fn test_parse_resources_templates_list() -> Result<(), Box<dyn std::error::Error>> {
1359        let request = make_request("resources/templates/list", None);
1360        let parsed = parse_request(&request)?;
1361        assert!(matches!(parsed, ParsedRequest::ResourcesTemplatesList(_)));
1362        Ok(())
1363    }
1364
1365    #[test]
1366    fn test_parse_resources_subscribe() -> Result<(), Box<dyn std::error::Error>> {
1367        let request = make_request(
1368            "resources/subscribe",
1369            Some(serde_json::json!({ "uri": "file:///var/log/app.log" })),
1370        );
1371        let parsed = parse_request(&request)?;
1372
1373        if let ParsedRequest::ResourcesSubscribe(params) = parsed {
1374            assert_eq!(params.uri, "file:///var/log/app.log");
1375        } else {
1376            panic!("Expected ResourcesSubscribe");
1377        }
1378        Ok(())
1379    }
1380
1381    #[test]
1382    fn test_parse_resources_unsubscribe() -> Result<(), Box<dyn std::error::Error>> {
1383        let request = make_request(
1384            "resources/unsubscribe",
1385            Some(serde_json::json!({ "uri": "file:///var/log/app.log" })),
1386        );
1387        let parsed = parse_request(&request)?;
1388
1389        if let ParsedRequest::ResourcesUnsubscribe(params) = parsed {
1390            assert_eq!(params.uri, "file:///var/log/app.log");
1391        } else {
1392            panic!("Expected ResourcesUnsubscribe");
1393        }
1394        Ok(())
1395    }
1396
1397    // =========================================================================
1398    // Prompt Methods
1399    // =========================================================================
1400
1401    #[test]
1402    fn test_parse_prompts_list() -> Result<(), Box<dyn std::error::Error>> {
1403        let request = make_request("prompts/list", None);
1404        let parsed = parse_request(&request)?;
1405        assert!(matches!(parsed, ParsedRequest::PromptsList(_)));
1406        Ok(())
1407    }
1408
1409    #[test]
1410    fn test_parse_prompts_get() -> Result<(), Box<dyn std::error::Error>> {
1411        let request = make_request(
1412            "prompts/get",
1413            Some(serde_json::json!({
1414                "name": "code-review",
1415                "arguments": { "language": "rust" }
1416            })),
1417        );
1418        let parsed = parse_request(&request)?;
1419
1420        if let ParsedRequest::PromptsGet(params) = parsed {
1421            assert_eq!(params.name, "code-review");
1422            assert!(params.arguments.is_some());
1423        } else {
1424            panic!("Expected PromptsGet");
1425        }
1426        Ok(())
1427    }
1428
1429    #[test]
1430    fn test_parse_prompts_get_without_arguments() -> Result<(), Box<dyn std::error::Error>> {
1431        let request = make_request(
1432            "prompts/get",
1433            Some(serde_json::json!({ "name": "simple-prompt" })),
1434        );
1435        let parsed = parse_request(&request)?;
1436
1437        if let ParsedRequest::PromptsGet(params) = parsed {
1438            assert_eq!(params.name, "simple-prompt");
1439            assert!(params.arguments.is_none());
1440        } else {
1441            panic!("Expected PromptsGet");
1442        }
1443        Ok(())
1444    }
1445
1446    #[test]
1447    fn test_parse_prompts_get_missing_name() {
1448        let request = make_request("prompts/get", Some(serde_json::json!({})));
1449        let result = parse_request(&request);
1450        assert!(result.is_err());
1451    }
1452
1453    // =========================================================================
1454    // Task Methods
1455    // =========================================================================
1456
1457    #[test]
1458    fn test_parse_tasks_list() -> Result<(), Box<dyn std::error::Error>> {
1459        let request = make_request("tasks/list", None);
1460        let parsed = parse_request(&request)?;
1461        assert!(matches!(parsed, ParsedRequest::TasksList(_)));
1462        Ok(())
1463    }
1464
1465    #[test]
1466    fn test_parse_tasks_get() -> Result<(), Box<dyn std::error::Error>> {
1467        let request = make_request(
1468            "tasks/get",
1469            Some(serde_json::json!({ "taskId": "task-123" })),
1470        );
1471        let parsed = parse_request(&request)?;
1472
1473        if let ParsedRequest::TasksGet(params) = parsed {
1474            assert_eq!(params.task_id, "task-123");
1475        } else {
1476            panic!("Expected TasksGet");
1477        }
1478        Ok(())
1479    }
1480
1481    #[test]
1482    fn test_parse_tasks_get_missing_id() {
1483        let request = make_request("tasks/get", Some(serde_json::json!({})));
1484        let result = parse_request(&request);
1485        assert!(result.is_err());
1486    }
1487
1488    #[test]
1489    fn test_parse_tasks_cancel() -> Result<(), Box<dyn std::error::Error>> {
1490        let request = make_request(
1491            "tasks/cancel",
1492            Some(serde_json::json!({ "taskId": "task-456" })),
1493        );
1494        let parsed = parse_request(&request)?;
1495
1496        if let ParsedRequest::TasksCancel(params) = parsed {
1497            assert_eq!(params.task_id, "task-456");
1498        } else {
1499            panic!("Expected TasksCancel");
1500        }
1501        Ok(())
1502    }
1503
1504    // =========================================================================
1505    // Sampling Methods
1506    // =========================================================================
1507
1508    #[test]
1509    fn test_parse_sampling_create_message() -> Result<(), Box<dyn std::error::Error>> {
1510        let request = make_request(
1511            "sampling/createMessage",
1512            Some(serde_json::json!({
1513                "messages": [
1514                    { "role": "user", "content": { "type": "text", "text": "Hello" } }
1515                ],
1516                "maxTokens": 100,
1517                "systemPrompt": "You are a helpful assistant."
1518            })),
1519        );
1520        let parsed = parse_request(&request)?;
1521
1522        if let ParsedRequest::SamplingCreateMessage(params) = parsed {
1523            assert_eq!(params.messages.len(), 1);
1524            assert_eq!(params.max_tokens, Some(100));
1525            assert_eq!(
1526                params.system_prompt,
1527                Some("You are a helpful assistant.".to_string())
1528            );
1529        } else {
1530            panic!("Expected SamplingCreateMessage");
1531        }
1532        Ok(())
1533    }
1534
1535    #[test]
1536    fn test_parse_sampling_create_message_missing_messages() {
1537        let request = make_request("sampling/createMessage", Some(serde_json::json!({})));
1538        let result = parse_request(&request);
1539        assert!(result.is_err());
1540    }
1541
1542    // =========================================================================
1543    // Completion Methods
1544    // =========================================================================
1545
1546    #[test]
1547    fn test_parse_completion_complete() -> Result<(), Box<dyn std::error::Error>> {
1548        let request = make_request(
1549            "completion/complete",
1550            Some(serde_json::json!({
1551                "ref": {
1552                    "type": "ref/resource",
1553                    "uri": "file:///home"
1554                },
1555                "argument": {
1556                    "name": "path",
1557                    "value": "/home/user"
1558                }
1559            })),
1560        );
1561        let parsed = parse_request(&request)?;
1562
1563        if let ParsedRequest::CompletionComplete(params) = parsed {
1564            assert_eq!(params.ref_type, "ref/resource");
1565            assert_eq!(params.ref_value, "file:///home");
1566            assert!(params.argument.is_some());
1567            let arg = params.argument.unwrap();
1568            assert_eq!(arg.name, "path");
1569            assert_eq!(arg.value, "/home/user");
1570        } else {
1571            panic!("Expected CompletionComplete");
1572        }
1573        Ok(())
1574    }
1575
1576    #[test]
1577    fn test_parse_completion_complete_prompt_ref() -> Result<(), Box<dyn std::error::Error>> {
1578        let request = make_request(
1579            "completion/complete",
1580            Some(serde_json::json!({
1581                "ref": {
1582                    "type": "ref/prompt",
1583                    "name": "code-review"
1584                }
1585            })),
1586        );
1587        let parsed = parse_request(&request)?;
1588
1589        if let ParsedRequest::CompletionComplete(params) = parsed {
1590            assert_eq!(params.ref_type, "ref/prompt");
1591            assert_eq!(params.ref_value, "code-review");
1592            assert!(params.argument.is_none());
1593        } else {
1594            panic!("Expected CompletionComplete");
1595        }
1596        Ok(())
1597    }
1598
1599    #[test]
1600    fn test_parse_completion_complete_missing_ref() {
1601        let request = make_request("completion/complete", Some(serde_json::json!({})));
1602        let result = parse_request(&request);
1603        assert!(result.is_err());
1604    }
1605
1606    // =========================================================================
1607    // Logging Methods
1608    // =========================================================================
1609
1610    #[test]
1611    fn test_parse_logging_set_level() -> Result<(), Box<dyn std::error::Error>> {
1612        let request = make_request(
1613            "logging/setLevel",
1614            Some(serde_json::json!({ "level": "debug" })),
1615        );
1616        let parsed = parse_request(&request)?;
1617
1618        if let ParsedRequest::LoggingSetLevel(params) = parsed {
1619            assert_eq!(params.level, "debug");
1620        } else {
1621            panic!("Expected LoggingSetLevel");
1622        }
1623        Ok(())
1624    }
1625
1626    #[test]
1627    fn test_parse_logging_set_level_missing_level() {
1628        let request = make_request("logging/setLevel", Some(serde_json::json!({})));
1629        let result = parse_request(&request);
1630        assert!(result.is_err());
1631    }
1632
1633    // =========================================================================
1634    // Method Constants
1635    // =========================================================================
1636
1637    #[test]
1638    fn test_method_constants() {
1639        // Verify method constants match the strings used in parsing
1640        assert_eq!(methods::INITIALIZE, "initialize");
1641        assert_eq!(methods::PING, "ping");
1642        assert_eq!(methods::TOOLS_LIST, "tools/list");
1643        assert_eq!(methods::TOOLS_CALL, "tools/call");
1644        assert_eq!(methods::RESOURCES_LIST, "resources/list");
1645        assert_eq!(methods::RESOURCES_READ, "resources/read");
1646        assert_eq!(
1647            methods::RESOURCES_TEMPLATES_LIST,
1648            "resources/templates/list"
1649        );
1650        assert_eq!(methods::RESOURCES_SUBSCRIBE, "resources/subscribe");
1651        assert_eq!(methods::RESOURCES_UNSUBSCRIBE, "resources/unsubscribe");
1652        assert_eq!(methods::PROMPTS_LIST, "prompts/list");
1653        assert_eq!(methods::PROMPTS_GET, "prompts/get");
1654        assert_eq!(methods::TASKS_LIST, "tasks/list");
1655        assert_eq!(methods::TASKS_GET, "tasks/get");
1656        assert_eq!(methods::TASKS_CANCEL, "tasks/cancel");
1657        assert_eq!(methods::SAMPLING_CREATE_MESSAGE, "sampling/createMessage");
1658        assert_eq!(methods::COMPLETION_COMPLETE, "completion/complete");
1659        assert_eq!(methods::LOGGING_SET_LEVEL, "logging/setLevel");
1660    }
1661
1662    // =========================================================================
1663    // Notification Constants
1664    // =========================================================================
1665
1666    #[test]
1667    fn test_notification_constants() {
1668        assert_eq!(notifications::INITIALIZED, "notifications/initialized");
1669        assert_eq!(notifications::CANCELLED, "notifications/cancelled");
1670        assert_eq!(notifications::PROGRESS, "notifications/progress");
1671        assert_eq!(notifications::MESSAGE, "notifications/message");
1672        assert_eq!(
1673            notifications::RESOURCES_UPDATED,
1674            "notifications/resources/updated"
1675        );
1676        assert_eq!(
1677            notifications::RESOURCES_LIST_CHANGED,
1678            "notifications/resources/list_changed"
1679        );
1680        assert_eq!(
1681            notifications::TOOLS_LIST_CHANGED,
1682            "notifications/tools/list_changed"
1683        );
1684        assert_eq!(
1685            notifications::PROMPTS_LIST_CHANGED,
1686            "notifications/prompts/list_changed"
1687        );
1688        assert_eq!(
1689            notifications::ROOTS_LIST_CHANGED,
1690            "notifications/roots/list_changed"
1691        );
1692        assert_eq!(
1693            notifications::ELICITATION_COMPLETE,
1694            "notifications/elicitation/complete"
1695        );
1696        assert_eq!(notifications::TASK_STATUS, "notifications/tasks/status");
1697    }
1698
1699    #[tokio::test]
1700    async fn route_tasks_dispatches_list_get_cancel() {
1701        use crate::capability::tasks::TaskService;
1702        use crate::context::NoOpPeer;
1703        use mcpkit_core::capability::{ClientCapabilities, ServerCapabilities};
1704        use mcpkit_core::protocol::RequestId;
1705        use mcpkit_core::protocol_version::ProtocolVersion;
1706
1707        let service = TaskService::new();
1708        let request_id = RequestId::Number(1);
1709        let client_caps = ClientCapabilities::default();
1710        let server_caps = ServerCapabilities::default();
1711        let peer = NoOpPeer;
1712        let ctx = Context::new(
1713            &request_id,
1714            None,
1715            &client_caps,
1716            &server_caps,
1717            ProtocolVersion::LATEST,
1718            &peer,
1719        );
1720
1721        // tasks/list -> { "tasks": [] } (no tasks registered).
1722        let listed = route_tasks(&service, methods::TASKS_LIST, None, &ctx)
1723            .await
1724            .expect("tasks/list is routed")
1725            .expect("ok");
1726        assert_eq!(listed, serde_json::json!({ "tasks": [] }));
1727
1728        // tasks/get with a missing taskId is a routed error.
1729        let got = route_tasks(&service, methods::TASKS_GET, None, &ctx)
1730            .await
1731            .expect("tasks/get is routed");
1732        assert!(got.is_err());
1733
1734        // An unknown task id is a routed error, not a panic.
1735        let cancel = route_tasks(
1736            &service,
1737            methods::TASKS_CANCEL,
1738            Some(&serde_json::json!({ "taskId": "nope" })),
1739            &ctx,
1740        )
1741        .await
1742        .expect("tasks/cancel is routed");
1743        assert!(cancel.is_err());
1744
1745        // A non-task method is not handled here.
1746        assert!(
1747            route_tasks(&service, methods::TOOLS_LIST, None, &ctx)
1748                .await
1749                .is_none()
1750        );
1751    }
1752
1753    #[tokio::test]
1754    async fn route_tasks_list_get_and_cancel_emit_result_meta() {
1755        use crate::context::NoOpPeer;
1756        use mcpkit_core::capability::{ClientCapabilities, ServerCapabilities};
1757        use mcpkit_core::protocol::RequestId;
1758        use mcpkit_core::protocol_version::ProtocolVersion;
1759        use mcpkit_core::types::{
1760            CancelTaskResult, GetTaskResult, ListTasksResult, Meta, Task, TaskId,
1761        };
1762
1763        // A handler that attaches result-level `_meta` on list/get/cancel and a
1764        // `nextCursor` on list.
1765        struct MetaTaskHandler;
1766        impl crate::handler::TaskHandler for MetaTaskHandler {
1767            async fn list_tasks(&self, _ctx: &Context<'_>) -> Result<ListTasksResult, McpError> {
1768                Ok(ListTasksResult {
1769                    tasks: vec![],
1770                    next_cursor: Some("page-2".to_string()),
1771                    meta: Some(Meta::new().with("origin", serde_json::json!("test"))),
1772                })
1773            }
1774            async fn get_task(
1775                &self,
1776                id: &TaskId,
1777                _ctx: &Context<'_>,
1778            ) -> Result<Option<GetTaskResult>, McpError> {
1779                let meta = Meta::new().with("origin", serde_json::json!("test"));
1780                Ok(Some(
1781                    GetTaskResult::from(Task::new(id.clone())).with_meta(meta),
1782                ))
1783            }
1784            async fn cancel_task(
1785                &self,
1786                id: &TaskId,
1787                _ctx: &Context<'_>,
1788            ) -> Result<Option<CancelTaskResult>, McpError> {
1789                let meta = Meta::new().with("origin", serde_json::json!("test"));
1790                Ok(Some(
1791                    CancelTaskResult::from(Task::new(id.clone())).with_meta(meta),
1792                ))
1793            }
1794        }
1795
1796        let handler = MetaTaskHandler;
1797        let request_id = RequestId::Number(1);
1798        let client_caps = ClientCapabilities::default();
1799        let server_caps = ServerCapabilities::default();
1800        let peer = NoOpPeer;
1801        let ctx = Context::new(
1802            &request_id,
1803            None,
1804            &client_caps,
1805            &server_caps,
1806            ProtocolVersion::LATEST,
1807            &peer,
1808        );
1809        let params = serde_json::json!({ "taskId": "t-1" });
1810
1811        for method in [methods::TASKS_GET, methods::TASKS_CANCEL] {
1812            let resp = route_tasks(&handler, method, Some(&params), &ctx)
1813                .await
1814                .expect("routed")
1815                .expect("ok");
1816            // Task fields flattened at the top level, plus result-level `_meta`.
1817            assert_eq!(resp["taskId"], "t-1", "{method}");
1818            assert_eq!(resp["_meta"]["origin"], "test", "{method}");
1819        }
1820
1821        // tasks/list now carries `nextCursor` + result-level `_meta` through the
1822        // `ListTasksResult` wrapper (previously hand-built as `{ "tasks": .. }`).
1823        let listed = route_tasks(&handler, methods::TASKS_LIST, None, &ctx)
1824            .await
1825            .expect("routed")
1826            .expect("ok");
1827        assert_eq!(listed["nextCursor"], "page-2");
1828        assert_eq!(listed["_meta"]["origin"], "test");
1829    }
1830
1831    #[tokio::test]
1832    async fn route_completion_dispatches_with_context_and_caps_values() {
1833        use crate::context::NoOpPeer;
1834        use crate::dispatch::DynCompletionHandler;
1835        use mcpkit_core::capability::{ClientCapabilities, ServerCapabilities};
1836        use mcpkit_core::protocol::RequestId;
1837        use mcpkit_core::protocol_version::ProtocolVersion;
1838        use mcpkit_core::types::{CompleteRequest, CompleteResult, Completion, Meta};
1839
1840        // Reads `context.arguments.owner`, returns more than the 100-value cap,
1841        // and attaches result-level `_meta`.
1842        struct CtxCompletion;
1843        impl crate::handler::CompletionHandler for CtxCompletion {
1844            async fn complete(
1845                &self,
1846                request: &CompleteRequest,
1847                _ctx: &Context<'_>,
1848            ) -> Result<CompleteResult, McpError> {
1849                let owner = request
1850                    .context
1851                    .as_ref()
1852                    .and_then(|c| c.arguments.as_ref())
1853                    .and_then(|a| a.get("owner").cloned())
1854                    .unwrap_or_default();
1855                let completion = Completion {
1856                    values: (0..150).map(|i| format!("{owner}-{i}")).collect(),
1857                    total: Some(150),
1858                    has_more: Some(false),
1859                };
1860                Ok(CompleteResult::from(completion)
1861                    .with_meta(Meta::new().with("origin", serde_json::json!("test"))))
1862            }
1863        }
1864
1865        let handler = CtxCompletion;
1866        let dyn_handler: &dyn DynCompletionHandler = &handler;
1867        let request_id = RequestId::Number(1);
1868        let client_caps = ClientCapabilities::default();
1869        let server_caps = ServerCapabilities::default();
1870        let peer = NoOpPeer;
1871        let ctx = Context::new(
1872            &request_id,
1873            None,
1874            &client_caps,
1875            &server_caps,
1876            ProtocolVersion::LATEST,
1877            &peer,
1878        );
1879        let params = serde_json::json!({
1880            "ref": {"type": "ref/prompt", "name": "p"},
1881            "argument": {"name": "a", "value": "x"},
1882            "context": {"arguments": {"owner": "acme"}}
1883        });
1884
1885        // No handler registered -> not dispatched (caller yields method-not-found).
1886        assert!(
1887            route_completion(None, methods::COMPLETION_COMPLETE, Some(&params), &ctx)
1888                .await
1889                .is_none()
1890        );
1891        // A non-completion method is not handled here.
1892        assert!(
1893            route_completion(Some(dyn_handler), methods::TOOLS_LIST, None, &ctx)
1894                .await
1895                .is_none()
1896        );
1897
1898        // Dispatched: context propagates and the 100-value cap is enforced.
1899        let resp = route_completion(
1900            Some(dyn_handler),
1901            methods::COMPLETION_COMPLETE,
1902            Some(&params),
1903            &ctx,
1904        )
1905        .await
1906        .expect("routed")
1907        .expect("ok");
1908        let values = resp["completion"]["values"].as_array().expect("values");
1909        assert_eq!(values.len(), 100); // capped from 150
1910        assert_eq!(values[0], "acme-0"); // context.arguments propagated
1911        assert_eq!(resp["completion"]["hasMore"], true); // forced true by the cap
1912        assert_eq!(resp["completion"]["total"], 150); // handler total preserved
1913        assert_eq!(resp["_meta"]["origin"], "test"); // handler result-level _meta
1914    }
1915
1916    #[tokio::test]
1917    async fn route_tools_paginates_tools_list() {
1918        use crate::context::NoOpPeer;
1919        use crate::handler::ToolHandler;
1920        use mcpkit_core::capability::{ClientCapabilities, ServerCapabilities};
1921        use mcpkit_core::protocol::RequestId;
1922        use mcpkit_core::protocol_version::ProtocolVersion;
1923        use mcpkit_core::types::{Tool, ToolOutput};
1924
1925        struct Tools;
1926        impl ToolHandler for Tools {
1927            async fn list_tools(&self, _ctx: &Context<'_>) -> Result<Vec<Tool>, McpError> {
1928                Ok(vec![Tool::new("a"), Tool::new("b"), Tool::new("c")])
1929            }
1930            async fn call_tool(
1931                &self,
1932                _name: &str,
1933                _args: serde_json::Map<String, Value>,
1934                _ctx: &Context<'_>,
1935            ) -> Result<ToolOutput, McpError> {
1936                Ok(ToolOutput::text("x"))
1937            }
1938        }
1939
1940        let handler = Tools;
1941        let request_id = RequestId::Number(1);
1942        let client_caps = ClientCapabilities::default();
1943        let server_caps = ServerCapabilities::default();
1944        let peer = NoOpPeer;
1945        let ctx = Context::new(
1946            &request_id,
1947            None,
1948            &client_caps,
1949            &server_caps,
1950            ProtocolVersion::LATEST,
1951            &peer,
1952        );
1953
1954        // Page 1 of size 2 -> two tools plus a nextCursor.
1955        let page1 = route_tools(&handler, methods::TOOLS_LIST, None, &ctx, Some(2))
1956            .await
1957            .unwrap()
1958            .unwrap();
1959        assert_eq!(page1["tools"].as_array().unwrap().len(), 2);
1960        let cursor = page1["nextCursor"]
1961            .as_str()
1962            .expect("nextCursor")
1963            .to_string();
1964
1965        // Page 2 via the cursor -> the last tool, no further cursor.
1966        let params = serde_json::json!({ "cursor": cursor });
1967        let page2 = route_tools(&handler, methods::TOOLS_LIST, Some(&params), &ctx, Some(2))
1968            .await
1969            .unwrap()
1970            .unwrap();
1971        assert_eq!(page2["tools"].as_array().unwrap().len(), 1);
1972        assert!(page2.get("nextCursor").is_none());
1973
1974        // Pagination disabled (None) -> all three, no cursor.
1975        let all = route_tools(&handler, methods::TOOLS_LIST, None, &ctx, None)
1976            .await
1977            .unwrap()
1978            .unwrap();
1979        assert_eq!(all["tools"].as_array().unwrap().len(), 3);
1980        assert!(all.get("nextCursor").is_none());
1981
1982        // An invalid cursor is a routed error.
1983        let bad = serde_json::json!({ "cursor": "not-a-cursor" });
1984        let err = route_tools(&handler, methods::TOOLS_LIST, Some(&bad), &ctx, Some(2))
1985            .await
1986            .unwrap();
1987        assert!(err.is_err());
1988    }
1989
1990    #[tokio::test]
1991    async fn route_resources_dispatches_subscribe_and_unsubscribe() {
1992        use crate::context::NoOpPeer;
1993        use crate::handler::ResourceHandler;
1994        use mcpkit_core::capability::{ClientCapabilities, ServerCapabilities};
1995        use mcpkit_core::protocol::RequestId;
1996        use mcpkit_core::protocol_version::ProtocolVersion;
1997        use mcpkit_core::types::{Resource, ResourceContents};
1998
1999        struct Res {
2000            ok: bool,
2001        }
2002        impl ResourceHandler for Res {
2003            async fn list_resources(&self, _ctx: &Context<'_>) -> Result<Vec<Resource>, McpError> {
2004                Ok(vec![])
2005            }
2006            async fn read_resource(
2007                &self,
2008                _uri: &str,
2009                _ctx: &Context<'_>,
2010            ) -> Result<Vec<ResourceContents>, McpError> {
2011                Ok(vec![])
2012            }
2013            async fn subscribe(&self, _uri: &str, _ctx: &Context<'_>) -> Result<bool, McpError> {
2014                Ok(self.ok)
2015            }
2016            async fn unsubscribe(&self, _uri: &str, _ctx: &Context<'_>) -> Result<bool, McpError> {
2017                Ok(self.ok)
2018            }
2019        }
2020
2021        let request_id = RequestId::Number(1);
2022        let client_caps = ClientCapabilities::default();
2023        let server_caps = ServerCapabilities::default();
2024        let peer = NoOpPeer;
2025        let ctx = Context::new(
2026            &request_id,
2027            None,
2028            &client_caps,
2029            &server_caps,
2030            ProtocolVersion::LATEST,
2031            &peer,
2032        );
2033        let params = serde_json::json!({ "uri": "file:///x" });
2034
2035        // subscribe -> Ok(true) yields an empty result (no longer method-not-found).
2036        let ok = route_resources(
2037            &Res { ok: true },
2038            methods::RESOURCES_SUBSCRIBE,
2039            Some(&params),
2040            &ctx,
2041            None,
2042        )
2043        .await
2044        .expect("subscribe is routed")
2045        .expect("ok");
2046        assert_eq!(ok, serde_json::json!({}));
2047
2048        // subscribe -> Ok(false) is an error, not a fake success.
2049        assert!(
2050            route_resources(
2051                &Res { ok: false },
2052                methods::RESOURCES_SUBSCRIBE,
2053                Some(&params),
2054                &ctx,
2055                None,
2056            )
2057            .await
2058            .expect("routed")
2059            .is_err()
2060        );
2061
2062        // Missing uri -> invalid params.
2063        assert!(
2064            route_resources(
2065                &Res { ok: true },
2066                methods::RESOURCES_SUBSCRIBE,
2067                Some(&serde_json::json!({})),
2068                &ctx,
2069                None,
2070            )
2071            .await
2072            .expect("routed")
2073            .is_err()
2074        );
2075
2076        // unsubscribe -> Ok(true) yields an empty result.
2077        let ok = route_resources(
2078            &Res { ok: true },
2079            methods::RESOURCES_UNSUBSCRIBE,
2080            Some(&params),
2081            &ctx,
2082            None,
2083        )
2084        .await
2085        .expect("unsubscribe is routed")
2086        .expect("ok");
2087        assert_eq!(ok, serde_json::json!({}));
2088    }
2089}