1use super::jsonrpc::JsonRpcEnvelope;
12
13#[derive(Debug, Clone)]
17pub struct McpMessage {
18 pub envelope: JsonRpcEnvelope,
19 pub kind: MessageKind,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum MessageKind {
25 Client(ClientKind),
26 Server(ServerKind),
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum ClientKind {
35 Request(ClientMethod),
36 Notification(ClientNotifMethod),
37 Result,
38 Error,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum ClientMethod {
43 Ping,
44 Lifecycle(LifecycleMethod),
45 Tools(ToolsMethod),
46 Resources(ResourcesMethod),
47 Prompts(PromptsMethod),
48 Completion(CompletionMethod),
49 Logging(LoggingMethod),
50 Tasks(TasksMethod),
51 Unknown(String),
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum LifecycleMethod {
56 Initialize,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum ToolsMethod {
61 List,
62 Call,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum ResourcesMethod {
67 List,
68 TemplatesList,
69 Read,
70 Subscribe,
71 Unsubscribe,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum PromptsMethod {
76 List,
77 Get,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum CompletionMethod {
82 Complete,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum LoggingMethod {
87 SetLevel,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum TasksMethod {
95 List,
96 Get,
97 Result,
98 Cancel,
99}
100
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub enum ClientNotifMethod {
103 Initialized,
104 Cancelled,
105 Progress,
106 RootsListChanged,
107 TaskStatus,
108 Unknown(String),
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
116pub enum ServerKind {
117 Request(ServerMethod),
118 Notification(ServerNotifMethod),
119 Result,
120 Error,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
124pub enum ServerMethod {
125 Ping,
126 Sampling,
127 Elicitation,
128 Roots,
129 Tasks(TasksMethod),
130 Unknown(String),
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub enum ServerNotifMethod {
135 Cancelled,
136 Progress,
137 LogMessage,
138 ResourcesListChanged,
139 ResourceUpdated,
140 ToolsListChanged,
141 PromptsListChanged,
142 ElicitationComplete,
143 TaskStatus,
144 Unknown(String),
145}
146
147impl ClientMethod {
150 pub fn parse(method: &str) -> Self {
151 match method {
152 "ping" => Self::Ping,
153 "initialize" => Self::Lifecycle(LifecycleMethod::Initialize),
154 "tools/list" => Self::Tools(ToolsMethod::List),
155 "tools/call" => Self::Tools(ToolsMethod::Call),
156 "resources/list" => Self::Resources(ResourcesMethod::List),
157 "resources/templates/list" => Self::Resources(ResourcesMethod::TemplatesList),
158 "resources/read" => Self::Resources(ResourcesMethod::Read),
159 "resources/subscribe" => Self::Resources(ResourcesMethod::Subscribe),
160 "resources/unsubscribe" => Self::Resources(ResourcesMethod::Unsubscribe),
161 "prompts/list" => Self::Prompts(PromptsMethod::List),
162 "prompts/get" => Self::Prompts(PromptsMethod::Get),
163 "completion/complete" => Self::Completion(CompletionMethod::Complete),
164 "logging/setLevel" => Self::Logging(LoggingMethod::SetLevel),
165 "tasks/list" => Self::Tasks(TasksMethod::List),
166 "tasks/get" => Self::Tasks(TasksMethod::Get),
167 "tasks/result" => Self::Tasks(TasksMethod::Result),
168 "tasks/cancel" => Self::Tasks(TasksMethod::Cancel),
169 other => Self::Unknown(other.to_owned()),
170 }
171 }
172
173 pub fn as_str(&self) -> Option<&'static str> {
177 Some(match self {
178 Self::Ping => "ping",
179 Self::Lifecycle(LifecycleMethod::Initialize) => "initialize",
180 Self::Tools(ToolsMethod::List) => "tools/list",
181 Self::Tools(ToolsMethod::Call) => "tools/call",
182 Self::Resources(ResourcesMethod::List) => "resources/list",
183 Self::Resources(ResourcesMethod::TemplatesList) => "resources/templates/list",
184 Self::Resources(ResourcesMethod::Read) => "resources/read",
185 Self::Resources(ResourcesMethod::Subscribe) => "resources/subscribe",
186 Self::Resources(ResourcesMethod::Unsubscribe) => "resources/unsubscribe",
187 Self::Prompts(PromptsMethod::List) => "prompts/list",
188 Self::Prompts(PromptsMethod::Get) => "prompts/get",
189 Self::Completion(CompletionMethod::Complete) => "completion/complete",
190 Self::Logging(LoggingMethod::SetLevel) => "logging/setLevel",
191 Self::Tasks(TasksMethod::List) => "tasks/list",
192 Self::Tasks(TasksMethod::Get) => "tasks/get",
193 Self::Tasks(TasksMethod::Result) => "tasks/result",
194 Self::Tasks(TasksMethod::Cancel) => "tasks/cancel",
195 Self::Unknown(_) => return None,
196 })
197 }
198}
199
200impl ClientNotifMethod {
201 pub fn parse(method: &str) -> Self {
202 match method {
203 "notifications/initialized" => Self::Initialized,
204 "notifications/cancelled" => Self::Cancelled,
205 "notifications/progress" => Self::Progress,
206 "notifications/roots/list_changed" => Self::RootsListChanged,
207 "notifications/tasks/status" => Self::TaskStatus,
208 other => Self::Unknown(other.to_owned()),
209 }
210 }
211}
212
213impl ServerMethod {
214 pub fn parse(method: &str) -> Self {
215 match method {
216 "ping" => Self::Ping,
217 "sampling/createMessage" => Self::Sampling,
218 "elicitation/create" => Self::Elicitation,
219 "roots/list" => Self::Roots,
220 "tasks/list" => Self::Tasks(TasksMethod::List),
221 "tasks/get" => Self::Tasks(TasksMethod::Get),
222 "tasks/result" => Self::Tasks(TasksMethod::Result),
223 "tasks/cancel" => Self::Tasks(TasksMethod::Cancel),
224 other => Self::Unknown(other.to_owned()),
225 }
226 }
227}
228
229impl ServerNotifMethod {
230 pub fn parse(method: &str) -> Self {
231 match method {
232 "notifications/cancelled" => Self::Cancelled,
233 "notifications/progress" => Self::Progress,
234 "notifications/message" => Self::LogMessage,
235 "notifications/resources/list_changed" => Self::ResourcesListChanged,
236 "notifications/resources/updated" => Self::ResourceUpdated,
237 "notifications/tools/list_changed" => Self::ToolsListChanged,
238 "notifications/prompts/list_changed" => Self::PromptsListChanged,
239 "notifications/elicitation/complete" => Self::ElicitationComplete,
240 "notifications/tasks/status" => Self::TaskStatus,
241 other => Self::Unknown(other.to_owned()),
242 }
243 }
244}
245
246pub fn classify_client(env: &JsonRpcEnvelope) -> ClientKind {
251 match (
252 env.method.as_deref(),
253 env.id.is_some(),
254 env.result.is_some(),
255 env.error.is_some(),
256 ) {
257 (Some(m), true, false, false) => ClientKind::Request(ClientMethod::parse(m)),
258 (Some(m), false, false, false) => ClientKind::Notification(ClientNotifMethod::parse(m)),
259 (None, true, true, false) => ClientKind::Result,
260 (None, true, false, true) => ClientKind::Error,
261 _ => {
262 debug_assert!(
263 false,
264 "classify_client: envelope shape should have been rejected by parse",
265 );
266 ClientKind::Error
267 }
268 }
269}
270
271pub fn classify_server(env: &JsonRpcEnvelope) -> ServerKind {
273 match (
274 env.method.as_deref(),
275 env.id.is_some(),
276 env.result.is_some(),
277 env.error.is_some(),
278 ) {
279 (Some(m), true, false, false) => ServerKind::Request(ServerMethod::parse(m)),
280 (Some(m), false, false, false) => ServerKind::Notification(ServerNotifMethod::parse(m)),
281 (None, true, true, false) => ServerKind::Result,
282 (None, true, false, true) => ServerKind::Error,
283 _ => {
284 debug_assert!(
285 false,
286 "classify_server: envelope shape should have been rejected by parse",
287 );
288 ServerKind::Error
289 }
290 }
291}
292
293#[cfg(test)]
294#[allow(non_snake_case)]
295mod tests {
296 use super::*;
297
298 fn parsed(bytes: &[u8]) -> JsonRpcEnvelope {
299 JsonRpcEnvelope::parse(bytes).unwrap()
300 }
301
302 #[test]
305 fn client_method__spec_coverage() {
306 let cases: &[(&str, ClientMethod)] = &[
307 ("ping", ClientMethod::Ping),
308 (
309 "initialize",
310 ClientMethod::Lifecycle(LifecycleMethod::Initialize),
311 ),
312 ("tools/list", ClientMethod::Tools(ToolsMethod::List)),
313 ("tools/call", ClientMethod::Tools(ToolsMethod::Call)),
314 (
315 "resources/list",
316 ClientMethod::Resources(ResourcesMethod::List),
317 ),
318 (
319 "resources/templates/list",
320 ClientMethod::Resources(ResourcesMethod::TemplatesList),
321 ),
322 (
323 "resources/read",
324 ClientMethod::Resources(ResourcesMethod::Read),
325 ),
326 (
327 "resources/subscribe",
328 ClientMethod::Resources(ResourcesMethod::Subscribe),
329 ),
330 (
331 "resources/unsubscribe",
332 ClientMethod::Resources(ResourcesMethod::Unsubscribe),
333 ),
334 ("prompts/list", ClientMethod::Prompts(PromptsMethod::List)),
335 ("prompts/get", ClientMethod::Prompts(PromptsMethod::Get)),
336 (
337 "completion/complete",
338 ClientMethod::Completion(CompletionMethod::Complete),
339 ),
340 (
341 "logging/setLevel",
342 ClientMethod::Logging(LoggingMethod::SetLevel),
343 ),
344 ("tasks/list", ClientMethod::Tasks(TasksMethod::List)),
345 ("tasks/get", ClientMethod::Tasks(TasksMethod::Get)),
346 ("tasks/result", ClientMethod::Tasks(TasksMethod::Result)),
347 ("tasks/cancel", ClientMethod::Tasks(TasksMethod::Cancel)),
348 ];
349 for (m, expected) in cases {
350 assert_eq!(ClientMethod::parse(m), *expected, "method = {m}");
351 }
352 }
353
354 #[test]
355 fn client_method__unknown_preserves_string() {
356 assert_eq!(
357 ClientMethod::parse("tools/future-method"),
358 ClientMethod::Unknown("tools/future-method".into()),
359 );
360 }
361
362 #[test]
365 fn client_notif_method__spec_coverage() {
366 let cases: &[(&str, ClientNotifMethod)] = &[
367 ("notifications/initialized", ClientNotifMethod::Initialized),
368 ("notifications/cancelled", ClientNotifMethod::Cancelled),
369 ("notifications/progress", ClientNotifMethod::Progress),
370 (
371 "notifications/roots/list_changed",
372 ClientNotifMethod::RootsListChanged,
373 ),
374 ("notifications/tasks/status", ClientNotifMethod::TaskStatus),
375 ];
376 for (m, expected) in cases {
377 assert_eq!(ClientNotifMethod::parse(m), *expected, "method = {m}");
378 }
379 }
380
381 #[test]
382 fn client_notif_method__unknown_preserves_string() {
383 assert_eq!(
384 ClientNotifMethod::parse("notifications/something"),
385 ClientNotifMethod::Unknown("notifications/something".into()),
386 );
387 }
388
389 #[test]
392 fn server_method__spec_coverage() {
393 let cases: &[(&str, ServerMethod)] = &[
394 ("ping", ServerMethod::Ping),
395 ("sampling/createMessage", ServerMethod::Sampling),
396 ("elicitation/create", ServerMethod::Elicitation),
397 ("roots/list", ServerMethod::Roots),
398 ("tasks/list", ServerMethod::Tasks(TasksMethod::List)),
399 ("tasks/get", ServerMethod::Tasks(TasksMethod::Get)),
400 ("tasks/result", ServerMethod::Tasks(TasksMethod::Result)),
401 ("tasks/cancel", ServerMethod::Tasks(TasksMethod::Cancel)),
402 ];
403 for (m, expected) in cases {
404 assert_eq!(ServerMethod::parse(m), *expected, "method = {m}");
405 }
406 }
407
408 #[test]
409 fn server_method__unknown_preserves_string() {
410 assert_eq!(
411 ServerMethod::parse("custom/method"),
412 ServerMethod::Unknown("custom/method".into()),
413 );
414 }
415
416 #[test]
419 fn server_notif_method__spec_coverage() {
420 let cases: &[(&str, ServerNotifMethod)] = &[
421 ("notifications/cancelled", ServerNotifMethod::Cancelled),
422 ("notifications/progress", ServerNotifMethod::Progress),
423 ("notifications/message", ServerNotifMethod::LogMessage),
424 (
425 "notifications/resources/list_changed",
426 ServerNotifMethod::ResourcesListChanged,
427 ),
428 (
429 "notifications/resources/updated",
430 ServerNotifMethod::ResourceUpdated,
431 ),
432 (
433 "notifications/tools/list_changed",
434 ServerNotifMethod::ToolsListChanged,
435 ),
436 (
437 "notifications/prompts/list_changed",
438 ServerNotifMethod::PromptsListChanged,
439 ),
440 (
441 "notifications/elicitation/complete",
442 ServerNotifMethod::ElicitationComplete,
443 ),
444 ("notifications/tasks/status", ServerNotifMethod::TaskStatus),
445 ];
446 for (m, expected) in cases {
447 assert_eq!(ServerNotifMethod::parse(m), *expected, "method = {m}");
448 }
449 }
450
451 #[test]
452 fn server_notif_method__unknown_preserves_string() {
453 assert_eq!(
454 ServerNotifMethod::parse("notifications/future"),
455 ServerNotifMethod::Unknown("notifications/future".into()),
456 );
457 }
458
459 #[test]
462 fn classify_client__request() {
463 let e = parsed(br#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#);
464 assert_eq!(
465 classify_client(&e),
466 ClientKind::Request(ClientMethod::Tools(ToolsMethod::List)),
467 );
468 }
469
470 #[test]
471 fn classify_client__notification() {
472 let e = parsed(br#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#);
473 assert_eq!(
474 classify_client(&e),
475 ClientKind::Notification(ClientNotifMethod::Initialized),
476 );
477 }
478
479 #[test]
480 fn classify_client__result() {
481 let e = parsed(br#"{"jsonrpc":"2.0","id":1,"result":{}}"#);
482 assert_eq!(classify_client(&e), ClientKind::Result);
483 }
484
485 #[test]
486 fn classify_client__error() {
487 let e = parsed(br#"{"jsonrpc":"2.0","id":1,"error":{"code":-1,"message":"x"}}"#);
488 assert_eq!(classify_client(&e), ClientKind::Error);
489 }
490
491 #[test]
492 fn classify_client__unknown_method() {
493 let e = parsed(br#"{"jsonrpc":"2.0","id":1,"method":"custom/method"}"#);
494 assert_eq!(
495 classify_client(&e),
496 ClientKind::Request(ClientMethod::Unknown("custom/method".into())),
497 );
498 }
499
500 #[test]
503 fn classify_server__request() {
504 let e = parsed(br#"{"jsonrpc":"2.0","id":1,"method":"sampling/createMessage"}"#);
505 assert_eq!(
506 classify_server(&e),
507 ServerKind::Request(ServerMethod::Sampling),
508 );
509 }
510
511 #[test]
512 fn classify_server__notification() {
513 let e = parsed(br#"{"jsonrpc":"2.0","method":"notifications/tools/list_changed"}"#);
514 assert_eq!(
515 classify_server(&e),
516 ServerKind::Notification(ServerNotifMethod::ToolsListChanged),
517 );
518 }
519
520 #[test]
521 fn classify_server__result() {
522 let e = parsed(br#"{"jsonrpc":"2.0","id":1,"result":{}}"#);
523 assert_eq!(classify_server(&e), ServerKind::Result);
524 }
525
526 #[test]
527 fn classify_server__error() {
528 let e = parsed(br#"{"jsonrpc":"2.0","id":1,"error":{"code":-1,"message":"x"}}"#);
529 assert_eq!(classify_server(&e), ServerKind::Error);
530 }
531}