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 serde_json::Value;
19
20/// Standard MCP method names as defined in the MCP specification.
21pub mod methods {
22    /// Initialize the connection and negotiate capabilities.
23    pub const INITIALIZE: &str = "initialize";
24    /// Ping to check if the connection is alive.
25    pub const PING: &str = "ping";
26
27    /// List available tools.
28    pub const TOOLS_LIST: &str = "tools/list";
29    /// Call a specific tool with arguments.
30    pub const TOOLS_CALL: &str = "tools/call";
31
32    /// List available resources.
33    pub const RESOURCES_LIST: &str = "resources/list";
34    /// Read the contents of a resource.
35    pub const RESOURCES_READ: &str = "resources/read";
36    /// List available resource templates.
37    pub const RESOURCES_TEMPLATES_LIST: &str = "resources/templates/list";
38    /// Subscribe to resource updates.
39    pub const RESOURCES_SUBSCRIBE: &str = "resources/subscribe";
40    /// Unsubscribe from resource updates.
41    pub const RESOURCES_UNSUBSCRIBE: &str = "resources/unsubscribe";
42
43    /// List available prompts.
44    pub const PROMPTS_LIST: &str = "prompts/list";
45    /// Get a specific prompt with arguments.
46    pub const PROMPTS_GET: &str = "prompts/get";
47
48    /// List running tasks.
49    pub const TASKS_LIST: &str = "tasks/list";
50    /// Get status of a specific task.
51    pub const TASKS_GET: &str = "tasks/get";
52    /// Cancel a running task.
53    pub const TASKS_CANCEL: &str = "tasks/cancel";
54
55    /// Request the client to sample from a language model.
56    pub const SAMPLING_CREATE_MESSAGE: &str = "sampling/createMessage";
57
58    /// Request completion suggestions.
59    pub const COMPLETION_COMPLETE: &str = "completion/complete";
60
61    /// Set the logging level.
62    pub const LOGGING_SET_LEVEL: &str = "logging/setLevel";
63
64    /// Create an elicitation request.
65    pub const ELICITATION_CREATE: &str = "elicitation/create";
66}
67
68/// Standard MCP notification names as defined in the MCP specification.
69pub mod notifications {
70    /// Sent by client after successful initialization.
71    pub const INITIALIZED: &str = "notifications/initialized";
72    /// Sent when a request is cancelled.
73    pub const CANCELLED: &str = "notifications/cancelled";
74    /// Sent to report progress on a long-running operation.
75    pub const PROGRESS: &str = "notifications/progress";
76    /// Sent to deliver a log message.
77    pub const MESSAGE: &str = "notifications/message";
78    /// Sent when a resource's content has changed.
79    pub const RESOURCES_UPDATED: &str = "notifications/resources/updated";
80    /// Sent when the list of available resources has changed.
81    pub const RESOURCES_LIST_CHANGED: &str = "notifications/resources/list_changed";
82    /// Sent when the list of available tools has changed.
83    pub const TOOLS_LIST_CHANGED: &str = "notifications/tools/list_changed";
84    /// Sent when the list of available prompts has changed.
85    pub const PROMPTS_LIST_CHANGED: &str = "notifications/prompts/list_changed";
86}
87
88/// Represents a parsed MCP request with typed parameters.
89///
90/// This enum provides a type-safe representation of all MCP request types,
91/// with parameters parsed into their appropriate structures.
92#[derive(Debug)]
93pub enum ParsedRequest {
94    /// Initialize request to establish connection.
95    Initialize(InitializeParams),
96    /// Ping request to check connection health.
97    Ping,
98
99    /// Request to list available tools.
100    ToolsList(ListParams),
101    /// Request to call a specific tool.
102    ToolsCall(ToolCallParams),
103
104    /// Request to list available resources.
105    ResourcesList(ListParams),
106    /// Request to read a resource's contents.
107    ResourcesRead(ResourceReadParams),
108    /// Request to list resource templates.
109    ResourcesTemplatesList(ListParams),
110    /// Request to subscribe to resource updates.
111    ResourcesSubscribe(ResourceSubscribeParams),
112    /// Request to unsubscribe from resource updates.
113    ResourcesUnsubscribe(ResourceUnsubscribeParams),
114
115    /// Request to list available prompts.
116    PromptsList(ListParams),
117    /// Request to get a specific prompt.
118    PromptsGet(PromptGetParams),
119
120    /// Request to list running tasks.
121    TasksList(ListParams),
122    /// Request to get a task's status.
123    TasksGet(TaskGetParams),
124    /// Request to cancel a running task.
125    TasksCancel(TaskCancelParams),
126
127    /// Request for the client to sample from a language model.
128    SamplingCreateMessage(SamplingParams),
129
130    /// Request for completion suggestions.
131    CompletionComplete(CompletionParams),
132
133    /// Request to set the logging level.
134    LoggingSetLevel(LogLevelParams),
135
136    /// An unrecognized method name.
137    Unknown(String),
138}
139
140/// Common list parameters with optional cursor for pagination.
141#[derive(Debug, Default)]
142pub struct ListParams {
143    /// Optional cursor for pagination.
144    pub cursor: Option<String>,
145}
146
147/// Initialize request parameters.
148#[derive(Debug)]
149pub struct InitializeParams {
150    /// The protocol version requested by the client.
151    pub protocol_version: String,
152    /// Information about the client.
153    pub client_info: ClientInfo,
154    /// Client capabilities.
155    pub capabilities: Value,
156}
157
158/// Client info from initialize request.
159#[derive(Debug)]
160pub struct ClientInfo {
161    /// The name of the client application.
162    pub name: String,
163    /// The version of the client application.
164    pub version: String,
165}
166
167/// Tool call parameters.
168#[derive(Debug)]
169pub struct ToolCallParams {
170    /// The name of the tool to call.
171    pub name: String,
172    /// Arguments to pass to the tool.
173    pub arguments: Value,
174}
175
176/// Resource read parameters.
177#[derive(Debug)]
178pub struct ResourceReadParams {
179    /// The URI of the resource to read.
180    pub uri: String,
181}
182
183/// Resource subscribe parameters.
184#[derive(Debug)]
185pub struct ResourceSubscribeParams {
186    /// The URI of the resource to subscribe to.
187    pub uri: String,
188}
189
190/// Resource unsubscribe parameters.
191#[derive(Debug)]
192pub struct ResourceUnsubscribeParams {
193    /// The URI of the resource to unsubscribe from.
194    pub uri: String,
195}
196
197/// Prompt get parameters.
198#[derive(Debug)]
199pub struct PromptGetParams {
200    /// The name of the prompt to get.
201    pub name: String,
202    /// Optional arguments to pass to the prompt.
203    pub arguments: Option<Value>,
204}
205
206/// Task get parameters.
207#[derive(Debug)]
208pub struct TaskGetParams {
209    /// The ID of the task to get.
210    pub task_id: String,
211}
212
213/// Task cancel parameters.
214#[derive(Debug)]
215pub struct TaskCancelParams {
216    /// The ID of the task to cancel.
217    pub task_id: String,
218}
219
220/// Sampling create message parameters.
221#[derive(Debug)]
222pub struct SamplingParams {
223    /// The messages to sample from.
224    pub messages: Vec<Value>,
225    /// Optional model preferences.
226    pub model_preferences: Option<Value>,
227    /// Optional system prompt.
228    pub system_prompt: Option<String>,
229    /// Optional maximum number of tokens.
230    pub max_tokens: Option<u32>,
231}
232
233/// Completion parameters.
234#[derive(Debug)]
235pub struct CompletionParams {
236    /// The type of reference (e.g., "ref/resource", "ref/prompt").
237    pub ref_type: String,
238    /// The value of the reference (URI or name).
239    pub ref_value: String,
240    /// Optional argument for completion context.
241    pub argument: Option<CompletionArgument>,
242}
243
244/// Completion argument providing context for completion.
245#[derive(Debug)]
246pub struct CompletionArgument {
247    /// The name of the argument.
248    pub name: String,
249    /// The current value being completed.
250    pub value: String,
251}
252
253/// Log level parameters.
254#[derive(Debug)]
255pub struct LogLevelParams {
256    /// The log level to set (e.g., "debug", "info", "warn", "error").
257    pub level: String,
258}
259
260/// Parse a request into a typed representation.
261pub fn parse_request(request: &Request) -> Result<ParsedRequest, McpError> {
262    let method = request.method.as_ref();
263    let params = request.params.as_ref();
264
265    match method {
266        methods::INITIALIZE => {
267            let params =
268                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
269
270            Ok(ParsedRequest::Initialize(InitializeParams {
271                protocol_version: params
272                    .get("protocolVersion")
273                    .and_then(|v| v.as_str())
274                    .unwrap_or("unknown")
275                    .to_string(),
276                client_info: ClientInfo {
277                    name: params
278                        .get("clientInfo")
279                        .and_then(|v| v.get("name"))
280                        .and_then(|v| v.as_str())
281                        .unwrap_or("unknown")
282                        .to_string(),
283                    version: params
284                        .get("clientInfo")
285                        .and_then(|v| v.get("version"))
286                        .and_then(|v| v.as_str())
287                        .unwrap_or("unknown")
288                        .to_string(),
289                },
290                capabilities: params
291                    .get("capabilities")
292                    .cloned()
293                    .unwrap_or_else(|| Value::Object(serde_json::Map::new())),
294            }))
295        }
296
297        methods::PING => Ok(ParsedRequest::Ping),
298
299        methods::TOOLS_LIST => Ok(ParsedRequest::ToolsList(parse_list_params(params))),
300
301        methods::TOOLS_CALL => {
302            let params =
303                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
304
305            let name = params
306                .get("name")
307                .and_then(|v| v.as_str())
308                .ok_or_else(|| McpError::invalid_params(method, "missing name"))?
309                .to_string();
310
311            let arguments = params
312                .get("arguments")
313                .cloned()
314                .unwrap_or_else(|| Value::Object(serde_json::Map::new()));
315
316            Ok(ParsedRequest::ToolsCall(ToolCallParams { name, arguments }))
317        }
318
319        methods::RESOURCES_LIST => Ok(ParsedRequest::ResourcesList(parse_list_params(params))),
320
321        methods::RESOURCES_READ => {
322            let params =
323                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
324
325            let uri = params
326                .get("uri")
327                .and_then(|v| v.as_str())
328                .ok_or_else(|| McpError::invalid_params(method, "missing uri"))?
329                .to_string();
330
331            Ok(ParsedRequest::ResourcesRead(ResourceReadParams { uri }))
332        }
333
334        methods::RESOURCES_TEMPLATES_LIST => Ok(ParsedRequest::ResourcesTemplatesList(
335            parse_list_params(params),
336        )),
337
338        methods::RESOURCES_SUBSCRIBE => {
339            let params =
340                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
341
342            let uri = params
343                .get("uri")
344                .and_then(|v| v.as_str())
345                .ok_or_else(|| McpError::invalid_params(method, "missing uri"))?
346                .to_string();
347
348            Ok(ParsedRequest::ResourcesSubscribe(ResourceSubscribeParams {
349                uri,
350            }))
351        }
352
353        methods::RESOURCES_UNSUBSCRIBE => {
354            let params =
355                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
356
357            let uri = params
358                .get("uri")
359                .and_then(|v| v.as_str())
360                .ok_or_else(|| McpError::invalid_params(method, "missing uri"))?
361                .to_string();
362
363            Ok(ParsedRequest::ResourcesUnsubscribe(
364                ResourceUnsubscribeParams { uri },
365            ))
366        }
367
368        methods::PROMPTS_LIST => Ok(ParsedRequest::PromptsList(parse_list_params(params))),
369
370        methods::PROMPTS_GET => {
371            let params =
372                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
373
374            let name = params
375                .get("name")
376                .and_then(|v| v.as_str())
377                .ok_or_else(|| McpError::invalid_params(method, "missing name"))?
378                .to_string();
379
380            let arguments = params.get("arguments").cloned();
381
382            Ok(ParsedRequest::PromptsGet(PromptGetParams {
383                name,
384                arguments,
385            }))
386        }
387
388        methods::TASKS_LIST => Ok(ParsedRequest::TasksList(parse_list_params(params))),
389
390        methods::TASKS_GET => {
391            let params =
392                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
393
394            let task_id = params
395                .get("taskId")
396                .and_then(|v| v.as_str())
397                .ok_or_else(|| McpError::invalid_params(method, "missing taskId"))?
398                .to_string();
399
400            Ok(ParsedRequest::TasksGet(TaskGetParams { task_id }))
401        }
402
403        methods::TASKS_CANCEL => {
404            let params =
405                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
406
407            let task_id = params
408                .get("taskId")
409                .and_then(|v| v.as_str())
410                .ok_or_else(|| McpError::invalid_params(method, "missing taskId"))?
411                .to_string();
412
413            Ok(ParsedRequest::TasksCancel(TaskCancelParams { task_id }))
414        }
415
416        methods::SAMPLING_CREATE_MESSAGE => {
417            let params =
418                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
419
420            let messages = params
421                .get("messages")
422                .and_then(|v| v.as_array())
423                .ok_or_else(|| McpError::invalid_params(method, "missing messages"))?
424                .clone();
425
426            Ok(ParsedRequest::SamplingCreateMessage(SamplingParams {
427                messages,
428                model_preferences: params.get("modelPreferences").cloned(),
429                system_prompt: params
430                    .get("systemPrompt")
431                    .and_then(|v| v.as_str())
432                    .map(String::from),
433                max_tokens: params
434                    .get("maxTokens")
435                    .and_then(serde_json::Value::as_u64)
436                    .map(|v| v as u32),
437            }))
438        }
439
440        methods::COMPLETION_COMPLETE => {
441            let params =
442                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
443
444            let ref_obj = params
445                .get("ref")
446                .ok_or_else(|| McpError::invalid_params(method, "missing ref"))?;
447
448            Ok(ParsedRequest::CompletionComplete(CompletionParams {
449                ref_type: ref_obj
450                    .get("type")
451                    .and_then(|v| v.as_str())
452                    .unwrap_or("")
453                    .to_string(),
454                ref_value: ref_obj
455                    .get("uri")
456                    .or_else(|| ref_obj.get("name"))
457                    .and_then(|v| v.as_str())
458                    .unwrap_or("")
459                    .to_string(),
460                argument: params.get("argument").map(|arg| CompletionArgument {
461                    name: arg
462                        .get("name")
463                        .and_then(|v| v.as_str())
464                        .unwrap_or("")
465                        .to_string(),
466                    value: arg
467                        .get("value")
468                        .and_then(|v| v.as_str())
469                        .unwrap_or("")
470                        .to_string(),
471                }),
472            }))
473        }
474
475        methods::LOGGING_SET_LEVEL => {
476            let params =
477                params.ok_or_else(|| McpError::invalid_params(method, "missing params"))?;
478
479            let level = params
480                .get("level")
481                .and_then(|v| v.as_str())
482                .ok_or_else(|| McpError::invalid_params(method, "missing level"))?
483                .to_string();
484
485            Ok(ParsedRequest::LoggingSetLevel(LogLevelParams { level }))
486        }
487
488        _ => Ok(ParsedRequest::Unknown(method.to_string())),
489    }
490}
491
492/// Parse common list parameters.
493fn parse_list_params(params: Option<&Value>) -> ListParams {
494    ListParams {
495        cursor: params
496            .and_then(|p| p.get("cursor"))
497            .and_then(|v| v.as_str())
498            .map(String::from),
499    }
500}
501
502// =============================================================================
503// Public routing functions for HTTP integrations
504//
505// These functions allow HTTP handlers (axum, actix, etc.) to properly route
506// requests to handler trait implementations.
507// =============================================================================
508
509use crate::context::Context;
510use crate::handler::{PromptHandler, ResourceHandler, ToolHandler};
511use mcpkit_core::types::CallToolResult;
512
513/// Route tool-related requests to a handler implementing [`ToolHandler`].
514///
515/// This function handles `tools/list` and `tools/call` methods.
516/// Returns `None` if the method is not tool-related.
517///
518/// # Example
519///
520/// ```ignore
521/// if let Some(result) = route_tools(&handler, method, params, &ctx).await {
522///     return result;
523/// }
524/// ```
525pub async fn route_tools<TH: ToolHandler + Send + Sync>(
526    handler: &TH,
527    method: &str,
528    params: Option<&serde_json::Value>,
529    ctx: &Context<'_>,
530) -> Option<Result<serde_json::Value, McpError>> {
531    match method {
532        methods::TOOLS_LIST => {
533            tracing::debug!("Listing available tools");
534            let result = handler.list_tools(ctx).await;
535            match &result {
536                Ok(tools) => tracing::debug!(count = tools.len(), "Listed tools"),
537                Err(e) => tracing::warn!(error = %e, "Failed to list tools"),
538            }
539            Some(result.map(|tools| serde_json::json!({ "tools": tools })))
540        }
541        methods::TOOLS_CALL => {
542            let result = async {
543                let params = params.ok_or_else(|| {
544                    McpError::invalid_params(methods::TOOLS_CALL, "missing params")
545                })?;
546                let name = params.get("name").and_then(|v| v.as_str()).ok_or_else(|| {
547                    McpError::invalid_params(methods::TOOLS_CALL, "missing tool name")
548                })?;
549                let args = params
550                    .get("arguments")
551                    .cloned()
552                    .unwrap_or_else(|| serde_json::json!({}));
553
554                tracing::info!(tool = %name, "Calling tool");
555                let start = std::time::Instant::now();
556                let output = handler.call_tool(name, args, ctx).await;
557                let duration = start.elapsed();
558
559                match &output {
560                    Ok(_) => tracing::info!(
561                        tool = %name,
562                        duration_ms = duration.as_millis(),
563                        "Tool call completed"
564                    ),
565                    Err(e) => tracing::warn!(
566                        tool = %name,
567                        duration_ms = duration.as_millis(),
568                        error = %e,
569                        "Tool call failed"
570                    ),
571                }
572
573                let output = output?;
574                let result: CallToolResult = output.into();
575                Ok(serde_json::to_value(result).unwrap_or_else(|_| serde_json::json!({})))
576            }
577            .await;
578            Some(result)
579        }
580        _ => None,
581    }
582}
583
584/// Route resource-related requests to a handler implementing [`ResourceHandler`].
585///
586/// This function handles `resources/list`, `resources/templates/list`, and `resources/read` methods.
587/// Returns `None` if the method is not resource-related.
588///
589/// # Example
590///
591/// ```ignore
592/// if let Some(result) = route_resources(&handler, method, params, &ctx).await {
593///     return result;
594/// }
595/// ```
596pub async fn route_resources<RH: ResourceHandler + Send + Sync>(
597    handler: &RH,
598    method: &str,
599    params: Option<&serde_json::Value>,
600    ctx: &Context<'_>,
601) -> Option<Result<serde_json::Value, McpError>> {
602    match method {
603        methods::RESOURCES_LIST => {
604            tracing::debug!("Listing available resources");
605            let result = handler.list_resources(ctx).await;
606            match &result {
607                Ok(resources) => tracing::debug!(count = resources.len(), "Listed resources"),
608                Err(e) => tracing::warn!(error = %e, "Failed to list resources"),
609            }
610            Some(result.map(|resources| serde_json::json!({ "resources": resources })))
611        }
612        methods::RESOURCES_TEMPLATES_LIST => {
613            tracing::debug!("Listing available resource templates");
614            let result = handler.list_resource_templates(ctx).await;
615            match &result {
616                Ok(templates) => {
617                    tracing::debug!(count = templates.len(), "Listed resource templates");
618                }
619                Err(e) => tracing::warn!(error = %e, "Failed to list resource templates"),
620            }
621            Some(result.map(|templates| serde_json::json!({ "resourceTemplates": templates })))
622        }
623        methods::RESOURCES_READ => {
624            let result = async {
625                let params = params.ok_or_else(|| {
626                    McpError::invalid_params(methods::RESOURCES_READ, "missing params")
627                })?;
628                let uri = params.get("uri").and_then(|v| v.as_str()).ok_or_else(|| {
629                    McpError::invalid_params(methods::RESOURCES_READ, "missing uri")
630                })?;
631
632                tracing::info!(uri = %uri, "Reading resource");
633                let start = std::time::Instant::now();
634                let contents = handler.read_resource(uri, ctx).await;
635                let duration = start.elapsed();
636
637                match &contents {
638                    Ok(_) => tracing::info!(
639                        uri = %uri,
640                        duration_ms = duration.as_millis(),
641                        "Resource read completed"
642                    ),
643                    Err(e) => tracing::warn!(
644                        uri = %uri,
645                        duration_ms = duration.as_millis(),
646                        error = %e,
647                        "Resource read failed"
648                    ),
649                }
650
651                let contents = contents?;
652                Ok(serde_json::json!({ "contents": contents }))
653            }
654            .await;
655            Some(result)
656        }
657        _ => None,
658    }
659}
660
661/// Route prompt-related requests to a handler implementing [`PromptHandler`].
662///
663/// This function handles `prompts/list` and `prompts/get` methods.
664/// Returns `None` if the method is not prompt-related.
665///
666/// # Example
667///
668/// ```ignore
669/// if let Some(result) = route_prompts(&handler, method, params, &ctx).await {
670///     return result;
671/// }
672/// ```
673pub async fn route_prompts<PH: PromptHandler + Send + Sync>(
674    handler: &PH,
675    method: &str,
676    params: Option<&serde_json::Value>,
677    ctx: &Context<'_>,
678) -> Option<Result<serde_json::Value, McpError>> {
679    match method {
680        methods::PROMPTS_LIST => {
681            tracing::debug!("Listing available prompts");
682            let result = handler.list_prompts(ctx).await;
683            match &result {
684                Ok(prompts) => tracing::debug!(count = prompts.len(), "Listed prompts"),
685                Err(e) => tracing::warn!(error = %e, "Failed to list prompts"),
686            }
687            Some(result.map(|prompts| serde_json::json!({ "prompts": prompts })))
688        }
689        methods::PROMPTS_GET => {
690            let result = async {
691                let params = params.ok_or_else(|| {
692                    McpError::invalid_params(methods::PROMPTS_GET, "missing params")
693                })?;
694                let name = params.get("name").and_then(|v| v.as_str()).ok_or_else(|| {
695                    McpError::invalid_params(methods::PROMPTS_GET, "missing prompt name")
696                })?;
697                let args = params.get("arguments").and_then(|v| v.as_object()).cloned();
698
699                tracing::info!(prompt = %name, "Getting prompt");
700                let start = std::time::Instant::now();
701                let prompt_result = handler.get_prompt(name, args, ctx).await;
702                let duration = start.elapsed();
703
704                match &prompt_result {
705                    Ok(_) => tracing::info!(
706                        prompt = %name,
707                        duration_ms = duration.as_millis(),
708                        "Prompt retrieval completed"
709                    ),
710                    Err(e) => tracing::warn!(
711                        prompt = %name,
712                        duration_ms = duration.as_millis(),
713                        error = %e,
714                        "Prompt retrieval failed"
715                    ),
716                }
717
718                let result = prompt_result?;
719                Ok(serde_json::to_value(result).unwrap_or_else(|_| serde_json::json!({})))
720            }
721            .await;
722            Some(result)
723        }
724        _ => None,
725    }
726}
727
728#[cfg(test)]
729mod tests {
730    use super::*;
731    use mcpkit_core::protocol::Request;
732
733    fn make_request(method: &'static str, params: Option<Value>) -> Request {
734        if let Some(p) = params {
735            Request::with_params(method, 1u64, p)
736        } else {
737            Request::new(method, 1u64)
738        }
739    }
740
741    #[test]
742    fn test_parse_ping() -> Result<(), Box<dyn std::error::Error>> {
743        let request = make_request("ping", None);
744        let parsed = parse_request(&request)?;
745        assert!(matches!(parsed, ParsedRequest::Ping));
746
747        Ok(())
748    }
749
750    #[test]
751    fn test_parse_tools_list() -> Result<(), Box<dyn std::error::Error>> {
752        let request = make_request("tools/list", None);
753        let parsed = parse_request(&request)?;
754        assert!(matches!(parsed, ParsedRequest::ToolsList(_)));
755
756        Ok(())
757    }
758
759    #[test]
760    fn test_parse_tools_list_with_cursor() -> Result<(), Box<dyn std::error::Error>> {
761        let request = make_request(
762            "tools/list",
763            Some(serde_json::json!({ "cursor": "abc123" })),
764        );
765        let parsed = parse_request(&request)?;
766
767        if let ParsedRequest::ToolsList(params) = parsed {
768            assert_eq!(params.cursor, Some("abc123".to_string()));
769        } else {
770            panic!("Expected ToolsList");
771        }
772
773        Ok(())
774    }
775
776    #[test]
777    fn test_parse_tools_call() -> Result<(), Box<dyn std::error::Error>> {
778        let request = make_request(
779            "tools/call",
780            Some(serde_json::json!({
781                "name": "search",
782                "arguments": {"query": "test"}
783            })),
784        );
785        let parsed = parse_request(&request)?;
786
787        if let ParsedRequest::ToolsCall(params) = parsed {
788            assert_eq!(params.name, "search");
789            assert_eq!(params.arguments["query"], "test");
790        } else {
791            panic!("Expected ToolsCall");
792        }
793
794        Ok(())
795    }
796
797    #[test]
798    fn test_parse_tools_call_missing_params() {
799        let request = make_request("tools/call", None);
800        let result = parse_request(&request);
801        assert!(result.is_err());
802    }
803
804    #[test]
805    fn test_parse_tools_call_missing_name() {
806        let request = make_request("tools/call", Some(serde_json::json!({"arguments": {}})));
807        let result = parse_request(&request);
808        assert!(result.is_err());
809    }
810
811    #[test]
812    fn test_parse_unknown_method() -> Result<(), Box<dyn std::error::Error>> {
813        let request = make_request("unknown/method", None);
814        let parsed = parse_request(&request)?;
815
816        if let ParsedRequest::Unknown(method) = parsed {
817            assert_eq!(method, "unknown/method");
818        } else {
819            panic!("Expected Unknown");
820        }
821
822        Ok(())
823    }
824
825    #[test]
826    fn test_parse_initialize() -> Result<(), Box<dyn std::error::Error>> {
827        let request = make_request(
828            "initialize",
829            Some(serde_json::json!({
830                "protocolVersion": "2025-11-25",
831                "clientInfo": {
832                    "name": "test-client",
833                    "version": "1.0.0"
834                },
835                "capabilities": {}
836            })),
837        );
838        let parsed = parse_request(&request)?;
839
840        if let ParsedRequest::Initialize(params) = parsed {
841            assert_eq!(params.protocol_version, "2025-11-25");
842            assert_eq!(params.client_info.name, "test-client");
843            assert_eq!(params.client_info.version, "1.0.0");
844        } else {
845            panic!("Expected Initialize");
846        }
847
848        Ok(())
849    }
850
851    #[test]
852    fn test_parse_initialize_missing_params() {
853        let request = make_request("initialize", None);
854        let result = parse_request(&request);
855        assert!(result.is_err());
856    }
857
858    // =========================================================================
859    // Resource Methods
860    // =========================================================================
861
862    #[test]
863    fn test_parse_resources_list() -> Result<(), Box<dyn std::error::Error>> {
864        let request = make_request("resources/list", None);
865        let parsed = parse_request(&request)?;
866        assert!(matches!(parsed, ParsedRequest::ResourcesList(_)));
867        Ok(())
868    }
869
870    #[test]
871    fn test_parse_resources_list_with_cursor() -> Result<(), Box<dyn std::error::Error>> {
872        let request = make_request(
873            "resources/list",
874            Some(serde_json::json!({ "cursor": "page2" })),
875        );
876        let parsed = parse_request(&request)?;
877
878        if let ParsedRequest::ResourcesList(params) = parsed {
879            assert_eq!(params.cursor, Some("page2".to_string()));
880        } else {
881            panic!("Expected ResourcesList");
882        }
883        Ok(())
884    }
885
886    #[test]
887    fn test_parse_resources_read() -> Result<(), Box<dyn std::error::Error>> {
888        let request = make_request(
889            "resources/read",
890            Some(serde_json::json!({ "uri": "file:///etc/hosts" })),
891        );
892        let parsed = parse_request(&request)?;
893
894        if let ParsedRequest::ResourcesRead(params) = parsed {
895            assert_eq!(params.uri, "file:///etc/hosts");
896        } else {
897            panic!("Expected ResourcesRead");
898        }
899        Ok(())
900    }
901
902    #[test]
903    fn test_parse_resources_read_missing_uri() {
904        let request = make_request("resources/read", Some(serde_json::json!({})));
905        let result = parse_request(&request);
906        assert!(result.is_err());
907    }
908
909    #[test]
910    fn test_parse_resources_templates_list() -> Result<(), Box<dyn std::error::Error>> {
911        let request = make_request("resources/templates/list", None);
912        let parsed = parse_request(&request)?;
913        assert!(matches!(parsed, ParsedRequest::ResourcesTemplatesList(_)));
914        Ok(())
915    }
916
917    #[test]
918    fn test_parse_resources_subscribe() -> Result<(), Box<dyn std::error::Error>> {
919        let request = make_request(
920            "resources/subscribe",
921            Some(serde_json::json!({ "uri": "file:///var/log/app.log" })),
922        );
923        let parsed = parse_request(&request)?;
924
925        if let ParsedRequest::ResourcesSubscribe(params) = parsed {
926            assert_eq!(params.uri, "file:///var/log/app.log");
927        } else {
928            panic!("Expected ResourcesSubscribe");
929        }
930        Ok(())
931    }
932
933    #[test]
934    fn test_parse_resources_unsubscribe() -> Result<(), Box<dyn std::error::Error>> {
935        let request = make_request(
936            "resources/unsubscribe",
937            Some(serde_json::json!({ "uri": "file:///var/log/app.log" })),
938        );
939        let parsed = parse_request(&request)?;
940
941        if let ParsedRequest::ResourcesUnsubscribe(params) = parsed {
942            assert_eq!(params.uri, "file:///var/log/app.log");
943        } else {
944            panic!("Expected ResourcesUnsubscribe");
945        }
946        Ok(())
947    }
948
949    // =========================================================================
950    // Prompt Methods
951    // =========================================================================
952
953    #[test]
954    fn test_parse_prompts_list() -> Result<(), Box<dyn std::error::Error>> {
955        let request = make_request("prompts/list", None);
956        let parsed = parse_request(&request)?;
957        assert!(matches!(parsed, ParsedRequest::PromptsList(_)));
958        Ok(())
959    }
960
961    #[test]
962    fn test_parse_prompts_get() -> Result<(), Box<dyn std::error::Error>> {
963        let request = make_request(
964            "prompts/get",
965            Some(serde_json::json!({
966                "name": "code-review",
967                "arguments": { "language": "rust" }
968            })),
969        );
970        let parsed = parse_request(&request)?;
971
972        if let ParsedRequest::PromptsGet(params) = parsed {
973            assert_eq!(params.name, "code-review");
974            assert!(params.arguments.is_some());
975        } else {
976            panic!("Expected PromptsGet");
977        }
978        Ok(())
979    }
980
981    #[test]
982    fn test_parse_prompts_get_without_arguments() -> Result<(), Box<dyn std::error::Error>> {
983        let request = make_request(
984            "prompts/get",
985            Some(serde_json::json!({ "name": "simple-prompt" })),
986        );
987        let parsed = parse_request(&request)?;
988
989        if let ParsedRequest::PromptsGet(params) = parsed {
990            assert_eq!(params.name, "simple-prompt");
991            assert!(params.arguments.is_none());
992        } else {
993            panic!("Expected PromptsGet");
994        }
995        Ok(())
996    }
997
998    #[test]
999    fn test_parse_prompts_get_missing_name() {
1000        let request = make_request("prompts/get", Some(serde_json::json!({})));
1001        let result = parse_request(&request);
1002        assert!(result.is_err());
1003    }
1004
1005    // =========================================================================
1006    // Task Methods
1007    // =========================================================================
1008
1009    #[test]
1010    fn test_parse_tasks_list() -> Result<(), Box<dyn std::error::Error>> {
1011        let request = make_request("tasks/list", None);
1012        let parsed = parse_request(&request)?;
1013        assert!(matches!(parsed, ParsedRequest::TasksList(_)));
1014        Ok(())
1015    }
1016
1017    #[test]
1018    fn test_parse_tasks_get() -> Result<(), Box<dyn std::error::Error>> {
1019        let request = make_request(
1020            "tasks/get",
1021            Some(serde_json::json!({ "taskId": "task-123" })),
1022        );
1023        let parsed = parse_request(&request)?;
1024
1025        if let ParsedRequest::TasksGet(params) = parsed {
1026            assert_eq!(params.task_id, "task-123");
1027        } else {
1028            panic!("Expected TasksGet");
1029        }
1030        Ok(())
1031    }
1032
1033    #[test]
1034    fn test_parse_tasks_get_missing_id() {
1035        let request = make_request("tasks/get", Some(serde_json::json!({})));
1036        let result = parse_request(&request);
1037        assert!(result.is_err());
1038    }
1039
1040    #[test]
1041    fn test_parse_tasks_cancel() -> Result<(), Box<dyn std::error::Error>> {
1042        let request = make_request(
1043            "tasks/cancel",
1044            Some(serde_json::json!({ "taskId": "task-456" })),
1045        );
1046        let parsed = parse_request(&request)?;
1047
1048        if let ParsedRequest::TasksCancel(params) = parsed {
1049            assert_eq!(params.task_id, "task-456");
1050        } else {
1051            panic!("Expected TasksCancel");
1052        }
1053        Ok(())
1054    }
1055
1056    // =========================================================================
1057    // Sampling Methods
1058    // =========================================================================
1059
1060    #[test]
1061    fn test_parse_sampling_create_message() -> Result<(), Box<dyn std::error::Error>> {
1062        let request = make_request(
1063            "sampling/createMessage",
1064            Some(serde_json::json!({
1065                "messages": [
1066                    { "role": "user", "content": { "type": "text", "text": "Hello" } }
1067                ],
1068                "maxTokens": 100,
1069                "systemPrompt": "You are a helpful assistant."
1070            })),
1071        );
1072        let parsed = parse_request(&request)?;
1073
1074        if let ParsedRequest::SamplingCreateMessage(params) = parsed {
1075            assert_eq!(params.messages.len(), 1);
1076            assert_eq!(params.max_tokens, Some(100));
1077            assert_eq!(
1078                params.system_prompt,
1079                Some("You are a helpful assistant.".to_string())
1080            );
1081        } else {
1082            panic!("Expected SamplingCreateMessage");
1083        }
1084        Ok(())
1085    }
1086
1087    #[test]
1088    fn test_parse_sampling_create_message_missing_messages() {
1089        let request = make_request("sampling/createMessage", Some(serde_json::json!({})));
1090        let result = parse_request(&request);
1091        assert!(result.is_err());
1092    }
1093
1094    // =========================================================================
1095    // Completion Methods
1096    // =========================================================================
1097
1098    #[test]
1099    fn test_parse_completion_complete() -> Result<(), Box<dyn std::error::Error>> {
1100        let request = make_request(
1101            "completion/complete",
1102            Some(serde_json::json!({
1103                "ref": {
1104                    "type": "ref/resource",
1105                    "uri": "file:///home"
1106                },
1107                "argument": {
1108                    "name": "path",
1109                    "value": "/home/user"
1110                }
1111            })),
1112        );
1113        let parsed = parse_request(&request)?;
1114
1115        if let ParsedRequest::CompletionComplete(params) = parsed {
1116            assert_eq!(params.ref_type, "ref/resource");
1117            assert_eq!(params.ref_value, "file:///home");
1118            assert!(params.argument.is_some());
1119            let arg = params.argument.unwrap();
1120            assert_eq!(arg.name, "path");
1121            assert_eq!(arg.value, "/home/user");
1122        } else {
1123            panic!("Expected CompletionComplete");
1124        }
1125        Ok(())
1126    }
1127
1128    #[test]
1129    fn test_parse_completion_complete_prompt_ref() -> Result<(), Box<dyn std::error::Error>> {
1130        let request = make_request(
1131            "completion/complete",
1132            Some(serde_json::json!({
1133                "ref": {
1134                    "type": "ref/prompt",
1135                    "name": "code-review"
1136                }
1137            })),
1138        );
1139        let parsed = parse_request(&request)?;
1140
1141        if let ParsedRequest::CompletionComplete(params) = parsed {
1142            assert_eq!(params.ref_type, "ref/prompt");
1143            assert_eq!(params.ref_value, "code-review");
1144            assert!(params.argument.is_none());
1145        } else {
1146            panic!("Expected CompletionComplete");
1147        }
1148        Ok(())
1149    }
1150
1151    #[test]
1152    fn test_parse_completion_complete_missing_ref() {
1153        let request = make_request("completion/complete", Some(serde_json::json!({})));
1154        let result = parse_request(&request);
1155        assert!(result.is_err());
1156    }
1157
1158    // =========================================================================
1159    // Logging Methods
1160    // =========================================================================
1161
1162    #[test]
1163    fn test_parse_logging_set_level() -> Result<(), Box<dyn std::error::Error>> {
1164        let request = make_request(
1165            "logging/setLevel",
1166            Some(serde_json::json!({ "level": "debug" })),
1167        );
1168        let parsed = parse_request(&request)?;
1169
1170        if let ParsedRequest::LoggingSetLevel(params) = parsed {
1171            assert_eq!(params.level, "debug");
1172        } else {
1173            panic!("Expected LoggingSetLevel");
1174        }
1175        Ok(())
1176    }
1177
1178    #[test]
1179    fn test_parse_logging_set_level_missing_level() {
1180        let request = make_request("logging/setLevel", Some(serde_json::json!({})));
1181        let result = parse_request(&request);
1182        assert!(result.is_err());
1183    }
1184
1185    // =========================================================================
1186    // Method Constants
1187    // =========================================================================
1188
1189    #[test]
1190    fn test_method_constants() {
1191        // Verify method constants match the strings used in parsing
1192        assert_eq!(methods::INITIALIZE, "initialize");
1193        assert_eq!(methods::PING, "ping");
1194        assert_eq!(methods::TOOLS_LIST, "tools/list");
1195        assert_eq!(methods::TOOLS_CALL, "tools/call");
1196        assert_eq!(methods::RESOURCES_LIST, "resources/list");
1197        assert_eq!(methods::RESOURCES_READ, "resources/read");
1198        assert_eq!(
1199            methods::RESOURCES_TEMPLATES_LIST,
1200            "resources/templates/list"
1201        );
1202        assert_eq!(methods::RESOURCES_SUBSCRIBE, "resources/subscribe");
1203        assert_eq!(methods::RESOURCES_UNSUBSCRIBE, "resources/unsubscribe");
1204        assert_eq!(methods::PROMPTS_LIST, "prompts/list");
1205        assert_eq!(methods::PROMPTS_GET, "prompts/get");
1206        assert_eq!(methods::TASKS_LIST, "tasks/list");
1207        assert_eq!(methods::TASKS_GET, "tasks/get");
1208        assert_eq!(methods::TASKS_CANCEL, "tasks/cancel");
1209        assert_eq!(methods::SAMPLING_CREATE_MESSAGE, "sampling/createMessage");
1210        assert_eq!(methods::COMPLETION_COMPLETE, "completion/complete");
1211        assert_eq!(methods::LOGGING_SET_LEVEL, "logging/setLevel");
1212    }
1213
1214    // =========================================================================
1215    // Notification Constants
1216    // =========================================================================
1217
1218    #[test]
1219    fn test_notification_constants() {
1220        assert_eq!(notifications::INITIALIZED, "notifications/initialized");
1221        assert_eq!(notifications::CANCELLED, "notifications/cancelled");
1222        assert_eq!(notifications::PROGRESS, "notifications/progress");
1223        assert_eq!(notifications::MESSAGE, "notifications/message");
1224        assert_eq!(
1225            notifications::RESOURCES_UPDATED,
1226            "notifications/resources/updated"
1227        );
1228        assert_eq!(
1229            notifications::RESOURCES_LIST_CHANGED,
1230            "notifications/resources/list_changed"
1231        );
1232        assert_eq!(
1233            notifications::TOOLS_LIST_CHANGED,
1234            "notifications/tools/list_changed"
1235        );
1236        assert_eq!(
1237            notifications::PROMPTS_LIST_CHANGED,
1238            "notifications/prompts/list_changed"
1239        );
1240    }
1241}