allframe_mcp/
lib.rs

1//! Model Context Protocol (MCP) Server
2//!
3//! Automatically exposes AllFrame Router handlers as LLM-callable tools.
4//!
5//! # Router-based MCP Server
6//!
7//! ```rust,no_run
8//! use allframe_core::router::Router;
9//! use allframe_mcp::McpServer;
10//!
11//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
12//! let mut router = Router::new();
13//! router.register("get_user", || async { "User data".to_string() });
14//! router.register("create_user", || async { "Created".to_string() });
15//!
16//! let mcp_server = McpServer::new(router);
17//! // mcp_server.serve_stdio().await?;
18//! # Ok(())
19//! # }
20//! ```
21//!
22//! # Forge MCP Server (Code Generation)
23//!
24//! ```rust,no_run
25//! use allframe_mcp::forge::ForgeMcpServer;
26//! use std::path::PathBuf;
27//!
28//! fn main() -> Result<(), Box<dyn std::error::Error>> {
29//!     let server = ForgeMcpServer::new(PathBuf::from("./my-project"))?;
30//!     server.serve_stdio();
31//!     Ok(())
32//! }
33//! ```
34
35pub mod forge;
36pub mod schema;
37pub mod server;
38pub mod stdio;
39pub mod tools;
40
41pub use schema::{coerce_type, extract_enum_values, openapi_to_json_schema, validate_input};
42pub use server::McpServer;
43pub use stdio::{init_tracing, StdioConfig, StdioTransport};
44pub use tools::McpTool;
45
46#[cfg(test)]
47mod tests {
48    use allframe_core::router::Router;
49
50    use super::*;
51
52    // Phase 1 Tests: Core MCP Server Functionality
53
54    #[test]
55    fn test_mcp_server_creation() {
56        let router = Router::new();
57        let mcp_server = McpServer::new(router);
58
59        // Should create server successfully
60        assert_eq!(mcp_server.tool_count(), 0);
61    }
62
63    #[test]
64    fn test_mcp_server_discovers_handlers() {
65        let mut router = Router::new();
66        router.register("get_user", || async { "User data".to_string() });
67        router.register("create_user", || async { "Created".to_string() });
68        router.register("update_user", || async { "Updated".to_string() });
69
70        let mcp_server = McpServer::new(router);
71
72        // Should discover all 3 handlers as tools
73        assert_eq!(mcp_server.tool_count(), 3);
74    }
75
76    #[tokio::test]
77    async fn test_mcp_server_lists_tools() {
78        let mut router = Router::new();
79        router.register("get_user", || async { "User data".to_string() });
80        router.register("create_user", || async { "Created".to_string() });
81
82        let mcp_server = McpServer::new(router);
83        let tools = mcp_server.list_tools().await;
84
85        // Should return 2 tools
86        assert_eq!(tools.len(), 2);
87
88        // Should contain expected tool names
89        let tool_names: Vec<String> = tools.iter().map(|t| t.name.clone()).collect();
90        assert!(tool_names.contains(&"get_user".to_string()));
91        assert!(tool_names.contains(&"create_user".to_string()));
92    }
93
94    #[tokio::test]
95    async fn test_mcp_tool_has_required_fields() {
96        let mut router = Router::new();
97        router.register("get_user", || async { "User data".to_string() });
98
99        let mcp_server = McpServer::new(router);
100        let tools = mcp_server.list_tools().await;
101        let tool = &tools[0];
102
103        // Tool should have name
104        assert_eq!(tool.name, "get_user");
105
106        // Tool should have description (can be auto-generated)
107        assert!(!tool.description.is_empty());
108
109        // Tool should have input schema
110        assert!(tool.input_schema.contains("type"));
111    }
112
113    #[tokio::test]
114    async fn test_mcp_server_calls_tool_successfully() {
115        let mut router = Router::new();
116        router.register("get_user", || async { "User data".to_string() });
117
118        let mcp_server = McpServer::new(router);
119
120        // Call tool without arguments
121        let result = mcp_server
122            .call_tool("get_user", serde_json::json!({}))
123            .await;
124        assert!(result.is_ok());
125        assert_eq!(result.unwrap(), serde_json::json!("User data"));
126    }
127
128    #[tokio::test]
129    async fn test_mcp_server_calls_tool_with_arguments() {
130        let mut router = Router::new();
131        router.register("echo", || async { "echoed".to_string() });
132
133        let mcp_server = McpServer::new(router);
134
135        // Call tool with arguments
136        let args = serde_json::json!({
137            "message": "Hello, World!"
138        });
139        let result = mcp_server.call_tool("echo", args).await;
140        assert!(result.is_ok());
141    }
142
143    #[tokio::test]
144    async fn test_mcp_server_error_on_unknown_tool() {
145        let router = Router::new();
146        let mcp_server = McpServer::new(router);
147
148        // Try to call non-existent tool
149        let result = mcp_server
150            .call_tool("unknown_tool", serde_json::json!({}))
151            .await;
152
153        // Should return error
154        assert!(result.is_err());
155        let error_msg = result.unwrap_err();
156        assert!(error_msg.contains("unknown_tool") || error_msg.contains("not found"));
157    }
158
159    #[tokio::test]
160    async fn test_mcp_server_handles_handler_errors() {
161        let mut router = Router::new();
162        // Handler that returns error in future (simulated by returning empty for now)
163        router.register("failing_handler", || async { "".to_string() });
164
165        let mcp_server = McpServer::new(router);
166
167        // Call should succeed but we can test error propagation later
168        let result = mcp_server
169            .call_tool("failing_handler", serde_json::json!({}))
170            .await;
171        assert!(result.is_ok()); // For now, handlers don't fail
172    }
173
174    #[test]
175    fn test_mcp_tool_creation() {
176        let tool = McpTool::new("test_tool", "A test tool", r#"{"type": "object"}"#);
177
178        assert_eq!(tool.name, "test_tool");
179        assert_eq!(tool.description, "A test tool");
180        assert_eq!(tool.input_schema, r#"{"type": "object"}"#);
181    }
182
183    #[test]
184    fn test_mcp_tool_from_handler_name() {
185        let tool = McpTool::from_handler_name("get_user");
186
187        // Should auto-generate description
188        assert!(!tool.description.is_empty());
189
190        // Should have basic schema
191        assert!(tool.input_schema.contains("object"));
192    }
193
194    #[tokio::test]
195    async fn test_mcp_server_multiple_tool_calls() {
196        let mut router = Router::new();
197        router.register("tool1", || async { "result1".to_string() });
198        router.register("tool2", || async { "result2".to_string() });
199
200        let mcp_server = McpServer::new(router);
201
202        // Call first tool
203        let result1 = mcp_server.call_tool("tool1", serde_json::json!({})).await;
204        assert!(result1.is_ok());
205        assert_eq!(result1.unwrap(), serde_json::json!("result1"));
206
207        // Call second tool
208        let result2 = mcp_server.call_tool("tool2", serde_json::json!({})).await;
209        assert!(result2.is_ok());
210        assert_eq!(result2.unwrap(), serde_json::json!("result2"));
211    }
212
213    #[tokio::test]
214    async fn test_mcp_server_tool_isolation() {
215        let mut router = Router::new();
216        router.register("tool_a", || async { "A".to_string() });
217        router.register("tool_b", || async { "B".to_string() });
218
219        let mcp_server = McpServer::new(router);
220
221        // Calling tool_a should not affect tool_b
222        let _ = mcp_server.call_tool("tool_a", serde_json::json!({})).await;
223        let result_b = mcp_server.call_tool("tool_b", serde_json::json!({})).await;
224
225        assert_eq!(result_b.unwrap(), serde_json::json!("B"));
226    }
227
228    #[test]
229    fn test_mcp_server_empty_router() {
230        let router = Router::new();
231        let mcp_server = McpServer::new(router);
232
233        // Should handle empty router gracefully
234        assert_eq!(mcp_server.tool_count(), 0);
235    }
236
237    #[tokio::test]
238    async fn test_mcp_server_list_tools_empty() {
239        let router = Router::new();
240        let mcp_server = McpServer::new(router);
241        let tools = mcp_server.list_tools().await;
242
243        // Should return empty list
244        assert_eq!(tools.len(), 0);
245    }
246}