1use std::collections::HashMap;
4use std::future::Future;
5use std::sync::Arc;
6
7use async_trait::async_trait;
8
9use crate::mcp::auth::BearerAuth;
10use crate::mcp::schema::{PromptDescription, ResourceDescription, ToolDescription};
11
12pub const SUPPORTED_PROTOCOL_VERSIONS: &[&str] =
20 &["2026-07-28", "2025-06-18", "2025-03-26", "2024-11-05"];
21
22pub const LEGACY_PROTOCOL_VERSIONS: &[&str] = &["2025-06-18", "2025-03-26", "2024-11-05"];
26
27pub const LATEST_PROTOCOL_VERSION: &str = "2026-07-28";
29
30pub const LEGACY_LATEST_PROTOCOL_VERSION: &str = "2025-06-18";
36
37pub const META_PROTOCOL_VERSION: &str = "io.modelcontextprotocol/protocolVersion";
39
40pub const META_SERVER_INFO: &str = "io.modelcontextprotocol/serverInfo";
42
43pub const META_SUBSCRIPTION_ID: &str = "io.modelcontextprotocol/subscriptionId";
46
47pub fn request_protocol_version(req: &serde_json::Value) -> Option<&str> {
50 req.pointer("/params/_meta")
51 .and_then(|m| m.get(META_PROTOCOL_VERSION))
52 .and_then(|v| v.as_str())
53}
54
55fn is_cacheable_method(method: &str) -> bool {
58 matches!(
59 method,
60 "server/discover"
61 | "tools/list"
62 | "resources/list"
63 | "resources/templates/list"
64 | "prompts/list"
65 | "resources/read"
66 )
67}
68
69pub(crate) fn shape_modern_result(
73 protocol_version: &str,
74 method: &str,
75 result: &mut serde_json::Value,
76) {
77 if protocol_version != LATEST_PROTOCOL_VERSION {
78 return;
79 }
80 if let Some(obj) = result.as_object_mut() {
81 obj.entry("resultType")
82 .or_insert_with(|| serde_json::json!("complete"));
83 if is_cacheable_method(method) {
84 obj.entry("ttlMs")
85 .or_insert_with(|| serde_json::json!(3_600_000));
86 obj.entry("cacheScope")
87 .or_insert_with(|| serde_json::json!("private"));
88 }
89 }
90}
91
92pub type Handler =
94 Box<dyn Fn(serde_json::Value) -> crate::error::Result<serde_json::Value> + Send + Sync>;
95
96#[async_trait]
102pub trait AsyncToolHandler: Send + Sync {
103 async fn call(&self, params: serde_json::Value) -> crate::error::Result<serde_json::Value>;
105}
106
107struct AsyncHandlerFn<F>(F);
111
112#[async_trait]
113impl<F, Fut> AsyncToolHandler for AsyncHandlerFn<F>
114where
115 F: Fn(serde_json::Value) -> Fut + Send + Sync + 'static,
116 Fut: Future<Output = crate::error::Result<serde_json::Value>> + Send,
117{
118 async fn call(&self, params: serde_json::Value) -> crate::error::Result<serde_json::Value> {
119 (self.0)(params).await
120 }
121}
122
123pub struct McpServer {
125 server_name: String,
126 server_version: String,
127 tools: Vec<ToolDescription>,
128 resources: Vec<ResourceDescription>,
129 prompts: Vec<PromptDescription>,
130 handlers: HashMap<String, Handler>,
131 async_handlers: HashMap<String, Arc<dyn AsyncToolHandler>>,
132 resource_handlers: HashMap<String, Handler>,
133 prompt_handlers: HashMap<String, Handler>,
134 auth: Option<BearerAuth>,
135}
136
137impl McpServer {
138 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
140 Self {
141 server_name: name.into(),
142 server_version: version.into(),
143 tools: Vec::new(),
144 resources: Vec::new(),
145 prompts: Vec::new(),
146 handlers: HashMap::new(),
147 async_handlers: HashMap::new(),
148 resource_handlers: HashMap::new(),
149 prompt_handlers: HashMap::new(),
150 auth: None,
151 }
152 }
153
154 pub fn with_bearer_auth(mut self, token: impl Into<String>) -> Self {
156 self.auth = Some(BearerAuth::new(token));
157 self
158 }
159
160 pub fn with_generated_auth(mut self) -> (Self, String) {
164 let bearer = BearerAuth::generate();
165 let token = bearer.token().to_string();
166 self.auth = Some(bearer);
167 (self, token)
168 }
169
170 pub fn check_auth(&self, authorization_header: &str) -> bool {
172 match &self.auth {
173 None => true,
174 Some(bearer) => bearer.validate(authorization_header),
175 }
176 }
177
178 pub fn auth_enabled(&self) -> bool {
180 self.auth.is_some()
181 }
182
183 pub fn register_tool(&mut self, tool: ToolDescription) {
185 self.tools.push(tool);
186 }
187
188 pub fn register_resource(&mut self, resource: ResourceDescription) {
190 self.resources.push(resource);
191 }
192
193 pub fn set_handler(
195 &mut self,
196 tool_name: &str,
197 handler: impl Fn(serde_json::Value) -> crate::error::Result<serde_json::Value>
198 + Send
199 + Sync
200 + 'static,
201 ) {
202 self.handlers
203 .insert(tool_name.to_string(), Box::new(handler));
204 }
205
206 pub fn set_async_handler<F, Fut>(&mut self, tool_name: &str, handler: F)
212 where
213 F: Fn(serde_json::Value) -> Fut + Send + Sync + 'static,
214 Fut: Future<Output = crate::error::Result<serde_json::Value>> + Send,
215 {
216 self.async_handlers
217 .insert(tool_name.to_string(), Arc::new(AsyncHandlerFn(handler)));
218 }
219
220 pub fn name(&self) -> &str {
222 &self.server_name
223 }
224
225 pub fn version(&self) -> &str {
227 &self.server_version
228 }
229
230 pub fn tools(&self) -> &[ToolDescription] {
232 &self.tools
233 }
234
235 pub fn resources(&self) -> &[ResourceDescription] {
237 &self.resources
238 }
239
240 pub fn set_resource_handler(
242 &mut self,
243 uri: &str,
244 handler: impl Fn(serde_json::Value) -> crate::error::Result<serde_json::Value>
245 + Send
246 + Sync
247 + 'static,
248 ) {
249 self.resource_handlers
250 .insert(uri.to_string(), Box::new(handler));
251 }
252
253 pub fn read_resource(
255 &self,
256 uri: &str,
257 params: serde_json::Value,
258 ) -> crate::error::Result<serde_json::Value> {
259 let handler = self
260 .resource_handlers
261 .get(uri)
262 .ok_or_else(|| crate::error::KernelError::Config(format!("unknown resource: {uri}")))?;
263 handler(params)
264 }
265
266 pub fn register_prompt(&mut self, prompt: PromptDescription) {
268 self.prompts.push(prompt);
269 }
270
271 pub fn set_prompt_handler(
276 &mut self,
277 prompt_name: &str,
278 handler: impl Fn(serde_json::Value) -> crate::error::Result<serde_json::Value>
279 + Send
280 + Sync
281 + 'static,
282 ) {
283 self.prompt_handlers
284 .insert(prompt_name.to_string(), Box::new(handler));
285 }
286
287 pub fn prompts(&self) -> &[PromptDescription] {
289 &self.prompts
290 }
291
292 pub fn get_prompt(
294 &self,
295 name: &str,
296 params: serde_json::Value,
297 ) -> crate::error::Result<serde_json::Value> {
298 let handler = self
299 .prompt_handlers
300 .get(name)
301 .ok_or_else(|| crate::error::KernelError::Config(format!("unknown prompt: {name}")))?;
302 handler(params)
303 }
304
305 pub fn has_tool(&self, name: &str) -> bool {
311 self.handlers.contains_key(name) || self.async_handlers.contains_key(name)
312 }
313
314 pub fn validate_tool_args(
326 &self,
327 name: &str,
328 arguments: &serde_json::Value,
329 ) -> Result<(), String> {
330 let Some(tool) = self.tools.iter().find(|t| t.name == name) else {
331 return Ok(());
332 };
333 if tool.input_schema.get("type").and_then(|t| t.as_str()) != Some("object") {
334 return Ok(());
335 }
336 let obj = match arguments {
338 serde_json::Value::Null => None,
339 serde_json::Value::Object(o) => Some(o),
340 _ => return Err(format!("tool '{name}' expects an object for arguments")),
341 };
342 let Some(required) = tool.input_schema.get("required").and_then(|r| r.as_array()) else {
343 return Ok(());
344 };
345 for field in required.iter().filter_map(|f| f.as_str()) {
346 if !obj.is_some_and(|o| o.contains_key(field)) {
347 return Err(format!(
348 "tool '{name}' is missing required argument '{field}'"
349 ));
350 }
351 }
352 Ok(())
353 }
354
355 pub fn async_only_tools(&self) -> Vec<&str> {
358 let mut names: Vec<&str> = self
359 .async_handlers
360 .keys()
361 .filter(|n| !self.handlers.contains_key(*n))
362 .map(String::as_str)
363 .collect();
364 names.sort_unstable();
365 names
366 }
367
368 pub fn call_tool(
375 &self,
376 name: &str,
377 params: serde_json::Value,
378 ) -> crate::error::Result<serde_json::Value> {
379 let handler = self.handlers.get(name).ok_or_else(|| {
380 if self.async_handlers.contains_key(name) {
384 crate::error::KernelError::Config(format!(
385 "tool '{name}' has only an async handler; use call_tool_async"
386 ))
387 } else {
388 crate::error::KernelError::Config(format!("unknown tool: {name}"))
389 }
390 })?;
391 handler(params)
392 }
393
394 pub async fn call_tool_async(
398 &self,
399 name: &str,
400 params: serde_json::Value,
401 ) -> crate::error::Result<serde_json::Value> {
402 if let Some(handler) = self.async_handlers.get(name) {
403 return handler.call(params).await;
404 }
405 if let Some(handler) = self.handlers.get(name) {
406 return handler(params);
407 }
408 Err(crate::error::KernelError::Config(format!(
409 "unknown tool: {name}"
410 )))
411 }
412
413 pub fn negotiate_protocol_version(&self, requested: Option<&str>) -> &'static str {
422 match requested {
423 Some(v) => LEGACY_PROTOCOL_VERSIONS
424 .iter()
425 .find(|&&s| s == v)
426 .copied()
427 .unwrap_or(LEGACY_LATEST_PROTOCOL_VERSION),
428 None => LEGACY_LATEST_PROTOCOL_VERSION,
429 }
430 }
431
432 fn capabilities(&self) -> serde_json::Value {
434 let mut capabilities = serde_json::json!({
435 "tools": { "listChanged": false },
436 "resources": { "subscribe": false, "listChanged": false },
437 });
438 if !self.prompts.is_empty() {
439 capabilities["prompts"] = serde_json::json!({ "listChanged": false });
440 }
441 capabilities
442 }
443
444 pub fn discover_response(&self) -> serde_json::Value {
448 serde_json::json!({
449 "supportedVersions": SUPPORTED_PROTOCOL_VERSIONS,
450 "capabilities": self.capabilities(),
451 "_meta": {
452 META_SERVER_INFO: {
453 "name": self.server_name,
454 "version": self.server_version,
455 }
456 },
457 "ttlMs": 3_600_000,
458 "cacheScope": "private",
459 })
460 }
461
462 pub fn subscription_ack_and_close(
470 &self,
471 id: &serde_json::Value,
472 ) -> (serde_json::Value, serde_json::Value) {
473 let subscription_tag = serde_json::json!({ META_SUBSCRIPTION_ID: id });
474 let ack = serde_json::json!({
475 "jsonrpc": "2.0",
476 "method": "notifications/subscriptions/acknowledged",
477 "params": {
478 "_meta": subscription_tag,
479 "notifications": {},
480 }
481 });
482 let mut close = serde_json::json!({ "_meta": subscription_tag });
483 shape_modern_result(LATEST_PROTOCOL_VERSION, "subscriptions/listen", &mut close);
484 (ack, close)
485 }
486
487 pub fn initialize_response(&self, requested_version: Option<&str>) -> serde_json::Value {
494 serde_json::json!({
495 "protocolVersion": self.negotiate_protocol_version(requested_version),
496 "capabilities": self.capabilities(),
497 "serverInfo": {
498 "name": self.server_name,
499 "version": self.server_version,
500 }
501 })
502 }
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508
509 #[test]
510 fn register_and_call_tool() {
511 let mut server = McpServer::new("test", "0.1.0");
512 server.register_tool(ToolDescription {
513 name: "echo".into(),
514 description: "Echo input".into(),
515 input_schema: serde_json::json!({"type": "object"}),
516 });
517 server.set_handler("echo", Ok);
518
519 let result = server
520 .call_tool("echo", serde_json::json!({"msg": "hi"}))
521 .unwrap();
522 assert_eq!(result["msg"], "hi");
523 }
524
525 #[test]
526 fn unknown_tool_returns_error() {
527 let server = McpServer::new("test", "0.1.0");
528 let result = server.call_tool("missing", serde_json::json!(null));
529 assert!(result.is_err());
530 }
531
532 #[test]
533 fn initialize_response_shape() {
534 let server = McpServer::new("my-server", "2.0.0");
535 let resp = server.initialize_response(None);
536 assert_eq!(resp["serverInfo"]["name"], "my-server");
537 assert_eq!(resp["protocolVersion"], LEGACY_LATEST_PROTOCOL_VERSION);
540 assert!(resp["capabilities"].get("prompts").is_none());
542 }
543
544 #[test]
545 fn initialize_negotiates_supported_version() {
546 let server = McpServer::new("s", "1.0");
547 assert_eq!(
549 server.initialize_response(Some("2024-11-05"))["protocolVersion"],
550 "2024-11-05"
551 );
552 assert_eq!(
555 server.initialize_response(Some("2026-07-28"))["protocolVersion"],
556 LEGACY_LATEST_PROTOCOL_VERSION
557 );
558 assert_eq!(
560 server.initialize_response(Some("1999-01-01"))["protocolVersion"],
561 LEGACY_LATEST_PROTOCOL_VERSION
562 );
563 }
564
565 #[test]
566 fn discover_response_lists_versions_and_identity() {
567 let mut server = McpServer::new("my-server", "2.0.0");
568 server.register_prompt(PromptDescription {
569 name: "greet".into(),
570 description: None,
571 arguments: Vec::new(),
572 });
573 let resp = server.discover_response();
574 assert_eq!(
575 resp["supportedVersions"][0], LATEST_PROTOCOL_VERSION,
576 "newest first: {resp}"
577 );
578 assert!(resp["capabilities"]["prompts"].is_object());
579 assert_eq!(resp["_meta"][META_SERVER_INFO]["name"], "my-server");
580 assert!(resp["ttlMs"].is_u64(), "cacheable result: {resp}");
581 assert_eq!(resp["cacheScope"], "private");
582 }
583
584 #[test]
585 fn shape_modern_result_adds_required_fields() {
586 let mut list = serde_json::json!({ "tools": [] });
588 shape_modern_result(LATEST_PROTOCOL_VERSION, "tools/list", &mut list);
589 assert_eq!(list["resultType"], "complete");
590 assert!(list["ttlMs"].is_u64());
591 assert_eq!(list["cacheScope"], "private");
592
593 let mut call = serde_json::json!({ "content": [], "isError": false });
595 shape_modern_result(LATEST_PROTOCOL_VERSION, "tools/call", &mut call);
596 assert_eq!(call["resultType"], "complete");
597 assert!(call.get("ttlMs").is_none());
598
599 let mut legacy = serde_json::json!({ "tools": [] });
601 shape_modern_result("2025-06-18", "tools/list", &mut legacy);
602 assert!(legacy.get("resultType").is_none());
603 }
604
605 #[test]
606 fn initialize_advertises_prompts_when_registered() {
607 let mut server = McpServer::new("s", "1.0");
608 server.register_prompt(PromptDescription {
609 name: "greet".into(),
610 description: None,
611 arguments: Vec::new(),
612 });
613 let resp = server.initialize_response(None);
614 assert!(resp["capabilities"]["prompts"].is_object());
615 }
616
617 #[test]
618 fn register_and_get_prompt() {
619 let mut server = McpServer::new("s", "1.0");
620 server.register_prompt(PromptDescription {
621 name: "greet".into(),
622 description: Some("Greet someone".into()),
623 arguments: vec![crate::mcp::schema::PromptArgument {
624 name: "name".into(),
625 description: None,
626 required: true,
627 arg_type: None,
628 }],
629 });
630 server.set_prompt_handler("greet", |params| {
631 let who = params
632 .get("name")
633 .and_then(|v| v.as_str())
634 .unwrap_or("world");
635 Ok(serde_json::json!({
636 "messages": [{
637 "role": "user",
638 "content": { "type": "text", "text": format!("Hello, {who}!") }
639 }]
640 }))
641 });
642 assert_eq!(server.prompts().len(), 1);
643 let result = server
644 .get_prompt("greet", serde_json::json!({ "name": "Ada" }))
645 .unwrap();
646 assert_eq!(result["messages"][0]["content"]["text"], "Hello, Ada!");
647 }
648
649 #[test]
650 fn unknown_prompt_returns_error() {
651 let server = McpServer::new("s", "1.0");
652 assert!(server.get_prompt("missing", serde_json::json!({})).is_err());
653 }
654
655 #[test]
656 fn has_tool_reports_registration() {
657 let mut server = McpServer::new("s", "1.0");
658 server.set_handler("echo", Ok);
659 assert!(server.has_tool("echo"));
660 assert!(!server.has_tool("nope"));
661 }
662
663 #[test]
664 fn list_tools() {
665 let mut server = McpServer::new("test", "0.1.0");
666 server.register_tool(ToolDescription {
667 name: "a".into(),
668 description: "Tool A".into(),
669 input_schema: serde_json::json!({}),
670 });
671 server.register_tool(ToolDescription {
672 name: "b".into(),
673 description: "Tool B".into(),
674 input_schema: serde_json::json!({}),
675 });
676 assert_eq!(server.tools().len(), 2);
677 }
678
679 #[test]
680 fn read_resource() {
681 let mut server = McpServer::new("test", "0.1.0");
682 server.register_resource(ResourceDescription {
683 uri: "docs://readme".into(),
684 name: "README".into(),
685 description: Some("Project readme".into()),
686 mime_type: Some("text/markdown".into()),
687 });
688 server.set_resource_handler("docs://readme", |_params| {
689 Ok(serde_json::json!("# Hello World"))
690 });
691
692 let result = server
693 .read_resource("docs://readme", serde_json::json!({}))
694 .unwrap();
695 assert_eq!(result, serde_json::json!("# Hello World"));
696 }
697
698 #[test]
699 fn unknown_resource_returns_error() {
700 let server = McpServer::new("test", "0.1.0");
701 let result = server.read_resource("missing://uri", serde_json::json!({}));
702 assert!(result.is_err());
703 }
704
705 #[test]
706 fn no_auth_by_default() {
707 let server = McpServer::new("test", "0.1.0");
708 assert!(!server.auth_enabled());
709 assert!(server.check_auth(""));
710 assert!(server.check_auth("Bearer whatever"));
711 }
712
713 #[test]
714 fn with_bearer_auth_validates_correctly() {
715 let server = McpServer::new("test", "0.1.0").with_bearer_auth("my-token");
716 assert!(server.auth_enabled());
717 assert!(server.check_auth("Bearer my-token"));
718 assert!(!server.check_auth("Bearer wrong"));
719 assert!(!server.check_auth(""));
720 }
721
722 #[test]
723 fn with_generated_auth_returns_token() {
724 let (server, token) = McpServer::new("test", "0.1.0").with_generated_auth();
725 assert!(server.auth_enabled());
726 assert_eq!(token.len(), 32);
727 assert!(server.check_auth(&format!("Bearer {token}")));
728 assert!(!server.check_auth("Bearer bad"));
729 }
730
731 #[tokio::test]
733 async fn async_handler_is_awaited() {
734 let mut server = McpServer::new("test", "0.1.0");
735 server.register_tool(ToolDescription {
736 name: "async-echo".into(),
737 description: "Echo input asynchronously".into(),
738 input_schema: serde_json::json!({"type": "object"}),
739 });
740 server.set_async_handler("async-echo", |params| async move { Ok(params) });
741
742 let result = server
743 .call_tool_async("async-echo", serde_json::json!({"msg": "hi"}))
744 .await
745 .unwrap();
746 assert_eq!(result["msg"], "hi");
747 }
748
749 #[tokio::test]
751 async fn async_dispatch_falls_back_to_sync() {
752 let mut server = McpServer::new("test", "0.1.0");
753 server.register_tool(ToolDescription {
754 name: "sync-echo".into(),
755 description: "Echo input synchronously".into(),
756 input_schema: serde_json::json!({"type": "object"}),
757 });
758 server.set_handler("sync-echo", Ok);
759
760 let result = server
761 .call_tool_async("sync-echo", serde_json::json!({"x": 1}))
762 .await
763 .unwrap();
764 assert_eq!(result["x"], 1);
765 }
766
767 #[tokio::test]
768 async fn call_tool_async_unknown_tool_errors() {
769 let server = McpServer::new("test", "0.1.0");
770 assert!(
771 server
772 .call_tool_async("missing", serde_json::json!(null))
773 .await
774 .is_err()
775 );
776 }
777}