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))]
131#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
132#[serde(transparent)]
133#[from(Arc<str>, String, &'static str)]
134#[non_exhaustive]
135pub struct McpRequestId(pub Arc<str>);
136
137impl McpRequestId {
138 #[must_use]
140 pub fn new(id: impl Into<Arc<str>>) -> Self {
141 Self(id.into())
142 }
143}
144
145#[serde_as]
149#[skip_serializing_none]
150#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
151#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
152#[serde(rename_all = "camelCase")]
153#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = MCP_MESSAGE_METHOD_NAME)))]
154#[non_exhaustive]
155pub struct MessageMcpRequest {
156 pub server_id: McpServerAcpId,
158 pub request_id: McpRequestId,
160 pub method: String,
162 #[serde(default)]
164 pub params: Option<serde_json::Map<String, serde_json::Value>>,
165 #[serde_as(deserialize_as = "DefaultOnError")]
167 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
168 #[serde(default)]
169 #[serde(rename = "_meta")]
170 pub meta: Option<Meta>,
171}
172
173impl MessageMcpRequest {
174 #[must_use]
176 pub fn new(
177 server_id: impl Into<McpServerAcpId>,
178 request_id: impl Into<McpRequestId>,
179 method: impl Into<String>,
180 ) -> Self {
181 Self {
182 server_id: server_id.into(),
183 request_id: request_id.into(),
184 method: method.into(),
185 params: None,
186 meta: None,
187 }
188 }
189
190 #[must_use]
192 pub fn params(
193 mut self,
194 params: impl IntoOption<serde_json::Map<String, serde_json::Value>>,
195 ) -> Self {
196 self.params = params.into_option();
197 self
198 }
199
200 #[must_use]
202 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
203 self.meta = meta.into_option();
204 self
205 }
206}
207
208#[serde_as]
213#[skip_serializing_none]
214#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
215#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
216#[serde(rename_all = "camelCase")]
217#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = MCP_MESSAGE_METHOD_NAME)))]
218#[non_exhaustive]
219pub struct MessageMcpNotification {
220 pub server_id: McpServerAcpId,
222 pub request_id: McpRequestId,
224 pub method: String,
226 #[serde(default)]
228 pub params: Option<serde_json::Map<String, serde_json::Value>>,
229 #[serde_as(deserialize_as = "DefaultOnError")]
231 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
232 #[serde(default)]
233 #[serde(rename = "_meta")]
234 pub meta: Option<Meta>,
235}
236
237impl MessageMcpNotification {
238 #[must_use]
240 pub fn new(
241 server_id: impl Into<McpServerAcpId>,
242 request_id: impl Into<McpRequestId>,
243 method: impl Into<String>,
244 ) -> Self {
245 Self {
246 server_id: server_id.into(),
247 request_id: request_id.into(),
248 method: method.into(),
249 params: None,
250 meta: None,
251 }
252 }
253
254 #[must_use]
256 pub fn params(
257 mut self,
258 params: impl IntoOption<serde_json::Map<String, serde_json::Value>>,
259 ) -> Self {
260 self.params = params.into_option();
261 self
262 }
263
264 #[must_use]
266 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
267 self.meta = meta.into_option();
268 self
269 }
270}
271
272pub(crate) const MCP_MESSAGE_METHOD_NAME: &str = "mcp/message";
274
275#[cfg(test)]
276mod tests {
277 use serde_json::{Value, json};
278
279 use super::{McpError, MessageMcpResponse};
280 use crate::MaybeUndefined;
281
282 #[test]
283 fn result_is_opaque_and_present_even_when_null() {
284 for result in [
285 Value::Null,
286 json!(false),
287 json!(42),
288 json!("opaque"),
289 json!([null, 1]),
290 json!({"resultType": "future", "unknown": {"value": true}}),
291 ] {
292 let response = MessageMcpResponse::success(result.clone());
293 let wire = json!({"result": result});
294 assert_eq!(serde_json::to_value(&response).unwrap(), wire);
295 assert_eq!(
296 serde_json::from_value::<MessageMcpResponse>(wire).unwrap(),
297 response
298 );
299 }
300 }
301
302 #[test]
303 fn error_round_trips_data_and_extensions_without_acp_translation() {
304 for data in [
305 MaybeUndefined::Undefined,
306 MaybeUndefined::Null,
307 MaybeUndefined::Value(json!({"arbitrary": [1, null]})),
308 ] {
309 let mut error = McpError::new(-32000, "inner error");
310 error.data = data.clone();
311 error.extra.insert("future".into(), json!({"key": 1}));
312 let response = MessageMcpResponse::error(error);
313 let wire = serde_json::to_value(&response).unwrap();
314 assert_eq!(wire["error"]["code"], -32000);
315 assert_eq!(wire["error"].get("data").is_some(), !data.is_undefined());
316 assert_eq!(wire["error"]["future"], json!({"key": 1}));
317 assert_eq!(
318 serde_json::from_value::<MessageMcpResponse>(wire).unwrap(),
319 response
320 );
321 }
322 assert_eq!(
323 McpError::new(1, "x").data(Value::Null).data,
324 MaybeUndefined::Null
325 );
326 }
327
328 #[test]
329 fn a_carrier_key_is_required_and_errors_must_be_valid() {
330 for wire in [
331 Value::Null,
332 json!([]),
333 json!({}),
334 json!({"_meta": null}),
335 json!({"unexpected": 1}),
336 json!({"error": null}),
337 json!({"error": 1}),
338 json!({"error": []}),
339 json!({"error": {}}),
340 json!({"error": {"code": 1}}),
341 json!({"error": {"message": "x"}}),
342 json!({"error": {"code": null, "message": "x"}}),
343 json!({"error": {"code": 1, "message": null}}),
344 json!({"error": {"code": 1.5, "message": "x"}}),
345 json!({"error": {"code": "1", "message": "x"}}),
346 ] {
347 assert!(
348 serde_json::from_value::<MessageMcpResponse>(wire.clone()).is_err(),
349 "accepted {wire}"
350 );
351 }
352 }
353
354 #[test]
355 fn result_takes_precedence_when_both_outcome_keys_are_present() {
356 for result in [Value::Null, json!(42), json!({"opaque": [null, true]})] {
357 for error in [
358 Value::Null,
359 json!({"code": 1, "message": "x"}),
360 json!({}),
361 json!({"code": 1, "message": null}),
362 ] {
363 let wire = json!({"result": result, "error": error});
364 assert_eq!(
365 serde_json::from_value::<MessageMcpResponse>(wire).unwrap(),
366 MessageMcpResponse::success(result.clone())
367 );
368 }
369 }
370 }
371
372 #[test]
373 fn unknown_outer_fields_are_ignored_and_inner_extensions_are_preserved() {
374 for wire in [
375 json!({"result": null, "unexpected": {"nested": true}}),
376 json!({"result": {"future": [null, {"error": "opaque"}]}, "unexpected": 1}),
377 json!({"error": {"code": 1, "message": "x", "future": [null, true]}, "unexpected": null}),
378 ] {
379 let parsed: MessageMcpResponse = serde_json::from_value(wire.clone()).unwrap();
380 let mut expected = wire;
381 expected.as_object_mut().unwrap().remove("unexpected");
382 assert_eq!(serde_json::to_value(parsed).unwrap(), expected);
383 }
384 }
385
386 #[test]
387 fn carrier_metadata_is_optional_and_invalid_values_are_salvaged() {
388 for outcome in [
389 json!({"result": null}),
390 json!({"error": {"code": 1, "message": "x"}}),
391 ] {
392 let parsed: MessageMcpResponse = serde_json::from_value(outcome.clone()).unwrap();
393 assert_eq!(serde_json::to_value(parsed).unwrap(), outcome);
394 for meta in [
395 Value::Null,
396 json!(true),
397 json!(1),
398 json!("invalid"),
399 json!([]),
400 ] {
401 let mut wire = outcome.clone();
402 wire["_meta"] = meta;
403 let parsed: MessageMcpResponse = serde_json::from_value(wire).unwrap();
404 assert_eq!(serde_json::to_value(parsed).unwrap(), outcome);
405 }
406 let mut wire = outcome;
407 wire["_meta"] = json!({"extension": [null, true]});
408 let parsed: MessageMcpResponse = serde_json::from_value(wire.clone()).unwrap();
409 assert_eq!(serde_json::to_value(parsed).unwrap(), wire);
410 }
411 let meta = json!({"extension": [null, true]})
412 .as_object()
413 .unwrap()
414 .clone();
415 let response =
416 MessageMcpResponse::success(json!({"_meta": {"inner": true}})).meta(meta.clone());
417 assert_eq!(
418 serde_json::to_value(response).unwrap(),
419 json!({"result": {"_meta": {"inner": true}}, "_meta": meta})
420 );
421 }
422
423 #[cfg(feature = "schemars")]
424 #[test]
425 fn response_schema_requires_a_key_without_closing_outer_fields() {
426 let schema = serde_json::to_value(schemars::schema_for!(MessageMcpResponse)).unwrap();
427 assert!(schema.get("not").is_none());
428 let branches = schema["anyOf"].as_array().unwrap();
429 assert_eq!(branches.len(), 2);
430 assert_eq!(branches[0]["required"], json!(["result"]));
431 assert_eq!(branches[1]["required"], json!(["error"]));
432 for branch in branches {
433 assert_ne!(branch.get("additionalProperties"), Some(&json!(false)));
434 assert_eq!(
435 branch["properties"]["_meta"]["x-deserialize-default-on-error"],
436 true
437 );
438 }
439 assert_ne!(schema.get("additionalProperties"), Some(&json!(false)));
440 }
441}