Skip to main content

tower_mcp/
jsonrpc.rs

1//! JSON-RPC 2.0 service layer
2//!
3//! Provides a Tower [`Layer`] and [`Service`] for JSON-RPC framing of MCP requests.
4//!
5//! - [`JsonRpcLayer`] - Tower layer for [`ServiceBuilder`](tower::ServiceBuilder) composition
6//! - [`JsonRpcService`] - Tower service wrapping an MCP router
7//!
8//! The service handles:
9//! - Single request processing
10//! - Exact-revision batch policy and concurrent request-batch execution
11//! - JSON-RPC version validation
12//! - Error conversion to JSON-RPC error responses
13
14use 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/// Tower layer that adds JSON-RPC 2.0 framing to an MCP service.
34///
35/// This is the standard way to compose `JsonRpcService` with other tower
36/// middleware via [`ServiceBuilder`](tower::ServiceBuilder).
37///
38/// # Example
39///
40/// ```rust
41/// use tower::ServiceBuilder;
42/// use tower_mcp::{McpRouter, JsonRpcLayer, JsonRpcService};
43///
44/// let router = McpRouter::new().server_info("my-server", "1.0.0");
45///
46/// // Compose with ServiceBuilder
47/// let service = ServiceBuilder::new()
48///     .layer(JsonRpcLayer::new())
49///     .service(router);
50/// ```
51#[derive(Debug, Clone, Copy, Default)]
52pub struct JsonRpcLayer {
53    _priv: (),
54}
55
56impl JsonRpcLayer {
57    /// Create a new `JsonRpcLayer`.
58    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
71/// Service that handles JSON-RPC framing.
72///
73/// Wraps an MCP service and handles JSON-RPC request/response conversion.
74/// Supports single requests for every implemented revision. Request batches
75/// are accepted only after the exact runtime revision is known to be
76/// `2025-03-26`; later MCP revisions removed top-level JSON-RPC batching.
77///
78/// Can be created directly via [`JsonRpcService::new`] or through the
79/// [`JsonRpcLayer`] for [`ServiceBuilder`](tower::ServiceBuilder) composition.
80///
81/// # Example
82///
83/// ```rust
84/// use tower_mcp::{McpRouter, JsonRpcService};
85///
86/// let router = McpRouter::new().server_info("my-server", "1.0.0");
87/// let service = JsonRpcService::new(router);
88/// ```
89pub 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    /// Create a new JSON-RPC service wrapping the given inner service
98    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    /// Set extensions to inject into every `RouterRequest` created by this service.
108    ///
109    /// This is used by transports to bridge data (e.g., `TokenClaims`) from the
110    /// HTTP/WebSocket layer into the MCP request pipeline.
111    pub fn with_extensions(mut self, ext: Extensions) -> Self {
112        self.extensions = ext;
113        self
114    }
115
116    /// Set the exact protocol versions this service accepts and advertises.
117    ///
118    /// The default enables every protocol implementation compiled into
119    /// `tower-mcp`. Transports expose the same policy so applications can
120    /// narrow support per server instance.
121    pub fn protocol_support(mut self, support: ProtocolSupport) -> Self {
122        self.protocol_support = support;
123        self
124    }
125
126    /// Construct and set an exact runtime protocol-version allow-list.
127    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    /// Validate a decoded inbound value using the exact runtime revision.
143    ///
144    /// Per-request metadata wins for the final protocol. Legacy traffic uses
145    /// the revision captured from a successful `initialize` response. A
146    /// single-version runtime policy is also exact; an allowlist with several
147    /// versions is never used to guess batch semantics.
148    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    /// Pin an already-negotiated revision supplied by a transport session.
162    #[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        // Before legacy initialization only version-invariant single calls
255        // such as `ping` are meaningful. Use the most-preferred enabled
256        // legacy implementation for their semantic profile; initialization
257        // itself is handled above and records the actual negotiated revision.
258        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    /// Validate the lifecycle metadata on a request without dispatching it.
273    ///
274    /// Long-lived transport-owned requests such as `subscriptions/listen`
275    /// need to be accepted by the binding before they reach the ordinary
276    /// request/response router. Keeping this validation here ensures those
277    /// transport paths honor the same runtime protocol allow-list and modern
278    /// metadata rules as [`Self::call_single`].
279    #[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    /// Process a single JSON-RPC request
297    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    /// Process a `2025-03-26` batch of JSON-RPC requests concurrently.
316    ///
317    /// The exact revision must have been negotiated by `initialize`, supplied
318    /// by a transport session, or be the sole entry in [`ProtocolSupport`].
319    /// Later revisions return an invalid-request error.
320    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        // Process all requests concurrently
343        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                            // Convert errors to error responses instead of dropping
364                            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        // Results will never be empty since we converted all errors to responses
377        Ok(results)
378    }
379
380    /// Process a JSON-RPC message (single or batch)
381    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
446/// Service implementation for JSON-RPC batch requests
447impl<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
470/// Helper function to process a single JSON-RPC request
471async 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    // Validate JSON-RPC version
486    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    // Parse the MCP request from JSON-RPC
521    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    // Create router request
532    let router_req = RouterRequest {
533        id: req.id,
534        inner: mcp_request,
535        extensions,
536    };
537
538    // Call the inner service (oneshot handles poll_ready)
539    let response = inner.oneshot(router_req).await.unwrap(); // Infallible
540
541    // Convert to JSON-RPC response
542    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
579/// Apply the shared types-only MCP profile after runtime policy has selected
580/// one exact revision.
581pub(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/// Validate a request using the final per-request metadata lifecycle.
619///
620/// The protocol-version key is the era discriminator on transports without
621/// headers (stdio and custom bindings). Legacy initialize traffic can coexist
622/// on the same connection because requests without that key retain the
623/// sessionful path.
624#[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/// Methods present in legacy protocol unions but removed from the final core.
691#[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
705/// Fill the required 2026-07-28 result envelope immediately before it reaches
706/// a JSON-RPC transport, while preserving legacy public result types.
707pub(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        // Initialize first
783        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        // Mark as initialized
792        router.handle_notification(crate::protocol::McpNotification::Initialized);
793
794        // Now list tools
795        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        // Initialize first
814        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        // Batch request
823        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        // Build service using the layer via ServiceBuilder
926        let mut service = ServiceBuilder::new()
927            .layer(JsonRpcLayer::new())
928            .service(router);
929
930        // Initialize
931        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        // List tools through the layer-composed service
944        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        // JsonRpcLayer implements Default
962        let _layer = JsonRpcLayer::default();
963    }
964
965    #[test]
966    fn test_jsonrpc_layer_clone() {
967        // JsonRpcLayer implements Clone and Copy
968        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        // Request with wrong jsonrpc version
979        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); // Invalid request
989            }
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        // Initialize
1000        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); // Method not found
1013            }
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        // Initialize
1024        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        // tools/call without required "name" field
1033        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); // Invalid params
1040            }
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        // tools/list before initialize should fail
1073        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); // Invalid request (session not initialized)
1078            }
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        // Ping should work even before initialize
1089        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        // Initialize
1110        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        // Message dispatch turns a top-level batch failure into one JSON-RPC
1163        // error response because there are no member IDs to attach it to.
1164        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        // Ping should work -- extensions are injected into RouterRequest
1186        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        // Initialize
1197        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        // Batch with one valid and one invalid request
1206        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        // First should succeed (ping)
1214        assert!(matches!(&responses[0], JsonRpcResponse::Result(_)));
1215        // Second should be an error (method not found)
1216        assert!(matches!(&responses[1], JsonRpcResponse::Error(_)));
1217    }
1218}