1use mcpkit_core::error::McpError;
17use mcpkit_core::protocol::Request;
18use mcpkit_core::types::Object;
19use serde_json::Value;
20
21pub use mcpkit_core::methods;
27
28pub use mcpkit_core::methods::notifications;
30
31#[derive(Debug)]
36pub enum ParsedRequest {
37 Initialize(InitializeParams),
39 Ping,
41
42 ToolsList(ListParams),
44 ToolsCall(ToolCallParams),
46
47 ResourcesList(ListParams),
49 ResourcesRead(ResourceReadParams),
51 ResourcesTemplatesList(ListParams),
53 ResourcesSubscribe(ResourceSubscribeParams),
55 ResourcesUnsubscribe(ResourceUnsubscribeParams),
57
58 PromptsList(ListParams),
60 PromptsGet(PromptGetParams),
62
63 TasksList(ListParams),
65 TasksGet(TaskGetParams),
67 TasksCancel(TaskCancelParams),
69
70 SamplingCreateMessage(SamplingParams),
72
73 CompletionComplete(CompletionParams),
75
76 LoggingSetLevel(LogLevelParams),
78
79 Unknown(String),
81}
82
83#[derive(Debug, Default)]
85pub struct ListParams {
86 pub cursor: Option<String>,
88}
89
90#[derive(Debug)]
92pub struct InitializeParams {
93 pub protocol_version: String,
95 pub client_info: ClientInfo,
97 pub capabilities: Value,
99}
100
101#[derive(Debug)]
103pub struct ClientInfo {
104 pub name: String,
106 pub version: String,
108}
109
110#[derive(Debug)]
112pub struct ToolCallParams {
113 pub name: String,
115 pub arguments: Object,
117}
118
119#[derive(Debug)]
121pub struct ResourceReadParams {
122 pub uri: String,
124}
125
126#[derive(Debug)]
128pub struct ResourceSubscribeParams {
129 pub uri: String,
131}
132
133#[derive(Debug)]
135pub struct ResourceUnsubscribeParams {
136 pub uri: String,
138}
139
140#[derive(Debug)]
142pub struct PromptGetParams {
143 pub name: String,
145 pub arguments: Option<Object>,
147}
148
149#[derive(Debug)]
151pub struct TaskGetParams {
152 pub task_id: String,
154}
155
156#[derive(Debug)]
158pub struct TaskCancelParams {
159 pub task_id: String,
161}
162
163#[derive(Debug)]
165pub struct SamplingParams {
166 pub messages: Vec<Value>,
168 pub model_preferences: Option<Value>,
170 pub system_prompt: Option<String>,
172 pub max_tokens: Option<u32>,
174}
175
176#[derive(Debug)]
178pub struct CompletionParams {
179 pub ref_type: String,
181 pub ref_value: String,
183 pub argument: Option<CompletionArgument>,
185}
186
187#[derive(Debug)]
189pub struct CompletionArgument {
190 pub name: String,
192 pub value: String,
194}
195
196#[derive(Debug)]
198pub struct LogLevelParams {
199 pub level: String,
201}
202
203pub 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
450fn 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
460use 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
476fn 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
490fn list_cursor(params: Option<&Value>) -> Option<&str> {
492 params.and_then(|p| p.get("cursor")).and_then(Value::as_str)
493}
494
495pub 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
577pub 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
597pub 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 notifications::ROOTS_LIST_CHANGED if ctx.client_caps.has_roots() => {
613 handler.on_roots_list_changed(ctx).await;
614 }
615 _ => {}
616 }
617}
618
619pub 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#[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 .with_related_task(handle.id().clone());
679 match call_tool_json(handler.as_ref(), &name, args, &ctx).await {
680 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 Err(e) => {
696 let _ = handle.fail_with_error(e.into());
697 }
698 }
699}
700
701pub enum AugmentedTaskOutcome {
704 NotApplicable,
707 Rejected(McpError),
710 Started(serde_json::Value, futures::future::BoxFuture<'static, ()>),
714}
715
716pub 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 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 Some(_) => return AugmentedTaskOutcome::NotApplicable,
754 };
755 let ttl = task_meta.get("ttl").and_then(serde_json::Value::as_u64);
756
757 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 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
803pub 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
938pub 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
1023pub 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 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
1074pub 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
1103pub 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 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
1138fn 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 #[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 #[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 #[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 #[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 #[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 #[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 #[test]
1638 fn test_method_constants() {
1639 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 #[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 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 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 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 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 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(¶ms), &ctx)
1813 .await
1814 .expect("routed")
1815 .expect("ok");
1816 assert_eq!(resp["taskId"], "t-1", "{method}");
1818 assert_eq!(resp["_meta"]["origin"], "test", "{method}");
1819 }
1820
1821 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 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 assert!(
1887 route_completion(None, methods::COMPLETION_COMPLETE, Some(¶ms), &ctx)
1888 .await
1889 .is_none()
1890 );
1891 assert!(
1893 route_completion(Some(dyn_handler), methods::TOOLS_LIST, None, &ctx)
1894 .await
1895 .is_none()
1896 );
1897
1898 let resp = route_completion(
1900 Some(dyn_handler),
1901 methods::COMPLETION_COMPLETE,
1902 Some(¶ms),
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); assert_eq!(values[0], "acme-0"); assert_eq!(resp["completion"]["hasMore"], true); assert_eq!(resp["completion"]["total"], 150); assert_eq!(resp["_meta"]["origin"], "test"); }
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 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 let params = serde_json::json!({ "cursor": cursor });
1967 let page2 = route_tools(&handler, methods::TOOLS_LIST, Some(¶ms), &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 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 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 let ok = route_resources(
2037 &Res { ok: true },
2038 methods::RESOURCES_SUBSCRIBE,
2039 Some(¶ms),
2040 &ctx,
2041 None,
2042 )
2043 .await
2044 .expect("subscribe is routed")
2045 .expect("ok");
2046 assert_eq!(ok, serde_json::json!({}));
2047
2048 assert!(
2050 route_resources(
2051 &Res { ok: false },
2052 methods::RESOURCES_SUBSCRIBE,
2053 Some(¶ms),
2054 &ctx,
2055 None,
2056 )
2057 .await
2058 .expect("routed")
2059 .is_err()
2060 );
2061
2062 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 let ok = route_resources(
2078 &Res { ok: true },
2079 methods::RESOURCES_UNSUBSCRIBE,
2080 Some(¶ms),
2081 &ctx,
2082 None,
2083 )
2084 .await
2085 .expect("unsubscribe is routed")
2086 .expect("ok");
2087 assert_eq!(ok, serde_json::json!({}));
2088 }
2089}