mcp-kit 0.1.0

Ergonomic, type-safe Rust library for building MCP servers
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.


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:

[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:

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"] }

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))
}

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

# 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/


Feature Flags

Control which features to compile:

[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

# 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