1use std::sync::Arc;
4
5use derive_more::{Display, From};
6use serde::{Deserialize, Serialize};
7use serde_json::{Map, Value};
8use serde_with::{DefaultOnError, serde_as, skip_serializing_none};
9
10use crate::{IntoOption, MaybeUndefined};
11
12use super::{McpServerAcpId, Meta};
13
14#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
22#[non_exhaustive]
23pub struct McpError {
24 pub code: i32,
26 pub message: String,
28 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
30 pub data: MaybeUndefined<Value>,
31 #[serde(flatten)]
33 pub extra: Map<String, Value>,
34}
35
36impl McpError {
37 #[must_use]
39 pub fn new(code: i32, message: impl Into<String>) -> Self {
40 Self {
41 code,
42 message: message.into(),
43 data: MaybeUndefined::Undefined,
44 extra: Map::new(),
45 }
46 }
47
48 #[must_use]
50 pub fn data(mut self, data: Value) -> Self {
51 self.data = if data.is_null() {
52 MaybeUndefined::Null
53 } else {
54 MaybeUndefined::Value(data)
55 };
56 self
57 }
58}
59
60#[serde_as]
71#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
72#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
73#[serde(untagged)]
74#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = "mcp/message")))]
75#[non_exhaustive]
76pub enum MessageMcpResponse {
77 Result {
79 #[serde(deserialize_with = "Deserialize::deserialize")]
81 result: Value,
82 #[serde_as(deserialize_as = "DefaultOnError")]
84 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
85 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
86 meta: Option<Map<String, Value>>,
87 },
88 Error {
90 error: McpError,
92 #[serde_as(deserialize_as = "DefaultOnError")]
94 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
95 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
96 meta: Option<Map<String, Value>>,
97 },
98}
99
100impl MessageMcpResponse {
101 #[must_use]
103 pub fn success(result: Value) -> Self {
104 Self::Result { result, meta: None }
105 }
106
107 #[must_use]
109 pub fn error(error: McpError) -> Self {
110 Self::Error { error, meta: None }
111 }
112
113 #[must_use]
115 pub fn meta(mut self, meta: impl IntoOption<Map<String, Value>>) -> Self {
116 match &mut self {
117 Self::Result { meta: field, .. } | Self::Error { meta: field, .. } => {
118 *field = meta.into_option();
119 }
120 }
121 self
122 }
123}
124
125#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
134#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
135#[serde(transparent)]
136#[from(Arc<str>, String, &'static str)]
137#[non_exhaustive]
138pub struct McpRequestId(pub Arc<str>);
139
140impl McpRequestId {
141 #[must_use]
143 pub fn new(id: impl Into<Arc<str>>) -> Self {
144 Self(id.into())
145 }
146}
147
148#[serde_as]
154#[skip_serializing_none]
155#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
156#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
157#[serde(rename_all = "camelCase")]
158#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = MCP_MESSAGE_METHOD_NAME)))]
159#[non_exhaustive]
160pub struct MessageMcpRequest {
161 pub server_id: McpServerAcpId,
163 pub request_id: McpRequestId,
165 pub method: String,
167 #[serde(default)]
171 pub params: Option<serde_json::Map<String, serde_json::Value>>,
172 #[serde_as(deserialize_as = "DefaultOnError")]
178 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
179 #[serde(default)]
180 #[serde(rename = "_meta")]
181 pub meta: Option<Meta>,
182}
183
184impl MessageMcpRequest {
185 #[must_use]
187 pub fn new(
188 server_id: impl Into<McpServerAcpId>,
189 request_id: impl Into<McpRequestId>,
190 method: impl Into<String>,
191 ) -> Self {
192 Self {
193 server_id: server_id.into(),
194 request_id: request_id.into(),
195 method: method.into(),
196 params: None,
197 meta: None,
198 }
199 }
200
201 #[must_use]
205 pub fn params(
206 mut self,
207 params: impl IntoOption<serde_json::Map<String, serde_json::Value>>,
208 ) -> Self {
209 self.params = params.into_option();
210 self
211 }
212
213 #[must_use]
219 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
220 self.meta = meta.into_option();
221 self
222 }
223}
224
225#[serde_as]
234#[skip_serializing_none]
235#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
236#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
237#[serde(rename_all = "camelCase")]
238#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = MCP_MESSAGE_METHOD_NAME)))]
239#[non_exhaustive]
240pub struct MessageMcpNotification {
241 pub server_id: McpServerAcpId,
243 pub request_id: McpRequestId,
245 pub method: String,
247 #[serde(default)]
251 pub params: Option<serde_json::Map<String, serde_json::Value>>,
252 #[serde_as(deserialize_as = "DefaultOnError")]
258 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
259 #[serde(default)]
260 #[serde(rename = "_meta")]
261 pub meta: Option<Meta>,
262}
263
264impl MessageMcpNotification {
265 #[must_use]
267 pub fn new(
268 server_id: impl Into<McpServerAcpId>,
269 request_id: impl Into<McpRequestId>,
270 method: impl Into<String>,
271 ) -> Self {
272 Self {
273 server_id: server_id.into(),
274 request_id: request_id.into(),
275 method: method.into(),
276 params: None,
277 meta: None,
278 }
279 }
280
281 #[must_use]
285 pub fn params(
286 mut self,
287 params: impl IntoOption<serde_json::Map<String, serde_json::Value>>,
288 ) -> Self {
289 self.params = params.into_option();
290 self
291 }
292
293 #[must_use]
299 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
300 self.meta = meta.into_option();
301 self
302 }
303}
304
305pub(crate) const MCP_MESSAGE_METHOD_NAME: &str = "mcp/message";
307
308#[cfg(test)]
309mod tests {
310 use serde_json::{Value, json};
311
312 use super::{McpError, MessageMcpResponse};
313 use crate::MaybeUndefined;
314
315 #[test]
316 fn result_is_opaque_and_present_even_when_null() {
317 for result in [
318 Value::Null,
319 json!(false),
320 json!(42),
321 json!("opaque"),
322 json!([null, 1]),
323 json!({"resultType": "future", "unknown": {"value": true}}),
324 ] {
325 let response = MessageMcpResponse::success(result.clone());
326 let wire = json!({"result": result});
327 assert_eq!(serde_json::to_value(&response).unwrap(), wire);
328 assert_eq!(
329 serde_json::from_value::<MessageMcpResponse>(wire).unwrap(),
330 response
331 );
332 }
333 }
334
335 #[test]
336 fn error_round_trips_data_and_extensions_without_acp_translation() {
337 for data in [
338 MaybeUndefined::Undefined,
339 MaybeUndefined::Null,
340 MaybeUndefined::Value(json!({"arbitrary": [1, null]})),
341 ] {
342 let mut error = McpError::new(-32000, "inner error");
343 error.data = data.clone();
344 error.extra.insert("future".into(), json!({"key": 1}));
345 let response = MessageMcpResponse::error(error);
346 let wire = serde_json::to_value(&response).unwrap();
347 assert_eq!(wire["error"]["code"], -32000);
348 assert_eq!(wire["error"].get("data").is_some(), !data.is_undefined());
349 assert_eq!(wire["error"]["future"], json!({"key": 1}));
350 assert_eq!(
351 serde_json::from_value::<MessageMcpResponse>(wire).unwrap(),
352 response
353 );
354 }
355 assert_eq!(
356 McpError::new(1, "x").data(Value::Null).data,
357 MaybeUndefined::Null
358 );
359 }
360
361 #[test]
362 fn a_carrier_key_is_required_and_errors_must_be_valid() {
363 for wire in [
364 Value::Null,
365 json!([]),
366 json!({}),
367 json!({"_meta": null}),
368 json!({"unexpected": 1}),
369 json!({"error": null}),
370 json!({"error": 1}),
371 json!({"error": []}),
372 json!({"error": {}}),
373 json!({"error": {"code": 1}}),
374 json!({"error": {"message": "x"}}),
375 json!({"error": {"code": null, "message": "x"}}),
376 json!({"error": {"code": 1, "message": null}}),
377 json!({"error": {"code": 1.5, "message": "x"}}),
378 json!({"error": {"code": "1", "message": "x"}}),
379 ] {
380 assert!(
381 serde_json::from_value::<MessageMcpResponse>(wire.clone()).is_err(),
382 "accepted {wire}"
383 );
384 }
385 }
386
387 #[test]
388 fn result_takes_precedence_when_both_outcome_keys_are_present() {
389 for result in [Value::Null, json!(42), json!({"opaque": [null, true]})] {
390 for error in [
391 Value::Null,
392 json!({"code": 1, "message": "x"}),
393 json!({}),
394 json!({"code": 1, "message": null}),
395 ] {
396 let wire = json!({"result": result, "error": error});
397 assert_eq!(
398 serde_json::from_value::<MessageMcpResponse>(wire).unwrap(),
399 MessageMcpResponse::success(result.clone())
400 );
401 }
402 }
403 }
404
405 #[test]
406 fn unknown_outer_fields_are_ignored_and_inner_extensions_are_preserved() {
407 for wire in [
408 json!({"result": null, "unexpected": {"nested": true}}),
409 json!({"result": {"future": [null, {"error": "opaque"}]}, "unexpected": 1}),
410 json!({"error": {"code": 1, "message": "x", "future": [null, true]}, "unexpected": null}),
411 ] {
412 let parsed: MessageMcpResponse = serde_json::from_value(wire.clone()).unwrap();
413 let mut expected = wire;
414 expected.as_object_mut().unwrap().remove("unexpected");
415 assert_eq!(serde_json::to_value(parsed).unwrap(), expected);
416 }
417 }
418
419 #[test]
420 fn carrier_metadata_is_optional_and_invalid_values_are_salvaged() {
421 for outcome in [
422 json!({"result": null}),
423 json!({"error": {"code": 1, "message": "x"}}),
424 ] {
425 let parsed: MessageMcpResponse = serde_json::from_value(outcome.clone()).unwrap();
426 assert_eq!(serde_json::to_value(parsed).unwrap(), outcome);
427 for meta in [
428 Value::Null,
429 json!(true),
430 json!(1),
431 json!("invalid"),
432 json!([]),
433 ] {
434 let mut wire = outcome.clone();
435 wire["_meta"] = meta;
436 let parsed: MessageMcpResponse = serde_json::from_value(wire).unwrap();
437 assert_eq!(serde_json::to_value(parsed).unwrap(), outcome);
438 }
439 let mut wire = outcome;
440 wire["_meta"] = json!({"extension": [null, true]});
441 let parsed: MessageMcpResponse = serde_json::from_value(wire.clone()).unwrap();
442 assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
443 }
444 let meta = json!({"extension": [null, true]})
445 .as_object()
446 .unwrap()
447 .clone();
448 let response =
449 MessageMcpResponse::success(json!({"_meta": {"inner": true}})).meta(meta.clone());
450 assert_eq!(
451 serde_json::to_value(response).unwrap(),
452 json!({"result": {"_meta": {"inner": true}}, "_meta": meta})
453 );
454 }
455
456 #[cfg(feature = "schemars")]
457 #[test]
458 fn response_schema_requires_a_key_without_closing_outer_fields() {
459 let schema = serde_json::to_value(schemars::schema_for!(MessageMcpResponse)).unwrap();
460 assert!(schema.get("not").is_none());
461 let branches = schema["anyOf"].as_array().unwrap();
462 assert_eq!(branches.len(), 2);
463 assert_eq!(branches[0]["required"], json!(["result"]));
464 assert_eq!(branches[1]["required"], json!(["error"]));
465 for branch in branches {
466 assert_ne!(branch.get("additionalProperties"), Some(&json!(false)));
467 assert_eq!(
468 branch["properties"]["_meta"]["x-deserialize-default-on-error"],
469 true
470 );
471 }
472 assert_ne!(schema.get("additionalProperties"), Some(&json!(false)));
473 }
474}