use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use llm_tool::{PromptRegistry, ResourceRegistry, ToolContext, ToolDefinition, ToolRegistry};
use tracing::warn;
#[cfg(test)]
pub(crate) use crate::protocol::{self, *};
use crate::protocol::{McpToolSchema, ToolsListResult};
mod builder;
mod dispatch;
mod transport;
pub use builder::McpServerBuilder;
pub use dispatch::RpcOutcome;
pub use transport::Transport;
#[derive(Clone)]
pub struct McpServer {
name: String,
version: String,
instructions: Option<String>,
registry: Arc<ToolRegistry>,
context: Arc<ToolContext>,
cached_tools_list: Arc<serde_json::Value>,
prompts: Arc<PromptRegistry>,
resources: Arc<ResourceRegistry>,
per_connection_identity: bool,
registry_factory: Option<Arc<dyn RegistryFactory>>,
caller_views: Arc<Mutex<HashMap<String, CallerView>>>,
}
pub trait RegistryFactory: Send + Sync {
fn registry_for(&self, caller: Option<&str>) -> ToolRegistry;
}
impl<F> RegistryFactory for F
where
F: Fn(Option<&str>) -> ToolRegistry + Send + Sync,
{
fn registry_for(&self, caller: Option<&str>) -> ToolRegistry {
self(caller)
}
}
#[derive(Clone)]
struct CallerView {
registry: Arc<ToolRegistry>,
tools_list: Arc<serde_json::Value>,
}
#[derive(Default)]
pub struct Connection {
ctx: Option<ToolContext>,
view: Option<CallerView>,
}
impl Connection {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl McpServer {
#[must_use]
pub fn new(
name: impl Into<String>,
version: impl Into<String>,
registry: ToolRegistry,
) -> Self {
McpServerBuilder::new(name, version, registry).build()
}
#[must_use]
pub fn builder(
name: impl Into<String>,
version: impl Into<String>,
registry: ToolRegistry,
) -> McpServerBuilder {
McpServerBuilder::new(name, version, registry)
}
#[must_use]
pub fn with_context(mut self, context: ToolContext) -> Self {
self.context = Arc::new(context);
self
}
#[must_use]
pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
self.instructions = Some(instructions.into());
self
}
#[must_use]
pub const fn with_per_connection_identity(mut self, enabled: bool) -> Self {
self.per_connection_identity = enabled;
self
}
#[must_use]
pub fn with_registry_factory<F: RegistryFactory + 'static>(mut self, factory: F) -> Self {
let factory: Arc<dyn RegistryFactory> = Arc::new(factory);
let registry = Arc::new(factory.registry_for(None));
let cached_tools_list = Arc::new(build_tools_list_value(®istry));
let mut views = HashMap::new();
views.insert(
String::new(),
CallerView {
registry: Arc::clone(®istry),
tools_list: Arc::clone(&cached_tools_list),
},
);
self.registry = registry;
self.cached_tools_list = cached_tools_list;
self.registry_factory = Some(factory);
self.caller_views = Arc::new(Mutex::new(views));
self
}
fn lock_views(&self) -> std::sync::MutexGuard<'_, HashMap<String, CallerView>> {
self.caller_views.lock().unwrap_or_else(|poisoned| {
warn!("caller-view cache mutex was poisoned; recovering guard");
poisoned.into_inner()
})
}
fn resolve_view(&self, caller: Option<&str>) -> Option<CallerView> {
let factory = self.registry_factory.as_ref()?;
let key = caller.unwrap_or("");
if let Some(view) = self.lock_views().get(key) {
return Some(view.clone());
}
let registry = Arc::new(factory.registry_for(caller));
let tools_list = Arc::new(build_tools_list_value(®istry));
let view = CallerView {
registry,
tools_list,
};
Some(
self.lock_views()
.entry(key.to_owned())
.or_insert(view)
.clone(),
)
}
#[must_use]
pub fn registry(&self) -> &ToolRegistry {
&self.registry
}
}
fn build_tools_list_value(registry: &ToolRegistry) -> serde_json::Value {
let tools = registry
.definitions()
.iter()
.map(definition_to_mcp_schema)
.collect();
let list = ToolsListResult { tools };
serde_json::to_value(list).expect("tools/list schema must be JSON-serializable")
}
fn definition_to_mcp_schema(def: &ToolDefinition) -> McpToolSchema {
McpToolSchema {
name: def.name.clone(),
description: def.description.clone(),
input_schema: def.parameter_schema.clone(),
}
}
#[cfg(test)]
mod tests;