# mcp-host
Production-grade Rust crate for building Model Context Protocol (MCP) servers.
## Features
- **MCP Protocol 2025-11-25**: Full JSON-RPC 2.0 with protocol version negotiation
- **Tools, Resources, Prompts**: All three core MCP primitives
- **Elicitation**: Request structured user input with JSON Schema validation
- **Tasks**: Async task execution with status tracking
- **Bidirectional Communication**: Server→Client requests (roots/list, sampling/createMessage)
- **Contextual Visibility**: Per-session tool/resource/prompt filtering
- **Transport Abstraction**: STDIO and HTTP transports
- **Middleware Chain**: Extensible request processing pipeline
- **Session Management**: Thread-safe multi-session support with lifecycle state machine
- **Background Notifications**: Send notifications from background tasks
- **Fluent Builder API**: Ergonomic server construction
### Resilience Patterns
- **Circuit Breakers**: Per-tool failure protection using `breaker-machines`
- **Retry with Backoff**: Exponential backoff with jitter via `chrono-machines`
- **Rate Limiting**: GCRA algorithm from `throttle-machines`
- **Structured Logging**: MCP-compliant `notifications/message` for LLM visibility
## Requirements
- Rust 1.92+ (edition 2024)
- Tokio runtime
## Installation
```toml
[dependencies]
mcp-host = "0.1"
```
## Quick Start
```rust
use mcp_host::prelude::*;
use serde_json::Value;
// Define a tool
struct EchoTool;
impl Tool for EchoTool {
fn name(&self) -> &str { "echo" }
fn description(&self) -> Option<&str> {
Some("Echoes back the input message")
}
fn input_schema(&self) -> Value {
serde_json::json!({
"type": "object",
"properties": {
"message": { "type": "string" }
},
"required": ["message"]
})
}
fn execute<'a>(&'a self, ctx: ExecutionContext<'a>) -> ToolFuture<'a> {
Box::pin(async move {
let msg = ctx.params["message"].as_str().unwrap_or("?");
Ok(ToolOutput::text(format!("Echo: {}", msg)))
})
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let server = Server::new("my-server", "0.1.0");
server.tool_registry().register(EchoTool);
server.set_capabilities(ServerCapabilities {
tools: Some(mcp_host::protocol::capabilities::ToolsCapability {
list_changed: Some(false),
}),
..Default::default()
});
server.run(StdioTransport::new()).await
}
```
## Architecture
### Core Components
| `Server` | Main MCP server with request routing and session management |
| `ToolRegistry` | Thread-safe registry for tool implementations |
| `ResourceManager` | Manages URI-addressable resources |
| `PromptManager` | Manages reusable prompt templates |
| `MiddlewareChain` | Request processing pipeline |
### Traits
```rust
// Implement custom tools
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> Option<&str>;
fn input_schema(&self) -> Value;
// Optional: contextual visibility (default: always visible)
fn is_visible(&self, ctx: &VisibilityContext) -> bool { true }
// Execute with full session context
fn execute<'a>(&'a self, ctx: ExecutionContext<'a>) -> ToolFuture<'a>;
}
// Implement custom resources
pub trait Resource: Send + Sync {
fn uri(&self) -> &str;
fn name(&self) -> &str;
fn description(&self) -> Option<&str>;
fn mime_type(&self) -> Option<&str>;
fn is_visible(&self, ctx: &VisibilityContext) -> bool { true }
fn read<'a>(&'a self, ctx: ExecutionContext<'a>) -> ResourceReadFuture<'a>;
}
// Implement custom prompts
pub trait Prompt: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> Option<&str>;
fn arguments(&self) -> Option<Vec<PromptArgument>>;
fn is_visible(&self, ctx: &VisibilityContext) -> bool { true }
fn get<'a>(&'a self, ctx: ExecutionContext<'a>) -> PromptFuture<'a>;
}
```
### Bidirectional Communication
Request data from clients:
```rust
// Request workspace roots (requires client roots capability)
let roots = server.request_roots("session-id", None).await?;
for root in roots {
println!("Root: {} ({})", root.name.unwrap_or_default(), root.uri);
}
// Request LLM completion (requires client sampling capability)
let params = CreateMessageParams {
messages: vec![SamplingMessage {
role: "user".to_string(),
content: SamplingContent::Text { text: "Hello!".to_string() },
}],
max_tokens: 1000,
..Default::default()
};
let result = server.request_sampling("session-id", params, None).await?;
```
### Visibility Context
Filter tools/resources/prompts based on session state:
```rust
struct GitCommitTool;
impl Tool for GitCommitTool {
// Only visible when in a git repo with uncommitted changes
fn is_visible(&self, ctx: &VisibilityContext) -> bool {
ctx.environment
.map(|e| e.has_git_repo() && !e.git_is_clean())
.unwrap_or(false)
}
// ...
}
struct AdminOnlyResource;
impl Resource for AdminOnlyResource {
// Only visible to admin users
fn is_visible(&self, ctx: &VisibilityContext) -> bool {
ctx.is_admin()
}
// ...
}
```
### Custom Context for Macros
`#[mcp_tool]`, `#[mcp_prompt]`, and resource macros can accept any context type
that implements `FromExecutionContext`. This lets you add your own helpers while
still accessing the built-in `Ctx` API.
```rust
use std::ops::Deref;
use mcp_host::prelude::*;
#[derive(Clone)]
struct MyCtx<'a> {
inner: Ctx<'a>,
}
impl<'a> FromExecutionContext<'a> for MyCtx<'a> {
fn from_execution_context(ctx: &'a ExecutionContext<'a>) -> Self {
Self { inner: Ctx::from_execution_context(ctx) }
}
}
impl<'a> Deref for MyCtx<'a> {
type Target = Ctx<'a>;
fn deref(&self) -> &Self::Target { &self.inner }
}
struct MyServer;
impl MyServer {
#[mcp_tool(name = "echo")]
async fn echo(&self, ctx: MyCtx<'_>, _params: Parameters<()>) -> ToolResult {
if !ctx.is_admin() { /* ... */ }
Ok(ToolOutput::text("ok"))
}
}
```
### Rust 2024 Module Layout (One Handler Per File)
The PROMETHEUS example uses a Rails-style, one-handler-per-file layout.
This keeps tools/prompts/resources small and composable.
```text
examples/
prometheus_project.rs
prometheus_project/
tools.rs
prompts.rs
resources.rs
tools/
echo.rs
read_file.rs
...
prompts/
greeting.rs
...
resources/
server_config.rs
timeline_data.rs
...
```
```rust
// examples/prometheus_project/tools/echo.rs
use super::super::*;
use mcp_host::registry::router::McpToolRouter;
impl PrometheusServer {
#[mcp_tool(name = "echo", read_only = true, idempotent = true)]
async fn echo(&self, _ctx: PrometheusCtx<'_>, params: Parameters<EchoParams>) -> ToolResult {
Ok(ToolOutput::text(format!("Echo: {}", params.0.message)))
}
}
pub fn mount(router: McpToolRouter<PrometheusServer>) -> McpToolRouter<PrometheusServer> {
router.with_tool(PrometheusServer::echo_tool_info(), PrometheusServer::echo_handler, None)
}
```
```rust
// examples/prometheus_project/tools.rs
#[path = "tools/echo.rs"]
pub mod echo;
use mcp_host::registry::router::McpToolRouter;
use crate::PrometheusServer;
pub fn router() -> McpToolRouter<PrometheusServer> {
let router = McpToolRouter::new();
echo::mount(router)
}
```
```rust
// examples/prometheus_project.rs
#[path = "prometheus_project/tools.rs"]
mod tools;
#[path = "prometheus_project/prompts.rs"]
mod prompts;
#[path = "prometheus_project/resources.rs"]
mod resources;
impl PrometheusServer {
fn router() -> mcp_host::registry::router::McpRouter<Self> {
mcp_host::registry::router::McpRouter::new(
tools::router(),
prompts::router(),
resources::router(),
resources::template_router(),
)
}
}
```
### Server Builder
```rust
use mcp_host::prelude::*;
let server = server("my-server", "1.0.0")
.with_tools(true)
.with_resources(true, false)
.with_prompts(true)
.with_logging()
.with_logging_middleware()
.with_validation_middleware()
.build();
```
### Resilience Configuration
```rust
use mcp_host::prelude::*;
let server = server("resilient-server", "1.0.0")
.with_tools(true)
.with_resources(true, false)
// Circuit breaker: opens after 3 failures in 60s
.with_circuit_breaker(ToolBreakerConfig {
failure_threshold: 3,
failure_window_secs: 60.0,
half_open_timeout_secs: 10.0,
success_threshold: 2,
})
// Retry: exponential backoff with full jitter
.with_retry(ResourceRetryConfig {
max_attempts: 3,
base_delay_ms: 100,
multiplier: 2.0,
max_delay_ms: 5000,
jitter_factor: 1.0,
})
// Rate limit: 100 req/s with burst of 20
.with_rate_limit(100.0, 20)
.build();
```
### MCP Logging (LLM-visible)
```rust
use mcp_host::prelude::*;
// Create logger with notification channel
let logger = McpLogger::new(notification_tx, "my-tool");
// Log messages are visible to the LLM via notifications/message
logger.info("Tool initialized successfully");
logger.warning("Rate limit approaching threshold");
logger.error("External API unavailable");
```
## Notifications
Send notifications from background tasks:
```rust
let server = Server::new("server", "1.0.0");
let notification_tx = server.notification_sender();
// Spawn background task
tokio::spawn(async move {
notification_tx.send(JsonRpcNotification::new(
"notifications/progress",
Some(serde_json::json!({ "progress": 50 })),
)).ok();
});
```
## Feature Flags
| `stdio` | STDIO transport support | ✓ |
| `http` | HTTP transport via rama | |
| `macros` | Proc macros (`#[mcp_tool]`, `#[mcp_prompt]`, `#[mcp_resource]`, `#[mcp_router]`) | |
| `full` | All features enabled | |
```toml
# Minimal (STDIO only)
mcp-host = "0.1"
# With HTTP transport
mcp-host = { version = "0.1", features = ["http"] }
# With macros for ergonomic tool definition
mcp-host = { version = "0.1", features = ["macros"] }
# Everything
mcp-host = { version = "0.1", features = ["full"] }
```
## Supported MCP Methods
### Client→Server
- `initialize` / `ping`
- `tools/list` / `tools/call`
- `resources/list` / `resources/read` / `resources/subscribe` / `resources/unsubscribe`
- `prompts/list` / `prompts/get`
- `completion/complete`
- `tasks/list` / `tasks/get` / `tasks/cancel`
### Server→Client (Bidirectional)
- `roots/list` - Request workspace roots from client
- `sampling/createMessage` - Request LLM completion from client
- `elicitation/create` - Request structured user input with schema validation
## Examples
```bash
# Run the comprehensive PROMETHEUS example with all features
cargo run --example prometheus_project --features macros
# Run with HTTP transport (requires http feature)
cargo run --example prometheus_project --features "macros,http" -- --http --port 8080
```
## License
BSD-3-Clause