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 call_tool(
245 &self,
246 name: &str,
247 params: serde_json::Value,
248 ) -> crate::error::Result<serde_json::Value> {
249 let handler = self
250 .handlers
251 .get(name)
252 .ok_or_else(|| crate::error::KernelError::Config(format!("unknown tool: {name}")))?;
253 handler(params)
254 }
255
256 pub async fn call_tool_async(
260 &self,
261 name: &str,
262 params: serde_json::Value,
263 ) -> crate::error::Result<serde_json::Value> {
264 if let Some(handler) = self.async_handlers.get(name) {
265 return handler.call(params).await;
266 }
267 if let Some(handler) = self.handlers.get(name) {
268 return handler(params);
269 }
270 Err(crate::error::KernelError::Config(format!(
271 "unknown tool: {name}"
272 )))
273 }
274
275 pub fn negotiate_protocol_version(&self, requested: Option<&str>) -> &'static str {
281 match requested {
282 Some(v) => SUPPORTED_PROTOCOL_VERSIONS
283 .iter()
284 .find(|&&s| s == v)
285 .copied()
286 .unwrap_or(LATEST_PROTOCOL_VERSION),
287 None => LATEST_PROTOCOL_VERSION,
288 }
289 }
290
291 pub fn initialize_response(&self, requested_version: Option<&str>) -> serde_json::Value {
298 let mut capabilities = serde_json::json!({
299 "tools": { "listChanged": false },
300 "resources": { "subscribe": false, "listChanged": false },
301 });
302 if !self.prompts.is_empty() {
303 capabilities["prompts"] = serde_json::json!({ "listChanged": false });
304 }
305 serde_json::json!({
306 "protocolVersion": self.negotiate_protocol_version(requested_version),
307 "capabilities": capabilities,
308 "serverInfo": {
309 "name": self.server_name,
310 "version": self.server_version,
311 }
312 })
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn register_and_call_tool() {
322 let mut server = McpServer::new("test", "0.1.0");
323 server.register_tool(ToolDescription {
324 name: "echo".into(),
325 description: "Echo input".into(),
326 input_schema: serde_json::json!({"type": "object"}),
327 });
328 server.set_handler("echo", Ok);
329
330 let result = server
331 .call_tool("echo", serde_json::json!({"msg": "hi"}))
332 .unwrap();
333 assert_eq!(result["msg"], "hi");
334 }
335
336 #[test]
337 fn unknown_tool_returns_error() {
338 let server = McpServer::new("test", "0.1.0");
339 let result = server.call_tool("missing", serde_json::json!(null));
340 assert!(result.is_err());
341 }
342
343 #[test]
344 fn initialize_response_shape() {
345 let server = McpServer::new("my-server", "2.0.0");
346 let resp = server.initialize_response(None);
347 assert_eq!(resp["serverInfo"]["name"], "my-server");
348 assert_eq!(resp["protocolVersion"], LATEST_PROTOCOL_VERSION);
349 assert!(resp["capabilities"].get("prompts").is_none());
351 }
352
353 #[test]
354 fn initialize_negotiates_supported_version() {
355 let server = McpServer::new("s", "1.0");
356 assert_eq!(
358 server.initialize_response(Some("2024-11-05"))["protocolVersion"],
359 "2024-11-05"
360 );
361 assert_eq!(
363 server.initialize_response(Some("1999-01-01"))["protocolVersion"],
364 LATEST_PROTOCOL_VERSION
365 );
366 }
367
368 #[test]
369 fn initialize_advertises_prompts_when_registered() {
370 let mut server = McpServer::new("s", "1.0");
371 server.register_prompt(PromptDescription {
372 name: "greet".into(),
373 description: None,
374 arguments: Vec::new(),
375 });
376 let resp = server.initialize_response(None);
377 assert!(resp["capabilities"]["prompts"].is_object());
378 }
379
380 #[test]
381 fn register_and_get_prompt() {
382 let mut server = McpServer::new("s", "1.0");
383 server.register_prompt(PromptDescription {
384 name: "greet".into(),
385 description: Some("Greet someone".into()),
386 arguments: vec![crate::mcp::schema::PromptArgument {
387 name: "name".into(),
388 description: None,
389 required: true,
390 }],
391 });
392 server.set_prompt_handler("greet", |params| {
393 let who = params
394 .get("name")
395 .and_then(|v| v.as_str())
396 .unwrap_or("world");
397 Ok(serde_json::json!({
398 "messages": [{
399 "role": "user",
400 "content": { "type": "text", "text": format!("Hello, {who}!") }
401 }]
402 }))
403 });
404 assert_eq!(server.prompts().len(), 1);
405 let result = server
406 .get_prompt("greet", serde_json::json!({ "name": "Ada" }))
407 .unwrap();
408 assert_eq!(result["messages"][0]["content"]["text"], "Hello, Ada!");
409 }
410
411 #[test]
412 fn unknown_prompt_returns_error() {
413 let server = McpServer::new("s", "1.0");
414 assert!(server.get_prompt("missing", serde_json::json!({})).is_err());
415 }
416
417 #[test]
418 fn has_tool_reports_registration() {
419 let mut server = McpServer::new("s", "1.0");
420 server.set_handler("echo", Ok);
421 assert!(server.has_tool("echo"));
422 assert!(!server.has_tool("nope"));
423 }
424
425 #[test]
426 fn list_tools() {
427 let mut server = McpServer::new("test", "0.1.0");
428 server.register_tool(ToolDescription {
429 name: "a".into(),
430 description: "Tool A".into(),
431 input_schema: serde_json::json!({}),
432 });
433 server.register_tool(ToolDescription {
434 name: "b".into(),
435 description: "Tool B".into(),
436 input_schema: serde_json::json!({}),
437 });
438 assert_eq!(server.tools().len(), 2);
439 }
440
441 #[test]
442 fn read_resource() {
443 let mut server = McpServer::new("test", "0.1.0");
444 server.register_resource(ResourceDescription {
445 uri: "docs://readme".into(),
446 name: "README".into(),
447 description: Some("Project readme".into()),
448 mime_type: Some("text/markdown".into()),
449 });
450 server.set_resource_handler("docs://readme", |_params| {
451 Ok(serde_json::json!("# Hello World"))
452 });
453
454 let result = server
455 .read_resource("docs://readme", serde_json::json!({}))
456 .unwrap();
457 assert_eq!(result, serde_json::json!("# Hello World"));
458 }
459
460 #[test]
461 fn unknown_resource_returns_error() {
462 let server = McpServer::new("test", "0.1.0");
463 let result = server.read_resource("missing://uri", serde_json::json!({}));
464 assert!(result.is_err());
465 }
466
467 #[test]
468 fn no_auth_by_default() {
469 let server = McpServer::new("test", "0.1.0");
470 assert!(!server.auth_enabled());
471 assert!(server.check_auth(""));
472 assert!(server.check_auth("Bearer whatever"));
473 }
474
475 #[test]
476 fn with_bearer_auth_validates_correctly() {
477 let server = McpServer::new("test", "0.1.0").with_bearer_auth("my-token");
478 assert!(server.auth_enabled());
479 assert!(server.check_auth("Bearer my-token"));
480 assert!(!server.check_auth("Bearer wrong"));
481 assert!(!server.check_auth(""));
482 }
483
484 #[test]
485 fn with_generated_auth_returns_token() {
486 let (server, token) = McpServer::new("test", "0.1.0").with_generated_auth();
487 assert!(server.auth_enabled());
488 assert_eq!(token.len(), 32);
489 assert!(server.check_auth(&format!("Bearer {token}")));
490 assert!(!server.check_auth("Bearer bad"));
491 }
492
493 #[tokio::test]
495 async fn async_handler_is_awaited() {
496 let mut server = McpServer::new("test", "0.1.0");
497 server.register_tool(ToolDescription {
498 name: "async-echo".into(),
499 description: "Echo input asynchronously".into(),
500 input_schema: serde_json::json!({"type": "object"}),
501 });
502 server.set_async_handler("async-echo", |params| async move { Ok(params) });
503
504 let result = server
505 .call_tool_async("async-echo", serde_json::json!({"msg": "hi"}))
506 .await
507 .unwrap();
508 assert_eq!(result["msg"], "hi");
509 }
510
511 #[tokio::test]
513 async fn async_dispatch_falls_back_to_sync() {
514 let mut server = McpServer::new("test", "0.1.0");
515 server.register_tool(ToolDescription {
516 name: "sync-echo".into(),
517 description: "Echo input synchronously".into(),
518 input_schema: serde_json::json!({"type": "object"}),
519 });
520 server.set_handler("sync-echo", Ok);
521
522 let result = server
523 .call_tool_async("sync-echo", serde_json::json!({"x": 1}))
524 .await
525 .unwrap();
526 assert_eq!(result["x"], 1);
527 }
528
529 #[tokio::test]
530 async fn call_tool_async_unknown_tool_errors() {
531 let server = McpServer::new("test", "0.1.0");
532 assert!(
533 server
534 .call_tool_async("missing", serde_json::json!(null))
535 .await
536 .is_err()
537 );
538 }
539}