Skip to main content

Overview

The MCP (Model Context Protocol) integration allows IronClaw to connect to external tool servers that provide additional capabilities through a standardized protocol. Supports both:
  • Local servers: Unauthenticated servers running on localhost
  • Hosted servers: OAuth-authenticated remote servers

McpClient

Client for connecting to MCP servers.

Constructor (Simple)

src/tools/mcp/client.rs
pub fn new(base_url: impl Into<String>) -> Self
Creates a simple MCP client without authentication (for local servers).
base_url
String
required
Base URL of the MCP server (e.g., “http://localhost:8080”)
Example:
let client = McpClient::new("http://localhost:8080");

Constructor (Authenticated)

src/tools/mcp/client.rs
pub fn new_authenticated(
    config: McpServerConfig,
    session_manager: Arc<McpSessionManager>,
    secrets: Arc<dyn SecretsStore + Send + Sync>,
    user_id: impl Into<String>,
) -> Self
Creates an authenticated MCP client for hosted servers.
config
McpServerConfig
required
Server configuration including OAuth settings
session_manager
Arc<McpSessionManager>
required
Session manager for token storage
secrets
Arc<dyn SecretsStore>
required
Secrets store for credential management
user_id
String
required
User ID for scoped authentication

create_tools

src/tools/mcp/client.rs
pub async fn create_tools(&self) -> Result<Vec<Arc<dyn Tool>>, McpError>
Lists available tools from the server and creates Tool implementations for each. Returns: Result<Vec<Arc<dyn Tool>>, McpError> - List of tools ready to register Example:
let tools = client.create_tools().await?;
for tool in tools {
    registry.register(tool).await;
}

initialize

src/tools/mcp/client.rs
pub async fn initialize(&self) -> Result<InitializeResult, McpError>
Initializes the connection to the MCP server. Returns: Server capabilities and metadata

list_tools

src/tools/mcp/client.rs
pub async fn list_tools(&self) -> Result<Vec<McpTool>, McpError>
Lists all tools available on the server.

call_tool

src/tools/mcp/client.rs
pub async fn call_tool(
    &self,
    name: &str,
    arguments: serde_json::Value,
) -> Result<serde_json::Value, McpError>
Calls a tool on the server.
name
&str
required
Tool name
arguments
serde_json::Value
required
Tool arguments

McpServerConfig

Configuration for an MCP server.
src/tools/mcp/config.rs
pub struct McpServerConfig {
    pub name: String,
    pub url: String,
    pub oauth: Option<OAuthConfig>,
    pub trust_level: String,
}
name
String
required
Unique server name
url
String
required
Server base URL
oauth
Option<OAuthConfig>
Optional OAuth configuration for authentication
trust_level
String
default:"untrusted"
Trust level: “trusted” or “untrusted”

OAuthConfig

OAuth authentication configuration.
src/tools/mcp/config.rs
pub struct OAuthConfig {
    pub client_id: String,
    pub auth_url: String,
    pub token_url: String,
    pub scopes: Vec<String>,
}
client_id
String
required
OAuth client ID
auth_url
String
required
Authorization endpoint URL
token_url
String
required
Token exchange endpoint URL
scopes
Vec<String>
required
OAuth scopes to request

McpServersFile

File format for MCP server configuration.
src/tools/mcp/config.rs
pub struct McpServersFile {
    pub servers: Vec<McpServerConfig>,
}

from_path

src/tools/mcp/config.rs
pub fn from_path(path: impl AsRef<Path>) -> Result<Self, ConfigError>
Loads MCP server configurations from a JSON file. Example mcp_servers.json:
{
  "servers": [
    {
      "name": "local-tools",
      "url": "http://localhost:8080",
      "trust_level": "trusted"
    },
    {
      "name": "example-hosted",
      "url": "https://mcp.example.com",
      "oauth": {
        "client_id": "your_client_id",
        "auth_url": "https://example.com/oauth/authorize",
        "token_url": "https://example.com/oauth/token",
        "scopes": ["tools:read", "tools:execute"]
      },
      "trust_level": "untrusted"
    }
  ]
}

McpTool

Tool metadata from an MCP server.
src/tools/mcp/protocol.rs
pub struct McpTool {
    pub name: String,
    pub description: String,
    pub input_schema: serde_json::Value,
}
name
String
required
Tool name
description
String
required
Human-readable description
input_schema
serde_json::Value
required
JSON Schema for tool parameters

McpSessionManager

Manages OAuth sessions and token refresh.

Constructor

src/tools/mcp/session.rs
pub fn new(store: Arc<dyn Database>) -> Self
Creates a new session manager.
store
Arc<dyn Database>
required
Database for persistent token storage

get_access_token

src/tools/mcp/session.rs
pub async fn get_access_token(
    &self,
    user_id: &str,
    server_name: &str,
) -> Result<Option<String>, SessionError>
Gets a valid access token for a server, refreshing if needed.

store_tokens

src/tools/mcp/session.rs
pub async fn store_tokens(
    &self,
    user_id: &str,
    server_name: &str,
    access_token: String,
    refresh_token: Option<String>,
    expires_in: u64,
) -> Result<(), SessionError>
Stores OAuth tokens after successful authentication.

Authentication Helpers

is_authenticated

src/tools/mcp/auth.rs
pub async fn is_authenticated(
    session_manager: &McpSessionManager,
    user_id: &str,
    server_name: &str,
) -> bool
Checks if the user has valid authentication for a server.

refresh_access_token

src/tools/mcp/auth.rs
pub async fn refresh_access_token(
    session_manager: &McpSessionManager,
    secrets: &Arc<dyn SecretsStore + Send + Sync>,
    user_id: &str,
    server_name: &str,
    config: &McpServerConfig,
) -> Result<String, AuthError>
Refreshes an expired access token.

Example: Local Server

use ironclaw::tools::mcp::McpClient;

// Connect to local server
let client = McpClient::new("http://localhost:8080");

// Initialize and list tools
let init_result = client.initialize().await?;
println!("Server: {}", init_result.server_name);

let tools = client.create_tools().await?;
for tool in tools {
    println!("Registering tool: {}", tool.name());
    registry.register(tool).await;
}

Example: Hosted Server with OAuth

use ironclaw::tools::mcp::{
    McpClient, McpServerConfig, McpSessionManager, OAuthConfig,
};
use std::sync::Arc;

// Load configuration
let config = McpServerConfig {
    name: "example-api".into(),
    url: "https://mcp.example.com".into(),
    oauth: Some(OAuthConfig {
        client_id: "your_client_id".into(),
        auth_url: "https://example.com/oauth/authorize".into(),
        token_url: "https://example.com/oauth/token".into(),
        scopes: vec!["tools:read".into(), "tools:execute".into()],
    }),
    trust_level: "untrusted".into(),
};

// Create session manager
let session_manager = Arc::new(McpSessionManager::new(db));

// Create authenticated client
let client = McpClient::new_authenticated(
    config,
    session_manager,
    secrets_store,
    "user_123",
);

// Check authentication
if !is_authenticated(&session_manager, "user_123", "example-api").await {
    // Guide user through OAuth flow
    println!("Please authenticate: {}", config.oauth.unwrap().auth_url);
    // After auth, tokens are stored automatically
}

// Use the client
let tools = client.create_tools().await?;
for tool in tools {
    registry.register(tool).await;
}

Example: Load from Configuration File

use ironclaw::tools::mcp::{McpServersFile, McpClient};

// Load servers from file
let servers_file = McpServersFile::from_path("mcp_servers.json")?;

// Connect to each server
for config in servers_file.servers {
    let client = if config.oauth.is_some() {
        McpClient::new_authenticated(
            config,
            session_manager.clone(),
            secrets.clone(),
            &user_id,
        )
    } else {
        McpClient::new(&config.url)
    };
    
    // Register tools from this server
    let tools = client.create_tools().await?;
    for tool in tools {
        registry.register(tool).await;
    }
}

Build docs developers (and LLMs) love