mcp_streamable_proxy/proxy_handler/
mod.rs1use crate::backend_client::{
5 BackendNotificationBridge, ProgressRouteGuard, UpstreamPeerRegistry, UpstreamPeerSlot,
6};
7use crate::backend_connector::{BackendConnector, BackendIsolation};
8use arc_swap::ArcSwapOption;
9pub use mcp_common::ToolFilter;
10use rmcp::{
11 ErrorData, RoleClient, RoleServer, ServerHandler, ServiceError,
12 model::{
13 CallToolRequestParams, CallToolResponse, CallToolResult, CancelTaskParams, ClientRequest,
14 ContentBlock, GetPromptResponse, GetTaskParams, GetTaskRequest, GetTaskResult,
15 Implementation, InitializeRequestParams, InitializeResult, ListToolsResult,
16 PaginatedRequestParams, ProtocolVersion, ReadResourceResponse, RequestMetaObject,
17 RequestParamsMeta, ServerConfig, ServerResult, SetLevelRequestMethod,
18 SubscribeRequestMethod, SubscribeRequestParams, UnsubscribeRequestMethod,
19 UnsubscribeRequestParams,
20 },
21 service::{NotificationContext, Peer, RequestContext, RunningService},
22};
23use std::future::Future;
24use std::sync::Arc;
25use std::sync::atomic::{AtomicU64, Ordering};
26use std::time::Instant;
27use tracing::{debug, error, info, warn};
28
29static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(1);
31
32fn merge_context_meta_into_params<P: RequestParamsMeta>(
38 params: &mut P,
39 context_meta: &RequestMetaObject,
40) {
41 if context_meta.is_empty() {
42 return;
43 }
44 let dest = params.meta_or_default();
45 for (key, value) in context_meta.iter() {
46 dest.entry(key.clone()).or_insert_with(|| value.clone());
47 }
48}
49
50fn negotiate_protocol_version(
52 client_requested: &ProtocolVersion,
53 server_fallback: ProtocolVersion,
54) -> ProtocolVersion {
55 if ProtocolVersion::KNOWN_VERSIONS.contains(client_requested) {
56 client_requested.clone()
57 } else {
58 warn!(
59 client_requested = %client_requested,
60 server_fallback = %server_fallback,
61 "client requested unsupported protocol version; falling back to server default"
62 );
63 server_fallback
64 }
65}
66
67pub(crate) fn peer_info_to_server_info(peer: rmcp::model::ServerPeerInfo) -> ServerConfig {
76 let mut info =
77 InitializeResult::new(peer.capabilities).with_protocol_version(peer.protocol_version);
78 if let Some(server_info) = peer.server_info {
79 info = info.with_server_info(server_info);
80 }
81 if let Some(instructions) = peer.instructions {
82 info = info.with_instructions(instructions);
83 }
84 info
85}
86
87pub type BackendRunningService = RunningService<RoleClient, BackendNotificationBridge>;
89
90#[derive(Debug)]
93struct PeerInner {
94 peer: Peer<RoleClient>,
96 #[allow(dead_code)]
98 _running: Arc<BackendRunningService>,
99}
100
101#[derive(Clone)]
103struct PerSessionState {
104 connector: Arc<dyn BackendConnector>,
105 notify_slot: UpstreamPeerSlot,
106 connect_lock: Arc<tokio::sync::Mutex<()>>,
107}
108
109impl std::fmt::Debug for PerSessionState {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 f.debug_struct("PerSessionState")
112 .field("notify_slot", &self.notify_slot)
113 .finish_non_exhaustive()
114 }
115}
116
117#[derive(Clone, Debug)]
124pub struct ProxyHandler {
125 peer: Arc<ArcSwapOption<PeerInner>>,
128 discovery: DiscoveryCache,
130 mcp_id: String,
132 tool_filter: ToolFilter,
134 backend_version: Arc<AtomicU64>,
136 upstream_peers: Arc<UpstreamPeerRegistry>,
138 isolation: BackendIsolation,
140 per_session: Option<PerSessionState>,
142}
143
144mod backend_bridge;
145mod connection_swap;
146mod discovery;
147mod forwarding;
148mod lifecycle;
149mod raw_dispatch;
150mod server_handler;
151
152use discovery::DiscoveryCache;