relay_knowledge/interfaces/agent/mcp/runtime/
transport.rs1use 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 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 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 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#[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;