1use serde::{Deserialize, Deserializer, Serialize};
7use std::collections::HashMap;
8
9pub const PROTOCOL_VERSION: &str = "2024-11-05";
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct JsonRpcRequest {
15 pub jsonrpc: String,
16 pub id: u64,
17 pub method: String,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub params: Option<serde_json::Value>,
20}
21
22impl JsonRpcRequest {
23 pub fn new(id: u64, method: &str, params: Option<serde_json::Value>) -> Self {
24 Self {
25 jsonrpc: "2.0".to_string(),
26 id,
27 method: method.to_string(),
28 params,
29 }
30 }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct JsonRpcResponse {
36 pub jsonrpc: String,
37 pub id: Option<u64>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub result: Option<serde_json::Value>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub error: Option<JsonRpcError>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct JsonRpcError {
47 pub code: i32,
48 pub message: String,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub data: Option<serde_json::Value>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct JsonRpcNotification {
56 pub jsonrpc: String,
57 pub method: String,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub params: Option<serde_json::Value>,
60}
61
62impl JsonRpcNotification {
63 pub fn new(method: &str, params: Option<serde_json::Value>) -> Self {
64 Self {
65 jsonrpc: "2.0".to_string(),
66 method: method.to_string(),
67 params,
68 }
69 }
70}
71
72#[derive(Debug, Clone, Default, Serialize, Deserialize)]
78pub struct ClientCapabilities {
79 #[serde(skip_serializing_if = "Option::is_none")]
80 pub roots: Option<RootsCapability>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub sampling: Option<SamplingCapability>,
83}
84
85#[derive(Debug, Clone, Default, Serialize, Deserialize)]
86pub struct RootsCapability {
87 #[serde(default)]
88 pub list_changed: bool,
89}
90
91#[derive(Debug, Clone, Default, Serialize, Deserialize)]
92pub struct SamplingCapability {}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct ClientInfo {
97 pub name: String,
98 pub version: String,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct InitializeParams {
105 pub protocol_version: String,
106 pub capabilities: ClientCapabilities,
107 pub client_info: ClientInfo,
108}
109
110#[derive(Debug, Clone, Default, Serialize, Deserialize)]
112pub struct ServerCapabilities {
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub tools: Option<ToolsCapability>,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub resources: Option<ResourcesCapability>,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub prompts: Option<PromptsCapability>,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub logging: Option<LoggingCapability>,
121}
122
123#[derive(Debug, Clone, Default, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct ToolsCapability {
126 #[serde(default)]
127 pub list_changed: bool,
128}
129
130#[derive(Debug, Clone, Default, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct ResourcesCapability {
133 #[serde(default)]
134 pub subscribe: bool,
135 #[serde(default)]
136 pub list_changed: bool,
137}
138
139#[derive(Debug, Clone, Default, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct PromptsCapability {
142 #[serde(default)]
143 pub list_changed: bool,
144}
145
146#[derive(Debug, Clone, Default, Serialize, Deserialize)]
147pub struct LoggingCapability {}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct ServerInfo {
152 pub name: String,
153 pub version: String,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158#[serde(rename_all = "camelCase")]
159pub struct InitializeResult {
160 pub protocol_version: String,
161 pub capabilities: ServerCapabilities,
162 pub server_info: ServerInfo,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct McpTool {
173 pub name: String,
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub description: Option<String>,
176 pub input_schema: serde_json::Value,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct ListToolsResult {
182 pub tools: Vec<McpTool>,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct CallToolParams {
188 pub name: String,
189 #[serde(skip_serializing_if = "Option::is_none")]
190 pub arguments: Option<serde_json::Value>,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195#[serde(tag = "type", rename_all = "lowercase")]
196pub enum ToolContent {
197 Text {
198 text: String,
199 },
200 Image {
201 data: String,
202 #[serde(rename = "mimeType")]
203 mime_type: String,
204 },
205 Resource {
206 resource: ResourceContent,
207 },
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
212#[serde(rename_all = "camelCase")]
213pub struct ResourceContent {
214 pub uri: String,
215 #[serde(skip_serializing_if = "Option::is_none")]
216 pub mime_type: Option<String>,
217 #[serde(skip_serializing_if = "Option::is_none")]
218 pub text: Option<String>,
219 #[serde(skip_serializing_if = "Option::is_none")]
220 pub blob: Option<String>,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize)]
225#[serde(rename_all = "camelCase")]
226pub struct CallToolResult {
227 pub content: Vec<ToolContent>,
228 #[serde(default)]
229 pub is_error: bool,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239pub struct McpResource {
240 pub uri: String,
241 pub name: String,
242 #[serde(skip_serializing_if = "Option::is_none")]
243 pub description: Option<String>,
244 #[serde(skip_serializing_if = "Option::is_none")]
245 pub mime_type: Option<String>,
246}
247
248#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct ListResourcesResult {
251 pub resources: Vec<McpResource>,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct ReadResourceParams {
257 pub uri: String,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct ReadResourceResult {
263 pub contents: Vec<ResourceContent>,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct McpPrompt {
273 pub name: String,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 pub description: Option<String>,
276 #[serde(skip_serializing_if = "Option::is_none")]
277 pub arguments: Option<Vec<PromptArgument>>,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct PromptArgument {
283 pub name: String,
284 #[serde(skip_serializing_if = "Option::is_none")]
285 pub description: Option<String>,
286 #[serde(default)]
287 pub required: bool,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct ListPromptsResult {
293 pub prompts: Vec<McpPrompt>,
294}
295
296#[derive(Debug, Clone)]
302pub enum McpNotification {
303 ToolsListChanged,
304 ResourcesListChanged,
305 PromptsListChanged,
306 Progress {
307 progress_token: String,
308 progress: f64,
309 total: Option<f64>,
310 },
311 Log {
312 level: String,
313 logger: Option<String>,
314 data: serde_json::Value,
315 },
316 Unknown {
317 method: String,
318 params: Option<serde_json::Value>,
319 },
320}
321
322impl McpNotification {
323 pub fn from_json_rpc(notification: &JsonRpcNotification) -> Self {
324 match notification.method.as_str() {
325 "notifications/tools/list_changed" => McpNotification::ToolsListChanged,
326 "notifications/resources/list_changed" => McpNotification::ResourcesListChanged,
327 "notifications/prompts/list_changed" => McpNotification::PromptsListChanged,
328 "notifications/progress" => {
329 if let Some(params) = ¬ification.params {
330 let progress_token = params
331 .get("progressToken")
332 .and_then(|v| v.as_str())
333 .unwrap_or("")
334 .to_string();
335 let progress = params
336 .get("progress")
337 .and_then(|v| v.as_f64())
338 .unwrap_or(0.0);
339 let total = params.get("total").and_then(|v| v.as_f64());
340 McpNotification::Progress {
341 progress_token,
342 progress,
343 total,
344 }
345 } else {
346 McpNotification::Unknown {
347 method: notification.method.clone(),
348 params: notification.params.clone(),
349 }
350 }
351 }
352 "notifications/message" => {
353 if let Some(params) = ¬ification.params {
354 let level = params
355 .get("level")
356 .and_then(|v| v.as_str())
357 .unwrap_or("info")
358 .to_string();
359 let logger = params
360 .get("logger")
361 .and_then(|v| v.as_str())
362 .map(|s| s.to_string());
363 let data = params
364 .get("data")
365 .cloned()
366 .unwrap_or(serde_json::Value::Null);
367 McpNotification::Log {
368 level,
369 logger,
370 data,
371 }
372 } else {
373 McpNotification::Unknown {
374 method: notification.method.clone(),
375 params: notification.params.clone(),
376 }
377 }
378 }
379 _ => McpNotification::Unknown {
380 method: notification.method.clone(),
381 params: notification.params.clone(),
382 },
383 }
384 }
385}
386
387#[derive(Debug, Clone, Serialize)]
393pub struct McpServerConfig {
394 pub name: String,
396 pub transport: McpTransportConfig,
398 #[serde(default = "default_true")]
400 pub enabled: bool,
401 #[serde(default)]
403 pub env: HashMap<String, String>,
404 #[serde(skip_serializing_if = "Option::is_none")]
406 pub oauth: Option<OAuthConfig>,
407 #[serde(default = "default_tool_timeout")]
409 pub tool_timeout_secs: u64,
410}
411
412impl<'de> Deserialize<'de> for McpServerConfig {
413 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
414 use serde::de::Error;
415 use serde_json::Value;
416
417 let mut map = serde_json::Map::deserialize(deserializer)?;
418
419 let transport = if let Some(t) = map.remove("transport") {
422 match &t {
423 Value::String(kind) => {
424 match kind.as_str() {
426 "stdio" => {
427 let command = map
428 .remove("command")
429 .and_then(|v| v.as_str().map(String::from))
430 .ok_or_else(|| D::Error::missing_field("command"))?;
431 let args = map
432 .remove("args")
433 .and_then(|v| serde_json::from_value(v).ok())
434 .unwrap_or_default();
435 McpTransportConfig::Stdio { command, args }
436 }
437 "http" => {
438 let url = map
439 .remove("url")
440 .and_then(|v| v.as_str().map(String::from))
441 .ok_or_else(|| D::Error::missing_field("url"))?;
442 let headers = map
443 .remove("headers")
444 .and_then(|v| serde_json::from_value(v).ok())
445 .unwrap_or_default();
446 McpTransportConfig::Http { url, headers }
447 }
448 "streamable-http" | "streamable_http" => {
449 let url = map
450 .remove("url")
451 .and_then(|v| v.as_str().map(String::from))
452 .ok_or_else(|| D::Error::missing_field("url"))?;
453 let headers = map
454 .remove("headers")
455 .and_then(|v| serde_json::from_value(v).ok())
456 .unwrap_or_default();
457 McpTransportConfig::StreamableHttp { url, headers }
458 }
459 other => {
460 return Err(D::Error::unknown_variant(
461 other,
462 &["stdio", "http", "streamable-http"],
463 ));
464 }
465 }
466 }
467 Value::Object(_) => serde_json::from_value(t).map_err(D::Error::custom)?,
469 _ => return Err(D::Error::custom("transport must be a string or object")),
470 }
471 } else {
472 return Err(D::Error::missing_field("transport"));
473 };
474
475 let name = map
476 .remove("name")
477 .and_then(|v| v.as_str().map(String::from))
478 .ok_or_else(|| D::Error::missing_field("name"))?;
479 let enabled = map
480 .remove("enabled")
481 .and_then(|v| v.as_bool())
482 .unwrap_or(true);
483 let env = map
484 .remove("env")
485 .and_then(|v| serde_json::from_value(v).ok())
486 .unwrap_or_default();
487 let oauth = map
488 .remove("oauth")
489 .and_then(|v| serde_json::from_value(v).ok());
490 let tool_timeout_secs = map
491 .remove("tool_timeout_secs")
492 .or_else(|| map.remove("toolTimeoutSecs"))
493 .and_then(|v| v.as_u64())
494 .unwrap_or(60);
495
496 Ok(McpServerConfig {
497 name,
498 transport,
499 enabled,
500 env,
501 oauth,
502 tool_timeout_secs,
503 })
504 }
505}
506
507#[allow(dead_code)] fn default_tool_timeout() -> u64 {
509 60
510}
511
512#[allow(dead_code)]
513fn default_true() -> bool {
514 true
515}
516
517#[derive(Debug, Clone, Serialize, Deserialize)]
519#[serde(tag = "type", rename_all = "kebab-case")]
520pub enum McpTransportConfig {
521 Stdio {
523 command: String,
524 #[serde(default)]
525 args: Vec<String>,
526 },
527 Http {
529 url: String,
530 #[serde(default)]
531 headers: HashMap<String, String>,
532 },
533 StreamableHttp {
538 url: String,
539 #[serde(default)]
540 headers: HashMap<String, String>,
541 },
542}
543
544#[derive(Debug, Clone, Serialize, Deserialize)]
546pub struct OAuthConfig {
547 pub auth_url: String,
548 pub token_url: String,
549 pub client_id: String,
550 #[serde(skip_serializing_if = "Option::is_none")]
551 pub client_secret: Option<String>,
552 #[serde(default)]
553 pub scopes: Vec<String>,
554 pub redirect_uri: String,
555 #[serde(skip_serializing_if = "Option::is_none")]
558 pub access_token: Option<String>,
559}
560
561#[cfg(test)]
562#[path = "protocol/tests.rs"]
563mod tests;