# 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) and SSE/HTTP 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"] }
```
---
## 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
```
**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
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
**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>