使用说明
mcp-kit
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_TOKENand 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 SDK —
mcp-kit-clientcrate for connecting to MCP servers - 🌐 Gateway —
mcp-kit-gatewaycrate 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-Idheader
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 changedresources_list_changed()— Available resources list changedtools_list_changed()— Available tools list changedprompts_list_changed()— Available prompts list changedlog_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 updateupdate_percent(0.0..1.0, message)— Progress as percentagecomplete(message)— Mark operation completeis_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 dialogprompt_text(message)— Request text inputprompt_number(message)— Request numeric inputchoose(message, options)— Multiple choice selectionelicit(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:
- Analyzes WASM function signatures
- Generates MCP tools for each exported function
- Handles type conversion from JSON to WASM values
- Manages memory allocation for string parameters
- 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 librariesplugin-wasm— Load WASM plugins (coming soon)plugin-hot-reload— Development hot reload (coming soon)
Plugin Resources
- 📖 Plugin System Documentation — Complete guide
- 📦 Plugin Examples — Jira, Confluence, GitHub templates
- 🌐 Example:
examples/wasm_plugin.rs— WebAssembly plugin demo - 🔌 Example:
examples/plugin_weather.rs— Native plugin example
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
- Configure upstreams — Define which MCP servers to connect to and how (SSE, WebSocket, Streamable HTTP, or stdio)
- Connect & discover — The gateway connects to each upstream via
McpClient, callslist_tools()/list_resources()/list_prompts()to discover capabilities - Namespace & register — Each discovered capability is registered in the local server router with a prefix (e.g., upstream tool
get_weatherbecomesweather/get_weather) - 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 featuressse— SSE upstream supportwebsocket— WebSocket upstream supportstreamable-http— Streamable HTTP upstream supportstdio— 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 toCallToolResult::textCallToolResult→ Used directlyResult<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 enabledserver— Core server functionalitystdio— Standard I/O transportsse— HTTP Server-Sent Events transportwebsocket— WebSocket transport
Authentication features:
auth— Core auth types and traitsauth-bearer— Bearer token authenticationauth-apikey— API key authenticationauth-basic— HTTP Basic authenticationauth-oauth2— OAuth 2.0 (JWT/JWKS + introspection)auth-mtls— Mutual TLS / client certificatesauth-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 librarymcp-kit-macros— Procedural macros (#[tool], etc.)mcp-kit-client— Client SDK for connecting to MCP serversmcp-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
- MCP Specification: https://modelcontextprotocol.io/
- Documentation: https://docs.rs/mcp-kit
- Repository: https://github.com/KSD-CO/mcp-kit
- Examples:
examples/ - CI/CD: GitHub Actions
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Ensure
cargo fmtandcargo clippypass - 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
