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, Eq)]
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, Eq)]
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")]
89 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
90 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
91 meta: Option<Map<String, Value>>,
92 },
93 Error {
95 error: McpError,
97 #[serde_as(deserialize_as = "DefaultOnError")]
104 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
105 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
106 meta: Option<Map<String, Value>>,
107 },
108}
109
110impl MessageMcpResponse {
111 #[must_use]
113 pub fn success(result: Value) -> Self {
114 Self::Result { result, meta: None }
115 }
116
117 #[must_use]
119 pub fn error(error: McpError) -> Self {
120 Self::Error { error, meta: None }
121 }
122
123 #[must_use]
125 pub fn meta(mut self, meta: impl IntoOption<Map<String, Value>>) -> Self {
126 match &mut self {
127 Self::Result { meta: field, .. } | Self::Error { meta: field, .. } => {
128 *field = meta.into_option();
129 }
130 }
131 self
132 }
133}
134
135#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
141#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
142#[serde(transparent)]
143#[from(Arc<str>, String, &'static str)]
144#[non_exhaustive]
145pub struct McpRequestId(pub Arc<str>);
146
147impl McpRequestId {
148 #[must_use]
150 pub fn new(id: impl Into<Arc<str>>) -> Self {
151 Self(id.into())
152 }
153}
154
155#[serde_as]
159#[skip_serializing_none]
160#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
161#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
162#[serde(rename_all = "camelCase")]
163#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = MCP_MESSAGE_METHOD_NAME)))]
164#[non_exhaustive]
165pub struct MessageMcpRequest {
166 pub server_id: McpServerAcpId,
168 pub request_id: McpRequestId,
170 pub method: String,
172 #[serde(default)]
174 pub params: Option<serde_json::Map<String, serde_json::Value>>,
175 #[serde_as(deserialize_as = "DefaultOnError")]
182 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
183 #[serde(default)]
184 #[serde(rename = "_meta")]
185 pub meta: Option<Meta>,
186}
187
188impl MessageMcpRequest {
189 #[must_use]
191 pub fn new(
192 server_id: impl Into<McpServerAcpId>,
193 request_id: impl Into<McpRequestId>,
194 method: impl Into<String>,
195 ) -> Self {
196 Self {
197 server_id: server_id.into(),
198 request_id: request_id.into(),
199 method: method.into(),
200 params: None,
201 meta: None,
202 }
203 }
204
205 #[must_use]
207 pub fn params(
208 mut self,
209 params: impl IntoOption<serde_json::Map<String, serde_json::Value>>,
210 ) -> Self {
211 self.params = params.into_option();
212 self
213 }
214
215 #[must_use]
217 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
218 self.meta = meta.into_option();
219 self
220 }
221}
222
223#[serde_as]
228#[skip_serializing_none]
229#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
230#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
231#[serde(rename_all = "camelCase")]
232#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = MCP_MESSAGE_METHOD_NAME)))]
233#[non_exhaustive]
234pub struct MessageMcpNotification {
235 pub server_id: McpServerAcpId,
237 pub request_id: McpRequestId,
239 pub method: String,
241 #[serde(default)]
243 pub params: Option<serde_json::Map<String, serde_json::Value>>,
244 #[serde_as(deserialize_as = "DefaultOnError")]
251 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
252 #[serde(default)]
253 #[serde(rename = "_meta")]
254 pub meta: Option<Meta>,
255}
256
257impl MessageMcpNotification {
258 #[must_use]
260 pub fn new(
261 server_id: impl Into<McpServerAcpId>,
262 request_id: impl Into<McpRequestId>,
263 method: impl Into<String>,
264 ) -> Self {
265 Self {
266 server_id: server_id.into(),
267 request_id: request_id.into(),
268 method: method.into(),
269 params: None,
270 meta: None,
271 }
272 }
273
274 #[must_use]
276 pub fn params(
277 mut self,
278 params: impl IntoOption<serde_json::Map<String, serde_json::Value>>,
279 ) -> Self {
280 self.params = params.into_option();
281 self
282 }
283
284 #[must_use]
286 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
287 self.meta = meta.into_option();
288 self
289 }
290}
291
292pub(crate) const MCP_MESSAGE_METHOD_NAME: &str = "mcp/message";
294
295#[cfg(test)]
296mod tests {
297 use serde_json::{Value, json};
298
299 use super::{McpError, MessageMcpResponse};
300 use crate::MaybeUndefined;
301
302 #[test]
303 fn result_is_opaque_and_present_even_when_null() {
304 for result in [
305 Value::Null,
306 json!(false),
307 json!(42),
308 json!("opaque"),
309 json!([null, 1]),
310 json!({"resultType": "future", "unknown": {"value": true}}),
311 ] {
312 let response = MessageMcpResponse::success(result.clone());
313 let wire = json!({"result": result});
314 assert_eq!(serde_json::to_value(&response).unwrap(), wire);
315 assert_eq!(
316 serde_json::from_value::<MessageMcpResponse>(wire).unwrap(),
317 response
318 );
319 }
320 }
321
322 #[test]
323 fn error_round_trips_data_and_extensions_without_acp_translation() {
324 for data in [
325 MaybeUndefined::Undefined,
326 MaybeUndefined::Null,
327 MaybeUndefined::Value(json!({"arbitrary": [1, null]})),
328 ] {
329 let mut error = McpError::new(-32000, "inner error");
330 error.data = data.clone();
331 error.extra.insert("future".into(), json!({"key": 1}));
332 let response = MessageMcpResponse::error(error);
333 let wire = serde_json::to_value(&response).unwrap();
334 assert_eq!(wire["error"]["code"], -32000);
335 assert_eq!(wire["error"].get("data").is_some(), !data.is_undefined());
336 assert_eq!(wire["error"]["future"], json!({"key": 1}));
337 assert_eq!(
338 serde_json::from_value::<MessageMcpResponse>(wire).unwrap(),
339 response
340 );
341 }
342 assert_eq!(
343 McpError::new(1, "x").data(Value::Null).data,
344 MaybeUndefined::Null
345 );
346 }
347
348 #[test]
349 fn a_carrier_key_is_required_and_errors_must_be_valid() {
350 for wire in [
351 Value::Null,
352 json!([]),
353 json!({}),
354 json!({"_meta": null}),
355 json!({"unexpected": 1}),
356 json!({"error": null}),
357 json!({"error": 1}),
358 json!({"error": []}),
359 json!({"error": {}}),
360 json!({"error": {"code": 1}}),
361 json!({"error": {"message": "x"}}),
362 json!({"error": {"code": null, "message": "x"}}),
363 json!({"error": {"code": 1, "message": null}}),
364 json!({"error": {"code": 1.5, "message": "x"}}),
365 json!({"error": {"code": "1", "message": "x"}}),
366 ] {
367 assert!(
368 serde_json::from_value::<MessageMcpResponse>(wire.clone()).is_err(),
369 "accepted {wire}"
370 );
371 }
372 }
373
374 #[test]
375 fn result_takes_precedence_when_both_outcome_keys_are_present() {
376 for result in [Value::Null, json!(42), json!({"opaque": [null, true]})] {
377 for error in [
378 Value::Null,
379 json!({"code": 1, "message": "x"}),
380 json!({}),
381 json!({"code": 1, "message": null}),
382 ] {
383 let wire = json!({"result": result, "error": error});
384 assert_eq!(
385 serde_json::from_value::<MessageMcpResponse>(wire).unwrap(),
386 MessageMcpResponse::success(result.clone())
387 );
388 }
389 }
390 }
391
392 #[test]
393 fn unknown_outer_fields_are_ignored_and_inner_extensions_are_preserved() {
394 for wire in [
395 json!({"result": null, "unexpected": {"nested": true}}),
396 json!({"result": {"future": [null, {"error": "opaque"}]}, "unexpected": 1}),
397 json!({"error": {"code": 1, "message": "x", "future": [null, true]}, "unexpected": null}),
398 ] {
399 let parsed: MessageMcpResponse = serde_json::from_value(wire.clone()).unwrap();
400 let mut expected = wire;
401 expected.as_object_mut().unwrap().remove("unexpected");
402 assert_eq!(serde_json::to_value(parsed).unwrap(), expected);
403 }
404 }
405
406 #[test]
407 fn carrier_metadata_is_optional_and_invalid_values_are_salvaged() {
408 for outcome in [
409 json!({"result": null}),
410 json!({"error": {"code": 1, "message": "x"}}),
411 ] {
412 let parsed: MessageMcpResponse = serde_json::from_value(outcome.clone()).unwrap();
413 assert_eq!(serde_json::to_value(parsed).unwrap(), outcome);
414 for meta in [
415 Value::Null,
416 json!(true),
417 json!(1),
418 json!("invalid"),
419 json!([]),
420 ] {
421 let mut wire = outcome.clone();
422 wire["_meta"] = meta;
423 let parsed: MessageMcpResponse = serde_json::from_value(wire).unwrap();
424 assert_eq!(serde_json::to_value(parsed).unwrap(), outcome);
425 }
426 let mut wire = outcome;
427 wire["_meta"] = json!({"extension": [null, true]});
428 let parsed: MessageMcpResponse = serde_json::from_value(wire.clone()).unwrap();
429 assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
430 }
431 let meta = json!({"extension": [null, true]})
432 .as_object()
433 .unwrap()
434 .clone();
435 let response =
436 MessageMcpResponse::success(json!({"_meta": {"inner": true}})).meta(meta.clone());
437 assert_eq!(
438 serde_json::to_value(response).unwrap(),
439 json!({"result": {"_meta": {"inner": true}}, "_meta": meta})
440 );
441 }
442
443 #[cfg(feature = "schemars")]
444 #[test]
445 fn response_schema_requires_a_key_without_closing_outer_fields() {
446 let schema = serde_json::to_value(schemars::schema_for!(MessageMcpResponse)).unwrap();
447 assert!(schema.get("not").is_none());
448 let branches = schema["anyOf"].as_array().unwrap();
449 assert_eq!(branches.len(), 2);
450 assert_eq!(branches[0]["required"], json!(["result"]));
451 assert_eq!(branches[1]["required"], json!(["error"]));
452 for branch in branches {
453 assert_ne!(branch.get("additionalProperties"), Some(&json!(false)));
454 assert_eq!(
455 branch["properties"]["_meta"]["x-deserialize-default-on-error"],
456 true
457 );
458 }
459 assert_ne!(schema.get("additionalProperties"), Some(&json!(false)));
460 }
461}