use std::future::Future;
use serde_json::Value;
use turbomcp_core::error::{McpError, McpResult};
pub use turbomcp_core::handler::McpHandler;
use super::RequestContext;
pub trait McpHandlerExt: McpHandler {
#[cfg(feature = "stdio")]
fn run(&self) -> impl Future<Output = McpResult<()>> + Send;
#[cfg(feature = "stdio")]
fn run_stdio(&self) -> impl Future<Output = McpResult<()>> + Send;
#[cfg(feature = "http")]
fn run_http(&self, addr: &str) -> impl Future<Output = McpResult<()>> + Send;
#[cfg(feature = "websocket")]
fn run_websocket(&self, addr: &str) -> impl Future<Output = McpResult<()>> + Send;
#[cfg(feature = "tcp")]
fn run_tcp(&self, addr: &str) -> impl Future<Output = McpResult<()>> + Send;
#[cfg(feature = "unix")]
fn run_unix(&self, path: &str) -> impl Future<Output = McpResult<()>> + Send;
fn handle_request(
&self,
request: Value,
ctx: RequestContext,
) -> impl Future<Output = McpResult<Value>> + Send;
}
impl<T: McpHandler> McpHandlerExt for T {
#[cfg(feature = "stdio")]
fn run(&self) -> impl Future<Output = McpResult<()>> + Send {
super::transport::stdio::run(self)
}
#[cfg(feature = "stdio")]
fn run_stdio(&self) -> impl Future<Output = McpResult<()>> + Send {
super::transport::stdio::run(self)
}
#[cfg(feature = "http")]
fn run_http(&self, addr: &str) -> impl Future<Output = McpResult<()>> + Send {
let addr = addr.to_string();
let handler = self.clone();
async move { super::transport::http::run(&handler, &addr).await }
}
#[cfg(feature = "websocket")]
fn run_websocket(&self, addr: &str) -> impl Future<Output = McpResult<()>> + Send {
let addr = addr.to_string();
let handler = self.clone();
async move { super::transport::websocket::run(&handler, &addr).await }
}
#[cfg(feature = "tcp")]
fn run_tcp(&self, addr: &str) -> impl Future<Output = McpResult<()>> + Send {
let addr = addr.to_string();
let handler = self.clone();
async move { super::transport::tcp::run(&handler, &addr).await }
}
#[cfg(feature = "unix")]
fn run_unix(&self, path: &str) -> impl Future<Output = McpResult<()>> + Send {
let path = path.to_string();
let handler = self.clone();
async move { super::transport::unix::run(&handler, &path).await }
}
fn handle_request(
&self,
request: Value,
ctx: RequestContext,
) -> impl Future<Output = McpResult<Value>> + Send {
let handler = self.clone();
async move {
let request_str = serde_json::to_string(&request)
.map_err(|e| McpError::internal(format!("Failed to serialize request: {e}")))?;
let parsed = super::router::parse_request(&request_str)?;
let core_ctx = ctx.to_core_context();
let response = super::router::route_request(&handler, parsed, &core_ctx).await;
serde_json::to_value(&response)
.map_err(|e| McpError::internal(format!("Failed to serialize response: {e}")))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use turbomcp_core::context::RequestContext as CoreRequestContext;
use turbomcp_types::{
Prompt, PromptResult, Resource, ResourceResult, ServerInfo, Tool, ToolResult,
};
#[derive(Clone)]
struct TestHandler;
impl McpHandler for TestHandler {
fn server_info(&self) -> ServerInfo {
ServerInfo::new("test", "1.0.0")
}
fn list_tools(&self) -> Vec<Tool> {
vec![Tool::new("test_tool", "A test tool")]
}
fn list_resources(&self) -> Vec<Resource> {
vec![]
}
fn list_prompts(&self) -> Vec<Prompt> {
vec![]
}
fn call_tool<'a>(
&'a self,
name: &'a str,
_args: Value,
_ctx: &'a CoreRequestContext,
) -> impl std::future::Future<Output = McpResult<ToolResult>> + Send + 'a {
let name = name.to_string();
async move {
if name == "test_tool" {
Ok(ToolResult::text("Tool executed"))
} else {
Err(McpError::tool_not_found(&name))
}
}
}
fn read_resource<'a>(
&'a self,
uri: &'a str,
_ctx: &'a CoreRequestContext,
) -> impl std::future::Future<Output = McpResult<ResourceResult>> + Send + 'a {
let uri = uri.to_string();
async move { Err(McpError::resource_not_found(&uri)) }
}
fn get_prompt<'a>(
&'a self,
name: &'a str,
_args: Option<Value>,
_ctx: &'a CoreRequestContext,
) -> impl std::future::Future<Output = McpResult<PromptResult>> + Send + 'a {
let name = name.to_string();
async move { Err(McpError::prompt_not_found(&name)) }
}
}
#[tokio::test]
async fn test_handle_request() {
let handler = TestHandler;
let ctx = RequestContext::stdio();
let request = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "ping"
});
let response = handler.handle_request(request, ctx).await.unwrap();
assert!(response.get("result").is_some());
}
#[tokio::test]
async fn test_handle_request_tools_list() {
let handler = TestHandler;
let ctx = RequestContext::stdio();
let request = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
});
let response = handler.handle_request(request, ctx).await.unwrap();
let result = response.get("result").unwrap();
let tools = result.get("tools").unwrap().as_array().unwrap();
assert_eq!(tools.len(), 1);
assert_eq!(tools[0]["name"], "test_tool");
}
}