Skip to main content

Module mcp

Module mcp 

Source
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§

McpRegistryExt
Extension trait for registering MCP services with a ToolRegistry.
McpService
Minimal contract for MCP-like tool sources.