1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9
10pub const JSONRPC_VERSION: &str = "2.0";
12
13pub const MCP_VERSION: &str = "2024-11-05";
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
18#[serde(untagged)]
19pub enum RequestId {
20 Number(i64),
22 String(String),
24}
25
26impl From<i64> for RequestId {
27 fn from(n: i64) -> Self {
28 Self::Number(n)
29 }
30}
31
32impl From<String> for RequestId {
33 fn from(s: String) -> Self {
34 Self::String(s)
35 }
36}
37
38impl From<&str> for RequestId {
39 fn from(s: &str) -> Self {
40 Self::String(s.to_string())
41 }
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct JsonRpcRequest {
47 pub jsonrpc: String,
49 pub id: RequestId,
51 pub method: String,
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub params: Option<Value>,
56}
57
58impl JsonRpcRequest {
59 pub fn new(id: impl Into<RequestId>, method: impl Into<String>) -> Self {
61 Self {
62 jsonrpc: JSONRPC_VERSION.to_string(),
63 id: id.into(),
64 method: method.into(),
65 params: None,
66 }
67 }
68
69 pub fn with_params(mut self, params: Value) -> Self {
71 self.params = Some(params);
72 self
73 }
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct JsonRpcResponse {
79 pub jsonrpc: String,
81 pub id: RequestId,
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub result: Option<Value>,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub error: Option<JsonRpcError>,
89}
90
91impl JsonRpcResponse {
92 pub fn success(id: impl Into<RequestId>, result: Value) -> Self {
94 Self {
95 jsonrpc: JSONRPC_VERSION.to_string(),
96 id: id.into(),
97 result: Some(result),
98 error: None,
99 }
100 }
101
102 pub fn error(id: impl Into<RequestId>, error: JsonRpcError) -> Self {
104 Self {
105 jsonrpc: JSONRPC_VERSION.to_string(),
106 id: id.into(),
107 result: None,
108 error: Some(error),
109 }
110 }
111
112 pub fn is_error(&self) -> bool {
114 self.error.is_some()
115 }
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct JsonRpcError {
121 pub code: i32,
123 pub message: String,
125 #[serde(skip_serializing_if = "Option::is_none")]
127 pub data: Option<Value>,
128}
129
130impl JsonRpcError {
131 pub fn new(code: i32, message: impl Into<String>) -> Self {
133 Self {
134 code,
135 message: message.into(),
136 data: None,
137 }
138 }
139
140 pub fn parse_error(message: impl Into<String>) -> Self {
142 Self::new(-32700, message)
143 }
144
145 pub fn invalid_request(message: impl Into<String>) -> Self {
147 Self::new(-32600, message)
148 }
149
150 pub fn method_not_found(message: impl Into<String>) -> Self {
152 Self::new(-32601, message)
153 }
154
155 pub fn invalid_params(message: impl Into<String>) -> Self {
157 Self::new(-32602, message)
158 }
159
160 pub fn internal_error(message: impl Into<String>) -> Self {
162 Self::new(-32603, message)
163 }
164}
165
166impl std::fmt::Display for JsonRpcError {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 write!(f, "[{}] {}", self.code, self.message)
169 }
170}
171
172impl std::error::Error for JsonRpcError {}
173
174#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct JsonRpcNotification {
177 pub jsonrpc: String,
179 pub method: String,
181 #[serde(skip_serializing_if = "Option::is_none")]
183 pub params: Option<Value>,
184}
185
186impl JsonRpcNotification {
187 pub fn new(method: impl Into<String>) -> Self {
189 Self {
190 jsonrpc: JSONRPC_VERSION.to_string(),
191 method: method.into(),
192 params: None,
193 }
194 }
195
196 pub fn with_params(mut self, params: Value) -> Self {
198 self.params = Some(params);
199 self
200 }
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct McpTool {
210 pub name: String,
212 #[serde(skip_serializing_if = "Option::is_none")]
214 pub description: Option<String>,
215 #[serde(rename = "inputSchema")]
217 pub input_schema: Value,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct McpResource {
223 pub uri: String,
225 pub name: String,
227 #[serde(skip_serializing_if = "Option::is_none")]
229 pub description: Option<String>,
230 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
232 pub mime_type: Option<String>,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct McpResourceTemplate {
238 #[serde(rename = "uriTemplate")]
240 pub uri_template: String,
241 pub name: String,
243 #[serde(skip_serializing_if = "Option::is_none")]
245 pub description: Option<String>,
246 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
248 pub mime_type: Option<String>,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct McpPrompt {
254 pub name: String,
256 #[serde(skip_serializing_if = "Option::is_none")]
258 pub description: Option<String>,
259 #[serde(skip_serializing_if = "Option::is_none")]
261 pub arguments: Option<Vec<McpPromptArgument>>,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct McpPromptArgument {
267 pub name: String,
269 #[serde(skip_serializing_if = "Option::is_none")]
271 pub description: Option<String>,
272 #[serde(skip_serializing_if = "Option::is_none")]
274 pub required: Option<bool>,
275}
276
277#[derive(Debug, Clone, Default, Serialize, Deserialize)]
283pub struct ServerCapabilities {
284 #[serde(skip_serializing_if = "Option::is_none")]
286 pub tools: Option<ToolCapabilities>,
287 #[serde(skip_serializing_if = "Option::is_none")]
289 pub resources: Option<ResourceCapabilities>,
290 #[serde(skip_serializing_if = "Option::is_none")]
292 pub prompts: Option<PromptCapabilities>,
293 #[serde(skip_serializing_if = "Option::is_none")]
295 pub logging: Option<LoggingCapabilities>,
296}
297
298#[derive(Debug, Clone, Default, Serialize, Deserialize)]
300pub struct ToolCapabilities {
301 #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
303 pub list_changed: Option<bool>,
304}
305
306#[derive(Debug, Clone, Default, Serialize, Deserialize)]
308pub struct ResourceCapabilities {
309 #[serde(skip_serializing_if = "Option::is_none")]
311 pub subscribe: Option<bool>,
312 #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
314 pub list_changed: Option<bool>,
315}
316
317#[derive(Debug, Clone, Default, Serialize, Deserialize)]
319pub struct PromptCapabilities {
320 #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
322 pub list_changed: Option<bool>,
323}
324
325#[derive(Debug, Clone, Default, Serialize, Deserialize)]
327pub struct LoggingCapabilities {}
328
329#[derive(Debug, Clone, Default, Serialize, Deserialize)]
331pub struct ClientCapabilities {
332 #[serde(skip_serializing_if = "Option::is_none")]
334 pub roots: Option<RootCapabilities>,
335 #[serde(skip_serializing_if = "Option::is_none")]
337 pub sampling: Option<SamplingCapabilities>,
338}
339
340#[derive(Debug, Clone, Default, Serialize, Deserialize)]
342pub struct RootCapabilities {
343 #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
345 pub list_changed: Option<bool>,
346}
347
348#[derive(Debug, Clone, Default, Serialize, Deserialize)]
350pub struct SamplingCapabilities {}
351
352#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct InitializeParams {
355 #[serde(rename = "protocolVersion")]
357 pub protocol_version: String,
358 pub capabilities: ClientCapabilities,
360 #[serde(rename = "clientInfo")]
362 pub client_info: ClientInfo,
363}
364
365impl Default for InitializeParams {
366 fn default() -> Self {
367 Self {
368 protocol_version: MCP_VERSION.to_string(),
369 capabilities: ClientCapabilities::default(),
370 client_info: ClientInfo::default(),
371 }
372 }
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct ClientInfo {
378 pub name: String,
380 pub version: String,
382}
383
384impl Default for ClientInfo {
385 fn default() -> Self {
386 Self {
387 name: "liteforge".to_string(),
388 version: env!("CARGO_PKG_VERSION").to_string(),
389 }
390 }
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct InitializeResult {
396 #[serde(rename = "protocolVersion")]
398 pub protocol_version: String,
399 pub capabilities: ServerCapabilities,
401 #[serde(rename = "serverInfo")]
403 pub server_info: ServerInfo,
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct ServerInfo {
409 pub name: String,
411 #[serde(skip_serializing_if = "Option::is_none")]
413 pub version: Option<String>,
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize)]
418pub struct CallToolParams {
419 pub name: String,
421 #[serde(skip_serializing_if = "Option::is_none")]
423 pub arguments: Option<HashMap<String, Value>>,
424}
425
426#[derive(Debug, Clone, Serialize, Deserialize)]
428pub struct CallToolResult {
429 pub content: Vec<ToolResultContent>,
431 #[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
433 pub is_error: Option<bool>,
434}
435
436#[derive(Debug, Clone, Serialize, Deserialize)]
438#[serde(tag = "type")]
439pub enum ToolResultContent {
440 #[serde(rename = "text")]
442 Text { text: String },
443 #[serde(rename = "image")]
445 Image { data: String, mime_type: String },
446 #[serde(rename = "resource")]
448 Resource { resource: McpResource, text: String },
449}
450
451#[derive(Debug, Clone, Serialize, Deserialize)]
453pub struct ListToolsResult {
454 pub tools: Vec<McpTool>,
456}
457
458#[derive(Debug, Clone, Serialize, Deserialize)]
460pub struct ListResourcesResult {
461 pub resources: Vec<McpResource>,
463}
464
465#[derive(Debug, Clone, Serialize, Deserialize)]
467pub struct ListPromptsResult {
468 pub prompts: Vec<McpPrompt>,
470}
471
472#[derive(Debug, Clone, Serialize, Deserialize)]
474pub struct ReadResourceResult {
475 pub contents: Vec<ResourceContent>,
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct ResourceContent {
482 pub uri: String,
484 #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
486 pub mime_type: Option<String>,
487 #[serde(skip_serializing_if = "Option::is_none")]
489 pub text: Option<String>,
490 #[serde(skip_serializing_if = "Option::is_none")]
492 pub blob: Option<String>,
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize)]
497pub struct GetPromptResult {
498 #[serde(skip_serializing_if = "Option::is_none")]
500 pub description: Option<String>,
501 pub messages: Vec<PromptMessage>,
503}
504
505#[derive(Debug, Clone, Serialize, Deserialize)]
507pub struct PromptMessage {
508 pub role: String,
510 pub content: PromptContent,
512}
513
514#[derive(Debug, Clone, Serialize, Deserialize)]
516#[serde(tag = "type")]
517pub enum PromptContent {
518 #[serde(rename = "text")]
520 Text { text: String },
521 #[serde(rename = "image")]
523 Image { data: String, mime_type: String },
524 #[serde(rename = "resource")]
526 Resource { resource: McpResource },
527}
528
529#[cfg(test)]
530mod tests {
531 use super::*;
532 use serde_json::json;
533
534 #[test]
535 fn test_request_id_from() {
536 let id: RequestId = 123.into();
537 assert_eq!(id, RequestId::Number(123));
538
539 let id: RequestId = "abc".into();
540 assert_eq!(id, RequestId::String("abc".to_string()));
541 }
542
543 #[test]
544 fn test_jsonrpc_request() {
545 let req = JsonRpcRequest::new(1, "tools/list");
546 assert_eq!(req.jsonrpc, "2.0");
547 assert_eq!(req.method, "tools/list");
548 assert!(req.params.is_none());
549
550 let req = req.with_params(json!({"cursor": null}));
551 assert!(req.params.is_some());
552 }
553
554 #[test]
555 fn test_jsonrpc_response_success() {
556 let resp = JsonRpcResponse::success(1, json!({"tools": []}));
557 assert!(!resp.is_error());
558 assert!(resp.result.is_some());
559 }
560
561 #[test]
562 fn test_jsonrpc_response_error() {
563 let err = JsonRpcError::method_not_found("Unknown method");
564 let resp = JsonRpcResponse::error(1, err);
565 assert!(resp.is_error());
566 assert!(resp.error.is_some());
567 assert_eq!(resp.error.unwrap().code, -32601);
568 }
569
570 #[test]
571 fn test_jsonrpc_error_display() {
572 let err = JsonRpcError::invalid_params("Missing required field");
573 assert_eq!(err.to_string(), "[-32602] Missing required field");
574 }
575
576 #[test]
577 fn test_mcp_tool_serialization() {
578 let tool = McpTool {
579 name: "read_file".to_string(),
580 description: Some("Read a file".to_string()),
581 input_schema: json!({
582 "type": "object",
583 "properties": {
584 "path": {"type": "string"}
585 },
586 "required": ["path"]
587 }),
588 };
589
590 let json_str = serde_json::to_string(&tool).unwrap();
591 assert!(json_str.contains("inputSchema"));
592 }
593
594 #[test]
595 fn test_initialize_params_default() {
596 let params = InitializeParams::default();
597 assert_eq!(params.protocol_version, MCP_VERSION);
598 assert_eq!(params.client_info.name, "liteforge");
599 }
600
601 #[test]
602 fn test_tool_result_content() {
603 let content = ToolResultContent::Text {
604 text: "Hello".to_string(),
605 };
606 let json = serde_json::to_value(&content).unwrap();
607 assert_eq!(json["type"], "text");
608 assert_eq!(json["text"], "Hello");
609 }
610}