1use std::future::Future;
15use std::pin::Pin;
16use std::sync::{Arc, RwLock};
17use std::task::{Context, Poll};
18
19use tower::{Layer, ServiceExt};
20use tower_service::Service;
21
22use crate::error::{Error, JsonRpcError, Result};
23use crate::inspection::{
24 McpDirection, McpInspection, McpInspectionError, McpInspectionErrorKind, McpInspector,
25 McpProtocolRevision,
26};
27use crate::protocol::{
28 JsonRpcMessage, JsonRpcRequest, JsonRpcResponse, JsonRpcResponseMessage, McpRequest, ResultType,
29};
30use crate::router::{Extensions, RouterRequest, RouterResponse};
31use crate::{ProtocolSupport, ProtocolSupportError};
32
33#[derive(Debug, Clone, Copy, Default)]
52pub struct JsonRpcLayer {
53 _priv: (),
54}
55
56impl JsonRpcLayer {
57 pub fn new() -> Self {
59 Self { _priv: () }
60 }
61}
62
63impl<S> Layer<S> for JsonRpcLayer {
64 type Service = JsonRpcService<S>;
65
66 fn layer(&self, inner: S) -> Self::Service {
67 JsonRpcService::new(inner)
68 }
69}
70
71pub struct JsonRpcService<S> {
90 inner: S,
91 extensions: Extensions,
92 protocol_support: ProtocolSupport,
93 negotiated_revision: Arc<RwLock<Option<McpProtocolRevision>>>,
94}
95
96impl<S> JsonRpcService<S> {
97 pub fn new(inner: S) -> Self {
99 Self {
100 inner,
101 extensions: Extensions::new(),
102 protocol_support: ProtocolSupport::default(),
103 negotiated_revision: Arc::new(RwLock::new(None)),
104 }
105 }
106
107 pub fn with_extensions(mut self, ext: Extensions) -> Self {
112 self.extensions = ext;
113 self
114 }
115
116 pub fn protocol_support(mut self, support: ProtocolSupport) -> Self {
122 self.protocol_support = support;
123 self
124 }
125
126 pub fn protocol_versions<I, V>(
128 self,
129 versions: I,
130 ) -> std::result::Result<Self, ProtocolSupportError>
131 where
132 I: IntoIterator<Item = V>,
133 V: Into<String>,
134 {
135 Ok(self.protocol_support(ProtocolSupport::try_new(versions)?))
136 }
137
138 pub(crate) fn configured_protocol_support(&self) -> &ProtocolSupport {
139 &self.protocol_support
140 }
141
142 pub(crate) fn inspect_incoming_value(
149 &self,
150 value: &serde_json::Value,
151 direction: McpDirection,
152 ) -> std::result::Result<McpInspection, JsonRpcError> {
153 let protocol_support = self
154 .extensions
155 .get::<ProtocolSupport>()
156 .unwrap_or(&self.protocol_support);
157 let revision = self.resolve_revision(value, protocol_support)?;
158 inspect_runtime_value(value, revision, protocol_support, direction)
159 }
160
161 #[cfg_attr(not(feature = "http"), allow(dead_code))]
163 pub(crate) fn with_negotiated_protocol_version(self, version: &str) -> Self {
164 if let Ok(revision) = version.parse()
165 && let Ok(mut selected) = self.negotiated_revision.write()
166 {
167 *selected = Some(revision);
168 }
169 self
170 }
171
172 fn resolve_revision(
173 &self,
174 value: &serde_json::Value,
175 protocol_support: &ProtocolSupport,
176 ) -> std::result::Result<McpProtocolRevision, JsonRpcError> {
177 if let Some(items) = value.as_array() {
178 let mut declared = items.iter().filter_map(|item| {
179 item.pointer("/params/_meta/io.modelcontextprotocol~1protocolVersion")
180 .and_then(serde_json::Value::as_str)
181 });
182 if let Some(version) = declared.next() {
183 if declared.any(|candidate| candidate != version) {
184 return Err(JsonRpcError::invalid_request(
185 "A JSON-RPC batch cannot mix MCP protocol revisions",
186 ));
187 }
188 return allowed_revision(version, protocol_support);
189 }
190 }
191
192 if let Some(version) = value
193 .pointer("/params/_meta/io.modelcontextprotocol~1protocolVersion")
194 .and_then(serde_json::Value::as_str)
195 {
196 return allowed_revision(version, protocol_support);
197 }
198
199 if value.get("method").and_then(serde_json::Value::as_str) == Some("initialize") {
200 let requested = value
201 .pointer("/params/protocolVersion")
202 .and_then(serde_json::Value::as_str);
203 let selected = requested
204 .filter(|version| {
205 crate::protocol::SUPPORTED_PROTOCOL_VERSIONS.contains(version)
206 && protocol_support.contains(version)
207 })
208 .or_else(|| {
209 protocol_support.versions().iter().find_map(|version| {
210 crate::protocol::SUPPORTED_PROTOCOL_VERSIONS
211 .contains(&version.as_str())
212 .then_some(version.as_str())
213 })
214 })
215 .ok_or_else(|| {
216 JsonRpcError::unsupported_protocol_version(
217 requested.unwrap_or("unknown"),
218 protocol_support.versions().iter().map(String::as_str),
219 )
220 })?;
221 return allowed_revision(selected, protocol_support);
222 }
223
224 if let Some(revision) = self
225 .extensions
226 .get::<McpProtocolRevision>()
227 .copied()
228 .or_else(|| {
229 self.negotiated_revision
230 .read()
231 .ok()
232 .and_then(|revision| *revision)
233 })
234 {
235 if protocol_support.contains(revision.as_str()) {
236 return Ok(revision);
237 }
238 return Err(JsonRpcError::unsupported_protocol_version(
239 revision.as_str(),
240 protocol_support.versions().iter().map(String::as_str),
241 ));
242 }
243
244 if protocol_support.versions().len() == 1 {
245 return allowed_revision(protocol_support.preferred(), protocol_support);
246 }
247
248 if value.is_array() {
249 return Err(JsonRpcError::invalid_request(
250 "Cannot determine the exact MCP revision for a batch before protocol negotiation",
251 ));
252 }
253
254 let provisional = protocol_support
259 .versions()
260 .iter()
261 .find(|version| {
262 crate::protocol::SUPPORTED_PROTOCOL_VERSIONS.contains(&version.as_str())
263 })
264 .ok_or_else(|| {
265 JsonRpcError::invalid_request(
266 "Request does not declare an MCP revision and no legacy revision is enabled",
267 )
268 })?;
269 allowed_revision(provisional, protocol_support)
270 }
271
272 #[cfg(feature = "stateless")]
280 pub(crate) fn validate_request_protocol(
281 &self,
282 req: &JsonRpcRequest,
283 ) -> std::result::Result<Option<String>, JsonRpcError> {
284 req.validate()?;
285 let value = serde_json::to_value(req)
286 .map_err(|error| JsonRpcError::invalid_request(error.to_string()))?;
287 self.inspect_incoming_value(&value, McpDirection::ClientToServer)?;
288 let mut extensions = self.extensions.clone();
289 let protocol_support = extensions
290 .get::<ProtocolSupport>()
291 .cloned()
292 .unwrap_or_else(|| self.protocol_support.clone());
293 prepare_modern_request(req, &mut extensions, &protocol_support)
294 }
295
296 pub async fn call_single(&mut self, req: JsonRpcRequest) -> Result<JsonRpcResponse>
298 where
299 S: Service<RouterRequest, Response = RouterResponse, Error = std::convert::Infallible>
300 + Clone
301 + Send
302 + 'static,
303 S::Future: Send,
304 {
305 process_single_request(
306 self.inner.clone(),
307 req,
308 self.extensions.clone(),
309 self.protocol_support.clone(),
310 self.negotiated_revision.clone(),
311 )
312 .await
313 }
314
315 pub async fn call_batch(
321 &mut self,
322 requests: Vec<JsonRpcRequest>,
323 ) -> Result<Vec<JsonRpcResponse>>
324 where
325 S: Service<RouterRequest, Response = RouterResponse, Error = std::convert::Infallible>
326 + Clone
327 + Send
328 + 'static,
329 S::Future: Send,
330 {
331 if requests.is_empty() {
332 return Err(Error::JsonRpc(JsonRpcError::invalid_request(
333 "Empty batch request",
334 )));
335 }
336
337 let value = serde_json::to_value(JsonRpcMessage::Batch(requests.clone()))
338 .map_err(Error::Serialization)?;
339 self.inspect_incoming_value(&value, McpDirection::ClientToServer)
340 .map_err(Error::JsonRpc)?;
341
342 let futures: Vec<_> = requests
344 .into_iter()
345 .map(|req| {
346 let inner = self.inner.clone();
347 let extensions = self.extensions.clone();
348 let protocol_support = self.protocol_support.clone();
349 let negotiated_revision = self.negotiated_revision.clone();
350 let req_id = req.id.clone();
351 async move {
352 match process_single_request(
353 inner,
354 req,
355 extensions,
356 protocol_support,
357 negotiated_revision,
358 )
359 .await
360 {
361 Ok(resp) => resp,
362 Err(e) => {
363 JsonRpcResponse::error(
365 Some(req_id),
366 JsonRpcError::internal_error(e.to_string()),
367 )
368 }
369 }
370 }
371 })
372 .collect();
373
374 let results: Vec<JsonRpcResponse> = futures::future::join_all(futures).await;
375
376 Ok(results)
378 }
379
380 pub async fn call_message(&mut self, msg: JsonRpcMessage) -> Result<JsonRpcResponseMessage>
382 where
383 S: Service<RouterRequest, Response = RouterResponse, Error = std::convert::Infallible>
384 + Clone
385 + Send
386 + 'static,
387 S::Future: Send,
388 {
389 match msg {
390 JsonRpcMessage::Single(req) => {
391 let response = self.call_single(req).await?;
392 Ok(JsonRpcResponseMessage::Single(response))
393 }
394 JsonRpcMessage::Batch(requests) => match self.call_batch(requests).await {
395 Ok(responses) => Ok(JsonRpcResponseMessage::Batch(responses)),
396 Err(Error::JsonRpc(error)) => Ok(JsonRpcResponseMessage::Single(
397 JsonRpcResponse::error(None, error),
398 )),
399 Err(error) => Err(error),
400 },
401 _ => Ok(JsonRpcResponseMessage::Single(JsonRpcResponse::error(
402 None,
403 JsonRpcError::invalid_request("Unsupported message type"),
404 ))),
405 }
406 }
407}
408
409impl<S> Clone for JsonRpcService<S>
410where
411 S: Clone,
412{
413 fn clone(&self) -> Self {
414 Self {
415 inner: self.inner.clone(),
416 extensions: self.extensions.clone(),
417 protocol_support: self.protocol_support.clone(),
418 negotiated_revision: self.negotiated_revision.clone(),
419 }
420 }
421}
422
423impl<S> Service<JsonRpcRequest> for JsonRpcService<S>
424where
425 S: Service<RouterRequest, Response = RouterResponse, Error = std::convert::Infallible>
426 + Clone
427 + Send
428 + 'static,
429 S::Future: Send,
430{
431 type Response = JsonRpcResponse;
432 type Error = Error;
433 type Future =
434 Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
435
436 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
437 self.inner.poll_ready(cx).map_err(|_| unreachable!())
438 }
439
440 fn call(&mut self, req: JsonRpcRequest) -> Self::Future {
441 let mut service = self.clone();
442 Box::pin(async move { service.call_single(req).await })
443 }
444}
445
446impl<S> Service<JsonRpcMessage> for JsonRpcService<S>
448where
449 S: Service<RouterRequest, Response = RouterResponse, Error = std::convert::Infallible>
450 + Clone
451 + Send
452 + 'static,
453 S::Future: Send,
454{
455 type Response = JsonRpcResponseMessage;
456 type Error = Error;
457 type Future =
458 Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
459
460 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
461 self.inner.poll_ready(cx).map_err(|_| unreachable!())
462 }
463
464 fn call(&mut self, msg: JsonRpcMessage) -> Self::Future {
465 let mut service = self.clone();
466 Box::pin(async move { service.call_message(msg).await })
467 }
468}
469
470async fn process_single_request<S>(
472 inner: S,
473 req: JsonRpcRequest,
474 mut extensions: Extensions,
475 configured_protocol_support: ProtocolSupport,
476 negotiated_revision: Arc<RwLock<Option<McpProtocolRevision>>>,
477) -> std::result::Result<JsonRpcResponse, Error>
478where
479 S: Service<RouterRequest, Response = RouterResponse, Error = std::convert::Infallible>
480 + Clone
481 + Send
482 + 'static,
483 S::Future: Send,
484{
485 if let Err(e) = req.validate() {
487 return Ok(JsonRpcResponse::error(Some(req.id), e));
488 }
489
490 let method = req.method.clone();
491 #[cfg(feature = "stateless")]
492 let request_id = req.id.clone();
493 let protocol_support = extensions
494 .get::<ProtocolSupport>()
495 .cloned()
496 .unwrap_or(configured_protocol_support);
497 extensions.insert(protocol_support.clone());
498
499 let inspection_service = JsonRpcService {
500 inner: inner.clone(),
501 extensions: extensions.clone(),
502 protocol_support: protocol_support.clone(),
503 negotiated_revision: negotiated_revision.clone(),
504 };
505 let request_value = serde_json::to_value(&req).map_err(Error::Serialization)?;
506 if let Err(error) =
507 inspection_service.inspect_incoming_value(&request_value, McpDirection::ClientToServer)
508 {
509 return Ok(JsonRpcResponse::error(Some(req.id), error));
510 }
511
512 #[cfg(feature = "stateless")]
513 let protocol_version = match prepare_modern_request(&req, &mut extensions, &protocol_support) {
514 Ok(version) => version,
515 Err(error) => return Ok(JsonRpcResponse::error(Some(request_id), error)),
516 };
517 #[cfg(not(feature = "stateless"))]
518 let protocol_version: Option<String> = None;
519
520 let mcp_request = match McpRequest::from_jsonrpc(&req) {
522 Ok(r) => r,
523 Err(e) => {
524 return Ok(JsonRpcResponse::error(
525 Some(req.id),
526 JsonRpcError::invalid_params(e.to_string()),
527 ));
528 }
529 };
530
531 let router_req = RouterRequest {
533 id: req.id,
534 inner: mcp_request,
535 extensions,
536 };
537
538 let response = inner.oneshot(router_req).await.unwrap(); let mut response = response.into_jsonrpc();
543 if method == "initialize"
544 && let JsonRpcResponse::Result(result) = &response
545 && let Some(version) = result
546 .result
547 .get("protocolVersion")
548 .and_then(serde_json::Value::as_str)
549 && protocol_support.contains(version)
550 && let Ok(revision) = version.parse::<McpProtocolRevision>()
551 && let Ok(mut selected) = negotiated_revision.write()
552 {
553 *selected = Some(revision);
554 }
555 if let Some(version) = protocol_version.as_deref() {
556 apply_protocol_result_fields(&mut response, &method, version);
557 }
558 Ok(response)
559}
560
561fn allowed_revision(
562 version: &str,
563 protocol_support: &ProtocolSupport,
564) -> std::result::Result<McpProtocolRevision, JsonRpcError> {
565 if !protocol_support.contains(version) {
566 return Err(JsonRpcError::unsupported_protocol_version(
567 version,
568 protocol_support.versions().iter().map(String::as_str),
569 ));
570 }
571 version.parse().map_err(|_| {
572 JsonRpcError::unsupported_protocol_version(
573 version,
574 protocol_support.versions().iter().map(String::as_str),
575 )
576 })
577}
578
579pub(crate) fn inspect_runtime_value(
582 value: &serde_json::Value,
583 revision: McpProtocolRevision,
584 protocol_support: &ProtocolSupport,
585 direction: McpDirection,
586) -> std::result::Result<McpInspection, JsonRpcError> {
587 if !protocol_support.contains(revision.as_str()) {
588 return Err(JsonRpcError::unsupported_protocol_version(
589 revision.as_str(),
590 protocol_support.versions().iter().map(String::as_str),
591 ));
592 }
593 McpInspector::for_revision(revision)
594 .inspect(value, Some(direction))
595 .map_err(inspection_error_to_json_rpc)
596}
597
598fn inspection_error_to_json_rpc(error: McpInspectionError) -> JsonRpcError {
599 match error.kind() {
600 McpInspectionErrorKind::MissingParams | McpInspectionErrorKind::InvalidParams => {
601 JsonRpcError::invalid_params(error.to_string())
602 }
603 McpInspectionErrorKind::UnsupportedProfile => JsonRpcError::unsupported_protocol_version(
604 error.revision().unwrap_or("unknown"),
605 crate::inspection::MCP_INSPECTION_PROFILES.iter().copied(),
606 ),
607 McpInspectionErrorKind::JsonRpc
608 | McpInspectionErrorKind::BatchUnavailable
609 | McpInspectionErrorKind::InitializeInBatch
610 | McpInspectionErrorKind::MessageKindMismatch
611 | McpInspectionErrorKind::DirectionMismatch => {
612 JsonRpcError::invalid_request(error.to_string())
613 }
614 _ => JsonRpcError::invalid_request(error.to_string()),
615 }
616}
617
618#[cfg(feature = "stateless")]
625fn prepare_modern_request(
626 req: &JsonRpcRequest,
627 extensions: &mut Extensions,
628 protocol_support: &ProtocolSupport,
629) -> std::result::Result<Option<String>, JsonRpcError> {
630 let Some(params) = req.params.as_ref() else {
631 return Ok(None);
632 };
633 let Some(meta_value) = params.as_object().and_then(|params| params.get("_meta")) else {
634 return Ok(None);
635 };
636 let claims_modern = meta_value
637 .as_object()
638 .is_some_and(|meta| meta.contains_key("io.modelcontextprotocol/protocolVersion"));
639 if !claims_modern {
640 return Ok(None);
641 }
642
643 crate::protocol::validate_meta_object(meta_value)
644 .map_err(|error| JsonRpcError::invalid_params(error.to_string()))?;
645 let meta_object = meta_value
646 .as_object()
647 .expect("validate_meta_object accepted a JSON object");
648 let protocol_version = meta_object
649 .get("io.modelcontextprotocol/protocolVersion")
650 .and_then(serde_json::Value::as_str)
651 .ok_or_else(|| {
652 JsonRpcError::invalid_params(
653 "Missing or invalid _meta.io.modelcontextprotocol/protocolVersion",
654 )
655 })?;
656 let client_capabilities = meta_object
657 .get("io.modelcontextprotocol/clientCapabilities")
658 .ok_or_else(|| {
659 JsonRpcError::invalid_params("Missing _meta.io.modelcontextprotocol/clientCapabilities")
660 })?;
661 if !client_capabilities.is_object()
662 || serde_json::from_value::<crate::protocol::ClientCapabilities>(
663 client_capabilities.clone(),
664 )
665 .is_err()
666 {
667 return Err(JsonRpcError::invalid_params(
668 "Invalid _meta.io.modelcontextprotocol/clientCapabilities",
669 ));
670 }
671 if !protocol_support.contains(protocol_version) {
672 return Err(JsonRpcError::unsupported_protocol_version(
673 protocol_version,
674 protocol_support.versions().iter().map(String::as_str),
675 ));
676 }
677 if protocol_version == crate::protocol::PROTOCOL_VERSION_2026_07_28
678 && is_removed_modern_method(&req.method)
679 {
680 return Err(JsonRpcError::method_not_found(&req.method));
681 }
682
683 let meta: crate::stateless::StatelessRequestMeta =
684 serde_json::from_value(meta_value.clone())
685 .map_err(|error| JsonRpcError::invalid_params(error.to_string()))?;
686 extensions.insert(meta);
687 Ok(Some(protocol_version.to_string()))
688}
689
690#[cfg(feature = "stateless")]
692fn is_removed_modern_method(method: &str) -> bool {
693 matches!(
694 method,
695 "initialize"
696 | "notifications/initialized"
697 | "ping"
698 | "logging/setLevel"
699 | "resources/subscribe"
700 | "resources/unsubscribe"
701 | "notifications/roots/list_changed"
702 )
703}
704
705pub(crate) fn apply_protocol_result_fields(
708 response: &mut JsonRpcResponse,
709 method: &str,
710 protocol_version: &str,
711) {
712 if protocol_version != crate::protocol::PROTOCOL_VERSION_2026_07_28 {
713 return;
714 }
715
716 let JsonRpcResponse::Result(result) = response else {
717 return;
718 };
719 ResultType::Complete.stamp_into(&mut result.result, protocol_version);
720
721 if !is_cacheable_result_method(method) {
722 return;
723 }
724 let Some(object) = result.result.as_object_mut() else {
725 return;
726 };
727 object
728 .entry("ttlMs")
729 .or_insert_with(|| serde_json::Value::Number(0.into()));
730 object
731 .entry("cacheScope")
732 .or_insert_with(|| serde_json::Value::String("private".to_string()));
733}
734
735fn is_cacheable_result_method(method: &str) -> bool {
736 matches!(
737 method,
738 "server/discover"
739 | "tools/list"
740 | "prompts/list"
741 | "resources/list"
742 | "resources/read"
743 | "resources/templates/list"
744 )
745}
746
747#[cfg(test)]
748mod tests {
749 use super::*;
750 use crate::McpRouter;
751 use crate::tool::ToolBuilder;
752 use schemars::JsonSchema;
753 use serde::Deserialize;
754
755 #[derive(Debug, Deserialize, JsonSchema)]
756 struct AddInput {
757 a: i32,
758 b: i32,
759 }
760
761 fn create_test_router() -> McpRouter {
762 let add_tool = ToolBuilder::new("add")
763 .description("Add two numbers")
764 .handler(|input: AddInput| async move {
765 Ok(crate::CallToolResult::text(format!(
766 "{}",
767 input.a + input.b
768 )))
769 })
770 .build();
771
772 McpRouter::new()
773 .server_info("test-server", "1.0.0")
774 .tool(add_tool)
775 }
776
777 #[tokio::test]
778 async fn test_jsonrpc_service() {
779 let router = create_test_router();
780 let mut service = JsonRpcService::new(router.clone());
781
782 let init_req = JsonRpcRequest::new(1, "initialize").with_params(serde_json::json!({
784 "protocolVersion": "2025-11-25",
785 "capabilities": {},
786 "clientInfo": { "name": "test", "version": "1.0" }
787 }));
788 let resp = service.call_single(init_req).await.unwrap();
789 assert!(matches!(resp, JsonRpcResponse::Result(_)));
790
791 router.handle_notification(crate::protocol::McpNotification::Initialized);
793
794 let req = JsonRpcRequest::new(2, "tools/list").with_params(serde_json::json!({}));
796 let resp = service.call_single(req).await.unwrap();
797
798 match resp {
799 JsonRpcResponse::Result(r) => {
800 let tools = r.result.get("tools").unwrap().as_array().unwrap();
801 assert_eq!(tools.len(), 1);
802 }
803 JsonRpcResponse::Error(e) => panic!("Expected result, got error: {:?}", e),
804 _ => panic!("unexpected response variant"),
805 }
806 }
807
808 #[tokio::test]
809 async fn test_batch_request() {
810 let router = create_test_router();
811 let mut service = JsonRpcService::new(router.clone());
812
813 let init_req = JsonRpcRequest::new(1, "initialize").with_params(serde_json::json!({
815 "protocolVersion": "2025-03-26",
816 "capabilities": {},
817 "clientInfo": { "name": "test", "version": "1.0" }
818 }));
819 service.call_single(init_req).await.unwrap();
820 router.handle_notification(crate::protocol::McpNotification::Initialized);
821
822 let requests = vec![
824 JsonRpcRequest::new(2, "tools/list").with_params(serde_json::json!({})),
825 JsonRpcRequest::new(3, "tools/call").with_params(serde_json::json!({
826 "name": "add",
827 "arguments": { "a": 1, "b": 2 }
828 })),
829 ];
830
831 let responses = service.call_batch(requests).await.unwrap();
832 assert_eq!(responses.len(), 2);
833 }
834
835 #[cfg(feature = "stateless")]
836 #[tokio::test]
837 async fn modern_batch_is_rejected_by_exact_profile() {
838 let router = create_test_router();
839 let mut service = JsonRpcService::new(router);
840 let final_request = JsonRpcRequest::new(1, "tools/list").with_params(serde_json::json!({
841 "_meta": {
842 "io.modelcontextprotocol/protocolVersion":
843 crate::protocol::PROTOCOL_VERSION_2026_07_28,
844 "io.modelcontextprotocol/clientCapabilities": {}
845 }
846 }));
847 let legacy_request =
848 JsonRpcRequest::new(2, "tools/list").with_params(serde_json::json!({}));
849
850 let error = service
851 .call_batch(vec![final_request, legacy_request])
852 .await
853 .unwrap_err();
854 let Error::JsonRpc(error) = error else {
855 panic!("final batch should fail with a JSON-RPC error");
856 };
857 assert_eq!(error.code, -32600);
858 assert!(
859 error
860 .message
861 .contains("does not permit top-level JSON-RPC batches")
862 );
863 }
864
865 #[cfg(feature = "stateless")]
866 #[tokio::test]
867 async fn modern_request_requires_client_capabilities() {
868 let router = create_test_router();
869 let mut service = JsonRpcService::new(router);
870 let request = JsonRpcRequest::new(1, "server/discover").with_params(serde_json::json!({
871 "_meta": {
872 "io.modelcontextprotocol/protocolVersion":
873 crate::protocol::PROTOCOL_VERSION_2026_07_28
874 }
875 }));
876
877 let response = service.call_single(request).await.unwrap();
878 let JsonRpcResponse::Error(response) = response else {
879 panic!("missing clientCapabilities must be rejected");
880 };
881 assert_eq!(response.error.code, -32602);
882 assert!(response.error.message.contains("clientCapabilities"));
883 }
884
885 #[cfg(feature = "stateless")]
886 #[tokio::test]
887 async fn final_only_policy_never_negotiates_final_via_initialize() {
888 let router = create_test_router();
889 let mut service = JsonRpcService::new(router).protocol_support(
890 ProtocolSupport::try_new([crate::protocol::PROTOCOL_VERSION_2026_07_28]).unwrap(),
891 );
892 let request = JsonRpcRequest::new(1, "initialize").with_params(serde_json::json!({
893 "protocolVersion": "2025-11-25",
894 "capabilities": {},
895 "clientInfo": {"name": "legacy-client", "version": "1.0.0"}
896 }));
897
898 let response = service.call_single(request).await.unwrap();
899 let JsonRpcResponse::Error(response) = response else {
900 panic!("final-only policy must reject the removed initialize lifecycle");
901 };
902 assert_eq!(response.error.code, -32022);
903 assert_eq!(
904 response.error.data.unwrap()["supported"],
905 serde_json::json!([crate::protocol::PROTOCOL_VERSION_2026_07_28])
906 );
907 }
908
909 #[tokio::test]
910 async fn test_empty_batch_error() {
911 let router = create_test_router();
912 let mut service = JsonRpcService::new(router);
913
914 let result = service.call_batch(vec![]).await;
915 assert!(result.is_err());
916 }
917
918 #[tokio::test]
919 async fn test_jsonrpc_layer() {
920 use tower::ServiceBuilder;
921
922 let router = create_test_router();
923 let router_clone = router.clone();
924
925 let mut service = ServiceBuilder::new()
927 .layer(JsonRpcLayer::new())
928 .service(router);
929
930 let init_req = JsonRpcRequest::new(1, "initialize").with_params(serde_json::json!({
932 "protocolVersion": "2025-03-26",
933 "capabilities": {},
934 "clientInfo": { "name": "test", "version": "1.0" }
935 }));
936 let resp = Service::<JsonRpcRequest>::call(&mut service, init_req)
937 .await
938 .unwrap();
939 assert!(matches!(resp, JsonRpcResponse::Result(_)));
940
941 router_clone.handle_notification(crate::protocol::McpNotification::Initialized);
942
943 let req = JsonRpcRequest::new(2, "tools/list").with_params(serde_json::json!({}));
945 let resp = Service::<JsonRpcRequest>::call(&mut service, req)
946 .await
947 .unwrap();
948
949 match resp {
950 JsonRpcResponse::Result(r) => {
951 let tools = r.result.get("tools").unwrap().as_array().unwrap();
952 assert_eq!(tools.len(), 1);
953 }
954 JsonRpcResponse::Error(e) => panic!("Expected result, got error: {:?}", e),
955 _ => panic!("unexpected response variant"),
956 }
957 }
958
959 #[test]
960 fn test_jsonrpc_layer_default() {
961 let _layer = JsonRpcLayer::default();
963 }
964
965 #[test]
966 fn test_jsonrpc_layer_clone() {
967 let layer = JsonRpcLayer::new();
969 let _cloned = layer;
970 let _copied = layer;
971 }
972
973 #[tokio::test]
974 async fn test_invalid_jsonrpc_version() {
975 let router = create_test_router();
976 let mut service = JsonRpcService::new(router);
977
978 let req = JsonRpcRequest {
980 jsonrpc: "1.0".to_string(),
981 id: crate::protocol::RequestId::Number(1),
982 method: "ping".to_string(),
983 params: None,
984 };
985 let resp = service.call_single(req).await.unwrap();
986 match resp {
987 JsonRpcResponse::Error(e) => {
988 assert_eq!(e.error.code, -32600); }
990 _ => panic!("Expected error for invalid jsonrpc version"),
991 }
992 }
993
994 #[tokio::test]
995 async fn test_unknown_method() {
996 let router = create_test_router();
997 let mut service = JsonRpcService::new(router.clone());
998
999 let init_req = JsonRpcRequest::new(1, "initialize").with_params(serde_json::json!({
1001 "protocolVersion": "2025-11-25",
1002 "capabilities": {},
1003 "clientInfo": { "name": "test", "version": "1.0" }
1004 }));
1005 service.call_single(init_req).await.unwrap();
1006 router.handle_notification(crate::protocol::McpNotification::Initialized);
1007
1008 let req = JsonRpcRequest::new(2, "nonexistent/method");
1009 let resp = service.call_single(req).await.unwrap();
1010 match resp {
1011 JsonRpcResponse::Error(e) => {
1012 assert_eq!(e.error.code, -32601); }
1014 _ => panic!("Expected error for unknown method"),
1015 }
1016 }
1017
1018 #[tokio::test]
1019 async fn test_invalid_params() {
1020 let router = create_test_router();
1021 let mut service = JsonRpcService::new(router.clone());
1022
1023 let init_req = JsonRpcRequest::new(1, "initialize").with_params(serde_json::json!({
1025 "protocolVersion": "2025-11-25",
1026 "capabilities": {},
1027 "clientInfo": { "name": "test", "version": "1.0" }
1028 }));
1029 service.call_single(init_req).await.unwrap();
1030 router.handle_notification(crate::protocol::McpNotification::Initialized);
1031
1032 let req = JsonRpcRequest::new(2, "tools/call").with_params(serde_json::json!({
1034 "wrong_field": "value"
1035 }));
1036 let resp = service.call_single(req).await.unwrap();
1037 match resp {
1038 JsonRpcResponse::Error(e) => {
1039 assert_eq!(e.error.code, -32602); }
1041 _ => panic!("Expected error for invalid params"),
1042 }
1043 }
1044
1045 #[tokio::test]
1046 async fn exact_profile_rejects_malformed_present_params() {
1047 let router = create_test_router();
1048 let mut service = JsonRpcService::new(router.clone());
1049 let initialize = JsonRpcRequest::new(0, "initialize").with_params(serde_json::json!({
1050 "protocolVersion": "2025-11-25",
1051 "capabilities": {},
1052 "clientInfo": {"name": "test", "version": "1.0"}
1053 }));
1054 service.call_single(initialize).await.unwrap();
1055 router.handle_notification(crate::protocol::McpNotification::Initialized);
1056
1057 let request = JsonRpcRequest::new(1, "tools/list")
1058 .with_params(serde_json::json!(["not", "an", "object"]));
1059 let response = service.call_single(request).await.unwrap();
1060 let JsonRpcResponse::Error(error) = response else {
1061 panic!("malformed present params should be rejected");
1062 };
1063 assert_eq!(error.error.code, -32602);
1064 assert!(error.error.message.contains("`tools/list` params"));
1065 }
1066
1067 #[tokio::test]
1068 async fn test_request_before_initialize() {
1069 let router = create_test_router();
1070 let mut service = JsonRpcService::new(router);
1071
1072 let req = JsonRpcRequest::new(1, "tools/list").with_params(serde_json::json!({}));
1074 let resp = service.call_single(req).await.unwrap();
1075 match resp {
1076 JsonRpcResponse::Error(e) => {
1077 assert_eq!(e.error.code, -32600); }
1079 _ => panic!("Expected error for request before initialize"),
1080 }
1081 }
1082
1083 #[tokio::test]
1084 async fn test_ping_before_initialize() {
1085 let router = create_test_router();
1086 let mut service = JsonRpcService::new(router);
1087
1088 let req = JsonRpcRequest::new(1, "ping");
1090 let resp = service.call_single(req).await.unwrap();
1091 assert!(matches!(resp, JsonRpcResponse::Result(_)));
1092 }
1093
1094 #[tokio::test]
1095 async fn test_call_message_single() {
1096 let router = create_test_router();
1097 let mut service = JsonRpcService::new(router);
1098
1099 let msg = JsonRpcMessage::Single(JsonRpcRequest::new(1, "ping"));
1100 let resp = service.call_message(msg).await.unwrap();
1101 assert!(matches!(resp, JsonRpcResponseMessage::Single(_)));
1102 }
1103
1104 #[tokio::test]
1105 async fn test_call_message_batch() {
1106 let router = create_test_router();
1107 let mut service = JsonRpcService::new(router.clone());
1108
1109 let init_req = JsonRpcRequest::new(1, "initialize").with_params(serde_json::json!({
1111 "protocolVersion": "2025-03-26",
1112 "capabilities": {},
1113 "clientInfo": { "name": "test", "version": "1.0" }
1114 }));
1115 service.call_single(init_req).await.unwrap();
1116 router.handle_notification(crate::protocol::McpNotification::Initialized);
1117
1118 let msg = JsonRpcMessage::Batch(vec![
1119 JsonRpcRequest::new(2, "ping"),
1120 JsonRpcRequest::new(3, "tools/list").with_params(serde_json::json!({})),
1121 ]);
1122 let resp = service.call_message(msg).await.unwrap();
1123 match resp {
1124 JsonRpcResponseMessage::Batch(responses) => {
1125 assert_eq!(responses.len(), 2);
1126 }
1127 _ => panic!("Expected batch response"),
1128 }
1129 }
1130
1131 #[tokio::test]
1132 async fn negotiated_2025_11_rejects_batch() {
1133 let router = create_test_router();
1134 let mut service = JsonRpcService::new(router.clone());
1135 let init_req = JsonRpcRequest::new(1, "initialize").with_params(serde_json::json!({
1136 "protocolVersion": "2025-11-25",
1137 "capabilities": {},
1138 "clientInfo": { "name": "test", "version": "1.0" }
1139 }));
1140 service.call_single(init_req).await.unwrap();
1141 router.handle_notification(crate::protocol::McpNotification::Initialized);
1142
1143 let message = JsonRpcMessage::Batch(vec![JsonRpcRequest::new(2, "ping")]);
1144 let response = service.call_message(message).await.unwrap();
1145 let JsonRpcResponseMessage::Single(JsonRpcResponse::Error(error)) = response else {
1146 panic!("2025-11-25 batch should produce one JSON-RPC error");
1147 };
1148 assert_eq!(error.error.code, -32600);
1149 assert!(
1150 error
1151 .error
1152 .message
1153 .contains("does not permit top-level JSON-RPC batches")
1154 );
1155 }
1156
1157 #[tokio::test]
1158 async fn test_call_message_empty_batch() {
1159 let router = create_test_router();
1160 let mut service = JsonRpcService::new(router);
1161
1162 let msg = JsonRpcMessage::Batch(vec![]);
1165 let result = service.call_message(msg).await.unwrap();
1166 let JsonRpcResponseMessage::Single(JsonRpcResponse::Error(error)) = result else {
1167 panic!("empty batch should produce one JSON-RPC error response");
1168 };
1169 assert_eq!(error.error.code, -32600);
1170 }
1171
1172 #[tokio::test]
1173 async fn test_extensions_bridging() {
1174 let router = create_test_router();
1175
1176 #[derive(Debug, Clone)]
1177 #[allow(dead_code)]
1178 struct TestClaim(String);
1179
1180 let mut ext = Extensions::new();
1181 ext.insert(TestClaim("admin".to_string()));
1182
1183 let mut service = JsonRpcService::new(router).with_extensions(ext);
1184
1185 let req = JsonRpcRequest::new(1, "ping");
1187 let resp = service.call_single(req).await.unwrap();
1188 assert!(matches!(resp, JsonRpcResponse::Result(_)));
1189 }
1190
1191 #[tokio::test]
1192 async fn test_batch_with_mixed_valid_invalid() {
1193 let router = create_test_router();
1194 let mut service = JsonRpcService::new(router.clone());
1195
1196 let init_req = JsonRpcRequest::new(1, "initialize").with_params(serde_json::json!({
1198 "protocolVersion": "2025-03-26",
1199 "capabilities": {},
1200 "clientInfo": { "name": "test", "version": "1.0" }
1201 }));
1202 service.call_single(init_req).await.unwrap();
1203 router.handle_notification(crate::protocol::McpNotification::Initialized);
1204
1205 let requests = vec![
1207 JsonRpcRequest::new(2, "ping"),
1208 JsonRpcRequest::new(3, "nonexistent/method"),
1209 ];
1210 let responses = service.call_batch(requests).await.unwrap();
1211 assert_eq!(responses.len(), 2);
1212
1213 assert!(matches!(&responses[0], JsonRpcResponse::Result(_)));
1215 assert!(matches!(&responses[1], JsonRpcResponse::Error(_)));
1217 }
1218}