# mcp-kit
[](https://crates.io/crates/mcp-kit)
[](https://docs.rs/mcp-kit)
[](LICENSE)
[](https://github.com/KSD-CO/mcp-kit/actions/workflows/ci.yml)
[](https://www.rust-lang.org)
**An ergonomic, type-safe Rust library for building [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) 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.
---
## 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 (default), SSE/HTTP, and HTTPS/TLS support
- 🔐 **Authentication** — Bearer, API Key, Basic, OAuth 2.0, and mTLS support
- 🧩 **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
---
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
mcp-kit = "0.1"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
schemars = "0.8"
anyhow = "1" # For error handling
```
**Minimum Supported Rust Version (MSRV):** 1.80
---
## Quick Start
### Using Macros (Recommended)
The fastest way to build an MCP server with automatic schema generation:
```rust
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:
```rust
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:
```rust
// 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:**
```rust
#[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:
```rust
// 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:
```rust
#[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:
```rust
server.serve_stdio().await?;
```
### SSE (Server-Sent Events)
HTTP-based transport for web clients:
```rust
// Requires the "sse" feature
server.serve_sse(([0, 0, 0, 0], 3000)).await?;
```
Enable in `Cargo.toml`:
```toml
[dependencies]
mcp-kit = { version = "0.1", features = ["sse"] }
```
### TLS/HTTPS
Secure HTTPS transport with optional mTLS:
```rust
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?;
```
---
## Authentication
Protect your MCP server with various authentication methods. All auth features are composable and can be combined.
### Bearer Token Authentication
```rust
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
```rust
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
```rust
use mcp_kit::auth::{AuthenticatedIdentity, BasicAuthProvider, IntoDynProvider};
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)
```rust
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)
```rust
use mcp_kit::auth::mtls::MtlsProvider;
use mcp_kit::transport::tls::{TlsConfig, ServeSseTlsExt};
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:
```rust
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:
```rust
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!"))
}
```
---
## Advanced Features
### State Management
Share state across tool invocations:
```rust
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:
```rust
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:
```bash
RUST_LOG=my_server=debug cargo run
```
### Error Handling
The library uses `McpResult<T>` and `McpError`:
```rust
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))
}
```
---
## Macro Reference
### `#[tool]`
Generate tools from async functions:
```rust
#[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:
```rust
#[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:
```rust
#[resource(uri = "file://{path}", name = "Files")]
```
### `#[prompt]`
Generate prompt handlers:
```rust
#[prompt(
name = "prompt-name",
description = "Prompt description",
arguments = ["arg1:required", "arg2:optional"]
)]
async fn handler(req: GetPromptRequest) -> McpResult<GetPromptResult> {
// Implementation
}
```
---
## Builder API Reference
```rust
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:
```bash
# Comprehensive showcase - all features
cargo run --example showcase
# Showcase with SSE transport on port 3000
cargo run --example showcase -- --sse
# Macro-specific examples
cargo run --example macros_demo
# 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
```
**Example Features:**
- ✅ Multiple tool types (math, async, state management)
- ✅ Static and template resources
- ✅ Prompts with arguments
- ✅ Error handling patterns
- ✅ State sharing between requests
- ✅ JSON content types
- ✅ Both stdio and SSE transports
- ✅ Bearer, API Key, Basic, OAuth 2.0, mTLS authentication
- ✅ Composite authentication (multiple methods)
Source code: [`examples/`](examples/)
---
## Feature Flags
Control which features to compile:
```toml
[dependencies]
mcp-kit = { version = "0.1", 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
**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)
**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
```
**Crate structure:**
- `mcp-kit` — Main library
- `mcp-kit-kit-macros` — Procedural macros (`#[tool]`, etc.)
---
## Testing
```bash
# 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/`](examples/)
- **CI/CD:** [GitHub Actions](.github/workflows/)
---
## 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`](AGENTS.md) for development guidelines.
---
## License
This project is licensed under the [MIT License](LICENSE).
---
## Changelog
See [GitHub Releases](https://github.com/KSD-CO/mcp-kit/releases) for version history.
---
<div align="center">
**Built with ❤️ in Rust**
[⭐ Star on GitHub](https://github.com/KSD-CO/mcp-kit) • [📦 View on crates.io](https://crates.io/crates/mcp-kit) • [📖 Read the docs](https://docs.rs/mcp-kit)
</div>