Skip to main content

oxios_mcp/
protocol.rs

1//! JSON-RPC 2.0 protocol types and MCP domain types.
2//!
3//! This module defines the wire types for MCP communication (JSON-RPC requests,
4//! responses, errors) and the domain-specific types for tools, capabilities,
5//! and initialization negotiation.
6//!
7//! This module is fully independent of the Oxios kernel. It defines MCP-native
8//! types only. Conversion to Oxios-specific types (like `ToolDef`) is handled
9//! by the kernel's adapter layer.
10
11use std::collections::HashMap;
12
13use anyhow::{anyhow, Result};
14use serde::{Deserialize, Serialize};
15
16// ---------------------------------------------------------------------------
17// Unique ID generator for JSON-RPC requests
18// ---------------------------------------------------------------------------
19
20static 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
26// ---------------------------------------------------------------------------
27// MCP Server Configuration
28// ---------------------------------------------------------------------------
29
30/// Type alias for backwards compatibility — use [McpServer] directly.
31pub type McpServerConfig = McpServer;
32
33/// MCP server capability definition
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct McpServer {
36    /// Server name (unique identifier)
37    pub name: String,
38    /// Command to execute (e.g., "npx", "python")
39    pub command: String,
40    /// Command arguments
41    pub args: Vec<String>,
42    /// Environment variables
43    pub env: HashMap<String, String>,
44    /// Whether this server is enabled
45    pub enabled: bool,
46}
47
48impl McpServer {
49    /// Create a new MCP server configuration
50    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    /// Set command arguments
61    pub fn with_args(mut self, args: Vec<String>) -> Self {
62        self.args = args;
63        self
64    }
65
66    /// Add an environment variable
67    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// ---------------------------------------------------------------------------
74// JSON-RPC 2.0 Protocol Types
75// ---------------------------------------------------------------------------
76
77/// MCP JSON-RPC request structure
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct McpRequest {
80    /// JSON-RPC version (always "2.0")
81    pub jsonrpc: String,
82    /// Request ID for correlation
83    pub id: serde_json::Value,
84    /// Method name to invoke
85    pub method: String,
86    /// Optional method parameters
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub params: Option<serde_json::Value>,
89}
90
91impl McpRequest {
92    /// Create a new JSON-RPC request with an auto-generated ID
93    pub fn new(method: &str) -> Self {
94        Self::with_id(next_request_id(), method)
95    }
96
97    /// Create a new JSON-RPC request with a specific ID
98    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    /// Add parameters to the request
108    pub fn with_params(mut self, params: serde_json::Value) -> Self {
109        self.params = Some(params);
110        self
111    }
112
113    /// Serialize to a JSON-line (JSONL) bytes ready for stdio write
114    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/// MCP JSON-RPC response structure
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct McpResponse {
124    /// JSON-RPC version (always "2.0")
125    pub jsonrpc: String,
126    /// Response ID (matches request ID)
127    pub id: serde_json::Value,
128    /// Response result if successful
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub result: Option<serde_json::Value>,
131    /// Error if request failed
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub error: Option<McpError>,
134}
135
136/// MCP JSON-RPC error structure
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct McpError {
139    /// Error code
140    pub code: i32,
141    /// Error message
142    pub message: String,
143    /// Additional error data
144    #[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    /// Create a new MCP error
165    pub fn new(code: i32, message: &str) -> Self {
166        Self {
167            code,
168            message: message.to_string(),
169            data: None,
170        }
171    }
172
173    /// JSON-RPC parse error (-32700)
174    pub fn parse_error() -> Self {
175        Self::new(-32700, "Parse error")
176    }
177
178    /// JSON-RPC invalid request (-32600)
179    pub fn invalid_request(msg: &str) -> Self {
180        Self::new(-32600, msg)
181    }
182
183    /// JSON-RPC method not found (-32601)
184    pub fn method_not_found() -> Self {
185        Self::new(-32601, "Method not found")
186    }
187
188    /// JSON-RPC invalid params (-32602)
189    pub fn invalid_params() -> Self {
190        Self::new(-32602, "Invalid params")
191    }
192
193    /// JSON-RPC internal error (-32603)
194    pub fn internal_error(msg: &str) -> Self {
195        Self::new(-32603, msg)
196    }
197
198    /// Server error (codes -32000 to -32099)
199    pub fn server_error(msg: &str) -> Self {
200        Self::new(-32000, msg)
201    }
202}
203
204impl McpResponse {
205    /// Check if this response contains an error
206    pub fn is_error(&self) -> bool {
207        self.error.is_some()
208    }
209
210    /// Extract the result value, erroring if there is one
211    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// ---------------------------------------------------------------------------
220// MCP Capability Negotiation
221// ---------------------------------------------------------------------------
222
223/// MCP server capabilities
224#[derive(Debug, Clone, Serialize, Deserialize, Default)]
225pub struct McpCapabilities {
226    /// Whether the server supports tools
227    pub tools: bool,
228    /// Whether the server supports resources
229    pub resources: bool,
230    /// Whether the server supports prompts
231    pub prompts: bool,
232}
233
234/// Initialize request params
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct InitializeParams {
237    /// Protocol version string.
238    pub protocol_version: String,
239    /// Client capabilities.
240    pub capabilities: McpCapabilities,
241    /// Information about the connecting client.
242    pub client_info: ClientInfo,
243}
244
245/// Client info sent during initialize
246#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct ClientInfo {
248    /// Client name.
249    pub name: String,
250    /// Client version.
251    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/// Initialize response from the server
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct InitializeResult {
270    /// Protocol version agreed upon.
271    pub protocol_version: String,
272    /// Server capabilities.
273    pub capabilities: McpCapabilities,
274    /// Information about the server.
275    pub server_info: ServerInfo,
276}
277
278/// Server info from initialize response
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct ServerInfo {
281    /// Server name.
282    pub name: String,
283    /// Server version.
284    pub version: String,
285}
286
287// ---------------------------------------------------------------------------
288// MCP Tool Types
289// ---------------------------------------------------------------------------
290
291/// MCP tool definition from a server
292#[derive(Debug, Clone, Serialize, Deserialize)]
293pub struct McpTool {
294    /// Tool name (unique within the server)
295    pub name: String,
296    /// Brief description of what the tool does
297    pub description: String,
298    /// JSON Schema for tool input arguments
299    pub input_schema: serde_json::Value,
300}
301
302impl McpTool {
303    /// Get the tool name.
304    pub fn name(&self) -> &str {
305        &self.name
306    }
307
308    /// Get the tool description.
309    pub fn description(&self) -> &str {
310        &self.description
311    }
312
313    /// Get the tool input JSON Schema.
314    pub fn input_schema(&self) -> &serde_json::Value {
315        &self.input_schema
316    }
317}
318
319/// MCP tools/list result
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct McpToolsResult {
322    /// Available tools from the server.
323    pub tools: Vec<McpTool>,
324}
325
326/// MCP tools/call result
327#[derive(Debug, Clone, Serialize, Deserialize)]
328pub struct McpToolCallResult {
329    /// Content blocks returned by the tool.
330    pub content: Vec<McpContentBlock>,
331    /// Whether the result is an error.
332    pub is_error: Option<bool>,
333}
334
335/// Content block in a tool call result
336#[derive(Debug, Clone, Serialize, Deserialize)]
337#[serde(tag = "type")]
338pub enum McpContentBlock {
339    /// Plain text content.
340    #[serde(rename = "text")]
341    Text {
342        /// The text content.
343        text: String,
344    },
345    /// Base64-encoded image data.
346    #[serde(rename = "image")]
347    Image {
348        /// Base64-encoded image data.
349        data: String,
350        /// MIME type of the image.
351        mime_type: Option<String>,
352    },
353    /// Embedded resource reference.
354    #[serde(rename = "resource")]
355    Resource {
356        /// The referenced resource.
357        resource: MappedResource,
358    },
359}
360
361/// Resource reference
362#[derive(Debug, Clone, Serialize, Deserialize)]
363pub struct MappedResource {
364    /// URI of the resource.
365    pub uri: String,
366    /// MIME type of the resource.
367    pub mime_type: Option<String>,
368}