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] = &["2025-06-18", "2025-03-26", "2024-11-05"];
17
18pub const LATEST_PROTOCOL_VERSION: &str = "2025-06-18";
20
21pub type Handler =
23 Box<dyn Fn(serde_json::Value) -> crate::error::Result<serde_json::Value> + Send + Sync>;
24
25#[async_trait]
31pub trait AsyncToolHandler: Send + Sync {
32 async fn call(&self, params: serde_json::Value) -> crate::error::Result<serde_json::Value>;
34}
35
36struct AsyncHandlerFn<F>(F);
40
41#[async_trait]
42impl<F, Fut> AsyncToolHandler for AsyncHandlerFn<F>
43where
44 F: Fn(serde_json::Value) -> Fut + Send + Sync + 'static,
45 Fut: Future<Output = crate::error::Result<serde_json::Value>> + Send,
46{
47 async fn call(&self, params: serde_json::Value) -> crate::error::Result<serde_json::Value> {
48 (self.0)(params).await
49 }
50}
51
52pub struct McpServer {
54 server_name: String,
55 server_version: String,
56 tools: Vec<ToolDescription>,
57 resources: Vec<ResourceDescription>,
58 prompts: Vec<PromptDescription>,
59 handlers: HashMap<String, Handler>,
60 async_handlers: HashMap<String, Arc<dyn AsyncToolHandler>>,
61 resource_handlers: HashMap<String, Handler>,
62 prompt_handlers: HashMap<String, Handler>,
63 auth: Option<BearerAuth>,
64}
65
66impl McpServer {
67 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
69 Self {
70 server_name: name.into(),
71 server_version: version.into(),
72 tools: Vec::new(),
73 resources: Vec::new(),
74 prompts: Vec::new(),
75 handlers: HashMap::new(),
76 async_handlers: HashMap::new(),
77 resource_handlers: HashMap::new(),
78 prompt_handlers: HashMap::new(),
79 auth: None,
80 }
81 }
82
83 pub fn with_bearer_auth(mut self, token: impl Into<String>) -> Self {
85 self.auth = Some(BearerAuth::new(token));
86 self
87 }
88
89 pub fn with_generated_auth(mut self) -> (Self, String) {
93 let bearer = BearerAuth::generate();
94 let token = bearer.token().to_string();
95 self.auth = Some(bearer);
96 (self, token)
97 }
98
99 pub fn check_auth(&self, authorization_header: &str) -> bool {
101 match &self.auth {
102 None => true,
103 Some(bearer) => bearer.validate(authorization_header),
104 }
105 }
106
107 pub fn auth_enabled(&self) -> bool {
109 self.auth.is_some()
110 }
111
112 pub fn register_tool(&mut self, tool: ToolDescription) {
114 self.tools.push(tool);
115 }
116
117 pub fn register_resource(&mut self, resource: ResourceDescription) {
119 self.resources.push(resource);
120 }
121
122 pub fn set_handler(
124 &mut self,
125 tool_name: &str,
126 handler: impl Fn(serde_json::Value) -> crate::error::Result<serde_json::Value>
127 + Send
128 + Sync
129 + 'static,
130 ) {
131 self.handlers
132 .insert(tool_name.to_string(), Box::new(handler));
133 }
134
135 pub fn set_async_handler<F, Fut>(&mut self, tool_name: &str, handler: F)
141 where
142 F: Fn(serde_json::Value) -> Fut + Send + Sync + 'static,
143 Fut: Future<Output = crate::error::Result<serde_json::Value>> + Send,
144 {
145 self.async_handlers
146 .insert(tool_name.to_string(), Arc::new(AsyncHandlerFn(handler)));
147 }
148
149 pub fn name(&self) -> &str {
151 &self.server_name
152 }
153
154 pub fn version(&self) -> &str {
156 &self.server_version
157 }
158
159 pub fn tools(&self) -> &[ToolDescription] {
161 &self.tools
162 }
163
164 pub fn resources(&self) -> &[ResourceDescription] {
166 &self.resources
167 }
168
169 pub fn set_resource_handler(
171 &mut self,
172 uri: &str,
173 handler: impl Fn(serde_json::Value) -> crate::error::Result<serde_json::Value>
174 + Send
175 + Sync
176 + 'static,
177 ) {
178 self.resource_handlers
179 .insert(uri.to_string(), Box::new(handler));
180 }
181
182 pub fn read_resource(
184 &self,
185 uri: &str,
186 params: serde_json::Value,
187 ) -> crate::error::Result<serde_json::Value> {
188 let handler = self
189 .resource_handlers
190 .get(uri)
191 .ok_or_else(|| crate::error::KernelError::Config(format!("unknown resource: {uri}")))?;
192 handler(params)
193 }
194
195 pub fn register_prompt(&mut self, prompt: PromptDescription) {
197 self.prompts.push(prompt);
198 }
199
200 pub fn set_prompt_handler(
205 &mut self,
206 prompt_name: &str,
207 handler: impl Fn(serde_json::Value) -> crate::error::Result<serde_json::Value>
208 + Send
209 + Sync
210 + 'static,
211 ) {
212 self.prompt_handlers
213 .insert(prompt_name.to_string(), Box::new(handler));
214 }
215
216 pub fn prompts(&self) -> &[PromptDescription] {
218 &self.prompts
219 }
220
221 pub fn get_prompt(
223 &self,
224 name: &str,
225 params: serde_json::Value,
226 ) -> crate::error::Result<serde_json::Value> {
227 let handler = self
228 .prompt_handlers
229 .get(name)
230 .ok_or_else(|| crate::error::KernelError::Config(format!("unknown prompt: {name}")))?;
231 handler(params)
232 }
233
234 pub fn has_tool(&self, name: &str) -> bool {
240 self.handlers.contains_key(name) || self.async_handlers.contains_key(name)
241 }
242
243 pub fn validate_tool_args(
255 &self,
256 name: &str,
257 arguments: &serde_json::Value,
258 ) -> Result<(), String> {
259 let Some(tool) = self.tools.iter().find(|t| t.name == name) else {
260 return Ok(());
261 };
262 if tool.input_schema.get("type").and_then(|t| t.as_str()) != Some("object") {
263 return Ok(());
264 }
265 let obj = match arguments {
267 serde_json::Value::Null => None,
268 serde_json::Value::Object(o) => Some(o),
269 _ => return Err(format!("tool '{name}' expects an object for arguments")),
270 };
271 let Some(required) = tool.input_schema.get("required").and_then(|r| r.as_array()) else {
272 return Ok(());
273 };
274 for field in required.iter().filter_map(|f| f.as_str()) {
275 if !obj.is_some_and(|o| o.contains_key(field)) {
276 return Err(format!(
277 "tool '{name}' is missing required argument '{field}'"
278 ));
279 }
280 }
281 Ok(())
282 }
283
284 pub fn async_only_tools(&self) -> Vec<&str> {
287 let mut names: Vec<&str> = self
288 .async_handlers
289 .keys()
290 .filter(|n| !self.handlers.contains_key(*n))
291 .map(String::as_str)
292 .collect();
293 names.sort_unstable();
294 names
295 }
296
297 pub fn call_tool(
304 &self,
305 name: &str,
306 params: serde_json::Value,
307 ) -> crate::error::Result<serde_json::Value> {
308 let handler = self.handlers.get(name).ok_or_else(|| {
309 if self.async_handlers.contains_key(name) {
313 crate::error::KernelError::Config(format!(
314 "tool '{name}' has only an async handler; use call_tool_async"
315 ))
316 } else {
317 crate::error::KernelError::Config(format!("unknown tool: {name}"))
318 }
319 })?;
320 handler(params)
321 }
322
323 pub async fn call_tool_async(
327 &self,
328 name: &str,
329 params: serde_json::Value,
330 ) -> crate::error::Result<serde_json::Value> {
331 if let Some(handler) = self.async_handlers.get(name) {
332 return handler.call(params).await;
333 }
334 if let Some(handler) = self.handlers.get(name) {
335 return handler(params);
336 }
337 Err(crate::error::KernelError::Config(format!(
338 "unknown tool: {name}"
339 )))
340 }
341
342 pub fn negotiate_protocol_version(&self, requested: Option<&str>) -> &'static str {
348 match requested {
349 Some(v) => SUPPORTED_PROTOCOL_VERSIONS
350 .iter()
351 .find(|&&s| s == v)
352 .copied()
353 .unwrap_or(LATEST_PROTOCOL_VERSION),
354 None => LATEST_PROTOCOL_VERSION,
355 }
356 }
357
358 pub fn initialize_response(&self, requested_version: Option<&str>) -> serde_json::Value {
365 let mut capabilities = serde_json::json!({
366 "tools": { "listChanged": false },
367 "resources": { "subscribe": false, "listChanged": false },
368 });
369 if !self.prompts.is_empty() {
370 capabilities["prompts"] = serde_json::json!({ "listChanged": false });
371 }
372 serde_json::json!({
373 "protocolVersion": self.negotiate_protocol_version(requested_version),
374 "capabilities": capabilities,
375 "serverInfo": {
376 "name": self.server_name,
377 "version": self.server_version,
378 }
379 })
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386
387 #[test]
388 fn register_and_call_tool() {
389 let mut server = McpServer::new("test", "0.1.0");
390 server.register_tool(ToolDescription {
391 name: "echo".into(),
392 description: "Echo input".into(),
393 input_schema: serde_json::json!({"type": "object"}),
394 });
395 server.set_handler("echo", Ok);
396
397 let result = server
398 .call_tool("echo", serde_json::json!({"msg": "hi"}))
399 .unwrap();
400 assert_eq!(result["msg"], "hi");
401 }
402
403 #[test]
404 fn unknown_tool_returns_error() {
405 let server = McpServer::new("test", "0.1.0");
406 let result = server.call_tool("missing", serde_json::json!(null));
407 assert!(result.is_err());
408 }
409
410 #[test]
411 fn initialize_response_shape() {
412 let server = McpServer::new("my-server", "2.0.0");
413 let resp = server.initialize_response(None);
414 assert_eq!(resp["serverInfo"]["name"], "my-server");
415 assert_eq!(resp["protocolVersion"], LATEST_PROTOCOL_VERSION);
416 assert!(resp["capabilities"].get("prompts").is_none());
418 }
419
420 #[test]
421 fn initialize_negotiates_supported_version() {
422 let server = McpServer::new("s", "1.0");
423 assert_eq!(
425 server.initialize_response(Some("2024-11-05"))["protocolVersion"],
426 "2024-11-05"
427 );
428 assert_eq!(
430 server.initialize_response(Some("1999-01-01"))["protocolVersion"],
431 LATEST_PROTOCOL_VERSION
432 );
433 }
434
435 #[test]
436 fn initialize_advertises_prompts_when_registered() {
437 let mut server = McpServer::new("s", "1.0");
438 server.register_prompt(PromptDescription {
439 name: "greet".into(),
440 description: None,
441 arguments: Vec::new(),
442 });
443 let resp = server.initialize_response(None);
444 assert!(resp["capabilities"]["prompts"].is_object());
445 }
446
447 #[test]
448 fn register_and_get_prompt() {
449 let mut server = McpServer::new("s", "1.0");
450 server.register_prompt(PromptDescription {
451 name: "greet".into(),
452 description: Some("Greet someone".into()),
453 arguments: vec![crate::mcp::schema::PromptArgument {
454 name: "name".into(),
455 description: None,
456 required: true,
457 }],
458 });
459 server.set_prompt_handler("greet", |params| {
460 let who = params
461 .get("name")
462 .and_then(|v| v.as_str())
463 .unwrap_or("world");
464 Ok(serde_json::json!({
465 "messages": [{
466 "role": "user",
467 "content": { "type": "text", "text": format!("Hello, {who}!") }
468 }]
469 }))
470 });
471 assert_eq!(server.prompts().len(), 1);
472 let result = server
473 .get_prompt("greet", serde_json::json!({ "name": "Ada" }))
474 .unwrap();
475 assert_eq!(result["messages"][0]["content"]["text"], "Hello, Ada!");
476 }
477
478 #[test]
479 fn unknown_prompt_returns_error() {
480 let server = McpServer::new("s", "1.0");
481 assert!(server.get_prompt("missing", serde_json::json!({})).is_err());
482 }
483
484 #[test]
485 fn has_tool_reports_registration() {
486 let mut server = McpServer::new("s", "1.0");
487 server.set_handler("echo", Ok);
488 assert!(server.has_tool("echo"));
489 assert!(!server.has_tool("nope"));
490 }
491
492 #[test]
493 fn list_tools() {
494 let mut server = McpServer::new("test", "0.1.0");
495 server.register_tool(ToolDescription {
496 name: "a".into(),
497 description: "Tool A".into(),
498 input_schema: serde_json::json!({}),
499 });
500 server.register_tool(ToolDescription {
501 name: "b".into(),
502 description: "Tool B".into(),
503 input_schema: serde_json::json!({}),
504 });
505 assert_eq!(server.tools().len(), 2);
506 }
507
508 #[test]
509 fn read_resource() {
510 let mut server = McpServer::new("test", "0.1.0");
511 server.register_resource(ResourceDescription {
512 uri: "docs://readme".into(),
513 name: "README".into(),
514 description: Some("Project readme".into()),
515 mime_type: Some("text/markdown".into()),
516 });
517 server.set_resource_handler("docs://readme", |_params| {
518 Ok(serde_json::json!("# Hello World"))
519 });
520
521 let result = server
522 .read_resource("docs://readme", serde_json::json!({}))
523 .unwrap();
524 assert_eq!(result, serde_json::json!("# Hello World"));
525 }
526
527 #[test]
528 fn unknown_resource_returns_error() {
529 let server = McpServer::new("test", "0.1.0");
530 let result = server.read_resource("missing://uri", serde_json::json!({}));
531 assert!(result.is_err());
532 }
533
534 #[test]
535 fn no_auth_by_default() {
536 let server = McpServer::new("test", "0.1.0");
537 assert!(!server.auth_enabled());
538 assert!(server.check_auth(""));
539 assert!(server.check_auth("Bearer whatever"));
540 }
541
542 #[test]
543 fn with_bearer_auth_validates_correctly() {
544 let server = McpServer::new("test", "0.1.0").with_bearer_auth("my-token");
545 assert!(server.auth_enabled());
546 assert!(server.check_auth("Bearer my-token"));
547 assert!(!server.check_auth("Bearer wrong"));
548 assert!(!server.check_auth(""));
549 }
550
551 #[test]
552 fn with_generated_auth_returns_token() {
553 let (server, token) = McpServer::new("test", "0.1.0").with_generated_auth();
554 assert!(server.auth_enabled());
555 assert_eq!(token.len(), 32);
556 assert!(server.check_auth(&format!("Bearer {token}")));
557 assert!(!server.check_auth("Bearer bad"));
558 }
559
560 #[tokio::test]
562 async fn async_handler_is_awaited() {
563 let mut server = McpServer::new("test", "0.1.0");
564 server.register_tool(ToolDescription {
565 name: "async-echo".into(),
566 description: "Echo input asynchronously".into(),
567 input_schema: serde_json::json!({"type": "object"}),
568 });
569 server.set_async_handler("async-echo", |params| async move { Ok(params) });
570
571 let result = server
572 .call_tool_async("async-echo", serde_json::json!({"msg": "hi"}))
573 .await
574 .unwrap();
575 assert_eq!(result["msg"], "hi");
576 }
577
578 #[tokio::test]
580 async fn async_dispatch_falls_back_to_sync() {
581 let mut server = McpServer::new("test", "0.1.0");
582 server.register_tool(ToolDescription {
583 name: "sync-echo".into(),
584 description: "Echo input synchronously".into(),
585 input_schema: serde_json::json!({"type": "object"}),
586 });
587 server.set_handler("sync-echo", Ok);
588
589 let result = server
590 .call_tool_async("sync-echo", serde_json::json!({"x": 1}))
591 .await
592 .unwrap();
593 assert_eq!(result["x"], 1);
594 }
595
596 #[tokio::test]
597 async fn call_tool_async_unknown_tool_errors() {
598 let server = McpServer::new("test", "0.1.0");
599 assert!(
600 server
601 .call_tool_async("missing", serde_json::json!(null))
602 .await
603 .is_err()
604 );
605 }
606}