Skip to main content

cortexai_mcp/
protocol.rs

1//! MCP Protocol types based on the official specification
2//!
3//! Reference: https://modelcontextprotocol.io/specification
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Current protocol version
9pub const PROTOCOL_VERSION: &str = "2024-11-05";
10
11// =============================================================================
12// JSON-RPC Base Types
13// =============================================================================
14
15/// JSON-RPC request
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct JsonRpcRequest {
18    pub jsonrpc: String,
19    pub id: RequestId,
20    pub method: String,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub params: Option<serde_json::Value>,
23}
24
25impl JsonRpcRequest {
26    pub fn new(id: impl Into<RequestId>, method: impl Into<String>) -> Self {
27        Self {
28            jsonrpc: "2.0".to_string(),
29            id: id.into(),
30            method: method.into(),
31            params: None,
32        }
33    }
34
35    pub fn with_params(mut self, params: serde_json::Value) -> Self {
36        self.params = Some(params);
37        self
38    }
39}
40
41/// JSON-RPC response
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct JsonRpcResponse {
44    pub jsonrpc: String,
45    pub id: RequestId,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub result: Option<serde_json::Value>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub error: Option<JsonRpcError>,
50}
51
52/// JSON-RPC error
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct JsonRpcError {
55    pub code: i32,
56    pub message: String,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub data: Option<serde_json::Value>,
59}
60
61/// JSON-RPC notification (no id, no response expected)
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct JsonRpcNotification {
64    pub jsonrpc: String,
65    pub method: String,
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub params: Option<serde_json::Value>,
68}
69
70impl JsonRpcNotification {
71    pub fn new(method: impl Into<String>) -> Self {
72        Self {
73            jsonrpc: "2.0".to_string(),
74            method: method.into(),
75            params: None,
76        }
77    }
78
79    pub fn with_params(mut self, params: serde_json::Value) -> Self {
80        self.params = Some(params);
81        self
82    }
83}
84
85/// Request ID (can be string or number)
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
87#[serde(untagged)]
88pub enum RequestId {
89    String(String),
90    Number(i64),
91}
92
93impl From<i64> for RequestId {
94    fn from(n: i64) -> Self {
95        RequestId::Number(n)
96    }
97}
98
99impl From<String> for RequestId {
100    fn from(s: String) -> Self {
101        RequestId::String(s)
102    }
103}
104
105impl From<&str> for RequestId {
106    fn from(s: &str) -> Self {
107        RequestId::String(s.to_string())
108    }
109}
110
111// =============================================================================
112// Lifecycle Messages
113// =============================================================================
114
115/// Initialize request params
116#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct InitializeParams {
119    pub protocol_version: String,
120    pub capabilities: ClientCapabilities,
121    pub client_info: Implementation,
122}
123
124/// Initialize response result
125#[derive(Debug, Clone, Serialize, Deserialize)]
126#[serde(rename_all = "camelCase")]
127pub struct InitializeResult {
128    pub protocol_version: String,
129    pub capabilities: ServerCapabilities,
130    pub server_info: Implementation,
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub instructions: Option<String>,
133}
134
135/// Client capabilities
136#[derive(Debug, Clone, Default, Serialize, Deserialize)]
137pub struct ClientCapabilities {
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub roots: Option<RootsCapability>,
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub sampling: Option<SamplingCapability>,
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub experimental: Option<HashMap<String, serde_json::Value>>,
144}
145
146/// Server capabilities
147#[derive(Debug, Clone, Default, Serialize, Deserialize)]
148#[serde(rename_all = "camelCase")]
149pub struct ServerCapabilities {
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub prompts: Option<PromptsCapability>,
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub resources: Option<ResourcesCapability>,
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub tools: Option<ToolsCapability>,
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub logging: Option<LoggingCapability>,
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub experimental: Option<HashMap<String, serde_json::Value>>,
160}
161
162/// Implementation info
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct Implementation {
165    pub name: String,
166    pub version: String,
167}
168
169// =============================================================================
170// Capability Types
171// =============================================================================
172
173#[derive(Debug, Clone, Default, Serialize, Deserialize)]
174#[serde(rename_all = "camelCase")]
175pub struct RootsCapability {
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub list_changed: Option<bool>,
178}
179
180#[derive(Debug, Clone, Default, Serialize, Deserialize)]
181pub struct SamplingCapability {}
182
183#[derive(Debug, Clone, Default, Serialize, Deserialize)]
184#[serde(rename_all = "camelCase")]
185pub struct PromptsCapability {
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub list_changed: Option<bool>,
188}
189
190#[derive(Debug, Clone, Default, Serialize, Deserialize)]
191#[serde(rename_all = "camelCase")]
192pub struct ResourcesCapability {
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub subscribe: Option<bool>,
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub list_changed: Option<bool>,
197}
198
199#[derive(Debug, Clone, Default, Serialize, Deserialize)]
200#[serde(rename_all = "camelCase")]
201pub struct ToolsCapability {
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub list_changed: Option<bool>,
204}
205
206#[derive(Debug, Clone, Default, Serialize, Deserialize)]
207pub struct LoggingCapability {}
208
209// =============================================================================
210// Tool Types
211// =============================================================================
212
213/// MCP Tool definition
214#[derive(Debug, Clone, Serialize, Deserialize)]
215#[serde(rename_all = "camelCase")]
216pub struct McpTool {
217    pub name: String,
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub description: Option<String>,
220    pub input_schema: serde_json::Value,
221}
222
223/// List tools request params
224#[derive(Debug, Clone, Default, Serialize, Deserialize)]
225pub struct ListToolsParams {
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub cursor: Option<String>,
228}
229
230/// List tools response
231#[derive(Debug, Clone, Serialize, Deserialize)]
232#[serde(rename_all = "camelCase")]
233pub struct ListToolsResult {
234    pub tools: Vec<McpTool>,
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub next_cursor: Option<String>,
237}
238
239/// Call tool request params
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct CallToolParams {
242    pub name: String,
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub arguments: Option<serde_json::Value>,
245}
246
247/// Call tool response
248#[derive(Debug, Clone, Serialize, Deserialize)]
249#[serde(rename_all = "camelCase")]
250pub struct CallToolResult {
251    pub content: Vec<ToolContent>,
252    #[serde(default)]
253    pub is_error: bool,
254}
255
256/// Tool content types
257#[derive(Debug, Clone, Serialize, Deserialize)]
258#[serde(tag = "type", rename_all = "camelCase")]
259pub enum ToolContent {
260    #[serde(rename = "text")]
261    Text { text: String },
262    #[serde(rename = "image")]
263    Image { data: String, mime_type: String },
264    #[serde(rename = "resource")]
265    Resource { resource: ResourceContent },
266}
267
268impl ToolContent {
269    pub fn text(s: impl Into<String>) -> Self {
270        ToolContent::Text { text: s.into() }
271    }
272
273    pub fn as_text(&self) -> Option<&str> {
274        match self {
275            ToolContent::Text { text } => Some(text),
276            _ => None,
277        }
278    }
279}
280
281// =============================================================================
282// Resource Types
283// =============================================================================
284
285/// Resource content
286#[derive(Debug, Clone, Serialize, Deserialize)]
287#[serde(rename_all = "camelCase")]
288pub struct ResourceContent {
289    pub uri: String,
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub mime_type: Option<String>,
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub text: Option<String>,
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub blob: Option<String>,
296}
297
298/// Resource definition
299#[derive(Debug, Clone, Serialize, Deserialize)]
300#[serde(rename_all = "camelCase")]
301pub struct McpResource {
302    pub uri: String,
303    pub name: String,
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub description: Option<String>,
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub mime_type: Option<String>,
308}
309
310/// List resources response
311#[derive(Debug, Clone, Serialize, Deserialize)]
312#[serde(rename_all = "camelCase")]
313pub struct ListResourcesResult {
314    pub resources: Vec<McpResource>,
315    #[serde(skip_serializing_if = "Option::is_none")]
316    pub next_cursor: Option<String>,
317}
318
319// =============================================================================
320// Prompt Types
321// =============================================================================
322
323/// Prompt definition
324#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct McpPrompt {
326    pub name: String,
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub description: Option<String>,
329    #[serde(skip_serializing_if = "Option::is_none")]
330    pub arguments: Option<Vec<PromptArgument>>,
331}
332
333/// Prompt argument
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct PromptArgument {
336    pub name: String,
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub description: Option<String>,
339    #[serde(skip_serializing_if = "Option::is_none")]
340    pub required: Option<bool>,
341}
342
343/// List prompts response
344#[derive(Debug, Clone, Serialize, Deserialize)]
345#[serde(rename_all = "camelCase")]
346pub struct ListPromptsResult {
347    pub prompts: Vec<McpPrompt>,
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub next_cursor: Option<String>,
350}