use crate::backend_client::{
BackendNotificationBridge, ProgressRouteGuard, UpstreamPeerRegistry, UpstreamPeerSlot,
};
use crate::backend_connector::{BackendConnector, BackendIsolation};
use arc_swap::ArcSwapOption;
pub use mcp_common::ToolFilter;
use rmcp::{
ErrorData, RoleClient, RoleServer, ServerHandler, ServiceError,
model::{
CallToolRequestParams, CallToolResponse, CallToolResult, CancelTaskParams, ClientRequest,
ContentBlock, GetPromptResponse, GetTaskParams, GetTaskRequest, GetTaskResult,
Implementation, InitializeRequestParams, InitializeResult, ListToolsResult,
PaginatedRequestParams, ProtocolVersion, ReadResourceResponse, RequestMetaObject,
RequestParamsMeta, ServerConfig, ServerResult, SetLevelRequestMethod,
SubscribeRequestMethod, SubscribeRequestParams, UnsubscribeRequestMethod,
UnsubscribeRequestParams,
},
service::{NotificationContext, Peer, RequestContext, RunningService},
};
use std::future::Future;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
use tracing::{debug, error, info, warn};
static REQUEST_COUNTER: AtomicU64 = AtomicU64::new(1);
fn merge_context_meta_into_params<P: RequestParamsMeta>(
params: &mut P,
context_meta: &RequestMetaObject,
) {
if context_meta.is_empty() {
return;
}
let dest = params.meta_or_default();
for (key, value) in context_meta.iter() {
dest.entry(key.clone()).or_insert_with(|| value.clone());
}
}
fn negotiate_protocol_version(
client_requested: &ProtocolVersion,
server_fallback: ProtocolVersion,
) -> ProtocolVersion {
if ProtocolVersion::KNOWN_VERSIONS.contains(client_requested) {
client_requested.clone()
} else {
warn!(
client_requested = %client_requested,
server_fallback = %server_fallback,
"client requested unsupported protocol version; falling back to server default"
);
server_fallback
}
}
pub(crate) fn peer_info_to_server_info(peer: rmcp::model::ServerPeerInfo) -> ServerConfig {
let mut info =
InitializeResult::new(peer.capabilities).with_protocol_version(peer.protocol_version);
if let Some(server_info) = peer.server_info {
info = info.with_server_info(server_info);
}
if let Some(instructions) = peer.instructions {
info = info.with_instructions(instructions);
}
info
}
pub type BackendRunningService = RunningService<RoleClient, BackendNotificationBridge>;
#[derive(Debug)]
struct PeerInner {
peer: Peer<RoleClient>,
#[allow(dead_code)]
_running: Arc<BackendRunningService>,
}
#[derive(Clone)]
struct PerSessionState {
connector: Arc<dyn BackendConnector>,
notify_slot: UpstreamPeerSlot,
connect_lock: Arc<tokio::sync::Mutex<()>>,
}
impl std::fmt::Debug for PerSessionState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PerSessionState")
.field("notify_slot", &self.notify_slot)
.finish_non_exhaustive()
}
}
#[derive(Clone, Debug)]
pub struct ProxyHandler {
peer: Arc<ArcSwapOption<PeerInner>>,
discovery: DiscoveryCache,
mcp_id: String,
tool_filter: ToolFilter,
backend_version: Arc<AtomicU64>,
upstream_peers: Arc<UpstreamPeerRegistry>,
isolation: BackendIsolation,
per_session: Option<PerSessionState>,
}
mod backend_bridge;
mod connection_swap;
mod discovery;
mod forwarding;
mod lifecycle;
mod raw_dispatch;
mod server_handler;
use discovery::DiscoveryCache;