1use std::collections::HashMap;
12
13use anyhow::{anyhow, Result};
14use serde::{Deserialize, Serialize};
15
16static REQUEST_ID: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(1);
21
22pub(crate) fn next_request_id() -> usize {
23 REQUEST_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
24}
25
26pub type McpServerConfig = McpServer;
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct McpServer {
36 pub name: String,
38 pub command: String,
40 pub args: Vec<String>,
42 pub env: HashMap<String, String>,
44 pub enabled: bool,
46}
47
48impl McpServer {
49 pub fn new(name: &str, command: &str) -> Self {
51 Self {
52 name: name.to_string(),
53 command: command.to_string(),
54 args: Vec::new(),
55 env: HashMap::new(),
56 enabled: true,
57 }
58 }
59
60 pub fn with_args(mut self, args: Vec<String>) -> Self {
62 self.args = args;
63 self
64 }
65
66 pub fn with_env(mut self, key: &str, value: &str) -> Self {
68 self.env.insert(key.to_string(), value.to_string());
69 self
70 }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct McpRequest {
80 pub jsonrpc: String,
82 pub id: serde_json::Value,
84 pub method: String,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub params: Option<serde_json::Value>,
89}
90
91impl McpRequest {
92 pub fn new(method: &str) -> Self {
94 Self::with_id(next_request_id(), method)
95 }
96
97 pub fn with_id(id: usize, method: &str) -> Self {
99 Self {
100 jsonrpc: "2.0".to_string(),
101 id: serde_json::json!(id),
102 method: method.to_string(),
103 params: None,
104 }
105 }
106
107 pub fn with_params(mut self, params: serde_json::Value) -> Self {
109 self.params = Some(params);
110 self
111 }
112
113 pub fn to_jsonl(&self) -> Result<Vec<u8>> {
115 let mut buf = serde_json::to_vec(self)?;
116 buf.push(b'\n');
117 Ok(buf)
118 }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct McpResponse {
124 pub jsonrpc: String,
126 pub id: serde_json::Value,
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub result: Option<serde_json::Value>,
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub error: Option<McpError>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct McpError {
139 pub code: i32,
141 pub message: String,
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub data: Option<serde_json::Value>,
146}
147
148impl std::fmt::Display for McpError {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 let error_type = match self.code {
151 -32700 => "parse error",
152 -32600 => "invalid request",
153 -32601 => "method not found",
154 -32602 => "invalid params",
155 -32603 => "internal error",
156 -32099..=-32000 => "server error",
157 _ => "unknown error",
158 };
159 write!(f, "{} (code {}): {}", error_type, self.code, self.message)
160 }
161}
162
163impl McpError {
164 pub fn new(code: i32, message: &str) -> Self {
166 Self {
167 code,
168 message: message.to_string(),
169 data: None,
170 }
171 }
172
173 pub fn parse_error() -> Self {
175 Self::new(-32700, "Parse error")
176 }
177
178 pub fn invalid_request(msg: &str) -> Self {
180 Self::new(-32600, msg)
181 }
182
183 pub fn method_not_found() -> Self {
185 Self::new(-32601, "Method not found")
186 }
187
188 pub fn invalid_params() -> Self {
190 Self::new(-32602, "Invalid params")
191 }
192
193 pub fn internal_error(msg: &str) -> Self {
195 Self::new(-32603, msg)
196 }
197
198 pub fn server_error(msg: &str) -> Self {
200 Self::new(-32000, msg)
201 }
202}
203
204impl McpResponse {
205 pub fn is_error(&self) -> bool {
207 self.error.is_some()
208 }
209
210 pub fn into_result(self) -> Result<serde_json::Value> {
212 if let Some(err) = self.error {
213 return Err(anyhow!("{}", err));
214 }
215 Ok(self.result.unwrap_or(serde_json::Value::Null))
216 }
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize, Default)]
225pub struct McpCapabilities {
226 pub tools: bool,
228 pub resources: bool,
230 pub prompts: bool,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct InitializeParams {
237 pub protocol_version: String,
239 pub capabilities: McpCapabilities,
241 pub client_info: ClientInfo,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct ClientInfo {
248 pub name: String,
250 pub version: String,
252}
253
254impl Default for InitializeParams {
255 fn default() -> Self {
256 Self {
257 protocol_version: "2024-11-05".to_string(),
258 capabilities: McpCapabilities::default(),
259 client_info: ClientInfo {
260 name: "oxios".to_string(),
261 version: env!("CARGO_PKG_VERSION").to_string(),
262 },
263 }
264 }
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct InitializeResult {
270 pub protocol_version: String,
272 pub capabilities: McpCapabilities,
274 pub server_info: ServerInfo,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct ServerInfo {
281 pub name: String,
283 pub version: String,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
293pub struct McpTool {
294 pub name: String,
296 pub description: String,
298 pub input_schema: serde_json::Value,
300}
301
302impl McpTool {
303 pub fn name(&self) -> &str {
305 &self.name
306 }
307
308 pub fn description(&self) -> &str {
310 &self.description
311 }
312
313 pub fn input_schema(&self) -> &serde_json::Value {
315 &self.input_schema
316 }
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct McpToolsResult {
322 pub tools: Vec<McpTool>,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct McpToolCallResult {
329 pub content: Vec<McpContentBlock>,
331 pub is_error: Option<bool>,
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize)]
337#[serde(tag = "type")]
338pub enum McpContentBlock {
339 #[serde(rename = "text")]
341 Text {
342 text: String,
344 },
345 #[serde(rename = "image")]
347 Image {
348 data: String,
350 mime_type: Option<String>,
352 },
353 #[serde(rename = "resource")]
355 Resource {
356 resource: MappedResource,
358 },
359}
360
361#[derive(Debug, Clone, Serialize, Deserialize)]
363pub struct MappedResource {
364 pub uri: String,
366 pub mime_type: Option<String>,
368}