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    /// Optional endpoint for the tool (e.g., HTTP URL)
221    pub endpoint: Option<String>,
222    /// Additional metadata about the tool
223    pub metadata: Option<Value>,
224    pub registered_at: std::time::SystemTime,
225}
226
227impl ToolMetadata {
228    pub fn new(name: String, description: Option<String>, input_schema: Value) -> Self {
229        Self {
230            name,
231            description,
232            input_schema,
233            endpoint: None,
234            metadata: None,
235            registered_at: std::time::SystemTime::now(),
236        }
237    }
238
239    pub fn with_endpoint(mut self, endpoint: String) -> Self {
240        self.endpoint = Some(endpoint);
241        self
242    }
243
244    pub fn with_metadata(mut self, metadata: Value) -> Self {
245        self.metadata = Some(metadata);
246        self
247    }
248}
249
250/// List tools response
251#[derive(Debug, Clone, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub struct ListToolsResult {
254    pub tools: Vec<McpTool>,
255    #[serde(default, skip_serializing_if = "Option::is_none")]
256    pub next_cursor: Option<String>,
257}
258
259/// Call tool parameters
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct CallToolParams {
262    pub name: String,
263    #[serde(default)]
264    pub arguments: Value,
265}
266
267/// Tool call result
268#[derive(Debug, Clone, Serialize, Deserialize)]
269#[serde(rename_all = "camelCase")]
270pub struct CallToolResult {
271    pub content: Vec<ToolContent>,
272    #[serde(default)]
273    pub is_error: bool,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
277#[serde(tag = "type", rename_all = "snake_case")]
278pub enum ToolContent {
279    Text { text: String },
280    Image { data: String, mime_type: String },
281    Resource { resource: ResourceContents },
282}
283
284// ===== MCP Resources =====
285
286/// Resource definition
287#[derive(Debug, Clone, Serialize, Deserialize)]
288#[serde(rename_all = "camelCase")]
289pub struct McpResource {
290    pub uri: String,
291    pub name: String,
292    #[serde(default, skip_serializing_if = "Option::is_none")]
293    pub description: Option<String>,
294    #[serde(default, skip_serializing_if = "Option::is_none")]
295    pub mime_type: Option<String>,
296}
297
298/// Resource metadata for storage and querying
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct ResourceMetadata {
301    pub uri: String,
302    pub name: String,
303    pub description: Option<String>,
304    pub mime_type: Option<String>,
305    pub registered_at: std::time::SystemTime,
306}
307
308impl ResourceMetadata {
309    pub fn new(
310        uri: String,
311        name: String,
312        description: Option<String>,
313        mime_type: Option<String>,
314    ) -> Self {
315        Self {
316            uri,
317            name,
318            description,
319            mime_type,
320            registered_at: std::time::SystemTime::now(),
321        }
322    }
323}
324
325/// List resources response
326#[derive(Debug, Clone, Serialize, Deserialize)]
327#[serde(rename_all = "camelCase")]
328pub struct ListResourcesResult {
329    pub resources: Vec<McpResource>,
330    #[serde(default, skip_serializing_if = "Option::is_none")]
331    pub next_cursor: Option<String>,
332}
333
334/// Read resource parameters
335#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct ReadResourceParams {
337    pub uri: String,
338}
339
340/// Resource contents
341#[derive(Debug, Clone, Serialize, Deserialize)]
342#[serde(rename_all = "camelCase")]
343pub struct ResourceContents {
344    pub uri: String,
345    #[serde(default, skip_serializing_if = "Option::is_none")]
346    pub mime_type: Option<String>,
347    #[serde(flatten)]
348    pub content: ResourceContent,
349}
350
351#[derive(Debug, Clone, Serialize, Deserialize)]
352#[serde(untagged)]
353pub enum ResourceContent {
354    Text { text: String },
355    Blob { blob: String }, // base64-encoded
356}
357
358/// Read resource response
359#[derive(Debug, Clone, Serialize, Deserialize)]
360pub struct ReadResourceResult {
361    pub contents: Vec<ResourceContents>,
362}
363
364// ===== MCP Prompts =====
365
366/// Prompt definition
367#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct McpPrompt {
369    pub name: String,
370    #[serde(default, skip_serializing_if = "Option::is_none")]
371    pub description: Option<String>,
372    #[serde(default, skip_serializing_if = "Vec::is_empty")]
373    pub arguments: Vec<PromptArgument>,
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct PromptArgument {
378    pub name: String,
379    #[serde(default, skip_serializing_if = "Option::is_none")]
380    pub description: Option<String>,
381    #[serde(default)]
382    pub required: bool,
383}
384
385/// List prompts response
386#[derive(Debug, Clone, Serialize, Deserialize)]
387#[serde(rename_all = "camelCase")]
388pub struct ListPromptsResult {
389    pub prompts: Vec<McpPrompt>,
390    #[serde(default, skip_serializing_if = "Option::is_none")]
391    pub next_cursor: Option<String>,
392}
393
394/// Get prompt parameters
395#[derive(Debug, Clone, Serialize, Deserialize)]
396pub struct GetPromptParams {
397    pub name: String,
398    #[serde(default)]
399    pub arguments: Value,
400}
401
402/// Prompt message
403#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct PromptMessage {
405    pub role: PromptRole,
406    pub content: PromptContent,
407}
408
409#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
410#[serde(rename_all = "lowercase")]
411pub enum PromptRole {
412    User,
413    Assistant,
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize)]
417#[serde(tag = "type", rename_all = "snake_case")]
418pub enum PromptContent {
419    Text { text: String },
420    Image { data: String, mime_type: String },
421    Resource { resource: ResourceContents },
422}
423
424/// Get prompt response
425#[derive(Debug, Clone, Serialize, Deserialize)]
426pub struct GetPromptResult {
427    #[serde(default, skip_serializing_if = "Option::is_none")]
428    pub description: Option<String>,
429    pub messages: Vec<PromptMessage>,
430}
431
432// ===== MCP Sampling =====
433
434/// Sampling request (for server -> client sampling)
435#[derive(Debug, Clone, Serialize, Deserialize)]
436#[serde(rename_all = "camelCase")]
437pub struct CreateMessageParams {
438    pub messages: Vec<SamplingMessage>,
439    #[serde(default, skip_serializing_if = "Option::is_none")]
440    pub model_preferences: Option<ModelPreferences>,
441    #[serde(default, skip_serializing_if = "Option::is_none")]
442    pub system_prompt: Option<String>,
443    #[serde(default, skip_serializing_if = "Option::is_none")]
444    pub max_tokens: Option<i32>,
445}
446
447#[derive(Debug, Clone, Serialize, Deserialize)]
448pub struct SamplingMessage {
449    pub role: PromptRole,
450    pub content: SamplingContent,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
454#[serde(tag = "type", rename_all = "snake_case")]
455pub enum SamplingContent {
456    Text { text: String },
457    Image { data: String, mime_type: String },
458}
459
460#[derive(Debug, Clone, Serialize, Deserialize)]
461#[serde(rename_all = "camelCase")]
462pub struct ModelPreferences {
463    #[serde(default, skip_serializing_if = "Vec::is_empty")]
464    pub hints: Vec<ModelHint>,
465    #[serde(default, skip_serializing_if = "Option::is_none")]
466    pub cost_priority: Option<f32>,
467    #[serde(default, skip_serializing_if = "Option::is_none")]
468    pub speed_priority: Option<f32>,
469    #[serde(default, skip_serializing_if = "Option::is_none")]
470    pub intelligence_priority: Option<f32>,
471}
472
473#[derive(Debug, Clone, Serialize, Deserialize)]
474pub struct ModelHint {
475    #[serde(default, skip_serializing_if = "Option::is_none")]
476    pub name: Option<String>,
477}
478
479/// Sampling response
480#[derive(Debug, Clone, Serialize, Deserialize)]
481#[serde(rename_all = "camelCase")]
482pub struct CreateMessageResult {
483    pub role: PromptRole,
484    pub content: SamplingContent,
485    pub model: String,
486    #[serde(default, skip_serializing_if = "Option::is_none")]
487    pub stop_reason: Option<String>,
488}
489
490// ===== MCP Logging =====
491
492#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
493#[serde(rename_all = "lowercase")]
494pub enum LogLevel {
495    Debug,
496    Info,
497    Notice,
498    Warning,
499    Error,
500    Critical,
501    Alert,
502    Emergency,
503}
504
505#[derive(Debug, Clone, Serialize, Deserialize)]
506pub struct LoggingMessageParams {
507    pub level: LogLevel,
508    #[serde(default, skip_serializing_if = "Option::is_none")]
509    pub logger: Option<String>,
510    pub data: Value,
511}
512
513// ===== MCP Protocol Version =====
514
515pub const PROTOCOL_VERSION: &str = "2024-11-05";
516pub const JSONRPC_VERSION: &str = "2.0";
517
518impl JsonRpcRequest {
519    pub fn new(id: impl Into<RequestId>, method: impl Into<String>, params: Option<Value>) -> Self {
520        Self {
521            jsonrpc: JSONRPC_VERSION.to_string(),
522            id: id.into(),
523            method: method.into(),
524            params,
525        }
526    }
527}
528
529impl JsonRpcResponse {
530    pub fn success(id: RequestId, result: Value) -> Self {
531        Self {
532            jsonrpc: JSONRPC_VERSION.to_string(),
533            id,
534            result: Some(result),
535            error: None,
536        }
537    }
538
539    pub fn error(id: RequestId, error: JsonRpcError) -> Self {
540        Self {
541            jsonrpc: JSONRPC_VERSION.to_string(),
542            id,
543            result: None,
544            error: Some(error),
545        }
546    }
547}
548
549impl JsonRpcNotification {
550    pub fn new(method: impl Into<String>, params: Option<Value>) -> Self {
551        Self {
552            jsonrpc: JSONRPC_VERSION.to_string(),
553            method: method.into(),
554            params,
555        }
556    }
557}