1use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Deserialize)]
12pub struct McpRequest {
13 pub jsonrpc: String,
14 pub id: Option<serde_json::Value>,
15 pub method: String,
16 #[serde(default)]
17 pub params: serde_json::Value,
18}
19
20#[derive(Debug, Clone, Serialize)]
22pub struct McpResponse {
23 pub jsonrpc: String,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub id: Option<serde_json::Value>,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub result: Option<serde_json::Value>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub error: Option<McpError>,
30}
31
32#[derive(Debug, Clone, Serialize)]
34pub struct McpError {
35 pub code: i32,
36 pub message: String,
37}
38
39impl McpResponse {
40 pub fn success(id: Option<serde_json::Value>, result: serde_json::Value) -> Self {
41 Self { jsonrpc: "2.0".to_string(), id, result: Some(result), error: None }
42 }
43
44 pub fn error(id: Option<serde_json::Value>, code: i32, message: impl Into<String>) -> Self {
45 Self {
46 jsonrpc: "2.0".to_string(),
47 id,
48 result: None,
49 error: Some(McpError { code, message: message.into() }),
50 }
51 }
52}
53
54#[derive(Debug, Clone, Serialize)]
56pub struct McpServerInfo {
57 pub name: String,
58 pub version: String,
59}
60
61#[derive(Debug, Clone, Serialize)]
63pub struct McpTool {
64 pub name: String,
65 pub description: String,
66 #[serde(rename = "inputSchema")]
67 pub input_schema: serde_json::Value,
68}
69
70#[derive(Debug, Clone, Serialize)]
72pub struct McpResource {
73 pub uri: String,
74 pub name: String,
75 pub description: String,
76 #[serde(rename = "mimeType")]
77 pub mime_type: String,
78}
79
80#[derive(Debug, Clone, Serialize)]
82pub struct McpPrompt {
83 pub name: String,
84 pub description: String,
85}
86
87pub fn handle_mcp_request(
89 request: &McpRequest,
90 tools: &super::tools::ToolRegistry,
91 prompts: &super::prompts::PromptStore,
92) -> McpResponse {
93 match request.method.as_str() {
94 "initialize" => handle_initialize(request),
95 "tools/list" => handle_tools_list(request, tools),
96 "tools/call" => handle_tools_call(request, tools),
97 "resources/list" => handle_resources_list(request),
98 "prompts/list" => handle_prompts_list(request, prompts),
99 "ping" => McpResponse::success(request.id.clone(), serde_json::json!({})),
100 _ => McpResponse::error(
101 request.id.clone(),
102 -32601,
103 format!("Method not found: {}", request.method),
104 ),
105 }
106}
107
108fn handle_initialize(request: &McpRequest) -> McpResponse {
109 McpResponse::success(
110 request.id.clone(),
111 serde_json::json!({
112 "protocolVersion": "2024-11-05",
113 "capabilities": {
114 "tools": {},
115 "resources": {},
116 "prompts": {}
117 },
118 "serverInfo": {
119 "name": "banco",
120 "version": env!("CARGO_PKG_VERSION")
121 }
122 }),
123 )
124}
125
126fn handle_tools_list(request: &McpRequest, tools: &super::tools::ToolRegistry) -> McpResponse {
127 let mcp_tools: Vec<McpTool> = tools
128 .list()
129 .into_iter()
130 .filter(|t| t.enabled)
131 .map(|t| McpTool { name: t.name, description: t.description, input_schema: t.parameters })
132 .collect();
133
134 McpResponse::success(request.id.clone(), serde_json::json!({ "tools": mcp_tools }))
135}
136
137fn handle_tools_call(request: &McpRequest, tools: &super::tools::ToolRegistry) -> McpResponse {
138 let name = request.params.get("name").and_then(|v| v.as_str()).unwrap_or("");
139 let arguments = request.params.get("arguments").cloned().unwrap_or(serde_json::json!({}));
140
141 if tools.get(name).is_none() {
142 return McpResponse::error(request.id.clone(), -32602, format!("Unknown tool: {name}"));
143 }
144
145 let call = super::tools::ToolCall {
146 id: format!("mcp-{}", epoch_secs()),
147 name: name.to_string(),
148 arguments,
149 };
150
151 let result = tools.execute(&call);
152
153 if let Some(err) = &result.error {
154 McpResponse::success(
155 request.id.clone(),
156 serde_json::json!({
157 "content": [{"type": "text", "text": err}],
158 "isError": true
159 }),
160 )
161 } else {
162 McpResponse::success(
163 request.id.clone(),
164 serde_json::json!({
165 "content": [{"type": "text", "text": result.content}]
166 }),
167 )
168 }
169}
170
171fn handle_resources_list(request: &McpRequest) -> McpResponse {
172 let resources = vec![
173 McpResource {
174 uri: "banco://system".to_string(),
175 name: "System Info".to_string(),
176 description: "Banco system status and configuration".to_string(),
177 mime_type: "application/json".to_string(),
178 },
179 McpResource {
180 uri: "banco://models".to_string(),
181 name: "Models".to_string(),
182 description: "Available and loaded models".to_string(),
183 mime_type: "application/json".to_string(),
184 },
185 ];
186 McpResponse::success(request.id.clone(), serde_json::json!({ "resources": resources }))
187}
188
189fn handle_prompts_list(request: &McpRequest, prompts: &super::prompts::PromptStore) -> McpResponse {
190 let mcp_prompts: Vec<McpPrompt> = prompts
191 .list()
192 .into_iter()
193 .map(|p| McpPrompt { name: p.name, description: p.content })
194 .collect();
195
196 McpResponse::success(request.id.clone(), serde_json::json!({ "prompts": mcp_prompts }))
197}
198
199fn epoch_secs() -> u64 {
200 std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs()
201}