faucet_cli/mcp/
protocol.rs1use serde::{Deserialize, Serialize};
9use serde_json::{Value, json};
10
11pub const PROTOCOL_VERSION: &str = "2024-11-05";
13
14pub const PARSE_ERROR: i64 = -32700;
16pub const INVALID_REQUEST: i64 = -32600;
17pub const METHOD_NOT_FOUND: i64 = -32601;
18pub const INVALID_PARAMS: i64 = -32602;
19pub const INTERNAL_ERROR: i64 = -32603;
20
21#[derive(Debug, Clone, Deserialize)]
25pub struct JsonRpcRequest {
26 #[serde(default)]
27 pub jsonrpc: String,
28 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub id: Option<Value>,
30 pub method: String,
31 #[serde(default)]
32 pub params: Value,
33}
34
35impl JsonRpcRequest {
36 pub fn is_notification(&self) -> bool {
38 self.id.is_none()
39 }
40}
41
42pub fn success(id: Value, result: Value) -> Value {
44 json!({ "jsonrpc": "2.0", "id": id, "result": result })
45}
46
47pub fn error(id: Value, code: i64, message: impl Into<String>) -> Value {
49 json!({
50 "jsonrpc": "2.0",
51 "id": id,
52 "error": { "code": code, "message": message.into() }
53 })
54}
55
56pub fn error_no_id(code: i64, message: impl Into<String>) -> Value {
59 error(Value::Null, code, message)
60}
61
62#[derive(Debug, Clone, Serialize)]
64pub struct ToolDef {
65 pub name: &'static str,
66 pub description: &'static str,
67 #[serde(rename = "inputSchema")]
68 pub input_schema: Value,
69}
70
71pub fn tool_text(text: impl Into<String>) -> Value {
73 json!({
74 "content": [ { "type": "text", "text": text.into() } ],
75 "isError": false
76 })
77}
78
79pub fn tool_error(text: impl Into<String>) -> Value {
85 json!({
86 "content": [ { "type": "text", "text": text.into() } ],
87 "isError": true
88 })
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn parses_request_with_id() {
97 let r: JsonRpcRequest =
98 serde_json::from_str(r#"{"jsonrpc":"2.0","id":1,"method":"ping"}"#).unwrap();
99 assert_eq!(r.method, "ping");
100 assert!(!r.is_notification());
101 assert_eq!(r.id, Some(json!(1)));
102 }
103
104 #[test]
105 fn parses_notification_without_id() {
106 let r: JsonRpcRequest =
107 serde_json::from_str(r#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#)
108 .unwrap();
109 assert!(r.is_notification());
110 }
111
112 #[test]
113 fn success_envelope_shape() {
114 let v = success(json!(7), json!({"ok": true}));
115 assert_eq!(v["jsonrpc"], "2.0");
116 assert_eq!(v["id"], 7);
117 assert_eq!(v["result"]["ok"], true);
118 }
119
120 #[test]
121 fn error_envelope_shape() {
122 let v = error(json!(7), METHOD_NOT_FOUND, "nope");
123 assert_eq!(v["error"]["code"], METHOD_NOT_FOUND);
124 assert_eq!(v["error"]["message"], "nope");
125 assert!(v.get("result").is_none());
126 }
127
128 #[test]
129 fn tool_text_and_error_shapes() {
130 let ok = tool_text("hi");
131 assert_eq!(ok["isError"], false);
132 assert_eq!(ok["content"][0]["type"], "text");
133 assert_eq!(ok["content"][0]["text"], "hi");
134 let err = tool_error("boom");
135 assert_eq!(err["isError"], true);
136 }
137}