use hashbrown::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use turbomcp_core::types::capabilities::ServerCapabilities;
use turbomcp_core::types::core::Implementation;
use turbomcp_core::types::prompts::Prompt;
use turbomcp_core::types::resources::{Resource, ResourceTemplate};
use turbomcp_core::types::tools::{Tool, ToolInputSchema};
use turbomcp_core::{MaybeSend, MaybeSync};
use super::context::RequestContext;
use super::handler::McpHandler;
use super::handler_traits::{
IntoPromptHandler, IntoPromptHandlerWithCtx, IntoResourceHandler, IntoResourceHandlerWithCtx,
IntoToolHandler, IntoToolHandlerWithCtx, NoArgs, PromptNoArgs, PromptWithCtxNoArgs, RawArgs,
WithCtxOnly, WithCtxRaw,
};
use super::response::IntoToolResponse;
use super::traits::IntoPromptResponse;
use super::types::{PromptResult, ResourceResult, ToolResult};
pub type ToolHandler =
Arc<dyn Fn(serde_json::Value) -> Pin<Box<dyn Future<Output = ToolResult>>> + Send + Sync>;
pub type ToolHandlerWithCtx = Arc<
dyn Fn(Arc<RequestContext>, serde_json::Value) -> Pin<Box<dyn Future<Output = ToolResult>>>
+ Send
+ Sync,
>;
pub type ResourceHandlerFn = Arc<
dyn Fn(String) -> Pin<Box<dyn Future<Output = Result<ResourceResult, String>>>> + Send + Sync,
>;
pub type ResourceHandlerWithCtxFn = Arc<
dyn Fn(
Arc<RequestContext>,
String,
) -> Pin<Box<dyn Future<Output = Result<ResourceResult, String>>>>
+ Send
+ Sync,
>;
pub type PromptHandlerFn = Arc<
dyn Fn(Option<serde_json::Value>) -> Pin<Box<dyn Future<Output = Result<PromptResult, String>>>>
+ Send
+ Sync,
>;
pub type PromptHandlerWithCtxFn = Arc<
dyn Fn(
Arc<RequestContext>,
Option<serde_json::Value>,
) -> Pin<Box<dyn Future<Output = Result<PromptResult, String>>>>
+ Send
+ Sync,
>;
#[derive(Clone)]
pub(crate) enum ToolHandlerKind {
NoCtx(ToolHandler),
WithCtx(ToolHandlerWithCtx),
}
#[derive(Clone)]
pub(crate) enum ResourceHandlerKind {
NoCtx(ResourceHandlerFn),
WithCtx(ResourceHandlerWithCtxFn),
}
#[derive(Clone)]
pub(crate) enum PromptHandlerKind {
NoCtx(PromptHandlerFn),
WithCtx(PromptHandlerWithCtxFn),
}
#[derive(Clone)]
pub(crate) struct RegisteredTool {
pub tool: Tool,
pub handler: ToolHandlerKind,
}
#[derive(Clone)]
pub(crate) struct RegisteredResource {
pub resource: Resource,
pub handler: ResourceHandlerKind,
}
#[derive(Clone)]
pub(crate) struct RegisteredResourceTemplate {
pub template: ResourceTemplate,
pub handler: ResourceHandlerKind,
}
#[derive(Clone)]
pub(crate) struct RegisteredPrompt {
pub prompt: Prompt,
pub handler: PromptHandlerKind,
}
pub struct McpServerBuilder {
name: String,
version: String,
description: Option<String>,
tools: HashMap<String, RegisteredTool>,
resources: HashMap<String, RegisteredResource>,
resource_templates: HashMap<String, RegisteredResourceTemplate>,
prompts: HashMap<String, RegisteredPrompt>,
instructions: Option<String>,
}
impl McpServerBuilder {
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
Self {
name: name.into(),
version: version.into(),
description: None,
tools: HashMap::new(),
resources: HashMap::new(),
resource_templates: HashMap::new(),
prompts: HashMap::new(),
instructions: None,
}
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
self.instructions = Some(instructions.into());
self
}
pub fn tool<A, M, H>(
mut self,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: IntoToolHandler<A, M>,
{
let name = name.into();
let description = description.into();
let schema_value = H::schema();
let properties = schema_value
.get("properties")
.and_then(|p| p.as_object())
.map(|obj| {
obj.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<HashMap<String, serde_json::Value>>()
});
let required = schema_value
.get("required")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
});
let tool = Tool {
name: name.clone(),
description: Some(description),
title: None,
icon: None,
input_schema: ToolInputSchema {
schema_type: "object".to_string(),
properties,
required,
additional_properties: None,
},
annotations: None,
};
let boxed_handler = handler.into_handler();
let wrapped_handler: ToolHandler = Arc::from(boxed_handler);
self.tools.insert(
name.clone(),
RegisteredTool {
tool,
handler: ToolHandlerKind::NoCtx(wrapped_handler),
},
);
self
}
pub fn tool_with_ctx<A, M, H>(
mut self,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: IntoToolHandlerWithCtx<A, M>,
{
let name = name.into();
let description = description.into();
let schema_value = H::schema();
let properties = schema_value
.get("properties")
.and_then(|p| p.as_object())
.map(|obj| {
obj.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<HashMap<String, serde_json::Value>>()
});
let required = schema_value
.get("required")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
});
let tool = Tool {
name: name.clone(),
description: Some(description),
title: None,
icon: None,
input_schema: ToolInputSchema {
schema_type: "object".to_string(),
properties,
required,
additional_properties: None,
},
annotations: None,
};
let boxed_handler = handler.into_handler_with_ctx();
let wrapped_handler: ToolHandlerWithCtx = Arc::from(boxed_handler);
self.tools.insert(
name.clone(),
RegisteredTool {
tool,
handler: ToolHandlerKind::WithCtx(wrapped_handler),
},
);
self
}
pub fn tool_no_args<H, Fut, Res>(
self,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: Fn() -> Fut + Clone + MaybeSend + MaybeSync + 'static,
Fut: Future<Output = Res> + MaybeSend + 'static,
Res: IntoToolResponse + 'static,
{
self.tool::<(), NoArgs, _>(name, description, handler)
}
pub fn tool_raw<H, Fut, Res>(
self,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: Fn(serde_json::Value) -> Fut + Clone + MaybeSend + MaybeSync + 'static,
Fut: Future<Output = Res> + MaybeSend + 'static,
Res: IntoToolResponse + 'static,
{
self.tool::<serde_json::Value, RawArgs, _>(name, description, handler)
}
pub fn tool_with_ctx_no_args<H, Fut, Res>(
self,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: Fn(Arc<RequestContext>) -> Fut + Clone + MaybeSend + MaybeSync + 'static,
Fut: Future<Output = Res> + MaybeSend + 'static,
Res: IntoToolResponse + 'static,
{
self.tool_with_ctx::<(), WithCtxOnly, _>(name, description, handler)
}
pub fn tool_with_ctx_raw<H, Fut, Res>(
self,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: Fn(Arc<RequestContext>, serde_json::Value) -> Fut
+ Clone
+ MaybeSend
+ MaybeSync
+ 'static,
Fut: Future<Output = Res> + MaybeSend + 'static,
Res: IntoToolResponse + 'static,
{
self.tool_with_ctx::<serde_json::Value, WithCtxRaw, _>(name, description, handler)
}
pub fn resource<H, M>(
mut self,
uri: impl Into<String>,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: IntoResourceHandler<M>,
{
let uri = uri.into();
let name = name.into();
let description = description.into();
let resource = Resource {
uri: uri.clone(),
name,
description: Some(description),
title: None,
icon: None,
mime_type: None,
size: None,
annotations: None,
};
let boxed_handler = handler.into_handler();
let wrapped_handler: ResourceHandlerFn = Arc::from(boxed_handler);
self.resources.insert(
uri.clone(),
RegisteredResource {
resource,
handler: ResourceHandlerKind::NoCtx(wrapped_handler),
},
);
self
}
pub fn resource_with_ctx<H, M>(
mut self,
uri: impl Into<String>,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: IntoResourceHandlerWithCtx<M>,
{
let uri = uri.into();
let name = name.into();
let description = description.into();
let resource = Resource {
uri: uri.clone(),
name,
description: Some(description),
title: None,
icon: None,
mime_type: None,
size: None,
annotations: None,
};
let boxed_handler = handler.into_handler_with_ctx();
let wrapped_handler: ResourceHandlerWithCtxFn = Arc::from(boxed_handler);
self.resources.insert(
uri.clone(),
RegisteredResource {
resource,
handler: ResourceHandlerKind::WithCtx(wrapped_handler),
},
);
self
}
pub fn resource_template<H, M>(
mut self,
uri_template: impl Into<String>,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: IntoResourceHandler<M>,
{
let uri_template = uri_template.into();
let name = name.into();
let description = description.into();
let template = ResourceTemplate {
uri_template: uri_template.clone(),
name,
description: Some(description),
title: None,
icon: None,
mime_type: None,
annotations: None,
};
let boxed_handler = handler.into_handler();
let wrapped_handler: ResourceHandlerFn = Arc::from(boxed_handler);
self.resource_templates.insert(
uri_template.clone(),
RegisteredResourceTemplate {
template,
handler: ResourceHandlerKind::NoCtx(wrapped_handler),
},
);
self
}
pub fn resource_template_with_ctx<H, M>(
mut self,
uri_template: impl Into<String>,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: IntoResourceHandlerWithCtx<M>,
{
let uri_template = uri_template.into();
let name = name.into();
let description = description.into();
let template = ResourceTemplate {
uri_template: uri_template.clone(),
name,
description: Some(description),
title: None,
icon: None,
mime_type: None,
annotations: None,
};
let boxed_handler = handler.into_handler_with_ctx();
let wrapped_handler: ResourceHandlerWithCtxFn = Arc::from(boxed_handler);
self.resource_templates.insert(
uri_template.clone(),
RegisteredResourceTemplate {
template,
handler: ResourceHandlerKind::WithCtx(wrapped_handler),
},
);
self
}
pub fn prompt<A, M, H>(
mut self,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: IntoPromptHandler<A, M>,
{
let name = name.into();
let description = description.into();
let arguments = H::arguments();
let prompt = Prompt {
name: name.clone(),
description: Some(description),
title: None,
icon: None,
arguments: if arguments.is_empty() {
None
} else {
Some(arguments)
},
};
let boxed_handler = handler.into_handler();
let wrapped_handler: PromptHandlerFn = Arc::from(boxed_handler);
self.prompts.insert(
name.clone(),
RegisteredPrompt {
prompt,
handler: PromptHandlerKind::NoCtx(wrapped_handler),
},
);
self
}
pub fn prompt_with_ctx<A, M, H>(
mut self,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: IntoPromptHandlerWithCtx<A, M>,
{
let name = name.into();
let description = description.into();
let arguments = H::arguments();
let prompt = Prompt {
name: name.clone(),
description: Some(description),
title: None,
icon: None,
arguments: if arguments.is_empty() {
None
} else {
Some(arguments)
},
};
let boxed_handler = handler.into_handler_with_ctx();
let wrapped_handler: PromptHandlerWithCtxFn = Arc::from(boxed_handler);
self.prompts.insert(
name.clone(),
RegisteredPrompt {
prompt,
handler: PromptHandlerKind::WithCtx(wrapped_handler),
},
);
self
}
pub fn prompt_no_args<H, Fut, Res>(
self,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: Fn() -> Fut + Clone + MaybeSend + MaybeSync + 'static,
Fut: Future<Output = Res> + MaybeSend + 'static,
Res: IntoPromptResponse + 'static,
{
self.prompt::<(), PromptNoArgs, _>(name, description, handler)
}
pub fn prompt_with_ctx_no_args<H, Fut, Res>(
self,
name: impl Into<String>,
description: impl Into<String>,
handler: H,
) -> Self
where
H: Fn(Arc<RequestContext>) -> Fut + Clone + MaybeSend + MaybeSync + 'static,
Fut: Future<Output = Res> + MaybeSend + 'static,
Res: IntoPromptResponse + 'static,
{
self.prompt_with_ctx::<(), PromptWithCtxNoArgs, _>(name, description, handler)
}
pub fn build(self) -> McpServer {
let capabilities = ServerCapabilities {
experimental: None,
logging: None,
tasks: None,
prompts: if self.prompts.is_empty() {
None
} else {
Some(turbomcp_core::types::capabilities::PromptsCapability {
list_changed: Some(false),
})
},
resources: if self.resources.is_empty() && self.resource_templates.is_empty() {
None
} else {
Some(turbomcp_core::types::capabilities::ResourcesCapability {
subscribe: Some(false),
list_changed: Some(false),
})
},
tools: if self.tools.is_empty() {
None
} else {
Some(turbomcp_core::types::capabilities::ToolsCapability {
list_changed: Some(false),
})
},
};
let server_info = Implementation {
name: self.name,
title: None,
description: self.description,
version: self.version,
icon: None,
};
McpServer {
server_info,
capabilities,
tools: self.tools,
resources: self.resources,
resource_templates: self.resource_templates,
prompts: self.prompts,
instructions: self.instructions,
}
}
}
#[derive(Clone)]
pub struct McpServer {
pub(crate) server_info: Implementation,
pub(crate) capabilities: ServerCapabilities,
pub(crate) tools: HashMap<String, RegisteredTool>,
pub(crate) resources: HashMap<String, RegisteredResource>,
pub(crate) resource_templates: HashMap<String, RegisteredResourceTemplate>,
pub(crate) prompts: HashMap<String, RegisteredPrompt>,
pub(crate) instructions: Option<String>,
}
impl McpServer {
pub fn builder(name: impl Into<String>, version: impl Into<String>) -> McpServerBuilder {
McpServerBuilder::new(name, version)
}
pub async fn handle(&self, req: worker::Request) -> worker::Result<worker::Response> {
McpHandler::new(self).handle(req).await
}
pub fn tools(&self) -> Vec<&Tool> {
self.tools.values().map(|r| &r.tool).collect()
}
pub fn resources(&self) -> Vec<&Resource> {
self.resources.values().map(|r| &r.resource).collect()
}
pub fn resource_templates(&self) -> Vec<&ResourceTemplate> {
self.resource_templates
.values()
.map(|r| &r.template)
.collect()
}
pub fn prompts(&self) -> Vec<&Prompt> {
self.prompts.values().map(|r| &r.prompt).collect()
}
pub(crate) async fn call_tool_internal(
&self,
name: &str,
args: serde_json::Value,
ctx: Arc<RequestContext>,
) -> Result<ToolResult, String> {
let registered = self
.tools
.get(name)
.ok_or_else(|| format!("Tool not found: {}", name))?;
let result = match ®istered.handler {
ToolHandlerKind::NoCtx(handler) => handler(args).await,
ToolHandlerKind::WithCtx(handler) => handler(ctx, args).await,
};
Ok(result)
}
pub(crate) async fn read_resource_internal(
&self,
uri: &str,
ctx: Arc<RequestContext>,
) -> Result<ResourceResult, String> {
if let Some(registered) = self.resources.get(uri) {
return match ®istered.handler {
ResourceHandlerKind::NoCtx(handler) => handler(uri.to_string()).await,
ResourceHandlerKind::WithCtx(handler) => handler(ctx, uri.to_string()).await,
};
}
for (template_uri, registered) in &self.resource_templates {
if Self::matches_template(template_uri, uri) {
return match ®istered.handler {
ResourceHandlerKind::NoCtx(handler) => handler(uri.to_string()).await,
ResourceHandlerKind::WithCtx(handler) => handler(ctx, uri.to_string()).await,
};
}
}
Err(format!("Resource not found: {}", uri))
}
pub(crate) async fn get_prompt_internal(
&self,
name: &str,
args: Option<serde_json::Value>,
ctx: Arc<RequestContext>,
) -> Result<PromptResult, String> {
let registered = self
.prompts
.get(name)
.ok_or_else(|| format!("Prompt not found: {}", name))?;
match ®istered.handler {
PromptHandlerKind::NoCtx(handler) => handler(args).await,
PromptHandlerKind::WithCtx(handler) => handler(ctx, args).await,
}
}
fn matches_template(template: &str, uri: &str) -> bool {
let template_parts: Vec<&str> = template.split('/').collect();
let uri_parts: Vec<&str> = uri.split('/').collect();
if template_parts.len() != uri_parts.len() {
return false;
}
for (t, u) in template_parts.iter().zip(uri_parts.iter()) {
if t.starts_with('{') && t.ends_with('}') {
continue;
}
if t != u {
return false;
}
}
true
}
}