Skip to main content

codetether_agent/mcp/
types.rs

1//! MCP types - JSON-RPC messages and protocol structures
2//!
3//! Based on the Model Context Protocol specification:
4//! https://modelcontextprotocol.io/specification
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9/// JSON-RPC 2.0 request
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct JsonRpcRequest {
12    pub jsonrpc: String,
13    pub id: RequestId,
14    pub method: String,
15    #[serde(default, skip_serializing_if = "Option::is_none")]
16    pub params: Option<Value>,
17}
18
19/// JSON-RPC 2.0 notification (no id)
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct JsonRpcNotification {
22    pub jsonrpc: String,
23    pub method: String,
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub params: Option<Value>,
26}
27
28/// JSON-RPC 2.0 response
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct JsonRpcResponse {
31    pub jsonrpc: String,
32    pub id: RequestId,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub result: Option<Value>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub error: Option<JsonRpcError>,
37}
38
39/// JSON-RPC request ID (can be string or number)
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
41#[serde(untagged)]
42pub enum RequestId {
43    String(String),
44    Number(i64),
45}
46
47impl From<i64> for RequestId {
48    fn from(n: i64) -> Self {
49        RequestId::Number(n)
50    }
51}
52
53impl From<String> for RequestId {
54    fn from(s: String) -> Self {
55        RequestId::String(s)
56    }
57}
58
59/// JSON-RPC error
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct JsonRpcError {
62    pub code: i32,
63    pub message: String,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub data: Option<Value>,
66}
67
68impl JsonRpcError {
69    pub fn parse_error(msg: impl Into<String>) -> Self {
70        Self { code: -32700, message: msg.into(), data: None }
71    }
72    
73    pub fn invalid_request(msg: impl Into<String>) -> Self {
74        Self { code: -32600, message: msg.into(), data: None }
75    }
76    
77    pub fn method_not_found(method: &str) -> Self {
78        Self { code: -32601, message: format!("Method not found: {}", method), data: None }
79    }
80    
81    pub fn invalid_params(msg: impl Into<String>) -> Self {
82        Self { code: -32602, message: msg.into(), data: None }
83    }
84    
85    pub fn internal_error(msg: impl Into<String>) -> Self {
86        Self { code: -32603, message: msg.into(), data: None }
87    }
88}
89
90// ===== MCP Initialization =====
91
92/// Client capabilities sent during initialize
93#[derive(Debug, Clone, Default, Serialize, Deserialize)]
94pub struct ClientCapabilities {
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub roots: Option<RootsCapability>,
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub sampling: Option<SamplingCapability>,
99    #[serde(default, skip_serializing_if = "Option::is_none")]
100    pub experimental: Option<Value>,
101}
102
103#[derive(Debug, Clone, Default, Serialize, Deserialize)]
104pub struct RootsCapability {
105    #[serde(default)]
106    pub list_changed: bool,
107}
108
109#[derive(Debug, Clone, Default, Serialize, Deserialize)]
110pub struct SamplingCapability {}
111
112/// Server capabilities returned during initialize
113#[derive(Debug, Clone, Default, Serialize, Deserialize)]
114pub struct ServerCapabilities {
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub tools: Option<ToolsCapability>,
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    pub resources: Option<ResourcesCapability>,
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub prompts: Option<PromptsCapability>,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub logging: Option<LoggingCapability>,
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub experimental: Option<Value>,
125}
126
127#[derive(Debug, Clone, Default, Serialize, Deserialize)]
128pub struct ToolsCapability {
129    #[serde(default)]
130    pub list_changed: bool,
131}
132
133#[derive(Debug, Clone, Default, Serialize, Deserialize)]
134pub struct ResourcesCapability {
135    #[serde(default)]
136    pub subscribe: bool,
137    #[serde(default)]
138    pub list_changed: bool,
139}
140
141#[derive(Debug, Clone, Default, Serialize, Deserialize)]
142pub struct PromptsCapability {
143    #[serde(default)]
144    pub list_changed: bool,
145}
146
147#[derive(Debug, Clone, Default, Serialize, Deserialize)]
148pub struct LoggingCapability {}
149
150/// Initialize request parameters
151#[derive(Debug, Clone, Serialize, Deserialize)]
152#[serde(rename_all = "camelCase")]
153pub struct InitializeParams {
154    pub protocol_version: String,
155    pub capabilities: ClientCapabilities,
156    pub client_info: ClientInfo,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ClientInfo {
161    pub name: String,
162    pub version: String,
163}
164
165/// Initialize response
166#[derive(Debug, Clone, Serialize, Deserialize)]
167#[serde(rename_all = "camelCase")]
168pub struct InitializeResult {
169    pub protocol_version: String,
170    pub capabilities: ServerCapabilities,
171    pub server_info: ServerInfo,
172    #[serde(default, skip_serializing_if = "Option::is_none")]
173    pub instructions: Option<String>,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct ServerInfo {
178    pub name: String,
179    pub version: String,
180}
181
182// ===== MCP Tools =====
183
184/// Tool definition
185#[derive(Debug, Clone, Serialize, Deserialize)]
186#[serde(rename_all = "camelCase")]
187pub struct McpTool {
188    pub name: String,
189    #[serde(default, skip_serializing_if = "Option::is_none")]
190    pub description: Option<String>,
191    pub input_schema: Value, // JSON Schema
192}
193
194/// Tool metadata for storage and querying
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct ToolMetadata {
197    pub name: String,
198    pub description: Option<String>,
199    pub input_schema: Value,
200    pub registered_at: std::time::SystemTime,
201}
202
203impl ToolMetadata {
204    pub fn new(name: String, description: Option<String>, input_schema: Value) -> Self {
205        Self {
206            name,
207            description,
208            input_schema,
209            registered_at: std::time::SystemTime::now(),
210        }
211    }
212}
213
214/// List tools response
215#[derive(Debug, Clone, Serialize, Deserialize)]
216#[serde(rename_all = "camelCase")]
217pub struct ListToolsResult {
218    pub tools: Vec<McpTool>,
219    #[serde(default, skip_serializing_if = "Option::is_none")]
220    pub next_cursor: Option<String>,
221}
222
223/// Call tool parameters
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct CallToolParams {
226    pub name: String,
227    #[serde(default)]
228    pub arguments: Value,
229}
230
231/// Tool call result
232#[derive(Debug, Clone, Serialize, Deserialize)]
233#[serde(rename_all = "camelCase")]
234pub struct CallToolResult {
235    pub content: Vec<ToolContent>,
236    #[serde(default)]
237    pub is_error: bool,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
241#[serde(tag = "type", rename_all = "snake_case")]
242pub enum ToolContent {
243    Text { text: String },
244    Image { data: String, mime_type: String },
245    Resource { resource: ResourceContents },
246}
247
248// ===== MCP Resources =====
249
250/// Resource definition
251#[derive(Debug, Clone, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub struct McpResource {
254    pub uri: String,
255    pub name: String,
256    #[serde(default, skip_serializing_if = "Option::is_none")]
257    pub description: Option<String>,
258    #[serde(default, skip_serializing_if = "Option::is_none")]
259    pub mime_type: Option<String>,
260}
261
262/// Resource metadata for storage and querying
263#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct ResourceMetadata {
265    pub uri: String,
266    pub name: String,
267    pub description: Option<String>,
268    pub mime_type: Option<String>,
269    pub registered_at: std::time::SystemTime,
270}
271
272impl ResourceMetadata {
273    pub fn new(uri: String, name: String, description: Option<String>, mime_type: Option<String>) -> Self {
274        Self {
275            uri,
276            name,
277            description,
278            mime_type,
279            registered_at: std::time::SystemTime::now(),
280        }
281    }
282}
283
284/// List resources response
285#[derive(Debug, Clone, Serialize, Deserialize)]
286#[serde(rename_all = "camelCase")]
287pub struct ListResourcesResult {
288    pub resources: Vec<McpResource>,
289    #[serde(default, skip_serializing_if = "Option::is_none")]
290    pub next_cursor: Option<String>,
291}
292
293/// Read resource parameters
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct ReadResourceParams {
296    pub uri: String,
297}
298
299/// Resource contents
300#[derive(Debug, Clone, Serialize, Deserialize)]
301#[serde(rename_all = "camelCase")]
302pub struct ResourceContents {
303    pub uri: String,
304    #[serde(default, skip_serializing_if = "Option::is_none")]
305    pub mime_type: Option<String>,
306    #[serde(flatten)]
307    pub content: ResourceContent,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
311#[serde(untagged)]
312pub enum ResourceContent {
313    Text { text: String },
314    Blob { blob: String }, // base64-encoded
315}
316
317/// Read resource response
318#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct ReadResourceResult {
320    pub contents: Vec<ResourceContents>,
321}
322
323// ===== MCP Prompts =====
324
325/// Prompt definition
326#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct McpPrompt {
328    pub name: String,
329    #[serde(default, skip_serializing_if = "Option::is_none")]
330    pub description: Option<String>,
331    #[serde(default, skip_serializing_if = "Vec::is_empty")]
332    pub arguments: Vec<PromptArgument>,
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct PromptArgument {
337    pub name: String,
338    #[serde(default, skip_serializing_if = "Option::is_none")]
339    pub description: Option<String>,
340    #[serde(default)]
341    pub required: bool,
342}
343
344/// List prompts response
345#[derive(Debug, Clone, Serialize, Deserialize)]
346#[serde(rename_all = "camelCase")]
347pub struct ListPromptsResult {
348    pub prompts: Vec<McpPrompt>,
349    #[serde(default, skip_serializing_if = "Option::is_none")]
350    pub next_cursor: Option<String>,
351}
352
353/// Get prompt parameters
354#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct GetPromptParams {
356    pub name: String,
357    #[serde(default)]
358    pub arguments: Value,
359}
360
361/// Prompt message
362#[derive(Debug, Clone, Serialize, Deserialize)]
363pub struct PromptMessage {
364    pub role: PromptRole,
365    pub content: PromptContent,
366}
367
368#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
369#[serde(rename_all = "lowercase")]
370pub enum PromptRole {
371    User,
372    Assistant,
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
376#[serde(tag = "type", rename_all = "snake_case")]
377pub enum PromptContent {
378    Text { text: String },
379    Image { data: String, mime_type: String },
380    Resource { resource: ResourceContents },
381}
382
383/// Get prompt response
384#[derive(Debug, Clone, Serialize, Deserialize)]
385pub struct GetPromptResult {
386    #[serde(default, skip_serializing_if = "Option::is_none")]
387    pub description: Option<String>,
388    pub messages: Vec<PromptMessage>,
389}
390
391// ===== MCP Sampling =====
392
393/// Sampling request (for server -> client sampling)
394#[derive(Debug, Clone, Serialize, Deserialize)]
395#[serde(rename_all = "camelCase")]
396pub struct CreateMessageParams {
397    pub messages: Vec<SamplingMessage>,
398    #[serde(default, skip_serializing_if = "Option::is_none")]
399    pub model_preferences: Option<ModelPreferences>,
400    #[serde(default, skip_serializing_if = "Option::is_none")]
401    pub system_prompt: Option<String>,
402    #[serde(default, skip_serializing_if = "Option::is_none")]
403    pub max_tokens: Option<i32>,
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize)]
407pub struct SamplingMessage {
408    pub role: PromptRole,
409    pub content: SamplingContent,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
413#[serde(tag = "type", rename_all = "snake_case")]
414pub enum SamplingContent {
415    Text { text: String },
416    Image { data: String, mime_type: String },
417}
418
419#[derive(Debug, Clone, Serialize, Deserialize)]
420#[serde(rename_all = "camelCase")]
421pub struct ModelPreferences {
422    #[serde(default, skip_serializing_if = "Vec::is_empty")]
423    pub hints: Vec<ModelHint>,
424    #[serde(default, skip_serializing_if = "Option::is_none")]
425    pub cost_priority: Option<f32>,
426    #[serde(default, skip_serializing_if = "Option::is_none")]
427    pub speed_priority: Option<f32>,
428    #[serde(default, skip_serializing_if = "Option::is_none")]
429    pub intelligence_priority: Option<f32>,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize)]
433pub struct ModelHint {
434    #[serde(default, skip_serializing_if = "Option::is_none")]
435    pub name: Option<String>,
436}
437
438/// Sampling response
439#[derive(Debug, Clone, Serialize, Deserialize)]
440#[serde(rename_all = "camelCase")]
441pub struct CreateMessageResult {
442    pub role: PromptRole,
443    pub content: SamplingContent,
444    pub model: String,
445    #[serde(default, skip_serializing_if = "Option::is_none")]
446    pub stop_reason: Option<String>,
447}
448
449// ===== MCP Logging =====
450
451#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
452#[serde(rename_all = "lowercase")]
453pub enum LogLevel {
454    Debug,
455    Info,
456    Notice,
457    Warning,
458    Error,
459    Critical,
460    Alert,
461    Emergency,
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize)]
465pub struct LoggingMessageParams {
466    pub level: LogLevel,
467    #[serde(default, skip_serializing_if = "Option::is_none")]
468    pub logger: Option<String>,
469    pub data: Value,
470}
471
472// ===== MCP Protocol Version =====
473
474pub const PROTOCOL_VERSION: &str = "2024-11-05";
475pub const JSONRPC_VERSION: &str = "2.0";
476
477impl JsonRpcRequest {
478    pub fn new(id: impl Into<RequestId>, method: impl Into<String>, params: Option<Value>) -> Self {
479        Self {
480            jsonrpc: JSONRPC_VERSION.to_string(),
481            id: id.into(),
482            method: method.into(),
483            params,
484        }
485    }
486}
487
488impl JsonRpcResponse {
489    pub fn success(id: RequestId, result: Value) -> Self {
490        Self {
491            jsonrpc: JSONRPC_VERSION.to_string(),
492            id,
493            result: Some(result),
494            error: None,
495        }
496    }
497    
498    pub fn error(id: RequestId, error: JsonRpcError) -> Self {
499        Self {
500            jsonrpc: JSONRPC_VERSION.to_string(),
501            id,
502            result: None,
503            error: Some(error),
504        }
505    }
506}
507
508impl JsonRpcNotification {
509    pub fn new(method: impl Into<String>, params: Option<Value>) -> Self {
510        Self {
511            jsonrpc: JSONRPC_VERSION.to_string(),
512            method: method.into(),
513            params,
514        }
515    }
516}