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]
69#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
70#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
71#[serde(untagged, deny_unknown_fields)]
72#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = "mcp/message")))]
73#[non_exhaustive]
74pub enum MessageMcpResponse {
75 Result {
77 result: Value,
79 #[serde_as(deserialize_as = "DefaultOnError")]
81 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
82 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
83 meta: Option<Map<String, Value>>,
84 },
85 Error {
87 error: McpError,
89 #[serde_as(deserialize_as = "DefaultOnError")]
91 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
92 #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
93 meta: Option<Map<String, Value>>,
94 },
95}
96
97impl MessageMcpResponse {
98 #[must_use]
100 pub fn success(result: Value) -> Self {
101 Self::Result { result, meta: None }
102 }
103
104 #[must_use]
106 pub fn error(error: McpError) -> Self {
107 Self::Error { error, meta: None }
108 }
109
110 #[must_use]
112 pub fn meta(mut self, meta: impl IntoOption<Map<String, Value>>) -> Self {
113 match &mut self {
114 Self::Result { meta: field, .. } | Self::Error { meta: field, .. } => {
115 *field = meta.into_option();
116 }
117 }
118 self
119 }
120}
121
122#[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]
151#[skip_serializing_none]
152#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
153#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
154#[serde(rename_all = "camelCase")]
155#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = MCP_MESSAGE_METHOD_NAME)))]
156#[non_exhaustive]
157pub struct MessageMcpRequest {
158 pub server_id: McpServerAcpId,
160 pub request_id: McpRequestId,
162 pub method: String,
164 #[serde(default)]
168 pub params: Option<serde_json::Map<String, serde_json::Value>>,
169 #[serde_as(deserialize_as = "DefaultOnError")]
175 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
176 #[serde(default)]
177 #[serde(rename = "_meta")]
178 pub meta: Option<Meta>,
179}
180
181impl MessageMcpRequest {
182 #[must_use]
184 pub fn new(
185 server_id: impl Into<McpServerAcpId>,
186 request_id: impl Into<McpRequestId>,
187 method: impl Into<String>,
188 ) -> Self {
189 Self {
190 server_id: server_id.into(),
191 request_id: request_id.into(),
192 method: method.into(),
193 params: None,
194 meta: None,
195 }
196 }
197
198 #[must_use]
202 pub fn params(
203 mut self,
204 params: impl IntoOption<serde_json::Map<String, serde_json::Value>>,
205 ) -> Self {
206 self.params = params.into_option();
207 self
208 }
209
210 #[must_use]
216 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
217 self.meta = meta.into_option();
218 self
219 }
220}
221
222#[serde_as]
231#[skip_serializing_none]
232#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
233#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
234#[serde(rename_all = "camelCase")]
235#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = MCP_MESSAGE_METHOD_NAME)))]
236#[non_exhaustive]
237pub struct MessageMcpNotification {
238 pub server_id: McpServerAcpId,
240 pub request_id: McpRequestId,
242 pub method: String,
244 #[serde(default)]
248 pub params: Option<serde_json::Map<String, serde_json::Value>>,
249 #[serde_as(deserialize_as = "DefaultOnError")]
255 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
256 #[serde(default)]
257 #[serde(rename = "_meta")]
258 pub meta: Option<Meta>,
259}
260
261impl MessageMcpNotification {
262 #[must_use]
264 pub fn new(
265 server_id: impl Into<McpServerAcpId>,
266 request_id: impl Into<McpRequestId>,
267 method: impl Into<String>,
268 ) -> Self {
269 Self {
270 server_id: server_id.into(),
271 request_id: request_id.into(),
272 method: method.into(),
273 params: None,
274 meta: None,
275 }
276 }
277
278 #[must_use]
282 pub fn params(
283 mut self,
284 params: impl IntoOption<serde_json::Map<String, serde_json::Value>>,
285 ) -> Self {
286 self.params = params.into_option();
287 self
288 }
289
290 #[must_use]
296 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
297 self.meta = meta.into_option();
298 self
299 }
300}
301
302pub(crate) const MCP_MESSAGE_METHOD_NAME: &str = "mcp/message";
304
305#[cfg(test)]
306mod tests {
307 use serde_json::{Value, json};
308
309 use super::{McpError, MessageMcpResponse};
310 use crate::MaybeUndefined;
311
312 #[test]
313 fn result_is_opaque_and_present_even_when_null() {
314 for result in [
315 Value::Null,
316 json!(false),
317 json!(42),
318 json!("opaque"),
319 json!([null, 1]),
320 json!({"resultType": "future", "unknown": {"value": true}}),
321 ] {
322 let response = MessageMcpResponse::success(result.clone());
323 let wire = json!({"result": result});
324 assert_eq!(serde_json::to_value(&response).unwrap(), wire);
325 assert_eq!(
326 serde_json::from_value::<MessageMcpResponse>(wire).unwrap(),
327 response
328 );
329 }
330 }
331
332 #[test]
333 fn error_round_trips_data_and_extensions_without_acp_translation() {
334 for data in [
335 MaybeUndefined::Undefined,
336 MaybeUndefined::Null,
337 MaybeUndefined::Value(json!({"arbitrary": [1, null]})),
338 ] {
339 let mut error = McpError::new(-32000, "inner error");
340 error.data = data.clone();
341 error.extra.insert("future".into(), json!({"key": 1}));
342 let response = MessageMcpResponse::error(error);
343 let wire = serde_json::to_value(&response).unwrap();
344 assert_eq!(wire["error"]["code"], -32000);
345 assert_eq!(wire["error"].get("data").is_some(), !data.is_undefined());
346 assert_eq!(wire["error"]["future"], json!({"key": 1}));
347 assert_eq!(
348 serde_json::from_value::<MessageMcpResponse>(wire).unwrap(),
349 response
350 );
351 }
352 assert_eq!(
353 McpError::new(1, "x").data(Value::Null).data,
354 MaybeUndefined::Null
355 );
356 }
357
358 #[test]
359 fn only_one_non_null_carrier_key_is_valid() {
360 for wire in [
361 Value::Null,
362 json!({}),
363 json!({"_meta": null}),
364 json!({"result": 1, "error": {"code": 1, "message": "x"}}),
365 json!({"result": 1, "error": null}),
366 json!({"error": null}),
367 json!({"error": 1}),
368 json!({"error": {}}),
369 json!({"error": {"code": null, "message": "x"}}),
370 json!({"error": {"code": 1, "message": null}}),
371 json!({"error": {"code": 1.5, "message": "x"}}),
372 json!({"unexpected": 1, "result": 1}),
373 ] {
374 assert!(
375 serde_json::from_value::<MessageMcpResponse>(wire.clone()).is_err(),
376 "accepted {wire}"
377 );
378 }
379 }
380
381 #[test]
382 fn carrier_metadata_is_optional_and_null_means_absent() {
383 for wire in [
384 json!({"result": null, "_meta": null}),
385 json!({"error": {"code": 1, "message": "x"}, "_meta": null}),
386 ] {
387 let parsed: MessageMcpResponse = serde_json::from_value(wire).unwrap();
388 assert!(serde_json::to_value(parsed).unwrap().get("_meta").is_none());
389 }
390 let meta = json!({"extension": [null, true]})
391 .as_object()
392 .unwrap()
393 .clone();
394 let response =
395 MessageMcpResponse::success(json!({"_meta": {"inner": true}})).meta(meta.clone());
396 assert_eq!(
397 serde_json::to_value(response).unwrap(),
398 json!({"result": {"_meta": {"inner": true}}, "_meta": meta})
399 );
400 }
401}