mcp-kit 0.4.0

Ergonomic, type-safe Rust library for building MCP servers with plugin system and real API integrations
Documentation

mcp-kit

Crates.io Documentation License: MIT CI MSRV

An ergonomic, type-safe Rust library for building Model Context Protocol (MCP) servers.

MCP enables AI assistants to securely access tools, data sources, and prompts through a standardized protocol. This library provides a modern, async-first implementation with powerful procedural macros for rapid development.


🎉 What's New in v0.4.0

🌐 WASM Plugin System

  • WebAssembly plugin support with wasmtime integration
  • Complete type safety: i32, i64, f32, f64, and string parameters
  • Automatic parameter type introspection from WASM function signatures
  • WASM linear memory operations for string handling
  • Production-ready performance (1000+ calls/second)
  • Cross-platform sandboxed execution

🧩 Enhanced Plugin System

  • Dynamic loading of tools, resources, and prompts
  • Native plugin support (.so, .dylib, .dll)
  • WASM plugin support (.wasm modules)
  • In-process plugin registration
  • Plugin configuration and lifecycle management

📦 Real API Integrations

  • GitHub Plugin - Create issues, list repos, manage PRs (4 tools)
  • Jira Plugin - Create/search issues, add comments (4 tools)
  • Confluence Plugin - Create/search wiki pages (4 tools)
  • ClickHouse Plugin - Run queries, generate reports, analytics (6 tools)
  • All with working REST API implementations!

🔧 Enhanced Developer Experience

  • Production-ready plugin examples
  • WASM plugin example with 4 demonstration modules
  • Comprehensive plugin documentation
  • Easy integration: just export API_TOKEN and run

Features

  • 🚀 Async-first — Built on Tokio for high-performance concurrent operations
  • 🛡️ Type-safe — Leverage Rust's type system with automatic JSON Schema generation
  • 🎯 Ergonomic macros#[tool], #[resource], #[prompt] attributes for minimal boilerplate
  • 🔌 Multiple transports — stdio, SSE/HTTP, Streamable HTTP, WebSocket, and HTTPS/TLS
  • 🔐 Authentication — Bearer, API Key, Basic, OAuth 2.0, and mTLS support
  • 🧩 Plugin system — Dynamic loading from native libraries (.so/.dll) and WASM modules
  • 🌐 WASM plugins — Sandboxed WebAssembly modules with full type system support
  • 📦 Real API integrations — Production-ready plugins for GitHub, Jira, and Confluence
  • 📊 Progress tracking — Report progress for long-running operations
  • 📢 Notifications — Push updates to clients (resource changes, log messages)
  • 🔄 Subscriptions — Subscribe to resource changes for real-time updates
  • Cancellation — Cancel long-running requests
  • 🤖 Sampling — Server-initiated LLM requests to clients
  • 💬 Elicitation — Request user input from clients during tool execution
  • 📁 Roots — File system sandboxing with client-provided roots
  • 🧩 Modular — Feature-gated architecture, WASM-compatible core
  • 📦 Batteries included — State management, error handling, tracing integration
  • 🎨 Flexible APIs — Choose between macro-based or manual builder patterns
  • 📡 Client SDKmcp-kit-client crate for connecting to MCP servers
  • 🌐 Gatewaymcp-kit-gateway crate for proxying/aggregating upstream MCP servers

🌟 Highlights

Plugin System — Build modular, extensible MCP servers:

McpServer::builder()
    .load_plugin("./plugins/github.so")?     // Load from file
    .with_plugin_manager(manager)            // Or use plugin manager
    .build()

Real API Integrations — Production-ready plugins included:

# WASM Plugins - sandboxed WebAssembly modules
cargo run --example wasm_plugin --features plugin,plugin-wasm

# GitHub - manage repos, issues, PRs
export GITHUB_TOKEN=ghp_xxx
cargo run --example plugin_github --features plugin

# Jira - create/search issues, add comments  
export JIRA_API_TOKEN=xxx
cargo run --example plugin_jira --features plugin

# Confluence - create/search wiki pages
export CONFLUENCE_API_TOKEN=xxx
cargo run --example plugin_confluence --features plugin

# ClickHouse - run SQL queries and generate reports
export CLICKHOUSE_URL=http://localhost:8123
cargo run --example plugin_clickhouse --features plugin

Type-Safe & Ergonomic — Minimal boilerplate with macros:

#[tool(description = "Add numbers")]
async fn add(a: f64, b: f64) -> String {
    format!("{}", a + b)
}

MCP Gateway — Aggregate tools from multiple upstream MCP servers:

use mcp_kit_gateway::{GatewayManager, UpstreamConfig, UpstreamTransport};

let mut gw = GatewayManager::new();
gw.add_upstream(UpstreamConfig {
    name: "weather".into(),
    transport: UpstreamTransport::Sse("http://localhost:3001/sse".into()),
    prefix: Some("weather".into()),
    client_name: None,
    client_version: None,
});

let server = gw.build_server(
    McpServer::builder().name("gateway").version("1.0.0")
).await?;

Installation

Add to your Cargo.toml:

[dependencies]
mcp-kit = "0.3"  # Latest with gateway support
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
schemars = "0.8"
anyhow = "1"  # For error handling

For plugin development, add:

[dependencies]
mcp-kit = { version = "0.2", features = ["plugin", "plugin-native"] }
reqwest = { version = "0.12", features = ["json"] }  # For API calls

Minimum Supported Rust Version (MSRV): 1.85


Quick Start

Using Macros (Recommended)

The fastest way to build an MCP server with automatic schema generation:

use mcp_kit::prelude::*;

/// Add two numbers
#[tool(description = "Add two numbers and return the sum")]
async fn add(a: f64, b: f64) -> String {
    format!("{}", a + b)
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    McpServer::builder()
        .name("calculator")
        .version("1.0.0")
        .tool_def(add_tool_def())  // Generated by #[tool] macro
        .build()
        .serve_stdio()
        .await?;
    Ok(())
}

Manual API

For more control over schema and behavior:

use mcp_kit::prelude::*;
use schemars::JsonSchema;
use serde::Deserialize;

#[derive(Deserialize, JsonSchema)]
struct AddInput {
    /// First operand
    a: f64,
    /// Second operand  
    b: f64,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let schema = serde_json::to_value(schemars::schema_for!(AddInput))?;

    McpServer::builder()
        .name("calculator")
        .version("1.0.0")
        .tool(
            Tool::new("add", "Add two numbers", schema),
            |params: AddInput| async move {
                CallToolResult::text(format!("{}", params.a + params.b))
            },
        )
        .build()
        .serve_stdio()
        .await?;
    Ok(())
}

Core Concepts

Tools

Tools are functions that AI models can invoke. Define them with the #[tool] macro or manually:

// Macro approach
#[tool(description = "Multiply two numbers")]
async fn multiply(x: f64, y: f64) -> String {
    format!("{}", x * y)
}

// Manual approach
let schema = serde_json::to_value(schemars::schema_for!(MultiplyInput))?;
builder.tool(
    Tool::new("multiply", "Multiply two numbers", schema),
    |params: MultiplyInput| async move {
        CallToolResult::text(format!("{}", params.x * params.y))
    }
);

Error Handling:

#[tool(description = "Divide two numbers")]
async fn divide(a: f64, b: f64) -> Result<String, String> {
    if b == 0.0 {
        return Err("Cannot divide by zero".to_string());
    }
    Ok(format!("{}", a / b))
}

Resources

Resources expose data (files, APIs, databases) to AI models:

// Static resource
#[resource(
    uri = "config://app",
    name = "Application Config",
    mime_type = "application/json"
)]
async fn get_config(_req: ReadResourceRequest) -> McpResult<ReadResourceResult> {
    let config = serde_json::json!({"version": "1.0", "debug": false});
    Ok(ReadResourceResult::text(
        "config://app",
        serde_json::to_string_pretty(&config)?
    ))
}

// Template resource (dynamic URIs)
#[resource(uri = "file://{path}", name = "File System")]
async fn read_file(req: ReadResourceRequest) -> McpResult<ReadResourceResult> {
    let path = req.uri.trim_start_matches("file://");
    let content = tokio::fs::read_to_string(path).await
        .map_err(|e| McpError::ResourceNotFound(e.to_string()))?;
    Ok(ReadResourceResult::text(req.uri.clone(), content))
}

Prompts

Prompts provide reusable templates for AI interactions:

#[prompt(
    name = "code-review",
    description = "Generate a code review prompt",
    arguments = ["code:required", "language:optional"]
)]
async fn code_review(req: GetPromptRequest) -> McpResult<GetPromptResult> {
    let code = req.arguments.get("code").cloned().unwrap_or_default();
    let lang = req.arguments.get("language").cloned().unwrap_or("".into());
    
    Ok(GetPromptResult::new(vec![
        PromptMessage::user_text(format!(
            "Review this {lang} code:\n\n```{lang}\n{code}\n```"
        ))
    ]))
}

Transports

Stdio (Default)

Standard input/output transport for local process communication:

server.serve_stdio().await?;

SSE (Server-Sent Events)

HTTP-based transport for web clients:

// Requires the "sse" feature
server.serve_sse(([0, 0, 0, 0], 3000)).await?;

Enable in Cargo.toml:

[dependencies]
mcp-kit = { version = "0.1", features = ["sse"] }

Streamable HTTP (MCP 2025-03-26)

Modern HTTP transport with a single endpoint that can return JSON or SSE:

// Requires the "sse" feature
server.serve_streamable(([0, 0, 0, 0], 3000)).await?;

Protocol:

POST /mcp
Content-Type: application/json
Mcp-Session-Id: <optional session id>

{"jsonrpc":"2.0","method":"tools/list","id":1}

Response (JSON for simple requests):
200 OK
Content-Type: application/json
Mcp-Session-Id: <session id>

{"jsonrpc":"2.0","result":{"tools":[...]},"id":1}

Response (SSE for streaming):
200 OK
Content-Type: text/event-stream
Mcp-Session-Id: <session id>

data: {"jsonrpc":"2.0","method":"notifications/progress",...}
data: {"jsonrpc":"2.0","result":{...},"id":1}

Advantages over SSE:

  • Single endpoint (instead of /sse + /message)
  • Server chooses JSON or stream per-request
  • Better for serverless/edge deployments
  • Session management via Mcp-Session-Id header

TLS/HTTPS

Secure HTTPS transport with optional mTLS:

use mcp_kit::transport::tls::{TlsConfig, ServeSseTlsExt};

let tls = TlsConfig::builder()
    .cert_pem("server.crt")
    .key_pem("server.key")
    .client_auth_ca_pem("ca.crt")  // Enable mTLS
    .build()?;

server.serve_tls("0.0.0.0:8443".parse()?, tls).await?;

WebSocket

Bidirectional WebSocket transport for real-time communication:

// Requires the "websocket" feature
server.serve_websocket("0.0.0.0:3001".parse()?).await?;

Enable in Cargo.toml:

[dependencies]
mcp-kit = { version = "0.1", features = ["websocket"] }

Authentication

Protect your MCP server with various authentication methods. All auth features are composable and can be combined.

Bearer Token Authentication

use mcp_kit::prelude::*;
use mcp_kit::auth::{BearerTokenProvider, IntoDynProvider};
use mcp_kit::Auth;
use std::sync::Arc;

// Protected tool - requires auth parameter
#[tool(description = "Say hello to the authenticated user")]
async fn greet(message: String, auth: Auth) -> McpResult<CallToolResult> {
    Ok(CallToolResult::text(format!(
        "Hello, {}! Message: {}", auth.subject, message
    )))
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let provider = Arc::new(BearerTokenProvider::new(["my-secret-token"]));

    McpServer::builder()
        .name("secure-server")
        .version("1.0.0")
        .auth(provider.into_dyn())
        .tool_def(greet_tool_def())
        .build()
        .serve_sse("0.0.0.0:3000".parse()?)
        .await?;
    Ok(())
}

Test with: curl -H "Authorization: Bearer my-secret-token" http://localhost:3000/sse

API Key Authentication

use mcp_kit::auth::{ApiKeyProvider, IntoDynProvider};

// Supports both header and query param
let provider = Arc::new(ApiKeyProvider::new(["api-key-123", "api-key-456"]));

McpServer::builder()
    .auth(provider.into_dyn())
    // ...

Test with:

  • Header: curl -H "X-Api-Key: api-key-123" http://localhost:3000/sse
  • Query: curl "http://localhost:3000/sse?api_key=api-key-123"

Basic Authentication

use mcp_kit::auth::{AuthenticatedIdentity, BasicAuthProvider, IntoDynProvider};

let provider = Arc::new(BasicAuthProvider::new(|username, password| {
    Box::pin(async move {
        if username == "admin" && password == "secret" {
            Ok(AuthenticatedIdentity::new("admin")
                .with_scopes(["read", "write", "admin"]))
        } else {
            Err(McpError::Unauthorized("invalid credentials".into()))
        }
    })
}));

Test with: curl -u admin:secret http://localhost:3000/sse

OAuth 2.0 (JWT/JWKS)

use mcp_kit::auth::oauth2::{OAuth2Config, OAuth2Provider};

// JWT validation with JWKS endpoint
let provider = Arc::new(OAuth2Provider::new(OAuth2Config::Jwt {
    jwks_url: "https://auth.example.com/.well-known/jwks.json".to_owned(),
    required_audience: Some("https://my-api.example.com".to_owned()),
    required_issuer: Some("https://auth.example.com/".to_owned()),
    jwks_refresh_secs: 3600,
}));

// Or token introspection (RFC 7662)
let provider = Arc::new(OAuth2Provider::new(OAuth2Config::Introspection {
    introspection_url: "https://auth.example.com/introspect".to_owned(),
    client_id: "my-client".to_owned(),
    client_secret: "my-secret".to_owned(),
    cache_ttl_secs: 60,
}));

mTLS (Mutual TLS)

use mcp_kit::auth::mtls::MtlsProvider;
use mcp_kit::transport::tls::{TlsConfig, ServeSseTlsExt};

let mtls = MtlsProvider::new(|cert_der: &[u8]| {
    // Validate client certificate, extract subject
    Ok(AuthenticatedIdentity::new("client-cn"))
});

let tls = TlsConfig::builder()
    .cert_pem("server.crt")
    .key_pem("server.key")
    .client_auth_ca_pem("ca.crt")
    .build()?;

McpServer::builder()
    .auth(Arc::new(mtls))
    .build()
    .serve_tls("0.0.0.0:8443".parse()?, tls)
    .await?;

Composite Authentication

Combine multiple auth methods:

use mcp_kit::auth::{
    BearerTokenProvider, ApiKeyProvider, BasicAuthProvider,
    CompositeAuthProvider, IntoDynProvider,
};

let composite = CompositeAuthProvider::new(vec![
    BearerTokenProvider::new(["service-token"]).into_dyn(),
    ApiKeyProvider::new(["api-key"]).into_dyn(),
    BasicAuthProvider::new(/* validator */).into_dyn(),
]);

McpServer::builder()
    .auth(Arc::new(composite))
    // ...

Auth Extractor in Tools

Access authentication info in tool handlers:

use mcp_kit::Auth;

#[tool(description = "Protected operation")]
async fn secure_op(data: String, auth: Auth) -> McpResult<CallToolResult> {
    // Access authenticated identity
    println!("User: {}", auth.subject);
    println!("Scopes: {:?}", auth.scopes);
    println!("Metadata: {:?}", auth.metadata);
    
    // Check scopes
    if !auth.has_scope("write") {
        return Err(McpError::Unauthorized("write scope required".into()));
    }
    
    Ok(CallToolResult::text("Success!"))
}

Completion

Provide auto-complete suggestions for prompt and resource arguments:

use mcp_kit::prelude::*;
use mcp_kit::types::messages::{CompleteRequest, CompletionReference};

McpServer::builder()
    .name("completion-demo")
    .version("1.0.0")
    // Prompt with completion handler
    .prompt_with_completion(
        Prompt::new("search")
            .with_description("Search with auto-complete")
            .with_arguments(vec![
                PromptArgument::required("query"),
                PromptArgument::optional("category"),
            ]),
        // Prompt handler
        |req: mcp_kit::types::messages::GetPromptRequest| async move {
            Ok(GetPromptResult::new(vec![
                PromptMessage::user_text(format!("Search: {}", req.arguments.get("query").unwrap()))
            ]))
        },
        // Completion handler
        |req: CompleteRequest| async move {
            let values = match req.argument.name.as_str() {
                "category" => vec!["books", "movies", "music", "games"],
                _ => vec![],
            };
            Ok(CompleteResult::new(values))
        },
    )
    // Global completion for resources
    .completion(|req: CompleteRequest| async move {
        match &req.reference {
            CompletionReference::Resource { uri } if uri.starts_with("file://") => {
                Ok(CompleteResult::new(vec!["file:///src/", "file:///docs/"]))
            }
            _ => Ok(CompleteResult::empty()),
        }
    })
    .build();

Notifications

Push updates from server to clients:

use mcp_kit::prelude::*;

// Create notification channel
let (notifier, mut receiver) = NotificationSender::channel(100);

// In a tool handler - notify about resource changes
async fn update_data(notifier: NotificationSender) {
    // ... update data ...
    
    // Notify clients the resource changed
    notifier.resource_updated("data://config").await.ok();
    
    // Notify about list changes
    notifier.resources_list_changed().await.ok();
    notifier.tools_list_changed().await.ok();
    notifier.prompts_list_changed().await.ok();
    
    // Send log messages
    notifier.log_info("update", "Data updated successfully").await.ok();
    notifier.log_warning("update", "Some items skipped").await.ok();
}

Available Notifications:

  • resource_updated(uri) — A specific resource's content changed
  • resources_list_changed() — Available resources list changed
  • tools_list_changed() — Available tools list changed
  • prompts_list_changed() — Available prompts list changed
  • log_debug/info/warning/error() — Log messages

Progress Tracking

Report progress for long-running operations:

use mcp_kit::prelude::*;

async fn process_files(notifier: NotificationSender, files: Vec<String>) {
    let tracker = ProgressTracker::new(notifier, Some("token-123".into()));
    
    for (i, file) in files.iter().enumerate() {
        // Process file...
        
        // Report progress
        tracker.update_with_message(
            i as f64 + 1.0,
            files.len() as f64,
            format!("Processing {}", file),
        ).await;
    }
    
    tracker.complete("All files processed").await;
}

ProgressTracker Methods:

  • update(progress, total, message) — Send progress update
  • update_percent(0.0..1.0, message) — Progress as percentage
  • complete(message) — Mark operation complete
  • is_tracking() — Check if progress token was provided

---

## Elicitation

Request user input from clients during tool execution:

```rust
use mcp_kit::prelude::*;

// Create elicitation client
let (client, mut rx) = ChannelElicitationClient::channel(10);

// Simple yes/no confirmation
let confirmed = client.confirm("Delete all temporary files?").await?;
if confirmed {
    // User confirmed, proceed
}

// Request text input
if let Some(name) = client.prompt_text("Enter project name").await? {
    println!("Creating project: {}", name);
}

// Multiple choice
let options = vec!["small".into(), "medium".into(), "large".into()];
if let Some(size) = client.choose("Select deployment size", options).await? {
    println!("Selected: {}", size);
}

// Complex form with builder
let request = ElicitationRequestBuilder::new("Configure your project")
    .text_required("name", "Project Name")
    .boolean("private", "Private Repository")
    .number("port", "Port Number")
    .select("language", "Language", &["rust", "python", "javascript"])
    .build();

let result = client.elicit(request).await?;
if result.is_accepted() {
    // Process user input from result.content
}

ElicitationClientExt Methods:

  • confirm(message) — Yes/no confirmation dialog
  • prompt_text(message) — Request text input
  • prompt_number(message) — Request numeric input
  • choose(message, options) — Multiple choice selection
  • elicit(request) — Send custom elicitation request

Advanced Features

State Management

Share state across tool invocations:

use std::sync::Arc;
use tokio::sync::Mutex;

#[derive(Clone)]
struct AppState {
    counter: Arc<Mutex<i32>>,
}

// In your tool handler
let state = AppState { counter: Arc::new(Mutex::new(0)) };

builder.tool(
    Tool::new("increment", "Increment counter", schema),
    {
        let state = state.clone();
        move |_: serde_json::Value| {
            let state = state.clone();
            async move {
                let mut counter = state.counter.lock().await;
                *counter += 1;
                CallToolResult::text(format!("Counter: {}", *counter))
            }
        }
    }
);

Logging

Integrate with tracing for structured logging:

tracing_subscriber::fmt()
    .with_writer(std::io::stderr)  // Log to stderr for stdio transport
    .with_env_filter("my_server=debug,mcp_kit=info")
    .init();

tracing::info!("Server starting");

Set log level:

RUST_LOG=my_server=debug cargo run

Error Handling

The library uses McpResult<T> and McpError:

use mcp_kit::{McpError, McpResult};

async fn my_tool() -> McpResult<CallToolResult> {
    // Automatic conversion from std::io::Error, serde_json::Error, etc.
    let data = tokio::fs::read_to_string("file.txt").await?;
    
    // Custom errors
    if data.is_empty() {
        return Err(McpError::InvalidParams("File is empty".into()));
    }
    
    Ok(CallToolResult::text(data))
}

Plugin System

The plugin system allows you to dynamically load and manage tools, resources, and prompts from external libraries, in-process modules, or sandboxed WebAssembly modules.

WASM Plugin Support 🌐

WebAssembly plugins provide:

  • Sandboxed execution — Isolated from host system for security
  • Cross-platform compatibility — Same .wasm file runs everywhere
  • Type safety — Full support for i32, i64, f32, f64, and string parameters
  • Memory operations — Proper string handling via WASM linear memory
  • High performance — 1000+ function calls per second

Example WASM Plugin:

cargo run --example wasm_plugin --features plugin,plugin-wasm

This creates and loads 4 WASM modules demonstrating:

  • Integer arithmetic (i32 + i32 → i32)
  • Float operations (f32 × f32 → f32)
  • Mixed types (i32 × f32 × f64 → f64)
  • String processing (string → length via memory operations)

WASM Module Example (WAT format):

(module
  (func (export "add") (param i32 i32) (result i32)
    local.get 0
    local.get 1
    i32.add))

The plugin system automatically:

  1. Analyzes WASM function signatures
  2. Generates MCP tools for each exported function
  3. Handles type conversion from JSON to WASM values
  4. Manages memory allocation for string parameters
  5. Converts return values back to JSON

Native Plugin Support

Quick Start

use mcp_kit::prelude::*;
use mcp_kit::plugin::{McpPlugin, PluginConfig, PluginManager, ToolDefinition};

// Define a plugin
struct WeatherPlugin;

impl McpPlugin for WeatherPlugin {
    fn name(&self) -> &str { "weather" }
    fn version(&self) -> &str { "1.0.0" }
    
    fn register_tools(&self) -> Vec<ToolDefinition> {
        vec![
            ToolDefinition::new(
                Tool::new("get_weather", "Get current weather", schema),
                |params: WeatherInput| async move {
                    CallToolResult::text(format!("Weather: {}", params.city))
                },
            ),
        ]
    }
    
    fn on_load(&mut self, config: &PluginConfig) -> McpResult<()> {
        // Initialize from config
        Ok(())
    }
}

// Load plugin into server
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut plugin_manager = PluginManager::new();
    
    // Register in-process plugin
    plugin_manager.register_plugin(WeatherPlugin, PluginConfig::default())?;
    
    // Or load from dynamic library
    // plugin_manager.load_from_path("./plugins/weather.so")?;
    
    let server = McpServer::builder()
        .name("my-server")
        .with_plugin_manager(plugin_manager)
        .build()
        .serve_stdio()
        .await?;
    
    Ok(())
}

Real-World Plugin Examples

The library includes PRODUCTION-READY plugins with REAL API integration:

✅ Weather Plugin — Fully working example with mock API

cargo run --example plugin_weather --features plugin,plugin-native
  • Get current weather for cities
  • Get multi-day forecasts
  • Mock implementation (works out of the box)

✅ GitHub Plugin (Real API) — Production-ready GitHub REST API v3

export GITHUB_TOKEN=ghp_your_token_here
cargo run --example plugin_github --features plugin,plugin-native
  • ✅ Get repository info with live data
  • ✅ List user repositories
  • ✅ Create issues
  • ✅ List pull requests

✅ Jira Plugin (Real API) — Production-ready Jira REST API v3

export JIRA_BASE_URL="https://your-domain.atlassian.net"
export JIRA_EMAIL="your-email@example.com"
export JIRA_API_TOKEN="your-api-token"
export JIRA_PROJECT_KEY="PROJ"
cargo run --example plugin_jira --features plugin,plugin-native
  • ✅ Create issues with real data
  • ✅ Get issue details
  • ✅ Search issues with JQL
  • ✅ Add comments

✅ Confluence Plugin (Real API) — Production-ready Confluence REST API

export CONFLUENCE_BASE_URL="https://your-domain.atlassian.net"
export CONFLUENCE_EMAIL="your-email@example.com"
export CONFLUENCE_API_TOKEN="your-api-token"
export CONFLUENCE_SPACE_KEY="TEAM"
cargo run --example plugin_confluence --features plugin,plugin-native
  • ✅ Create wiki pages
  • ✅ Get page content
  • ✅ Search with CQL
  • ✅ List pages in space

✅ ClickHouse Plugin (Real Database) — Production-ready ClickHouse integration

# Start ClickHouse (Docker):
docker run -d -p 8123:8123 clickhouse/clickhouse-server

# Configure and run:
export CLICKHOUSE_URL="http://localhost:8123"
export CLICKHOUSE_DATABASE="default"
cargo run --example plugin_clickhouse --features plugin,plugin-native
  • ✅ Execute SQL queries
  • ✅ Get table schema and stats
  • ✅ Generate analytics reports (daily/hourly/top users)
  • ✅ List all tables
  • ✅ Database statistics
  • ✅ Insert data

See examples/PLUGINS.md for detailed setup guides.


cargo run --example plugin_jira --features plugin,plugin-native
  • Create, update, search issues
  • Manage sprints & transitions
  • Add comments & attachments
  • Ready for Jira REST API integration

🚧 Confluence Plugin — Complete template (8 tools, 559 lines)

cargo run --example plugin_confluence --features plugin,plugin-native
  • Create/update wiki pages
  • Search with CQL
  • Manage spaces & attachments
  • Ready for Confluence REST API integration

✅ GitHub Plugin (Mock) — Extended template with 8 tools

cargo run --example plugin_github --features plugin,plugin-native
  • Manage repos, issues, PRs
  • List commits & branches
  • Trigger GitHub Actions
  • Uses mock data (for reference and learning)

See examples/PLUGINS.md for detailed guides on each plugin.

Plugin Configuration

Pass configuration at load time:

let config = PluginConfig {
    config: serde_json::json!({
        "api_key": "secret-key-123",
        "base_url": "https://api.example.com"
    }),
    enabled: true,
    priority: 10,  // Higher = loads first
    permissions: PluginPermissions {
        network: true,
        filesystem: false,
        ..Default::default()
    },
};

plugin_manager.register_plugin(MyPlugin::new(), config)?;

Builder Integration

Load plugins directly in the builder:

McpServer::builder()
    .name("my-server")
    .load_plugin("./plugins/jira.so")?       // Load from file
    .load_plugin("./plugins/github.so")?     // Chain multiple
    .build()

Plugin Management

// List all loaded plugins
for plugin in plugin_manager.list_plugins() {
    println!("{} v{}: {} tools, {} resources",
        plugin.name, plugin.version,
        plugin.tool_count, plugin.resource_count);
}

// Get plugin metadata
if let Some(meta) = plugin_manager.get_metadata("weather") {
    println!("Weather plugin: {:?}", meta);
}

// Unload a plugin
plugin_manager.unload("weather")?;

Native Plugin (Shared Library)

Create a plugin as a .so, .dylib, or .dll:

// Plugin crate: lib.rs
use mcp_kit::plugin::McpPlugin;

struct MyPlugin;
impl McpPlugin for MyPlugin { /* ... */ }

// Export constructor
#[no_mangle]
pub extern "C" fn _mcp_plugin_create() -> *mut dyn McpPlugin {
    Box::into_raw(Box::new(MyPlugin))
}

Build as dynamic library:

[lib]
crate-type = ["cdylib"]

[dependencies]
mcp-kit = { version = "0.1", features = ["plugin"] }
cargo build --release
# Produces: target/release/libmy_plugin.so

Load in server:

plugin_manager.load_from_path("./target/release/libmy_plugin.so")?;

Feature Flags

[dependencies]
mcp-kit = { version = "0.1", features = ["plugin", "plugin-native"] }

Available plugin features:

  • plugin — Core plugin system (required)
  • plugin-native — Load native shared libraries
  • plugin-wasm — Load WASM plugins (coming soon)
  • plugin-hot-reload — Development hot reload (coming soon)

Plugin Resources


MCP Gateway

The mcp-kit-gateway crate lets you build an MCP gateway server that connects to one or more upstream MCP servers, discovers their tools/resources/prompts, and exposes them through a single gateway endpoint. Each upstream's capabilities are namespaced with a prefix to avoid collisions.

Installation

[dependencies]
mcp-kit-gateway = "0.1"
mcp-kit = { version = "0.3", features = ["sse"] }  # or "websocket", etc.
tokio = { version = "1", features = ["full"] }

Quick Start

use mcp_kit::prelude::*;
use mcp_kit_gateway::{GatewayManager, UpstreamConfig, UpstreamTransport};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut gw = GatewayManager::new();
    
    // Add upstream servers
    gw.add_upstream(UpstreamConfig {
        name: "weather".into(),
        transport: UpstreamTransport::Sse("http://localhost:3001/sse".into()),
        prefix: Some("weather".into()),
        client_name: None,
        client_version: None,
    });
    
    gw.add_upstream(UpstreamConfig {
        name: "tools".into(),
        transport: UpstreamTransport::WebSocket("ws://localhost:3002/ws".into()),
        prefix: Some("tools".into()),
        client_name: None,
        client_version: None,
    });

    // Build gateway server — connects to upstreams and discovers capabilities
    let server = gw.build_server(
        McpServer::builder()
            .name("my-gateway")
            .version("1.0.0")
            // You can mix local tools with proxied upstream tools
            .tool(
                Tool::no_params("gateway/status", "Check gateway status"),
                |_args: serde_json::Value| async move {
                    CallToolResult::text("Gateway is running")
                },
            )
    ).await?;

    server.serve_sse(([0, 0, 0, 0], 3000)).await?;
    Ok(())
}

How It Works

  1. Configure upstreams — Define which MCP servers to connect to and how (SSE, WebSocket, Streamable HTTP, or stdio)
  2. Connect & discover — The gateway connects to each upstream via McpClient, calls list_tools()/list_resources()/list_prompts() to discover capabilities
  3. Namespace & register — Each discovered capability is registered in the local server router with a prefix (e.g., upstream tool get_weather becomes weather/get_weather)
  4. Proxy requests — When a client calls a proxied tool/resource/prompt, the gateway forwards the request to the appropriate upstream and returns the result

Upstream Transports

// SSE (HTTP Server-Sent Events)
UpstreamTransport::Sse("http://localhost:3001/sse".into())

// WebSocket
UpstreamTransport::WebSocket("ws://localhost:3002/ws".into())

// Streamable HTTP (MCP 2025-03-26)
UpstreamTransport::StreamableHttp("http://localhost:3003/mcp".into())

// Stdio (spawn subprocess)
UpstreamTransport::Stdio {
    program: "/path/to/mcp-server".into(),
    args: vec!["--flag".into()],
    env: vec![("API_KEY".into(), "secret".into())],
}

Feature Flags

[dependencies]
mcp-kit-gateway = { version = "0.1", default-features = false, features = ["sse"] }
  • full (default) — All transport features
  • sse — SSE upstream support
  • websocket — WebSocket upstream support
  • streamable-http — Streamable HTTP upstream support
  • stdio — Stdio subprocess upstream support

Error Handling

Upstreams that fail to connect are logged as warnings and skipped — the gateway server will still start with the remaining upstreams. Individual discovery failures (listing tools, resources, or prompts) are also non-fatal.

See gateway/README.md for the full gateway documentation.


Macro Reference

#[tool]

Generate tools from async functions:

#[tool(description = "Description here")]
async fn my_tool(param: Type) -> ReturnType {
    // Implementation
}

Attributes:

  • description = "..." — Tool description (required)
  • name = "..." — Tool name (optional, defaults to function name)

Supported return types:

  • String → Converted to CallToolResult::text
  • CallToolResult → Used directly
  • Result<T, E> → Error handling support

#[resource]

Generate resource handlers:

#[resource(
    uri = "scheme://path",
    name = "Resource Name",
    description = "Optional description",
    mime_type = "text/plain"
)]
async fn handler(req: ReadResourceRequest) -> McpResult<ReadResourceResult> {
    // Implementation
}

URI Templates: Use {variable} syntax for dynamic resources:

#[resource(uri = "file://{path}", name = "Files")]

#[prompt]

Generate prompt handlers:

#[prompt(
    name = "prompt-name",
    description = "Prompt description",
    arguments = ["arg1:required", "arg2:optional"]
)]
async fn handler(req: GetPromptRequest) -> McpResult<GetPromptResult> {
    // Implementation
}

Builder API Reference

McpServer::builder()
    // Server metadata
    .name("server-name")
    .version("1.0.0")
    .instructions("What this server does")

    // Register tools
    .tool(tool, handler)           // Manual API
    .tool_def(macro_generated_def) // From #[tool] macro

    // Register resources
    .resource(resource, handler)           // Static resource
    .resource_template(template, handler)  // URI template
    .resource_def(macro_generated_def)     // From #[resource] macro

    // Register prompts
    .prompt(prompt, handler)
    .prompt_def(macro_generated_def)  // From #[prompt] macro

    .build()

Examples

Run the included examples to see all features in action:

# Comprehensive showcase - all features
cargo run --example showcase

# Showcase with SSE transport on port 3000
cargo run --example showcase -- --sse

# WebSocket transport example
cargo run --example websocket

# Macro-specific examples
cargo run --example macros_demo

# Completion auto-complete example
cargo run --example completion

# Notifications and progress example
cargo run --example notifications

# Authentication examples
cargo run --example auth_bearer --features auth-full
cargo run --example auth_apikey --features auth-full
cargo run --example auth_basic --features auth-full
cargo run --example auth_composite --features auth-full
cargo run --example auth_oauth2 --features auth-oauth2
cargo run --example auth_mtls --features auth-mtls

# Plugin examples
cargo run --example plugin_weather --features plugin,plugin-native          # ✅ Working (mock)
cargo run --example plugin_github --features plugin,plugin-native      # ✅ Real GitHub API
cargo run --example plugin_jira --features plugin,plugin-native        # ✅ Real Jira API
cargo run --example plugin_confluence --features plugin,plugin-native  # ✅ Real Confluence API
cargo run --example plugin_clickhouse --features plugin,plugin-native       # ✅ Real ClickHouse DB

# Client SDK example (requires running server first)
cargo run -p mcp-kit-client --example client_demo

# Gateway example (requires running upstream server first)
UPSTREAM_URL=http://localhost:3001/sse cargo run -p mcp-kit-gateway --example gateway

Example Features:

  • ✅ Multiple tool types (math, async, state management)
  • ✅ Static and template resources
  • ✅ Prompts with arguments
  • ✅ Argument completion (auto-complete)
  • ✅ Notifications (resource updates, logging)
  • ✅ Progress tracking for long operations
  • ✅ Resource subscriptions
  • ✅ Request cancellation
  • ✅ Error handling patterns
  • ✅ State sharing between requests
  • ✅ JSON content types
  • ✅ Stdio, SSE, and WebSocket transports
  • ✅ Bearer, API Key, Basic, OAuth 2.0, mTLS authentication
  • ✅ Composite authentication (multiple methods)
  • ✅ Plugin system (native and WASM)
  • ✅ Real API integrations (GitHub, Jira, Confluence, ClickHouse)
  • ✅ Database integrations (ClickHouse)
  • ✅ Client SDK for connecting to servers
  • ✅ MCP Gateway for proxying upstream servers

Source code: examples/


Feature Flags

Control which features to compile:

[dependencies]
mcp-kit = { version = "0.2", default-features = false, features = ["server", "stdio"] }

Available features:

  • full (default) — All features enabled
  • server — Core server functionality
  • stdio — Standard I/O transport
  • sse — HTTP Server-Sent Events transport
  • websocket — WebSocket transport

Authentication features:

  • auth — Core auth types and traits
  • auth-bearer — Bearer token authentication
  • auth-apikey — API key authentication
  • auth-basic — HTTP Basic authentication
  • auth-oauth2 — OAuth 2.0 (JWT/JWKS + introspection)
  • auth-mtls — Mutual TLS / client certificates
  • auth-full — All auth features (bearer, apikey, basic)

Plugin features:

  • plugin — Core plugin system (trait, manager, lifecycle)
  • plugin-native — Load native shared libraries (.so, .dylib, .dll)
  • plugin-wasm — Load WASM plugins (coming soon)
  • plugin-hot-reload — Hot reload during development (coming soon)

WASM compatibility: Use default-features = false for WASM targets (only core protocol types).


Architecture

mcp-kit/
├── src/
│   ├── lib.rs           # Public API and re-exports
│   ├── error.rs         # Error types
│   ├── protocol.rs      # JSON-RPC 2.0 implementation
│   ├── types/           # MCP protocol types
│   ├── server/          # Server implementation [feature = "server"]
│   └── transport/       # Transport implementations
├── macros/              # Procedural macros crate
├── client/              # Client SDK crate
└── gateway/             # Gateway crate (upstream proxying)

Crate structure:

  • mcp-kit — Main server library
  • mcp-kit-macros — Procedural macros (#[tool], etc.)
  • mcp-kit-client — Client SDK for connecting to MCP servers
  • mcp-kit-gateway — Gateway for proxying upstream MCP servers

Client SDK

The mcp-kit-client crate provides a client library for connecting to MCP servers:

[dependencies]
mcp-kit-client = "0.2"

Quick Start

use mcp_kit_client::prelude::*;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Connect via WebSocket
    let client = McpClient::websocket("ws://localhost:3001/ws").await?;
    
    // Initialize connection
    let server_info = client.initialize("my-app", "1.0.0").await?;
    println!("Connected to: {}", server_info.name);
    
    // List and call tools
    let tools = client.list_tools().await?;
    let result = client.call_tool("greet", serde_json::json!({
        "name": "World"
    })).await?;
    
    Ok(())
}

Transport Options

// Stdio - spawn subprocess
let client = McpClient::stdio("/path/to/mcp-server").await?;

// SSE - HTTP Server-Sent Events
let client = McpClient::sse("http://localhost:3000").await?;

// WebSocket
let client = McpClient::websocket("ws://localhost:3001/ws").await?;

Available Operations

Method Description
initialize() Initialize the MCP connection
list_tools() List available tools
call_tool() Call a tool with arguments
list_resources() List available resources
read_resource() Read a resource by URI
list_prompts() List available prompts
get_prompt() Get a prompt by name
subscribe() Subscribe to resource updates
unsubscribe() Unsubscribe from updates

See client/README.md for full documentation.


Testing

# Run all tests
cargo test --workspace --all-features

# Check formatting
cargo fmt --all -- --check

# Run lints
cargo clippy --workspace --all-features -- -D warnings

# Check MSRV
cargo check --workspace --all-features

Resources


Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes with tests
  4. Ensure cargo fmt and cargo clippy pass
  5. Submit a pull request

See AGENTS.md for development guidelines.


License

This project is licensed under the MIT License.


Changelog

See GitHub Releases for version history.


Built with ❤️ in Rust

⭐ Star on GitHub📦 View on crates.io📖 Read the docs