use super::*;
#[derive(Clone, Default)]
pub struct McpToolRegistry {
tools: BTreeMap<String, Arc<dyn ToolExecutor>>,
}
impl McpToolRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_tool<Call>(
&mut self,
definition: ToolDefinition,
call: Call,
) -> Result<(), McpToolError>
where
Call: Fn(McpToolCall) -> ToolCallResult + Send + Sync + 'static,
{
let name = definition.name.to_string();
validate_tool_definition(&definition)?;
if self.tools.contains_key(&name) {
return Err(McpToolError::duplicate_tool(name));
}
self.tools.insert(
name,
Arc::new(RegisteredTool {
definition,
call: Arc::new(move |arguments| Box::pin(std::future::ready(call(arguments)))),
}),
);
Ok(())
}
pub fn add_typed_tool<Input, Call>(
&mut self,
definition: McpTypedTool<Input>,
call: Call,
) -> Result<(), McpToolError>
where
Input: McpToolInput,
Call: Fn(Input) -> ToolCallResult + Send + Sync + 'static,
{
self.add_tool(definition.into_definition(), move |tool_call| {
let input = match Input::from_tool_call(tool_call) {
Ok(input) => input,
Err(error) => return tool_error_result_for(error),
};
call(input)
})
}
pub fn add_tool_async<Call, Fut>(
&mut self,
definition: ToolDefinition,
call: Call,
) -> Result<(), McpToolError>
where
Call: Fn(McpToolCall) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ToolCallResult> + Send + 'static,
{
let name = definition.name.to_string();
validate_tool_definition(&definition)?;
if self.tools.contains_key(&name) {
return Err(McpToolError::duplicate_tool(name));
}
self.tools.insert(
name,
Arc::new(RegisteredTool {
definition,
call: Arc::new(move |arguments| Box::pin(call(arguments))),
}),
);
Ok(())
}
pub fn add_typed_tool_async<Input, Call, Fut>(
&mut self,
definition: McpTypedTool<Input>,
call: Call,
) -> Result<(), McpToolError>
where
Input: McpToolInput,
Call: Fn(Input) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ToolCallResult> + Send + 'static,
{
self.add_tool_async(definition.into_definition(), move |tool_call| {
let input = Input::from_tool_call(tool_call);
let future = match input {
Ok(input) => call(input),
Err(error) => {
return Box::pin(std::future::ready(tool_error_result_for(error)))
as ToolFuture;
},
};
Box::pin(future) as ToolFuture
})
}
#[must_use]
pub fn list_tools(&self) -> Vec<ToolDefinition> {
self.tools
.values()
.map(|executor| executor.definition())
.collect()
}
#[must_use]
pub fn contains_tool(&self, name: &str) -> bool {
self.tools.contains_key(name)
}
#[must_use]
pub fn tool_count(&self) -> usize {
self.tools.len()
}
#[must_use]
pub fn call_tool(&self, name: &str, arguments: Option<Value>) -> ToolCallResult {
let call = match McpToolCall::from_value(arguments) {
Ok(call) => call,
Err(error) => return tool_error_result_for(error),
};
let (definition, call) = match self.resolve_tool(name, call) {
Ok(resolved) => resolved,
Err(error) => return tool_error_result_for(error),
};
validate_tool_call_result(
name,
definition.output_schema.as_deref(),
block_on_tool_future(call),
)
}
pub async fn call_tool_async(&self, name: &str, arguments: Option<Value>) -> ToolCallResult {
let call = match McpToolCall::from_value(arguments) {
Ok(call) => call,
Err(error) => return tool_error_result_for(error),
};
let (definition, call) = match self.resolve_tool(name, call) {
Ok(resolved) => resolved,
Err(error) => return tool_error_result_for(error),
};
validate_tool_call_result(name, definition.output_schema.as_deref(), call.await)
}
fn definition(&self, name: &str) -> Option<ToolDefinition> {
self.tools.get(name).map(|executor| executor.definition())
}
fn resolve_tool(
&self,
name: &str,
call: McpToolCall,
) -> Result<(ToolDefinition, ToolFuture), McpToolError> {
match self.tools.get(name) {
Some(executor) => Ok((executor.definition(), executor.call(call))),
None => Err(McpToolError::UnknownTool {
name: name.to_string(),
}),
}
}
}
#[derive(Clone)]
pub struct McpServer {
pub(crate) server_name: Cow<'static, str>,
pub(crate) server_version: Cow<'static, str>,
tools: McpToolRegistry,
resources: BTreeMap<String, Arc<dyn ResourceReader>>,
resource_templates: Vec<ResourceTemplate>,
prompts: BTreeMap<String, Arc<dyn PromptExecutor>>,
}
impl McpServer {
pub fn new(
server_name: impl Into<Cow<'static, str>>,
server_version: impl Into<Cow<'static, str>>,
) -> Self {
Self {
server_name: server_name.into(),
server_version: server_version.into(),
tools: McpToolRegistry::new(),
resources: BTreeMap::new(),
resource_templates: Vec::new(),
prompts: BTreeMap::new(),
}
}
pub fn builder(
server_name: impl Into<Cow<'static, str>>,
server_version: impl Into<Cow<'static, str>>,
) -> McpServerBuilder {
McpServerBuilder::new(server_name, server_version)
}
#[must_use]
pub fn from_tool_registry(
server_name: impl Into<Cow<'static, str>>,
server_version: impl Into<Cow<'static, str>>,
tools: McpToolRegistry,
) -> Self {
Self {
tools,
..Self::new(server_name, server_version)
}
}
#[must_use]
pub fn tool_registry(&self) -> &McpToolRegistry {
&self.tools
}
pub fn tool_registry_mut(&mut self) -> &mut McpToolRegistry {
&mut self.tools
}
#[must_use]
pub fn into_tool_registry(self) -> McpToolRegistry {
self.tools
}
pub fn add_tool<Call>(
&mut self,
definition: ToolDefinition,
call: Call,
) -> Result<(), McpToolError>
where
Call: Fn(McpToolCall) -> ToolCallResult + Send + Sync + 'static,
{
self.tools.add_tool(definition, call)
}
pub fn add_typed_tool<Input, Call>(
&mut self,
definition: McpTypedTool<Input>,
call: Call,
) -> Result<(), McpToolError>
where
Input: McpToolInput,
Call: Fn(Input) -> ToolCallResult + Send + Sync + 'static,
{
self.tools.add_typed_tool(definition, call)
}
pub fn add_tool_async<Call, Fut>(
&mut self,
definition: ToolDefinition,
call: Call,
) -> Result<(), McpToolError>
where
Call: Fn(McpToolCall) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ToolCallResult> + Send + 'static,
{
self.tools.add_tool_async(definition, call)
}
pub fn add_typed_tool_async<Input, Call, Fut>(
&mut self,
definition: McpTypedTool<Input>,
call: Call,
) -> Result<(), McpToolError>
where
Input: McpToolInput,
Call: Fn(Input) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ToolCallResult> + Send + 'static,
{
self.tools.add_typed_tool_async(definition, call)
}
#[must_use]
pub fn list_tools(&self) -> Vec<ToolDefinition> {
self.tools.list_tools()
}
#[must_use]
pub fn contains_tool(&self, name: &str) -> bool {
self.tools.contains_tool(name)
}
#[must_use]
pub fn tool_count(&self) -> usize {
self.tools.tool_count()
}
pub fn add_resource<Read>(
&mut self,
definition: ResourceDefinition,
read: Read,
) -> Result<(), McpToolError>
where
Read: Fn() -> ReadResourceResult + Send + Sync + 'static,
{
self.add_resource_async(definition, move || {
let result = read();
std::future::ready(Ok(result))
})
}
pub fn add_resource_async<Read, Fut>(
&mut self,
definition: ResourceDefinition,
read: Read,
) -> Result<(), McpToolError>
where
Read: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<ReadResourceResult, ErrorData>> + Send + 'static,
{
let uri = definition.uri.clone();
validate_resource_definition(&definition)?;
if self.resources.contains_key(&uri) {
return Err(McpToolError::duplicate_resource(uri));
}
self.resources.insert(
uri,
Arc::new(RegisteredResource {
definition,
read: Arc::new(move || Box::pin(read())),
}),
);
Ok(())
}
pub fn add_resource_template(
&mut self,
definition: ResourceTemplateDefinition,
) -> Result<(), McpToolError> {
validate_resource_template(&definition)?;
self.resource_templates.push(definition);
Ok(())
}
pub fn list_resources(&self) -> Vec<ResourceDefinition> {
self.resources
.values()
.map(|resource| resource.definition())
.collect()
}
pub fn list_resource_templates(&self) -> Vec<ResourceTemplateDefinition> {
self.resource_templates.clone()
}
pub fn contains_resource(&self, uri: &str) -> bool {
self.resources.contains_key(uri)
}
pub fn resource_count(&self) -> usize {
self.resources.len()
}
pub fn add_prompt<Get>(
&mut self,
definition: PromptDefinition,
get: Get,
) -> Result<(), McpToolError>
where
Get: Fn(Option<JsonObject>) -> GetPromptResult + Send + Sync + 'static,
{
self.add_prompt_async(definition, move |arguments| {
let result = get(arguments);
std::future::ready(Ok(result))
})
}
pub fn add_prompt_async<Get, Fut>(
&mut self,
definition: PromptDefinition,
get: Get,
) -> Result<(), McpToolError>
where
Get: Fn(Option<JsonObject>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<GetPromptResult, ErrorData>> + Send + 'static,
{
let name = definition.name.clone();
validate_prompt_definition(&definition)?;
if self.prompts.contains_key(&name) {
return Err(McpToolError::duplicate_prompt(name));
}
self.prompts.insert(
name,
Arc::new(RegisteredPrompt {
definition,
get: Arc::new(move |arguments| Box::pin(get(arguments))),
}),
);
Ok(())
}
pub fn list_prompts(&self) -> Vec<PromptDefinition> {
self.prompts
.values()
.map(|prompt| prompt.definition())
.collect()
}
pub fn contains_prompt(&self, name: &str) -> bool {
self.prompts.contains_key(name)
}
pub fn prompt_count(&self) -> usize {
self.prompts.len()
}
#[must_use]
pub fn call_tool(&self, name: &str, arguments: Option<Value>) -> ToolCallResult {
self.tools.call_tool(name, arguments)
}
pub async fn call_tool_async(&self, name: &str, arguments: Option<Value>) -> ToolCallResult {
self.tools.call_tool_async(name, arguments).await
}
pub async fn serve_stdio(self) -> ServeStdioResult {
let service = self.serve(stdio()).await?;
service.waiting().await?;
Ok(())
}
pub fn serve_stdio_blocking(self) -> ServeStdioResult {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?;
runtime.block_on(self.serve_stdio())
}
fn result_meta(&self) -> MetaObject {
let mut meta = MetaObject::default();
meta.0.insert(
"io.modelcontextprotocol/serverInfo".to_string(),
json!({
"name": self.server_name,
"version": self.server_version,
}),
);
meta
}
fn prepare_cacheable_result(
&self,
protocol_version: Option<&ProtocolVersion>,
ttl_ms: &mut Option<u64>,
cache_scope: &mut Option<CacheScope>,
meta: &mut Option<MetaObject>,
) {
if protocol_version.is_some_and(|version| version >= &ProtocolVersion::V_2026_07_28) {
*ttl_ms = Some(0);
*cache_scope = Some(CacheScope::Private);
}
*meta = Some(self.result_meta());
}
}
#[derive(Clone)]
pub struct McpServerBuilder {
server: Result<McpServer, McpToolError>,
}
impl McpServerBuilder {
pub fn new(
server_name: impl Into<Cow<'static, str>>,
server_version: impl Into<Cow<'static, str>>,
) -> Self {
Self {
server: Ok(McpServer::new(server_name, server_version)),
}
}
pub fn register<Register>(mut self, register: Register) -> Self
where
Register: FnOnce(&mut McpServer) -> Result<(), McpToolError>,
{
if let Ok(server) = self.server.as_mut()
&& let Err(error) = register(server)
{
self.server = Err(error);
}
self
}
pub fn tool<Call>(self, definition: ToolDefinition, call: Call) -> Self
where
Call: Fn(McpToolCall) -> ToolCallResult + Send + Sync + 'static,
{
self.register(move |server| server.add_tool(definition, call))
}
pub fn typed_tool<Input, Call>(self, definition: McpTypedTool<Input>, call: Call) -> Self
where
Input: McpToolInput,
Call: Fn(Input) -> ToolCallResult + Send + Sync + 'static,
{
self.register(move |server| server.add_typed_tool(definition, call))
}
pub fn tool_async<Call, Fut>(self, definition: ToolDefinition, call: Call) -> Self
where
Call: Fn(McpToolCall) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ToolCallResult> + Send + 'static,
{
self.register(move |server| server.add_tool_async(definition, call))
}
pub fn typed_tool_async<Input, Call, Fut>(
self,
definition: McpTypedTool<Input>,
call: Call,
) -> Self
where
Input: McpToolInput,
Call: Fn(Input) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ToolCallResult> + Send + 'static,
{
self.register(move |server| server.add_typed_tool_async(definition, call))
}
pub fn resource<Read>(self, definition: ResourceDefinition, read: Read) -> Self
where
Read: Fn() -> ReadResourceResult + Send + Sync + 'static,
{
self.register(move |server| server.add_resource(definition, read))
}
pub fn resource_async<Read, Fut>(self, definition: ResourceDefinition, read: Read) -> Self
where
Read: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<ReadResourceResult, ErrorData>> + Send + 'static,
{
self.register(move |server| server.add_resource_async(definition, read))
}
pub fn resource_template(self, definition: ResourceTemplateDefinition) -> Self {
self.register(move |server| server.add_resource_template(definition))
}
pub fn prompt<Get>(self, definition: PromptDefinition, get: Get) -> Self
where
Get: Fn(Option<JsonObject>) -> GetPromptResult + Send + Sync + 'static,
{
self.register(move |server| server.add_prompt(definition, get))
}
pub fn prompt_async<Get, Fut>(self, definition: PromptDefinition, get: Get) -> Self
where
Get: Fn(Option<JsonObject>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<GetPromptResult, ErrorData>> + Send + 'static,
{
self.register(move |server| server.add_prompt_async(definition, get))
}
pub fn build(self) -> Result<McpServer, McpToolError> {
self.server
}
pub async fn serve_stdio(self) -> ServeStdioResult {
self.build()?.serve_stdio().await
}
pub fn serve_stdio_blocking(self) -> ServeStdioResult {
self.build()?.serve_stdio_blocking()
}
}
impl ServerHandler for McpServer {
fn get_info(&self) -> ServerInfo {
let mut capabilities = ServerCapabilities::builder().enable_tools().build();
capabilities.resources = (!self.resources.is_empty()
|| !self.resource_templates.is_empty())
.then(Default::default);
capabilities.prompts = (!self.prompts.is_empty()).then(Default::default);
ServerInfo::new(capabilities)
.with_protocol_version(ProtocolVersion::V_2026_07_28)
.with_server_info(Implementation::new(
self.server_name.clone(),
self.server_version.clone(),
))
}
fn list_tools(
&self,
_request: Option<PaginatedRequestParams>,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<ListToolsResult, ErrorData>> + MaybeSendFuture + '_ {
let mut result = ListToolsResult::with_all_items(self.list_tools());
self.prepare_cacheable_result(
context.protocol_version().as_ref(),
&mut result.ttl_ms,
&mut result.cache_scope,
&mut result.meta,
);
std::future::ready(Ok(result))
}
fn get_tool(&self, name: &str) -> Option<Tool> {
self.tools.definition(name)
}
fn call_tool(
&self,
request: CallToolRequestParams,
_context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<CallToolResponse, ErrorData>> + MaybeSendFuture + '_ {
let name = request.name.to_string();
let call = McpToolCall::new(request.arguments.unwrap_or_default());
let call = match self.tools.resolve_tool(&name, call) {
Ok((definition, call)) => {
let output_schema = definition.output_schema;
let name = name.clone();
Box::pin(async move {
validate_tool_call_result(&name, output_schema.as_deref(), call.await)
}) as ToolFuture
},
Err(error) => {
let result = tool_error_result_for(error);
Box::pin(std::future::ready(result))
},
};
let result_meta = self.result_meta();
async move {
let mut result = call.await;
result.meta = Some(result_meta);
Ok(result.into())
}
}
fn list_resources(
&self,
_request: Option<PaginatedRequestParams>,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<ListResourcesResult, ErrorData>> + MaybeSendFuture + '_ {
let mut result = ListResourcesResult::with_all_items(self.list_resources());
self.prepare_cacheable_result(
context.protocol_version().as_ref(),
&mut result.ttl_ms,
&mut result.cache_scope,
&mut result.meta,
);
std::future::ready(Ok(result))
}
fn list_resource_templates(
&self,
_request: Option<PaginatedRequestParams>,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<ListResourceTemplatesResult, ErrorData>> + MaybeSendFuture + '_
{
let mut result =
ListResourceTemplatesResult::with_all_items(self.list_resource_templates());
self.prepare_cacheable_result(
context.protocol_version().as_ref(),
&mut result.ttl_ms,
&mut result.cache_scope,
&mut result.meta,
);
std::future::ready(Ok(result))
}
fn read_resource(
&self,
request: ReadResourceRequestParams,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<ReadResourceResponse, ErrorData>> + MaybeSendFuture + '_ {
let uri = request.uri;
let read = self.resources.get(&uri).map(|resource| resource.read());
let protocol_version = context.protocol_version();
let result_meta = self.result_meta();
async move {
match read {
Some(read) => {
let mut result = read.await?;
if protocol_version
.as_ref()
.is_some_and(|version| version >= &ProtocolVersion::V_2026_07_28)
{
result.ttl_ms = Some(0);
result.cache_scope = Some(CacheScope::Private);
}
result.meta = Some(result_meta);
Ok(result.into())
},
None => Err(ErrorData::resource_not_found(
format!("resource `{uri}` not found"),
Some(McpToolError::unknown_resource(uri).to_structured_value()),
)),
}
}
}
fn list_prompts(
&self,
_request: Option<PaginatedRequestParams>,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<ListPromptsResult, ErrorData>> + MaybeSendFuture + '_ {
let mut result = ListPromptsResult::with_all_items(self.list_prompts());
self.prepare_cacheable_result(
context.protocol_version().as_ref(),
&mut result.ttl_ms,
&mut result.cache_scope,
&mut result.meta,
);
std::future::ready(Ok(result))
}
fn get_prompt(
&self,
request: GetPromptRequestParams,
_context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<GetPromptResponse, ErrorData>> + MaybeSendFuture + '_ {
let name = request.name;
let get = self
.prompts
.get(&name)
.map(|prompt| prompt.get(request.arguments));
let result_meta = self.result_meta();
async move {
match get {
Some(get) => {
let mut result = get.await?;
result.meta = Some(result_meta);
Ok(result.into())
},
None => Err(ErrorData::invalid_params(
format!("prompt `{name}` not found"),
Some(McpToolError::unknown_prompt(name).to_structured_value()),
)),
}
}
}
}
trait ToolExecutor: Send + Sync {
fn definition(&self) -> ToolDefinition;
fn call(&self, call: McpToolCall) -> ToolFuture;
}
struct RegisteredTool {
definition: ToolDefinition,
call: Arc<dyn Fn(McpToolCall) -> ToolFuture + Send + Sync>,
}
impl ToolExecutor for RegisteredTool {
fn definition(&self) -> ToolDefinition {
self.definition.clone()
}
fn call(&self, call: McpToolCall) -> ToolFuture {
(self.call)(call)
}
}
trait ResourceReader: Send + Sync {
fn definition(&self) -> ResourceDefinition;
fn read(&self) -> ResourceFuture;
}
struct RegisteredResource {
definition: ResourceDefinition,
read: Arc<dyn Fn() -> ResourceFuture + Send + Sync>,
}
impl ResourceReader for RegisteredResource {
fn definition(&self) -> ResourceDefinition {
self.definition.clone()
}
fn read(&self) -> ResourceFuture {
(self.read)()
}
}
trait PromptExecutor: Send + Sync {
fn definition(&self) -> PromptDefinition;
fn get(&self, arguments: Option<JsonObject>) -> PromptFuture;
}
struct RegisteredPrompt {
definition: PromptDefinition,
get: Arc<dyn Fn(Option<JsonObject>) -> PromptFuture + Send + Sync>,
}
impl PromptExecutor for RegisteredPrompt {
fn definition(&self) -> PromptDefinition {
self.definition.clone()
}
fn get(&self, arguments: Option<JsonObject>) -> PromptFuture {
(self.get)(arguments)
}
}
fn validate_tool_call_result(
tool_name: &str,
output_schema: Option<&JsonObject>,
result: ToolCallResult,
) -> ToolCallResult {
let Some(output_schema) = output_schema else {
return result;
};
if result.is_error == Some(true) {
return result;
}
let Some(structured_content) = result.structured_content.as_ref() else {
return tool_error_result_for(McpToolError::invalid_tool_output(
tool_name,
"tool declares output_schema but returned no structured_content",
));
};
let output_schema = Value::Object(output_schema.clone());
if let Err(error) = validate_value_against_closed_schema(
"structured_content",
&output_schema,
structured_content,
) {
return tool_error_result_for(McpToolError::invalid_tool_output(
tool_name,
error.to_string(),
));
}
result
}
fn block_on_tool_future(future: ToolFuture) -> ToolCallResult {
let join = std::thread::spawn(move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(runtime.block_on(future))
})
.join();
match join {
Ok(Ok(result)) => result,
Ok(Err(error)) => tool_error_result_for(McpToolError::handler(format!(
"failed to run async tool handler: {error}"
))),
Err(_) => {
tool_error_result_for(McpToolError::handler("async tool handler runtime panicked"))
},
}
}