mod line;
mod session;
use std::panic::AssertUnwindSafe;
use std::sync::Arc;
use dashmap::DashMap;
use futures::FutureExt;
use serde_json::Value;
use tokio_util::sync::CancellationToken;
use turbomcp_core::error::McpError;
use turbomcp_core::handler::McpHandler;
use turbomcp_types::{ClientCapabilities, ProtocolVersion};
use crate::context::RequestContext;
use crate::router::{self, JsonRpcIncoming, JsonRpcOutgoing};
pub(crate) const SERVER_REQUEST_TIMEOUT: core::time::Duration = core::time::Duration::from_secs(60);
pub(crate) async fn route_catching_panics<H: McpHandler>(
handler: &H,
request: JsonRpcIncoming,
ctx: &RequestContext,
version: &ProtocolVersion,
) -> JsonRpcOutgoing {
let id = request.id.clone();
let method = request.method.clone();
match AssertUnwindSafe(router::route_request_versioned(
handler, request, ctx, version,
))
.catch_unwind()
.await
{
Ok(response) => response,
Err(payload) => {
let detail = panic_detail(&payload);
tracing::error!(
method = %method,
panic = %detail,
"Handler panicked; answering with an internal error"
);
if id.is_none() {
return JsonRpcOutgoing::notification_ack();
}
JsonRpcOutgoing::error(
id,
McpError::internal(format!("Handler panicked while serving {method}")),
)
}
}
}
fn panic_detail(payload: &Box<dyn std::any::Any + Send>) -> String {
if let Some(s) = payload.downcast_ref::<&str>() {
(*s).to_string()
} else if let Some(s) = payload.downcast_ref::<String>() {
s.clone()
} else {
"non-string panic payload".to_string()
}
}
pub(crate) struct PendingHandlerGuard {
handlers: Arc<DashMap<String, CancellationToken>>,
key: Option<String>,
}
impl PendingHandlerGuard {
pub(crate) fn new(
handlers: Arc<DashMap<String, CancellationToken>>,
key: Option<String>,
) -> Self {
Self { handlers, key }
}
}
impl Drop for PendingHandlerGuard {
fn drop(&mut self) {
if let Some(key) = self.key.take() {
self.handlers.remove(&key);
}
}
}
pub(crate) fn register_pending_handler(
handlers: &Arc<DashMap<String, CancellationToken>>,
id: Option<&Value>,
) -> (CancellationToken, PendingHandlerGuard) {
let token = CancellationToken::new();
let key = id.map(jsonrpc_id_key);
if let Some(ref key) = key
&& handlers.insert(key.clone(), token.clone()).is_some()
{
tracing::warn!(
request_id = %key,
"Request id reused while the first is still in flight",
);
}
(token, PendingHandlerGuard::new(Arc::clone(handlers), key))
}
pub(crate) fn cancel_pending_handler(
handlers: &DashMap<String, CancellationToken>,
params: Option<&Value>,
) {
let Some(request_id) = params.and_then(|p| p.get("requestId")) else {
return;
};
let key = jsonrpc_id_key(request_id);
if let Some((_, token)) = handlers.remove(&key) {
let reason = params
.and_then(|p| p.get("reason"))
.and_then(|r| r.as_str())
.unwrap_or("client requested cancellation");
tracing::debug!(request_id = %key, reason = %reason, "Cancelling in-flight handler");
token.cancel();
}
}
#[derive(Debug, Clone)]
pub(crate) enum SessionState {
Uninitialized,
Initialized(InitializedSessionState),
}
#[derive(Debug, Clone)]
pub(crate) struct InitializedSessionState {
protocol_version: ProtocolVersion,
}
impl InitializedSessionState {
pub(crate) fn new(protocol_version: ProtocolVersion) -> Self {
Self { protocol_version }
}
pub(crate) fn protocol_version(&self) -> &ProtocolVersion {
&self.protocol_version
}
}
pub(crate) fn jsonrpc_id_key(id: &Value) -> String {
match id {
Value::String(s) => s.clone(),
Value::Number(n) => n.to_string(),
other => other.to_string(),
}
}
pub(crate) fn client_capabilities_from_initialize_params(
params: Option<&Value>,
) -> ClientCapabilities {
let Some(caps) = params.and_then(|params| params.get("capabilities")) else {
return ClientCapabilities::default();
};
if let Ok(parsed) = serde_json::from_value::<ClientCapabilities>(caps.clone()) {
return parsed;
}
let mut out = ClientCapabilities::default();
let field = |name: &str| caps.get(name).cloned();
out.roots = field("roots").and_then(|v| serde_json::from_value(v).ok());
out.sampling = field("sampling").and_then(|v| serde_json::from_value(v).ok());
out.elicitation = field("elicitation").and_then(|v| serde_json::from_value(v).ok());
out.experimental = field("experimental").and_then(|v| serde_json::from_value(v).ok());
out.tasks = field("tasks").and_then(|v| serde_json::from_value(v).ok());
tracing::debug!(
"client capabilities did not deserialize as a whole; recovered per field: \
roots={} sampling={} elicitation={}",
out.roots.is_some(),
out.sampling.is_some(),
out.elicitation.is_some(),
);
out
}
#[cfg(feature = "stdio")]
pub mod stdio;
#[cfg(feature = "tcp")]
pub mod tcp;
#[cfg(all(feature = "unix", unix))]
pub mod unix;
#[cfg(feature = "channel")]
pub mod channel;
#[cfg(feature = "http")]
pub mod http;
#[cfg(feature = "websocket")]
pub mod websocket;
pub use line::{LineReader, LineTransportRunner, LineWriter};
pub use crate::config::DEFAULT_MAX_MESSAGE_SIZE;
pub const MAX_MESSAGE_SIZE: usize = DEFAULT_MAX_MESSAGE_SIZE;