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, ServerInfo, ServerResult, SetLevelRequestMethod, SubscribeRequestMethod,
18 SubscribeRequestParams, UnsubscribeRequestMethod, UnsubscribeRequestParams,
19 },
20 service::{NotificationContext, Peer, RequestContext, RunningService},
21};
22use std::future::Future;
23use std::sync::Arc;
24use std::sync::atomic::{AtomicU64, Ordering};
25use std::time::Instant;
26use tracing::{debug, error, info, warn};
27
28static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(1);
30
31fn merge_context_meta_into_params<P: RequestParamsMeta>(
37 params: &mut P,
38 context_meta: &RequestMetaObject,
39) {
40 if context_meta.is_empty() {
41 return;
42 }
43 let dest = params.meta_or_default();
44 for (key, value) in context_meta.iter() {
45 dest.entry(key.clone()).or_insert_with(|| value.clone());
46 }
47}
48
49fn negotiate_protocol_version(
51 client_requested: &ProtocolVersion,
52 server_fallback: ProtocolVersion,
53) -> ProtocolVersion {
54 if ProtocolVersion::KNOWN_VERSIONS.contains(client_requested) {
55 client_requested.clone()
56 } else {
57 warn!(
58 client_requested = %client_requested,
59 server_fallback = %server_fallback,
60 "client requested unsupported protocol version; falling back to server default"
61 );
62 server_fallback
63 }
64}
65
66pub(crate) fn peer_info_to_server_info(peer: rmcp::model::ServerPeerInfo) -> ServerInfo {
75 let mut info = InitializeResult::new(peer.capabilities).with_protocol_version(peer.protocol_version);
76 if let Some(server_info) = peer.server_info {
77 info = info.with_server_info(server_info);
78 }
79 if let Some(instructions) = peer.instructions {
80 info = info.with_instructions(instructions);
81 }
82 info
83}
84
85pub type BackendRunningService = RunningService<RoleClient, BackendNotificationBridge>;
87
88#[derive(Debug)]
91struct PeerInner {
92 peer: Peer<RoleClient>,
94 #[allow(dead_code)]
96 _running: Arc<BackendRunningService>,
97}
98
99#[derive(Clone)]
101struct PerSessionState {
102 connector: Arc<dyn BackendConnector>,
103 notify_slot: UpstreamPeerSlot,
104 connect_lock: Arc<tokio::sync::Mutex<()>>,
105}
106
107impl std::fmt::Debug for PerSessionState {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 f.debug_struct("PerSessionState")
110 .field("notify_slot", &self.notify_slot)
111 .finish_non_exhaustive()
112 }
113}
114
115#[derive(Clone, Debug)]
122pub struct ProxyHandler {
123 peer: Arc<ArcSwapOption<PeerInner>>,
126 discovery: DiscoveryCache,
128 mcp_id: String,
130 tool_filter: ToolFilter,
132 backend_version: Arc<AtomicU64>,
134 upstream_peers: Arc<UpstreamPeerRegistry>,
136 isolation: BackendIsolation,
138 per_session: Option<PerSessionState>,
140}
141
142mod backend_bridge;
143mod connection_swap;
144mod discovery;
145mod forwarding;
146mod lifecycle;
147mod raw_dispatch;
148mod server_handler;
149
150use discovery::DiscoveryCache;