1use std::collections::HashMap;
8
9use anyhow::{anyhow, Result};
10use serde::{Deserialize, Serialize};
11
12use crate::program::ToolDef;
13
14static REQUEST_ID: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(1);
19
20pub(crate) fn next_request_id() -> usize {
21 REQUEST_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
22}
23
24pub type McpServerConfig = McpServer;
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct McpServer {
34 pub name: String,
36 pub command: String,
38 pub args: Vec<String>,
40 pub env: HashMap<String, String>,
42 pub enabled: bool,
44}
45
46impl McpServer {
47 pub fn new(name: &str, command: &str) -> Self {
49 Self {
50 name: name.to_string(),
51 command: command.to_string(),
52 args: Vec::new(),
53 env: HashMap::new(),
54 enabled: true,
55 }
56 }
57
58 pub fn with_args(mut self, args: Vec<String>) -> Self {
60 self.args = args;
61 self
62 }
63
64 pub fn with_env(mut self, key: &str, value: &str) -> Self {
66 self.env.insert(key.to_string(), value.to_string());
67 self
68 }
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct McpRequest {
78 pub jsonrpc: String,
80 pub id: serde_json::Value,
82 pub method: String,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub params: Option<serde_json::Value>,
87}
88
89impl McpRequest {
90 pub fn new(method: &str) -> Self {
92 Self::with_id(next_request_id(), method)
93 }
94
95 pub fn with_id(id: usize, method: &str) -> Self {
97 Self {
98 jsonrpc: "2.0".to_string(),
99 id: serde_json::json!(id),
100 method: method.to_string(),
101 params: None,
102 }
103 }
104
105 pub fn with_params(mut self, params: serde_json::Value) -> Self {
107 self.params = Some(params);
108 self
109 }
110
111 pub fn to_jsonl(&self) -> Result<Vec<u8>> {
113 let mut buf = serde_json::to_vec(self)?;
114 buf.push(b'\n');
115 Ok(buf)
116 }
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct McpResponse {
122 pub jsonrpc: String,
124 pub id: serde_json::Value,
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub result: Option<serde_json::Value>,
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub error: Option<McpError>,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct McpError {
137 pub code: i32,
139 pub message: String,
141 #[serde(skip_serializing_if = "Option::is_none")]
143 pub data: Option<serde_json::Value>,
144}
145
146impl std::fmt::Display for McpError {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 let error_type = match self.code {
149 -32700 => "parse error",
150 -32600 => "invalid request",
151 -32601 => "method not found",
152 -32602 => "invalid params",
153 -32603 => "internal error",
154 -32099..=-32000 => "server error",
155 _ => "unknown error",
156 };
157 write!(f, "{} (code {}): {}", error_type, self.code, self.message)
158 }
159}
160
161impl McpError {
162 pub fn new(code: i32, message: &str) -> Self {
164 Self {
165 code,
166 message: message.to_string(),
167 data: None,
168 }
169 }
170
171 pub fn parse_error() -> Self {
173 Self::new(-32700, "Parse error")
174 }
175
176 pub fn invalid_request(msg: &str) -> Self {
178 Self::new(-32600, msg)
179 }
180
181 pub fn method_not_found() -> Self {
183 Self::new(-32601, "Method not found")
184 }
185
186 pub fn invalid_params() -> Self {
188 Self::new(-32602, "Invalid params")
189 }
190
191 pub fn internal_error(msg: &str) -> Self {
193 Self::new(-32603, msg)
194 }
195
196 pub fn server_error(msg: &str) -> Self {
198 Self::new(-32000, msg)
199 }
200}
201
202impl McpResponse {
203 pub fn is_error(&self) -> bool {
205 self.error.is_some()
206 }
207
208 pub fn into_result(self) -> Result<serde_json::Value> {
210 if let Some(err) = self.error {
211 return Err(anyhow!("{}", err));
212 }
213 Ok(self.result.unwrap_or(serde_json::Value::Null))
214 }
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, Default)]
223pub struct McpCapabilities {
224 pub tools: bool,
226 pub resources: bool,
228 pub prompts: bool,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct InitializeParams {
235 pub protocol_version: String,
237 pub capabilities: McpCapabilities,
239 pub client_info: ClientInfo,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
245pub struct ClientInfo {
246 pub name: String,
248 pub version: String,
250}
251
252impl Default for InitializeParams {
253 fn default() -> Self {
254 Self {
255 protocol_version: "2024-11-05".to_string(),
256 capabilities: McpCapabilities::default(),
257 client_info: ClientInfo {
258 name: "oxios".to_string(),
259 version: env!("CARGO_PKG_VERSION").to_string(),
260 },
261 }
262 }
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct InitializeResult {
268 pub protocol_version: String,
270 pub capabilities: McpCapabilities,
272 pub server_info: ServerInfo,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct ServerInfo {
279 pub name: String,
281 pub version: String,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct McpTool {
292 pub name: String,
294 pub description: String,
296 pub input_schema: serde_json::Value,
298}
299
300impl McpTool {
301 pub fn to_tool_def(&self) -> ToolDef {
307 let arguments = if let Some(properties) = self
308 .input_schema
309 .get("properties")
310 .and_then(|p| p.as_object())
311 {
312 let required_list: Vec<&str> = self
313 .input_schema
314 .get("required")
315 .and_then(|r| r.as_array())
316 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
317 .unwrap_or_default();
318
319 properties
320 .iter()
321 .map(|(name, schema)| {
322 let description = schema
323 .get("description")
324 .and_then(|d| d.as_str())
325 .unwrap_or("No description")
326 .to_string();
327 let required =
328 required_list.iter().any(|r| *r == name) && schema.get("default").is_none();
329
330 crate::program::ArgumentDef {
331 name: name.clone(),
332 description,
333 required,
334 default: schema
335 .get("default")
336 .and_then(|d| d.as_str().map(String::from)),
337 }
338 })
339 .collect()
340 } else {
341 Vec::new()
342 };
343
344 ToolDef {
345 name: self.name.clone(),
346 description: self.description.clone(),
347 arguments,
348 command: String::new(), }
350 }
351}
352
353#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct McpToolsResult {
356 pub tools: Vec<McpTool>,
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct McpToolCallResult {
363 pub content: Vec<McpContentBlock>,
365 pub is_error: Option<bool>,
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
371#[serde(tag = "type")]
372pub enum McpContentBlock {
373 #[serde(rename = "text")]
375 Text {
376 text: String,
378 },
379 #[serde(rename = "image")]
381 Image {
382 data: String,
384 mime_type: Option<String>,
386 },
387 #[serde(rename = "resource")]
389 Resource {
390 resource: MappedResource,
392 },
393}
394
395#[derive(Debug, Clone, Serialize, Deserialize)]
397pub struct MappedResource {
398 pub uri: String,
400 pub mime_type: Option<String>,
402}