使用说明
mcp服务器
]
模块化 模型上下文协议 用Rust编写的服务器。
支持两种传输方式(在运行时通过以下方式选择 .env):
| 运输 | 何时使用 |
|---|---|
| 标准 | Claude Desktop,CLI客户端,默认 |
| 可流式传输的HTTP | Web客户端、多会话、远程访问 |
目录
建筑
src/
├── main.rs # Wires config → engine. Nothing else lives here.
├── config.rs # Reads .env / env vars → Config struct
├── server.rs # McpServer — implements rmcp::ServerHandler using
│ # all registered tools, resources, and prompts
├── engine/
│ ├── mod.rs # Dispatches to stdio or HTTP based on Config
│ └── http.rs # Streamable HTTP transport (Axum + rmcp)
├── tools/
│ ├── mod.rs # McpTool trait + ToolRegistration + all_tools()
│ ├── count_lines.rs # Counts lines in files by extension
│ └── query_db.rs # Executes user-scoped SELECT queries against MySQL
├── resources/
│ └── mod.rs # McpResource trait + ResourceRegistration + all_resources()
└── prompts/
└── mod.rs # McpPrompt trait + PromptRegistration + all_prompts()
自动发现的工作原理
三层协同工作,将文件放入文件夹就足够了:
-
build.rs--跑步前rustc在每一个cargo build.它扫描src/tools/,src/resources/,以及src/prompts/并写了一个pub mod <name>;线路/.rs鱼贯而入OUT_DIR/<dir>_modules.rs. -
include!--每个mod.rs有一个单一include!(concat!(env!("OUT_DIR"), "/<dir>_modules.rs"))这就拉进来了 生成的声明。Rust编译器可以看到 文件夹,无需任何手动操作。 -
inventory板条箱 --每个插件文件调用inventory::submit!一次 在模块级别。在链接时,链接器会收集所有提交的内容。all_tools()/all_resources()/all_prompts()在以下位置迭代它们 启动。
配置(.env)
复制 .env 并根据需要进行编辑。所有值都有内置默认值。
| 变量 | 默认值 | 描述 |
|---|---|---|
MCP_PORT |
3333 |
用于流式HTTP传输的TCP端口 |
MCP_LISTENING_ADDRESS |
127.0.0.1 |
流式HTTP传输的绑定地址 |
MCP_COMMUNICATION |
stdio |
运输: stdio 或 Streamable_HTTP |
DB_URL |
-- | MySQL主机(例如。 localhost)--需要 query_db |
DB_NAME |
-- | MySQL数据库名称--必填 query_db |
DB_USER |
-- | MySQL用户名--必填 query_db |
DB_PASSWORD |
(空) | MySQL密码——必填 query_db |
示例——在所有接口上作为HTTP服务器运行:
MCP_COMMUNICATION=Streamable_HTTP
MCP_LISTENING_ADDRESS=0.0.0.0
MCP_PORT=3333
运输
标准
默认值。客户端将此二进制文件作为子进程生成。JSON-RPC
消息通过以换行符分隔的stdin/stdout进行交换。
eprintln! (stderr)用于所有日志记录,以免损坏
协议通道。
MCP客户端配置示例(Claude Desktop/ .mcp.json):
{
"mcpServers": {
"mcp-server": {
"command": "/path/to/target/release/mcp_server"
}
}
}
可流式传输的HTTP
实施 可流式HTTP传输 来自MCP规范(2025-11-25)。
服务器在以下位置挂载一个端点 /mcp:
| 方法 | 目的 |
|---|---|
POST /mcp |
发送JSON-RPC消息。响应为 application/json 或 text/event-stream 这取决于消息类型。 |
GET /mcp |
为服务器发起的消息打开持久的SSE流。 |
DELETE /mcp |
明确结束会议。 |
会话管理(MCP-Session-Id), MCP-Protocol-Version 头球
验证、SSE保持活动和流恢复都在内部处理
通过rmcp StreamableHttpService.
测试
以下示例涵盖了运输和锻炼工具、资源和 提示。根据您的需求调整工具名称、资源URI和提示名称 已注册。
标准
二进制文件从以下地址每行读取一条JSON-RPC消息 stdin 并写道
回应 stdout.直接用管道传输换行符分隔的消息序列
使用here文档将二进制文件转换为二进制文件。
初始化
./target/release/mcp_server << 'EOF'
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl-test","version":"0.0.1"}}}
EOF
确认初始化(通知-无响应)
./target/release/mcp_server << 'EOF'
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl-test","version":"0.0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
EOF
工具列表
./target/release/mcp_server << 'EOF'
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl-test","version":"0.0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
EOF
工具--调用(count_lines)
./target/release/mcp_server << 'EOF'
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl-test","version":"0.0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"count_lines","arguments":{"path":"/tmp","extension":"rs"}}}
EOF
工具--调用(query_db)
对MySQL执行用户范围的SELECT查询。查询必须包含
:user_id 只有一次,所以结果总是会被过滤到给定的用户。
./target/release/mcp_server << 'EOF'
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl-test","version":"0.0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"query_db","arguments":{"user_id":"1","query":"SELECT * FROM Entries WHERE user_id = :user_id"}}}
EOF
资源-列表
./target/release/mcp_server << 'EOF'
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl-test","version":"0.0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"resources/list"}
EOF
资源--阅读
将URI替换为返回的URI resources/list.
./target/release/mcp_server << 'EOF'
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl-test","version":"0.0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"resources/read","params":{"uri":"file:///config/settings.json"}}
EOF
提示-列表
./target/release/mcp_server << 'EOF'
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl-test","version":"0.0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"prompts/list"}
EOF
提示--获取
将名称和参数替换为返回的值 prompts/list.
./target/release/mcp_server << 'EOF'
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl-test","version":"0.0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"prompts/get","params":{"name":"summarise","arguments":{"text":"Hello world"}}}
EOF
每个回复都写在 stdout 在加工过程中,它会自行生产。
诊断/日志输出转到 stderr 并且不会干扰。
可流式传输的HTTP
先启动服务器:
MCP_COMMUNICATION=Streamable_HTTP ./target/release/mcp_server
每一个请求都是 POST /mcp服务器返回一个 MCP-Session-Id 头球
在 initialize 回应;所有后续请求都必须包含该标头。
1.初始化--捕获会话ID
curl -si -X POST http://127.0.0.1:3333/mcp \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2025-11-25' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl-test","version":"0.0.1"}}}' \
-D /tmp/mcp_headers.txt
# Extract the session ID from the saved headers
SESSION_ID=$(grep -i 'mcp-session-id' /tmp/mcp_headers.txt | awk '{print $2}' | tr -d '\r\n')
echo "Session: $SESSION_ID"
2.确认初始化(通知——无响应正文)
curl -s -X POST http://127.0.0.1:3333/mcp \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2025-11-25' \
-H "MCP-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
3.工具清单
curl -s -X POST http://127.0.0.1:3333/mcp \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2025-11-25' \
-H "MCP-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
4.工具--调用(count_lines)
curl -s -X POST http://127.0.0.1:3333/mcp \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2025-11-25' \
-H "MCP-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"count_lines","arguments":{"path":"/tmp","extension":"rs"}}}'
5.工具--调用(query_db)
对MySQL执行用户范围的SELECT查询。查询必须包含
:user_id 只有一次,所以结果总是会被过滤到给定的用户。
curl -s -X POST http://127.0.0.1:3333/mcp \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2025-11-25' \
-H "MCP-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"query_db","arguments":{"user_id":"1","query":"SELECT * FROM Entries WHERE user_id = :user_id"}}}'
7.资源----清单
curl -s -X POST http://127.0.0.1:3333/mcp \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2025-11-25' \
-H "MCP-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":5,"method":"resources/list"}'
8.资源----阅读
将URI替换为返回的URI resources/list.
curl -s -X POST http://127.0.0.1:3333/mcp \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2025-11-25' \
-H "MCP-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":6,"method":"resources/read","params":{"uri":"file:///config/settings.json"}}'
9.提示-列表
curl -s -X POST http://127.0.0.1:3333/mcp \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2025-11-25' \
-H "MCP-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":7,"method":"prompts/list"}'
10.提示--获取
将名称和参数替换为返回的值 prompts/list.
curl -s -X POST http://127.0.0.1:3333/mcp \
-H 'Content-Type: application/json' \
-H 'MCP-Protocol-Version: 2025-11-25' \
-H "MCP-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":7,"method":"prompts/get","params":{"name":"summarise","arguments":{"text":"Hello world"}}}'
10.结束会话(可选)
curl -s -X DELETE http://127.0.0.1:3333/mcp \
-H 'MCP-Protocol-Version: 2025-11-25' \
-H "MCP-Session-Id: $SESSION_ID"
构建
cargo build --release
构建脚本会自动运行,不需要手动步骤。
添加新工具
-
创建
src/tools/<your_tool>.rs. -
实施 这
McpTool特质:
use anyhow::Result;
use serde_json::{json, Map, Value};
use crate::tools::{BoxFuture, McpTool, ToolRegistration};
pub struct MyTool;
impl McpTool for MyTool {
fn name(&self) -> &'static str {
"my_tool" // unique snake_case identifier
}
fn description(&self) -> &'static str {
"Does something useful"
}
fn schema(&self) -> Map<String, Value> {
// Must be a JSON Schema object with "type": "object"
json!({
"type": "object",
"properties": {
"input": { "type": "string", "description": "The input value" }
},
"required": ["input"]
})
.as_object()
.unwrap()
.clone()
}
fn call(&self, params: Map<String, Value>) -> BoxFuture<'_, Result<String>> {
Box::pin(async move {
let input = params["input"].as_str().unwrap_or("");
Ok(format!("You said: {}", input))
})
}
}
- 注册 它位于同一文件的底部:
inventory::submit! { ToolRegistration { factory: || Box::new(MyTool) } }
- 跑
cargo build--工具是活的。没有其他文件需要编辑。
编写测试
添加一个 #[cfg(test)] 同一文件中的模块和测试 call 直接:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_expected_output() {
let tool = MyTool;
let params = serde_json::json!({ "input": "hello" });
let result = tokio::runtime::Runtime::new()
.unwrap()
.block_on(tool.call(params.as_object().unwrap().clone()))
.unwrap();
assert_eq!(result, "You said: hello");
}
}
添加新资源
-
创建
src/resources/<your_resource>.rs. -
实施 这
McpResource特质:
use anyhow::Result;
use crate::resources::{BoxFuture, McpResource, ResourceRegistration};
pub struct ConfigResource;
impl McpResource for ConfigResource {
fn uri(&self) -> &'static str {
"file:///config/settings.json" // unique URI
}
fn name(&self) -> &'static str {
"Application Settings"
}
fn description(&self) -> Option<&'static str> {
Some("Current application configuration")
}
fn mime_type(&self) -> Option<&'static str> {
Some("application/json")
}
fn read(&self) -> BoxFuture<'_, Result<String>> {
Box::pin(async move {
// Read from disk, database, network, etc.
Ok(r#"{"theme": "dark"}"#.to_string())
})
}
}
- 注册 它
inventory::submit! { ResourceRegistration { factory: || Box::new(ConfigResource) } }
- 跑
cargo build.
添加新提示
-
创建
src/prompts/<your_prompt>.rs. -
实施 这
McpPrompt特质:
use anyhow::Result;
use serde_json::{Map, Value};
use crate::prompts::{
BoxFuture, McpPrompt, PromptArg, PromptMessage, PromptRegistration, Role,
};
pub struct SummarisePrompt;
impl McpPrompt for SummarisePrompt {
fn name(&self) -> &'static str {
"summarise" // unique identifier
}
fn description(&self) -> Option<&'static str> {
Some("Summarise a piece of text")
}
fn arguments(&self) -> Vec<PromptArg> {
vec![PromptArg {
name: "text",
description: Some("The text to summarise"),
required: true,
}]
}
fn get(&self, args: Map<String, Value>) -> BoxFuture<'_, Result<Vec<PromptMessage>>> {
Box::pin(async move {
let text = args["text"].as_str().unwrap_or("(no text provided)");
Ok(vec![PromptMessage {
role: Role::User,
text: format!("Please summarise the following:\n\n{}", text),
}])
})
}
}
- 注册 它
inventory::submit! { PromptRegistration { factory: || Box::new(SummarisePrompt) } }
- 跑
cargo build.
运行测试
cargo test
每个模块都有自己的单元测试。特定练习中的工具测试 使用真实的文件系统进行端到端的实际调用逻辑。