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))]
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
129#[serde(transparent)]
130#[from(Arc<str>, String, &'static str)]
131#[non_exhaustive]
132pub struct McpRequestId(pub Arc<str>);
133
134impl McpRequestId {
135 #[must_use]
137 pub fn new(id: impl Into<Arc<str>>) -> Self {
138 Self(id.into())
139 }
140}
141
142#[serde_as]
146#[skip_serializing_none]
147#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
149#[serde(rename_all = "camelCase")]
150#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "client", "x-method" = MCP_MESSAGE_METHOD_NAME)))]
151#[non_exhaustive]
152pub struct MessageMcpRequest {
153 pub server_id: McpServerAcpId,
155 pub request_id: McpRequestId,
157 pub method: String,
159 #[serde(default)]
161 pub params: Option<serde_json::Map<String, serde_json::Value>>,
162 #[serde_as(deserialize_as = "DefaultOnError")]
164 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
165 #[serde(default)]
166 #[serde(rename = "_meta")]
167 pub meta: Option<Meta>,
168}
169
170impl MessageMcpRequest {
171 #[must_use]
173 pub fn new(
174 server_id: impl Into<McpServerAcpId>,
175 request_id: impl Into<McpRequestId>,
176 method: impl Into<String>,
177 ) -> Self {
178 Self {
179 server_id: server_id.into(),
180 request_id: request_id.into(),
181 method: method.into(),
182 params: None,
183 meta: None,
184 }
185 }
186
187 #[must_use]
189 pub fn params(
190 mut self,
191 params: impl IntoOption<serde_json::Map<String, serde_json::Value>>,
192 ) -> Self {
193 self.params = params.into_option();
194 self
195 }
196
197 #[must_use]
199 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
200 self.meta = meta.into_option();
201 self
202 }
203}
204
205#[serde_as]
210#[skip_serializing_none]
211#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
212#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
213#[serde(rename_all = "camelCase")]
214#[cfg_attr(feature = "schemars", schemars(extend("x-side" = "agent", "x-method" = MCP_MESSAGE_METHOD_NAME)))]
215#[non_exhaustive]
216pub struct MessageMcpNotification {
217 pub server_id: McpServerAcpId,
219 pub request_id: McpRequestId,
221 pub method: String,
223 #[serde(default)]
225 pub params: Option<serde_json::Map<String, serde_json::Value>>,
226 #[serde_as(deserialize_as = "DefaultOnError")]
228 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
229 #[serde(default)]
230 #[serde(rename = "_meta")]
231 pub meta: Option<Meta>,
232}
233
234impl MessageMcpNotification {
235 #[must_use]
237 pub fn new(
238 server_id: impl Into<McpServerAcpId>,
239 request_id: impl Into<McpRequestId>,
240 method: impl Into<String>,
241 ) -> Self {
242 Self {
243 server_id: server_id.into(),
244 request_id: request_id.into(),
245 method: method.into(),
246 params: None,
247 meta: None,
248 }
249 }
250
251 #[must_use]
253 pub fn params(
254 mut self,
255 params: impl IntoOption<serde_json::Map<String, serde_json::Value>>,
256 ) -> Self {
257 self.params = params.into_option();
258 self
259 }
260
261 #[must_use]
263 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
264 self.meta = meta.into_option();
265 self
266 }
267}
268
269pub(crate) const MCP_MESSAGE_METHOD_NAME: &str = "mcp/message";
271
272#[cfg(test)]
273mod tests {
274 use serde_json::{Value, json};
275
276 use super::{McpError, MessageMcpResponse};
277 use crate::MaybeUndefined;
278
279 #[test]
280 fn result_is_opaque_and_present_even_when_null() {
281 for result in [
282 Value::Null,
283 json!(false),
284 json!(42),
285 json!("opaque"),
286 json!([null, 1]),
287 json!({"resultType": "future", "unknown": {"value": true}}),
288 ] {
289 let response = MessageMcpResponse::success(result.clone());
290 let wire = json!({"result": result});
291 assert_eq!(serde_json::to_value(&response).unwrap(), wire);
292 assert_eq!(
293 serde_json::from_value::<MessageMcpResponse>(wire).unwrap(),
294 response
295 );
296 }
297 }
298
299 #[test]
300 fn error_round_trips_data_and_extensions_without_acp_translation() {
301 for data in [
302 MaybeUndefined::Undefined,
303 MaybeUndefined::Null,
304 MaybeUndefined::Value(json!({"arbitrary": [1, null]})),
305 ] {
306 let mut error = McpError::new(-32000, "inner error");
307 error.data = data.clone();
308 error.extra.insert("future".into(), json!({"key": 1}));
309 let response = MessageMcpResponse::error(error);
310 let wire = serde_json::to_value(&response).unwrap();
311 assert_eq!(wire["error"]["code"], -32000);
312 assert_eq!(wire["error"].get("data").is_some(), !data.is_undefined());
313 assert_eq!(wire["error"]["future"], json!({"key": 1}));
314 assert_eq!(
315 serde_json::from_value::<MessageMcpResponse>(wire).unwrap(),
316 response
317 );
318 }
319 assert_eq!(
320 McpError::new(1, "x").data(Value::Null).data,
321 MaybeUndefined::Null
322 );
323 }
324
325 #[test]
326 fn only_one_non_null_carrier_key_is_valid() {
327 for wire in [
328 Value::Null,
329 json!({}),
330 json!({"_meta": null}),
331 json!({"result": 1, "error": {"code": 1, "message": "x"}}),
332 json!({"result": 1, "error": null}),
333 json!({"error": null}),
334 json!({"error": 1}),
335 json!({"error": {}}),
336 json!({"error": {"code": null, "message": "x"}}),
337 json!({"error": {"code": 1, "message": null}}),
338 json!({"error": {"code": 1.5, "message": "x"}}),
339 json!({"unexpected": 1, "result": 1}),
340 ] {
341 assert!(
342 serde_json::from_value::<MessageMcpResponse>(wire.clone()).is_err(),
343 "accepted {wire}"
344 );
345 }
346 }
347
348 #[test]
349 fn carrier_metadata_is_optional_and_null_means_absent() {
350 for wire in [
351 json!({"result": null, "_meta": null}),
352 json!({"error": {"code": 1, "message": "x"}, "_meta": null}),
353 ] {
354 let parsed: MessageMcpResponse = serde_json::from_value(wire).unwrap();
355 assert!(serde_json::to_value(parsed).unwrap().get("_meta").is_none());
356 }
357 let meta = json!({"extension": [null, true]})
358 .as_object()
359 .unwrap()
360 .clone();
361 let response =
362 MessageMcpResponse::success(json!({"_meta": {"inner": true}})).meta(meta.clone());
363 assert_eq!(
364 serde_json::to_value(response).unwrap(),
365 json!({"result": {"_meta": {"inner": true}}, "_meta": meta})
366 );
367 }
368}