塔mcp
      
塔本地 模型上下文协议 Rust的(MCP)实现。
概述
tower mcp提供了一种可组合的、中间件友好的方法来构建mcp服务器,该方法使用 塔 服务抽象。与框架风格的MCP实现不同,tower MCP将MCP视为另一种可以通过tower提供服务的协议 Service 特质。
这意味着:
- 标准塔式中间件(跟踪、度量、速率限制、身份验证)刚刚工作
- 同一服务可以通过多种传输方式(stdio、HTTP、WebSocket)公开
- 易于与现有的基于塔式的应用程序(axum、tonic)集成
axum用户熟悉
如果你用过 阿克苏姆,tower-mcp的API会感觉很熟悉:
- 提取器模式:工具处理程序使用提取器,如
State,Json,以及Context - 路由器组成:
McpRouter::merge()和McpRouter::nest()像axum的路由器方法一样工作 - 每处理程序中间件:通过将Tower图层应用于单个工具、资源或提示
.layer() - 建造者模式:工具、资源和提示的构建者流利
为什么选择塔mcp?
优势
| Tower原生中间件 | 超时、速率限制、身份验证、跟踪——在整个服务器或单个工具上。任何 tower::Layer 作品。 |
| 所有运输 | stdio、HTTP/SSE(带流恢复)、WebSocket和子进程。相同的路由器,任何传输方式。 |
| 过程测试 | TestClient 允许您测试MCP服务器,而无需生成子进程或打开套接字。 |
| 一致性 | 39/39服务器和265/265客户端一致性检查在每个PR上通过CI |
| 能力过滤 | 多租户模式的基于会话的工具/资源/提示可见性。 |
| 不需要proc宏 | 生成器模式API,带有可选的基于训练的工具。没有什么隐藏在后面 #[derive].可选 #[tool_fn] / #[prompt_fn] / #[resource_fn] 为方便起见,可使用宏(功能: macros). |
| 异步任务 | 完整的任务生命周期——后台执行、取消、TTL清理、每工具任务支持模式。客户端可以轮询或等待长时间运行的工具结果。 |
| 多服务器代理 | 通过每个后端中间件和命名空间隔离,在单个端点后聚合N个后端服务器。 |
| axum生态系统 | HTTP和WebSocket传输建立在axum之上,因此现有的axum中间件和提取器可以工作 |
权衡
- 比基于宏观的方法更老套 对于简单的服务器,虽然可选
macros该功能显著缩小了这一差距。 - 需要熟悉塔楼/服务。 这
.layer()组合模型很强大,但如果你以前没有使用过Tower,它有一个学习曲线。 - 更重的依赖树 比最小的单一传输实现,特别是
features = ["full"].
快速开始
use tower_mcp::{McpRouter, ToolBuilder, CallToolResult};
use schemars::JsonSchema;
use serde::Deserialize;
// Define your input type - schema is auto-generated
#[derive(Debug, Deserialize, JsonSchema)]
struct GreetInput {
name: String,
}
// Build a tool with type-safe handler
let greet = ToolBuilder::new("greet")
.title("Greet")
.description("Greet someone by name")
.handler(|input: GreetInput| async move {
Ok(CallToolResult::text(format!("Hello, {}!", input.name)))
})
.build();
// Create router with tools
let router = McpRouter::new()
.server_info("my-server", "1.0.0")
.instructions("This server provides greeting functionality")
.tool(greet);
// The router implements tower::Service and can be composed with middleware安装
添加到您的 Cargo.toml:
[dependencies]
tower-mcp = "0.9"功能标志
| 特性 | 描述 |
|---|---|
full | 启用所有可选功能 |
http | 支持SSE的HTTP传输(添加axum、hyper) |
websocket | 用于全双工通信的WebSocket传输 |
childproc | 用于生成子进程MCP服务器的子进程传输 |
oauth | OAuth 2.1资源服务器支持——JWT验证、受保护的资源元数据(需要 http) |
jwks | 远程密钥集的JWKS端点获取(需要 oauth) |
http-client | 用于连接到远程MCP服务器的HTTP客户端传输 |
oauth-client | OAuth 2.0客户端令牌获取——客户端凭据授予、自动发现、令牌缓存(需要 http-client) |
testing | 测试工具(TestClient)用于过程测试 |
dynamic-tools | 工具、提示和资源的运行时注册/注销 |
proxy | 多服务器聚合代理(McpProxy) |
macros | 可选proc宏(#[tool_fn], #[prompt_fn], #[resource_fn], #[resource_template_fn]) |
resilience | 再出口塔架弹性断路器、速率限制器和舱壁层 |
stateless | SEP-1442无状态MCP模式(实验)——无会话服务请求 |
功能示例:
[dependencies]
tower-mcp = { version = "0.9", features = ["full"] }仅类型
如果你只需要MCP协议类型和错误类型,而不需要tower、tokio或axum-- 使用 tower-mcp-types 直接装箱。 这对于编辑器集成、代码生成器、协议验证器或 您希望在没有运行时的情况下序列化/反序列化MCP消息的任何上下文。
[dependencies]
tower-mcp-types = "0.9"tower-mcp-types 提供以下所有类型 tower_mcp::protocol 和 tower_mcp::error 依赖性最小(serde, serde_json, thiserror, base64).完整版 tower-mcp 板条箱再出口从 tower-mcp-types,所以没有 如果你两者都用,那就重复。
工具定义
生成器模式(推荐)
use tower_mcp::{ToolBuilder, CallToolResult};
use schemars::JsonSchema;
use serde::Deserialize;
#[derive(Debug, Deserialize, JsonSchema)]
struct AddInput {
a: i64,
b: i64,
}
let add = ToolBuilder::new("add")
.description("Add two numbers")
.read_only() // Hint: this tool doesn't modify state
.handler(|input: AddInput| async move {
Ok(CallToolResult::text(format!("{}", input.a + input.b)))
})
.build();Proc宏(可选)
启用 features = ["macros"]宏生成构建器代码——您始终可以弹出到构建器模式以进行完全控制。
use tower_mcp::{tool_fn, prompt_fn, resource_fn, resource_template_fn};
use tower_mcp::{CallToolResult, McpRouter};
use tower_mcp::protocol::{GetPromptResult, ReadResourceResult};
#[derive(Debug, Deserialize, JsonSchema)]
struct AddInput { a: i64, b: i64 }
#[tool_fn(description = "Add two numbers")]
async fn add(input: AddInput) -> Result {
Ok(CallToolResult::text(format!("{}", input.a + input.b)))
}
#[prompt_fn(description = "Greet someone", args(name = "Name to greet"))]
async fn greet(args: HashMap) -> Result {
let name = args.get("name").cloned().unwrap_or_default();
Ok(GetPromptResult::user_message(format!("Hello, {name}!")))
}
#[resource_fn(uri = "app://config", description = "App configuration")]
async fn config() -> Result {
Ok(ReadResourceResult::text("app://config", "debug=true"))
}
// Each macro generates a constructor: add_tool(), greet_prompt(), config_resource()
let router = McpRouter::new()
.server_info("my-server", "1.0.0")
.tool(add_tool())
.prompt(greet_prompt())
.resource(config_resource());基于特性(适用于复杂工具)
use tower_mcp::tool::McpTool;
use tower_mcp::{Result, CallToolResult};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
struct Calculator {
precision: u32,
}
#[derive(Debug, Deserialize, JsonSchema)]
struct CalcInput {
expression: String,
}
impl McpTool for Calculator {
const NAME: &'static str = "calculate";
const DESCRIPTION: &'static str = "Evaluate a mathematical expression";
type Input = CalcInput;
type Output = f64;
async fn call(&self, input: Self::Input) -> Result {
// Your calculation logic here
Ok(42.0)
}
}
// Convert to Tool and register
let calc = Calculator { precision: 10 };
let router = McpRouter::new().tool(calc.into_tool());带提取器的处理程序(状态、上下文、JSON)
使用axum样式的提取器来访问状态、上下文和键入的输入:
use std::sync::Arc;
use tower_mcp::{ToolBuilder, CallToolResult};
use tower_mcp::extract::{State, Context, Json};
#[derive(Clone)]
struct AppState { db_url: String }
let state = Arc::new(AppState { db_url: "postgres://...".into() });
let search = ToolBuilder::new("search")
.description("Search with progress updates")
.extractor_handler(state, |
State(app): State>,
ctx: Context,
Json(input): Json,
| async move {
// Report progress
ctx.report_progress(0.5, Some(1.0), Some("Searching...")).await;
// Use state
let results = format!("Searched {} for: {}", app.db_url, input.query);
Ok(CallToolResult::text(results))
})
.build();看 docs.rs 了解更多模式,包括每个工具的中间件、图标和标题、原始JSON处理程序和输出模式。
资源定义
use tower_mcp::ResourceBuilder;
// Static resource with inline content
let config = ResourceBuilder::new("file:///config.json")
.name("Configuration")
.description("Server configuration")
.json(serde_json::json!({
"version": "1.0.0",
"debug": true
}))
.build();
// Dynamic resource with handler
let status = ResourceBuilder::new("app:///status")
.name("Server Status")
.description("Current server status")
.handler(|| async {
Ok("Running".to_string())
})
.build();
let router = McpRouter::new()
.resource(config)
.resource(status);快速定义
use tower_mcp::{PromptBuilder, GetPromptResult};
let greet = PromptBuilder::new("greet")
.description("Generate a greeting")
.required_arg("name", "Name to greet")
.optional_arg("style", "Greeting style (formal/casual)")
.handler(|args| async move {
let name = args.get("name").map(|s| s.as_str()).unwrap_or("World");
let style = args.get("style").map(|s| s.as_str()).unwrap_or("casual");
let text = match style {
"formal" => format!("Good day, {}. How may I assist you?", name),
_ => format!("Hey {}!", name),
};
// Builder handles message construction
Ok(GetPromptResult::builder()
.description("A friendly greeting")
.user(text)
.build())
})
.build();
let router = McpRouter::new().prompt(greet);路由器组成
像axum一样组合路由器:
// Merge routers (combines all tools/resources/prompts)
let api_router = McpRouter::new()
.tool(search_tool)
.tool(fetch_tool);
let admin_router = McpRouter::new()
.tool(reset_tool)
.tool(stats_tool);
let combined = McpRouter::new()
.merge(api_router)
.merge(admin_router);
// Nest with prefix (adds prefix to all tool names)
let v1 = McpRouter::new().tool(legacy_tool);
let v2 = McpRouter::new().tool(new_tool);
let versioned = McpRouter::new()
.nest("v1", v1) // Tools become "v1_legacy_tool"
.nest("v2", v2); // Tools become "v2_new_tool"多服务器代理
在单个端点后聚合多个后端MCP服务器 McpProxy (特点: proxy).每个后端的工具、资源和提示都有命名空间,以避免冲突:
use tower_mcp::proxy::McpProxy;
use tower_mcp::client::StdioClientTransport;
let proxy = McpProxy::builder("my-proxy", "1.0.0")
.backend("db", StdioClientTransport::spawn("db-server", &[]).await?)
.await
.backend("fs", StdioClientTransport::spawn("fs-server", &[]).await?)
.await
.build()
.await?;
// Tools become db_query, fs_read, etc.
// Serve over any transport.
StdioTransport::new(proxy).run().await?;每后端Tower中间件适用于单个后端:
use std::time::Duration;
use tower::timeout::TimeoutLayer;
let proxy = McpProxy::builder("proxy", "1.0.0")
.backend("fast", cache_transport).await
.backend_layer(TimeoutLayer::new(Duration::from_secs(2)))
.backend("slow", llm_transport).await
.backend_layer(TimeoutLayer::new(Duration::from_secs(60)))
.build().await?;代理还支持通知转发(后端列表更改事件传播到客户端)、健康检查(proxy.health_check().await),并通过以下方式请求合并 tower-resiliences CoalesceLayer.
后端不需要使用tower mcp构建——代理通过标准mcp(JSON-RPC)进行通信,因此它可以与用任何语言或框架编写的服务器一起工作:Python(FastMCP)、TypeScript、Go或任何使用mcp协议的东西。这使得tower mcp成为多语言mcp部署的自然聚合和中间件层。
看 proxy 模块文档 和 examples/proxy.rs.
路由器级别状态
使用以下命令在所有处理程序之间共享状态 with_state():
use std::sync::Arc;
use tower_mcp::extract::Extension;
#[derive(Clone)]
struct AppState {
db: DatabasePool,
config: Config,
}
let state = Arc::new(AppState { /* ... */ });
// Tools access state via Extension extractor
let tool = ToolBuilder::new("query")
.extractor_handler(
(),
|Extension(app): Extension>, Json(input): Json| async move {
let result = app.db.query(&input.sql).await?;
Ok(CallToolResult::text(result))
},
)
.build();
let router = McpRouter::new()
.with_state(state) // Makes AppState available to all handlers
.tool(tool);运输
Stdio(命令行界面/本地)
use tower_mcp::{McpRouter, StdioTransport};
let router = McpRouter::new()
.server_info("my-server", "1.0.0")
.tool(my_tool);
// Serve over stdin/stdout
StdioTransport::new(router).serve().await?;HTTP与SSE
use tower_mcp::{McpRouter, HttpTransport};
let router = McpRouter::new()
.server_info("my-server", "1.0.0")
.tool(my_tool);
let transport = HttpTransport::new(router);
let app = transport.into_router();
// Serve with axum
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
axum::serve(listener, app).await?;使用身份验证中间件
use tower_mcp::auth::extract_api_key;
use axum::middleware;
// Add auth layer to the HTTP transport
let app = transport.into_router()
.layer(middleware::from_fn(auth_middleware));MCP中间件
tower mcp在标准tower中间件的基础上提供了三个mcp特定的中间件层:
| 层 | 目标 | 目的 |
|---|---|---|
McpTracingLayer | 所有请求 | 具有请求生命周期跨度的结构化跟踪 |
ToolCallLoggingLayer | tools/call 仅 | 带有注释提示的聚焦工具调用审核日志记录 |
AuditLayer | 所有请求 | 全面的审计事件(mcp::audit 追踪目标) |
use tower::ServiceBuilder;
use tower_mcp::middleware::{AuditLayer, McpTracingLayer};
let transport = StdioTransport::new(router)
.layer(
ServiceBuilder::new()
.layer(McpTracingLayer::new())
.layer(AuditLayer::new())
.into_inner(),
);标准塔式中间件(超时、速率限制、并发)也通过 .layer() 关于运输工具和个人工具。
测试
塔mcp包括 TestClient (特点: testing)对于进程内服务器测试——无子进程、无网络、无端口管理:
use tower_mcp::TestClient;
use serde_json::json;
let mut client = TestClient::from_router(router);
client.initialize().await;
// List and call tools
let tools = client.list_tools().await;
assert_eq!(tools.len(), 1);
let result = client.call_tool("greet", json!({"name": "World"})).await;
assert_eq!(result.all_text(), "Hello, World!");
// Typed deserialization
let stats: ServerStats = client.call_tool_typed("stats", json!({})).await;
// Assert expected errors
let err = client.call_tool_expect_error("missing", json!({})).await;TestClient 处理JSON-RPC帧、请求ID和协议初始化。方法对意外错误感到恐慌,保持测试代码简洁。
能力筛选
控制每个会话可以看到哪些工具、资源和提示。这实现了多租户模式,其中不同的客户端根据身份验证声明或会话状态获得不同的功能:
use tower_mcp::CapabilityFilter;
// Hide write tools from sessions that aren't authorized
let router = McpRouter::new()
.tool(read_tool)
.tool(write_tool)
.tool_filter(CapabilityFilter::write_guard(|session| {
session.get::()
.map(|r| r.is_admin())
.unwrap_or(false)
}));write_guard 使用工具注释:标记的工具 .read_only() 始终可见,而其他工具仅显示给谓词返回的会话 true.默认情况下,隐藏工具返回“找不到方法”,或配置 DenialBehavior::Unauthorized 在不授予访问权限的情况下披露其存在。
过滤器也适用于资源和提示:
let router = McpRouter::new()
.resource(public_resource)
.resource(internal_resource)
.resource_filter(CapabilityFilter::new(|session, resource: &Resource| {
!resource.name().contains("internal") || session.get::().is_some()
}));建筑
+-----------------+
| Your App |
+-----------------+
|
+-----------------+
| Tower Middleware| <-- tracing, metrics, auth, etc.
+-----------------+
|
+-----------------+
| JsonRpcService | <-- JSON-RPC 2.0 framing
+-----------------+
|
+-----------------+
| McpRouter | <-- Request dispatch
+-----------------+
|
+------------+------------+
| | |
+--------+ +--------+ +--------+
| Tool 1 | | Tool 2 | | Tool N |
+--------+ +--------+ +--------+协议遵从
- \[x\] JSON-RPC 2.0消息格式
- \[x\] 协议版本协商 (支持
2025-11-25和2025-03-26) - \[x\] 能力协商
- \[x\] 初始化/初始化生命周期
- \[x\] 工具/列表和工具/调用
- \[x\] 工具注释
- \[x\] 批量请求
- \[x\] 资源/列表、资源/读取、资源/订阅
- \[x\] 资源/模板/列表
- \[x\] 提示/列表,提示/获取
- \[x\] 日志记录(通知/消息、日志记录/setLevel)
- \[x\] 工具/资源/提示上的图标(SEP-973)
- \[x\] 实施元数据
- \[x\] 使用工具取样/工具选择(SEP-1577)
- \[x\] 引用(表单和URL模式)
- \[x\] 会话管理
- \[x\] 进度通知
- \[x\] 请求取消
- \[x\] 完成(自动完成)
- \[x\] 根(文件系统发现)
- \[x\] 采样 (所有运输工具)
- \[x\] 异步任务 (任务ID、状态跟踪、TTL清理、每工具任务支持模式)
- \[x\] SSE事件ID和流恢复 (1999年9月)
- \[x\]
_meta所有协议类型上的字段
我们跟踪所有MCP规范增强提案(SEP) 。每周工作流同步上游规范存储库的状态。
示例
回购包括23个按主题组织的重点示例:
| 类别 | 示例 |
|---|---|
| 入门 | getting_started --工具、资源、提示、stdio传输 |
| 运输 | http_server, websocket_server |
| 中间件 | middleware (运输、每个工具、每个资源、每个提示、警卫), rate_limiting, capability_filtering, tool_selection |
| 认证 | http_auth, oauth_client, external_api_auth |
| 客户 | client_cli, http_client, http_sse_client |
| 双向 | sampling_server, client_handler |
| 动态的 | dynamic_capabilities --运行时工具/提示/资源注册 |
| 高级 | proxy, resource_templates, structured_output, error_handling, testing |
| 真实的 | weather_server --外部API集成 |
| 宏 | tool_macro -- #[tool_fn], #[prompt_fn], #[resource_fn] |
克隆仓库和 .mcp.json 自动配置示例服务器:
git clone https://github.com/joshrotenberg/tower-mcp
cd tower-mcp
# Run your MCP agent here - servers will be available automatically发展
# Format, lint, and test
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features许可证
麻省理工学院或阿帕奇-2.0
