Skip to main content

relay_knowledge/interfaces/agent/mcp/runtime/
transport.rs

1use std::{error::Error, fmt, future::Future};
2
3use axum::{
4    Router,
5    routing::{get, post},
6};
7use tower_http::{limit::RequestBodyLimitLayer, trace::TraceLayer};
8
9use crate::net::http::HttpServeError;
10
11use super::super::{http_contract::ensure_remote_bind_allowed, metrics};
12use super::{
13    dispatch::{handle_mcp_delete, handle_mcp_post},
14    server::McpServer,
15};
16
17impl McpServer {
18    /// Builds the Streamable HTTP router without opening sockets.
19    pub fn router(self) -> Router {
20        let config = self.network.current();
21        let endpoint = self.agent.mcp_endpoint.clone();
22        let metrics_endpoint = metrics::metrics_endpoint(&endpoint);
23        let body_limit = usize::try_from(config.http.max_request_body_bytes).unwrap_or(usize::MAX);
24
25        Router::new()
26            .route(&endpoint, post(handle_mcp_post))
27            .route(&endpoint, axum::routing::delete(handle_mcp_delete))
28            .route(&metrics_endpoint, get(metrics::handle_metrics_get))
29            .with_state(self)
30            .layer(TraceLayer::new_for_http())
31            .layer(RequestBodyLimitLayer::new(body_limit))
32    }
33
34    /// Starts the MCP HTTP listener through `net::http`.
35    pub async fn serve_until_shutdown(
36        self,
37        shutdown: impl Future<Output = ()> + Send + 'static,
38    ) -> Result<(), McpServeError> {
39        let network_config = self.network.current();
40        let config = network_config.http;
41        let qos_policy = network_config.qos;
42        let qos = self.qos.clone();
43        let router = self.checked_router()?;
44
45        crate::net::http::serve_router_with_qos(router, config, qos, qos_policy, shutdown)
46            .await
47            .map_err(McpServeError::Http)
48    }
49
50    /// Builds the Streamable HTTP router after validating listener policy.
51    pub fn checked_router(self) -> Result<Router, McpServeError> {
52        if !self.agent.mcp_streamable_http_enabled {
53            return Err(McpServeError::Disabled);
54        }
55        ensure_remote_bind_allowed(&self.network.current().http, &self.agent.access_policy)?;
56
57        Ok(self.router())
58    }
59}
60
61/// MCP server startup error.
62#[derive(Debug)]
63pub enum McpServeError {
64    Disabled,
65    RemoteBindDisabled,
66    Http(HttpServeError),
67}
68
69impl fmt::Display for McpServeError {
70    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
71        match self {
72            Self::Disabled => write!(formatter, "MCP Streamable HTTP is not enabled"),
73            Self::RemoteBindDisabled => {
74                write!(
75                    formatter,
76                    "MCP remote bind requires allow_remote_clients=true"
77                )
78            }
79            Self::Http(error) => write!(formatter, "{error}"),
80        }
81    }
82}
83
84impl Error for McpServeError {}
85
86#[cfg(test)]
87#[path = "transport_tests.rs"]
88mod tests;