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