1use serde::{Deserialize, Serialize};
7
8pub const JSONRPC_VERSION: &str = "2.0";
12
13pub const PARSE_ERROR: i64 = -32700;
17
18pub const INVALID_REQUEST: i64 = -32600;
20
21pub const METHOD_NOT_FOUND: i64 = -32601;
23
24pub const INVALID_PARAMS: i64 = -32602;
26
27pub const INTERNAL_ERROR: i64 = -32603;
29
30pub const METHOD_INITIALIZE: &str = "initialize";
38
39pub const METHOD_PING: &str = "ping";
41
42pub const METHOD_LOGGING_SET_LEVEL: &str = "logging/setLevel";
44
45pub const METHOD_NOTIFICATIONS_INITIALIZED: &str = "notifications/initialized";
47
48pub const METHOD_INITIALIZED: &str = "initialized";
50
51pub const METHOD_NOTIFICATIONS_CANCELLED: &str = "notifications/cancelled";
53
54pub const METHOD_TOOLS_LIST: &str = "tools/list";
56
57pub const METHOD_TOOLS_CALL: &str = "tools/call";
59
60pub const METHOD_RESOURCES_LIST: &str = "resources/list";
62
63pub const METHOD_RESOURCES_TEMPLATES_LIST: &str = "resources/templates/list";
65
66pub const METHOD_RESOURCES_READ: &str = "resources/read";
68
69pub const METHOD_PROMPTS_LIST: &str = "prompts/list";
71
72pub const METHOD_PROMPTS_GET: &str = "prompts/get";
74
75pub const METHOD_COMPLETION_COMPLETE: &str = "completion/complete";
77
78pub const METHOD_NOTIFICATIONS_PROGRESS: &str = "notifications/progress";
80
81pub const METHOD_NOTIFICATIONS_MESSAGE: &str = "notifications/message";
83
84#[derive(Debug, Deserialize)]
88pub struct JsonRpcRequest {
89 #[serde(rename = "jsonrpc")]
91 pub version: String,
92
93 pub id: Option<serde_json::Value>,
95
96 pub method: String,
98
99 #[serde(default)]
101 pub params: Option<serde_json::Value>,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
108pub struct JsonRpcResponse {
109 pub jsonrpc: &'static str,
111
112 pub id: Option<serde_json::Value>,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub result: Option<serde_json::Value>,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub error: Option<JsonRpcError>,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
126pub struct JsonRpcError {
127 pub code: i64,
129 pub message: String,
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub data: Option<serde_json::Value>,
134}
135
136impl JsonRpcResponse {
137 #[must_use]
144 pub fn success(id: Option<serde_json::Value>, result: impl Serialize) -> Self {
145 Self {
146 jsonrpc: "2.0",
147 id,
148 result: Some(
149 serde_json::to_value(result).expect("MCP result type must be JSON-serializable"),
150 ),
151 error: None,
152 }
153 }
154
155 #[must_use]
157 pub fn error(id: Option<serde_json::Value>, code: i64, message: impl Into<String>) -> Self {
158 Self {
159 jsonrpc: "2.0",
160 id,
161 result: None,
162 error: Some(JsonRpcError {
163 code,
164 message: message.into(),
165 data: None,
166 }),
167 }
168 }
169
170 #[must_use]
172 pub fn error_with_data(
173 id: Option<serde_json::Value>,
174 code: i64,
175 message: impl Into<String>,
176 data: serde_json::Value,
177 ) -> Self {
178 Self {
179 jsonrpc: "2.0",
180 id,
181 result: None,
182 error: Some(JsonRpcError {
183 code,
184 message: message.into(),
185 data: Some(data),
186 }),
187 }
188 }
189}
190
191#[derive(Debug, Serialize)]
195#[serde(rename_all = "camelCase")]
196pub struct InitializeResult {
197 pub protocol_version: &'static str,
199 pub server_info: ServerInfo,
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub instructions: Option<String>,
204 pub capabilities: Capabilities,
206}
207
208#[derive(Debug, Serialize)]
210pub struct ServerInfo {
211 pub name: String,
213 pub version: String,
215}
216
217#[derive(Debug, Default, Serialize)]
219pub struct Capabilities {
220 #[serde(skip_serializing_if = "Option::is_none")]
223 pub tools: Option<ToolCapabilities>,
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub resources: Option<ResourceCapabilities>,
227 #[serde(skip_serializing_if = "Option::is_none")]
229 pub prompts: Option<PromptCapabilities>,
230}
231
232#[derive(Debug, Default, Serialize)]
234pub struct ToolCapabilities {}
235
236#[derive(Debug, Default, Serialize)]
238pub struct ResourceCapabilities {}
239
240#[derive(Debug, Default, Serialize)]
242pub struct PromptCapabilities {}
243
244#[derive(Clone, Debug, Serialize)]
246pub struct ToolsListResult {
247 pub tools: Vec<McpToolSchema>,
249}
250
251#[derive(Clone, Debug, Serialize)]
253#[serde(rename_all = "camelCase")]
254pub struct McpToolSchema {
255 pub name: String,
257 pub description: String,
259 pub input_schema: serde_json::Value,
261}
262
263#[derive(Debug, Deserialize)]
265pub struct ToolCallParams {
266 pub name: String,
268 #[serde(default = "empty_object")]
270 pub arguments: serde_json::Value,
271}
272
273fn empty_object() -> serde_json::Value {
276 serde_json::Value::Object(serde_json::Map::new())
277}
278
279#[derive(Debug, Serialize)]
281#[serde(rename_all = "camelCase")]
282pub struct ToolCallResult {
283 pub content: Vec<ContentItem>,
285 #[serde(skip_serializing_if = "std::ops::Not::not")]
287 pub is_error: bool,
288}
289
290impl ToolCallResult {
291 #[must_use]
298 pub fn text(&self) -> Option<&str> {
299 self.content.first().map(|item| item.text.as_str())
300 }
301}
302
303pub const CONTENT_TYPE_TEXT: &str = "text";
306
307#[derive(Debug, Serialize)]
309pub struct ContentItem {
310 #[serde(rename = "type")]
312 pub content_type: &'static str,
313 pub text: String,
315}
316
317impl ContentItem {
318 pub fn text(text: impl Into<String>) -> Self {
323 Self {
324 content_type: CONTENT_TYPE_TEXT,
325 text: text.into(),
326 }
327 }
328}
329
330#[derive(Clone, Debug, Serialize)]
334pub struct PromptsListResult {
335 pub prompts: Vec<PromptDefinition>,
337}
338
339pub use llm_tool::{PromptArgumentDefinition, PromptDefinition};
340
341#[derive(Debug, Deserialize)]
343pub struct GetPromptParams {
344 pub name: String,
346 #[serde(default = "empty_object")]
348 pub arguments: serde_json::Value,
349}
350
351#[derive(Debug, Serialize)]
353pub struct GetPromptResult {
354 #[serde(skip_serializing_if = "Option::is_none")]
356 pub description: Option<String>,
357 pub messages: Vec<PromptMessage>,
359}
360
361#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
363pub struct PromptMessage {
364 pub role: String,
366 pub content: PromptMessageContent,
368}
369
370#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
372#[serde(tag = "type")]
373pub enum PromptMessageContent {
374 #[serde(rename = "text")]
376 Text {
377 text: String,
379 },
380 #[serde(rename = "resource")]
382 Resource {
383 resource: ResourceContent,
385 },
386}
387
388#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
392#[serde(rename_all = "camelCase")]
393pub struct McpResource {
394 pub uri: String,
396 pub name: String,
398 #[serde(default, skip_serializing_if = "String::is_empty")]
400 pub description: String,
401 #[serde(skip_serializing_if = "Option::is_none")]
403 pub mime_type: Option<String>,
404}
405
406pub use McpResource as Resource;
407
408#[derive(Clone, Debug, Serialize, Deserialize)]
410pub struct ResourcesListResult {
411 pub resources: Vec<McpResource>,
413}
414
415pub use llm_tool::ResourceDefinition;
416
417#[derive(Debug, Deserialize)]
419pub struct ReadResourceParams {
420 pub uri: String,
422}
423
424#[derive(Debug, Serialize, Deserialize)]
426pub struct ReadResourceResult {
427 pub contents: Vec<ResourceContent>,
429}
430
431pub use llm_tool::ResourceOutputContent as ResourceContent;
432
433#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
435pub struct EmptyResult {}
436
437#[derive(Clone, Debug, Serialize, Deserialize)]
439#[serde(rename_all = "camelCase")]
440pub struct ResourceTemplatesListResult {
441 pub resource_templates: Vec<ResourceDefinition>,
443}
444
445#[derive(Clone, Debug, Serialize, Deserialize, Default)]
447pub struct CompletionCompleteResult {
448 pub completion: CompletionResultData,
450}
451
452#[derive(Clone, Debug, Serialize, Deserialize, Default)]
454#[serde(rename_all = "camelCase")]
455pub struct CompletionResultData {
456 pub values: Vec<String>,
458 pub total: usize,
460 pub has_more: bool,
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467
468 #[test]
469 fn deserialize_request_with_params() {
470 let json = r#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"add"}}"#;
471 let req: JsonRpcRequest = serde_json::from_str(json).unwrap();
472 assert_eq!(req.version, "2.0");
473 assert_eq!(req.id, Some(serde_json::json!(1)));
474 assert_eq!(req.method, "tools/call");
475 assert!(req.params.is_some());
476 }
477
478 #[test]
479 fn deserialize_request_without_params() {
480 let json = r#"{"jsonrpc":"2.0","id":2,"method":"tools/list"}"#;
481 let req: JsonRpcRequest = serde_json::from_str(json).unwrap();
482 assert!(req.params.is_none());
483 }
484
485 #[test]
486 fn deserialize_notification_without_id() {
487 let json = r#"{"jsonrpc":"2.0","method":"initialized"}"#;
488 let req: JsonRpcRequest = serde_json::from_str(json).unwrap();
489 assert!(req.id.is_none());
490 }
491
492 #[test]
493 fn serialize_success_response() {
494 let resp =
495 JsonRpcResponse::success(Some(serde_json::json!(1)), serde_json::json!({"ok": true}));
496 let json = serde_json::to_string(&resp).unwrap();
497 assert!(json.contains(r#""jsonrpc":"2.0""#));
498 assert!(json.contains(r#""result":{""#));
499 assert!(!json.contains("error"));
500 }
501
502 #[test]
503 fn serialize_error_response() {
504 let resp = JsonRpcResponse::error(Some(serde_json::json!(1)), PARSE_ERROR, "bad json");
505 let json = serde_json::to_string(&resp).unwrap();
506 assert!(json.contains(r#""code":-32700"#));
507 assert!(json.contains(r#""message":"bad json""#));
508 assert!(!json.contains("result"));
509 }
510
511 #[test]
512 fn serialize_error_omits_null_id() {
513 let resp = JsonRpcResponse::error(None, METHOD_NOT_FOUND, "no such method");
514 let json = serde_json::to_string(&resp).unwrap();
515 assert!(json.contains(r#""id":null"#));
516 }
517
518 #[test]
519 fn response_jsonrpc_field_is_static() {
520 let resp = JsonRpcResponse::success(None, serde_json::json!(null));
521 assert_eq!(resp.jsonrpc, "2.0");
523 }
524
525 #[test]
526 fn error_without_data_omits_data_field() {
527 let resp = JsonRpcResponse::error(Some(serde_json::json!(1)), PARSE_ERROR, "bad");
528 let json = serde_json::to_string(&resp).unwrap();
529 assert!(!json.contains("data"));
530 }
531
532 #[test]
533 fn error_with_data_includes_data_field() {
534 let resp = JsonRpcResponse::error_with_data(
535 Some(serde_json::json!(1)),
536 INTERNAL_ERROR,
537 "boom",
538 serde_json::json!({"detail": "stack trace"}),
539 );
540 let json = serde_json::to_string(&resp).unwrap();
541 assert!(json.contains(r#""data":{"detail":"stack trace"}"#));
542 }
543
544 #[test]
545 fn jsonrpc_version_constant() {
546 assert_eq!(JSONRPC_VERSION, "2.0");
547 }
548
549 #[test]
550 fn method_consts_match_wire_strings() {
551 assert_eq!(METHOD_INITIALIZE, "initialize");
552 assert_eq!(METHOD_PING, "ping");
553 assert_eq!(METHOD_LOGGING_SET_LEVEL, "logging/setLevel");
554 assert_eq!(
555 METHOD_NOTIFICATIONS_INITIALIZED,
556 "notifications/initialized"
557 );
558 assert_eq!(METHOD_INITIALIZED, "initialized");
559 assert_eq!(METHOD_NOTIFICATIONS_CANCELLED, "notifications/cancelled");
560 assert_eq!(METHOD_TOOLS_LIST, "tools/list");
561 assert_eq!(METHOD_TOOLS_CALL, "tools/call");
562 assert_eq!(METHOD_RESOURCES_LIST, "resources/list");
563 assert_eq!(METHOD_RESOURCES_TEMPLATES_LIST, "resources/templates/list");
564 assert_eq!(METHOD_RESOURCES_READ, "resources/read");
565 assert_eq!(METHOD_PROMPTS_LIST, "prompts/list");
566 assert_eq!(METHOD_PROMPTS_GET, "prompts/get");
567 assert_eq!(METHOD_COMPLETION_COMPLETE, "completion/complete");
568 assert_eq!(METHOD_NOTIFICATIONS_PROGRESS, "notifications/progress");
569 assert_eq!(METHOD_NOTIFICATIONS_MESSAGE, "notifications/message");
570 }
571
572 #[test]
573 fn content_item_text_constructor_sets_type() {
574 let item = ContentItem::text("hello");
575 assert_eq!(item.content_type, CONTENT_TYPE_TEXT);
576 assert_eq!(item.content_type, "text");
577 assert_eq!(item.text, "hello");
578 }
579
580 #[test]
581 fn tool_call_result_text_returns_first_block() {
582 let result = ToolCallResult {
583 content: vec![ContentItem::text("first"), ContentItem::text("second")],
584 is_error: false,
585 };
586 assert_eq!(result.text(), Some("first"));
587
588 let empty = ToolCallResult {
589 content: vec![],
590 is_error: true,
591 };
592 assert_eq!(empty.text(), None);
593 }
594}