1pub mod tools;
7
8use std::io::{BufRead, Write};
9
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13use crate::api::AuthyClient;
14
15#[derive(Debug, Deserialize)]
18pub struct JsonRpcRequest {
19 #[allow(dead_code)]
20 pub jsonrpc: String,
21 pub id: Option<Value>,
22 pub method: String,
23 #[serde(default)]
24 pub params: Value,
25}
26
27#[derive(Debug, Serialize)]
28pub struct JsonRpcResponse {
29 pub jsonrpc: String,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub id: Option<Value>,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub result: Option<Value>,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub error: Option<JsonRpcError>,
36}
37
38#[derive(Debug, Serialize)]
39pub struct JsonRpcError {
40 pub code: i64,
41 pub message: String,
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub data: Option<Value>,
44}
45
46impl JsonRpcResponse {
47 fn success(id: Option<Value>, result: Value) -> Self {
48 Self {
49 jsonrpc: "2.0".to_string(),
50 id,
51 result: Some(result),
52 error: None,
53 }
54 }
55
56 fn error(id: Option<Value>, code: i64, message: impl Into<String>) -> Self {
57 Self {
58 jsonrpc: "2.0".to_string(),
59 id,
60 result: None,
61 error: Some(JsonRpcError {
62 code,
63 message: message.into(),
64 data: None,
65 }),
66 }
67 }
68}
69
70pub struct McpServer {
74 client: Option<AuthyClient>,
75}
76
77impl McpServer {
78 pub fn new(client: Option<AuthyClient>) -> Self {
79 Self { client }
80 }
81
82 pub fn run<R: BufRead, W: Write>(&self, reader: R, mut writer: W) -> std::io::Result<()> {
87 for line in reader.lines() {
88 let line = line?;
89 let line = line.trim();
90 if line.is_empty() {
91 continue;
92 }
93
94 let request: JsonRpcRequest = match serde_json::from_str(line) {
95 Ok(r) => r,
96 Err(e) => {
97 let resp =
98 JsonRpcResponse::error(None, -32700, format!("Parse error: {}", e));
99 Self::write_response(&mut writer, &resp)?;
100 continue;
101 }
102 };
103
104 let is_notification = request.id.is_none();
106 let response = self.dispatch(&request);
107
108 if !is_notification {
109 if let Some(resp) = response {
110 Self::write_response(&mut writer, &resp)?;
111 }
112 }
113 }
114 Ok(())
115 }
116
117 fn write_response<W: Write>(writer: &mut W, resp: &JsonRpcResponse) -> std::io::Result<()> {
118 let json = serde_json::to_string(resp)
119 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
120 writeln!(writer, "{}", json)?;
121 writer.flush()
122 }
123
124 fn dispatch(&self, req: &JsonRpcRequest) -> Option<JsonRpcResponse> {
125 match req.method.as_str() {
126 "initialize" => Some(self.handle_initialize(req)),
127 "notifications/initialized" => None,
128 "ping" => Some(self.handle_ping(req)),
129 "tools/list" => Some(self.handle_tools_list(req)),
130 "tools/call" => Some(self.handle_tools_call(req)),
131 _ => Some(JsonRpcResponse::error(
132 req.id.clone(),
133 -32601,
134 format!("Method not found: {}", req.method),
135 )),
136 }
137 }
138
139 fn handle_initialize(&self, req: &JsonRpcRequest) -> JsonRpcResponse {
140 let result = serde_json::json!({
141 "protocolVersion": "2024-11-05",
142 "capabilities": {
143 "tools": {}
144 },
145 "serverInfo": {
146 "name": "authy",
147 "version": env!("CARGO_PKG_VERSION")
148 }
149 });
150 JsonRpcResponse::success(req.id.clone(), result)
151 }
152
153 fn handle_ping(&self, req: &JsonRpcRequest) -> JsonRpcResponse {
154 JsonRpcResponse::success(req.id.clone(), serde_json::json!({}))
155 }
156
157 fn handle_tools_list(&self, req: &JsonRpcRequest) -> JsonRpcResponse {
158 let tool_defs = tools::tool_definitions();
159 let result = serde_json::json!({ "tools": tool_defs });
160 JsonRpcResponse::success(req.id.clone(), result)
161 }
162
163 fn handle_tools_call(&self, req: &JsonRpcRequest) -> JsonRpcResponse {
164 let tool_name = req
165 .params
166 .get("name")
167 .and_then(|v| v.as_str())
168 .unwrap_or("");
169 let arguments = req
170 .params
171 .get("arguments")
172 .cloned()
173 .unwrap_or(Value::Object(Default::default()));
174
175 let client = match &self.client {
176 Some(c) => c,
177 None => {
178 let result = tools::error_result(
179 "No credentials configured. Set AUTHY_KEYFILE or AUTHY_PASSPHRASE.",
180 );
181 return JsonRpcResponse::success(req.id.clone(), result);
182 }
183 };
184
185 let result = tools::dispatch(client, tool_name, &arguments);
186 JsonRpcResponse::success(req.id.clone(), result)
187 }
188}