Expand description
MCP (Model Context Protocol) integration for ToolRegistry.
This module defines the McpService trait - a minimal contract for MCP-like
tool sources. Users implement this trait for their chosen MCP client library
(e.g., rmcp), keeping version coupling in their code rather than in llm-core.
§Design Philosophy
Rather than depending on a specific MCP client library, llm-core defines a simple trait that any MCP implementation can satisfy. This means:
- No forced dependency upgrades when MCP libraries release new versions
- Users choose their preferred MCP client and version
- Easy to mock for testing
- Simple enough to implement (~50 lines for rmcp)
§Example: Implementing for rmcp
ⓘ
use std::sync::Arc;
use rmcp::service::RunningService;
use rmcp::handler::client::ClientHandler;
use rmcp::RoleClient;
use llm_stack::{McpService, McpError, ToolDefinition, JsonSchema};
/// Adapter: wraps rmcp RunningService to implement llm-core's McpService
pub struct RmcpAdapter<S: ClientHandler> {
service: Arc<RunningService<RoleClient, S>>,
}
impl<S: ClientHandler> RmcpAdapter<S> {
pub fn new(service: Arc<RunningService<RoleClient, S>>) -> Self {
Self { service }
}
}
impl<S: ClientHandler> McpService for RmcpAdapter<S> {
async fn list_tools(&self) -> Result<Vec<ToolDefinition>, McpError> {
let tools = self.service
.list_all_tools()
.await
.map_err(|e| McpError::Protocol(e.to_string()))?;
Ok(tools.into_iter().map(|t| ToolDefinition {
name: t.name.to_string(),
description: t.description.map(|d| d.to_string()).unwrap_or_default(),
parameters: JsonSchema::new(
serde_json::to_value(&*t.input_schema).unwrap_or_default()
),
}).collect())
}
async fn call_tool(&self, name: &str, args: serde_json::Value) -> Result<String, McpError> {
use rmcp::model::{CallToolRequestParams, RawContent};
let params = CallToolRequestParams {
meta: None,
name: name.to_string().into(),
arguments: args.as_object().cloned(),
task: None,
};
let result = self.service
.call_tool(params)
.await
.map_err(|e| McpError::ToolExecution(e.to_string()))?;
if result.is_error.unwrap_or(false) {
return Err(McpError::ToolExecution(extract_text(&result.content)));
}
Ok(extract_text(&result.content))
}
}
fn extract_text(content: &[rmcp::model::Content]) -> String {
use rmcp::model::RawContent;
content.iter().map(|c| match &c.raw {
RawContent::Text(t) => t.text.clone(),
_ => "[non-text]".into(),
}).collect::<Vec<_>>().join("\n")
}§Usage
ⓘ
use std::sync::Arc;
use llm_stack::{ToolRegistry, McpRegistryExt};
// Create your MCP service (using rmcp or any other library)
let mcp_service = Arc::new(RmcpAdapter::new(rmcp_client));
// Register tools with llm-core
let mut registry = ToolRegistry::new();
registry.register_mcp_service(&mcp_service).await?;Enums§
- McpError
- Error type for MCP operations.
Traits§
- McpRegistry
Ext - Extension trait for registering MCP services with a
ToolRegistry. - McpService
- Minimal contract for MCP-like tool sources.