SKILL.md
readonlyread-only
name
rust-mcp-server-generator
description
使用官方 rmcp SDK 生成一個完整的 Rust Model Context Protocol 伺服器專案,包含工具、提示、資源與測試
Rust MCP Server Generator
你是一個 Rust MCP 伺服器生成器。使用官方 rmcp SDK 建立一個完整、可上線的 Rust MCP 伺服器專案。
專案需求
詢問使用者:
- 專案名稱(例如 "my-mcp-server")
- 伺服器描述(例如 "一個天氣資料 MCP 伺服器")
- 傳輸類型(stdio、sse、http 或全部)
- 要包含的工具(例如 "天氣查詢"、"預報"、"警報")
- 是否包含提示與資源
專案結構
生成以下結構:
{project-name}/
├── Cargo.toml
├── .gitignore
├── README.md
├── src/
│ ├── main.rs
│ ├── handler.rs
│ ├── tools/
│ │ ├── mod.rs
│ │ └── {tool_name}.rs
│ ├── prompts/
│ │ ├── mod.rs
│ │ └── {prompt_name}.rs
│ ├── resources/
│ │ ├── mod.rs
│ │ └── {resource_name}.rs
│ └── state.rs
└── tests/
└── integration_test.rs
檔案範本
Cargo.toml
[package]
name = "{project-name}"
version = "0.1.0"
edition = "2021"
[dependencies]
rmcp = { version = "0.8.1", features = ["server"] }
rmcp-macros = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0"
tracing = "0.1"
tracing-subscriber = "0.3"
schemars = { version = "0.8", features = ["derive"] }
async-trait = "0.1"
# 選用:用於 HTTP 傳輸
axum = { version = "0.7", optional = true }
tower-http = { version = "0.5", features = ["cors"], optional = true }
[dev-dependencies]
tokio-test = "0.4"
[features]
default = []
http = ["dep:axum", "dep:tower-http"]
[[bin]]
name = "{project-name}"
path = "src/main.rs"
.gitignore
/target
Cargo.lock
*.swp
*.swo
*~
.DS_Store
README.md
# {Project Name}
{Server description}
## 安裝
```bash
cargo build --release
使用方式
Stdio 傳輸
cargo run
SSE 傳輸
cargo run --features http -- --transport sse
HTTP 傳輸
cargo run --features http -- --transport http
設定
在您的 MCP 客戶端(例如 Claude Desktop)中設定:
{
"mcpServers": {
"{project-name}": {
"command": "path/to/target/release/{project-name}",
"args": []
}
}
}
工具
- {tool_name}:{Tool description}
開發
執行測試:
cargo test
啟用日誌執行:
RUST_LOG=debug cargo run
#### src/main.rs
```rust
use anyhow::Result;
use rmcp::{
protocol::ServerCapabilities,
server::Server,
transport::StdioTransport,
};
use tokio::signal;
use tracing_subscriber;
mod handler;
mod state;
mod tools;
mod prompts;
mod resources;
use handler::McpHandler;
#[tokio::main]
async fn main() -> Result<()> {
// 初始化 tracing
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_target(false)
.init();
tracing::info!("Starting {project-name} MCP server");
// 建立 handler
let handler = McpHandler::new();
// 建立傳輸(預設 stdio)
let transport = StdioTransport::new();
// 建立伺服器並設定功能
let server = Server::builder()
.with_handler(handler)
.with_capabilities(ServerCapabilities {
tools: Some(Default::default()),
prompts: Some(Default::default()),
resources: Some(Default::default()),
..Default::default()
})
.build(transport)?;
tracing::info!("Server started, waiting for requests");
// 執行伺服器直到按下 Ctrl+C
server.run(signal::ctrl_c()).await?;
tracing::info!("Server shutting down");
Ok(())
}
src/handler.rs
use rmcp::{
model::*,
protocol::*,
server::{RequestContext, ServerHandler, RoleServer, ToolRouter},
ErrorData,
};
use rmcp::{tool_router, tool_handler};
use async_trait::async_trait;
use crate::state::ServerState;
use crate::tools;
pub struct McpHandler {
state: ServerState,
tool_router: ToolRouter,
}
#[tool_router]
impl McpHandler {
// 從 tools 模組引入工具定義
#[tool(
name = "example_tool",
description = "一個範例工具",
annotations(read_only_hint = true)
)]
async fn example_tool(params: Parameters<tools::ExampleParams>) -> Result<String, String> {
tools::example::execute(params).await
}
pub fn new() -> Self {
Self {
state: ServerState::new(),
tool_router: Self::tool_router(),
}
}
}
#[tool_handler]
#[async_trait]
impl ServerHandler for McpHandler {
async fn list_prompts(
&self,
_request: Option<PaginatedRequestParam>,
_context: RequestContext<RoleServer>,
) -> Result<ListPromptsResult, ErrorData> {
let prompts = vec![
Prompt {
name: "example-prompt".to_string(),
description: Some("一個範例提示".to_string()),
arguments: Some(vec![
PromptArgument {
name: "topic".to_string(),
description: Some("要討論的主題".to_string()),
required: Some(true),
},
]),
},
];
Ok(ListPromptsResult { prompts })
}
async fn get_prompt(
&self,
request: GetPromptRequestParam,
_context: RequestContext<RoleServer>,
) -> Result<GetPromptResult, ErrorData> {
match request.name.as_str() {
"example-prompt" => {
let topic = request.arguments
.as_ref()
.and_then(|args| args.get("topic"))
.ok_or_else(|| ErrorData::invalid_params("需要 topic 參數"))?;
Ok(GetPromptResult {
description: Some("範例提示".to_string()),
messages: vec![
PromptMessage::user(format!("讓我們討論:{}", topic)),
],
})
}
_ => Err(ErrorData::invalid_params("未知的提示")),
}
}
async fn list_resources(
&self,
_request: Option<PaginatedRequestParam>,
_context: RequestContext<RoleServer>,
) -> Result<ListResourcesResult, ErrorData> {
let resources = vec![
Resource {
uri: "example://data/info".to_string(),
name: "範例資源".to_string(),
description: Some("一個範例資源".to_string()),
mime_type: Some("text/plain".to_string()),
},
];
Ok(ListResourcesResult { resources })
}
async fn read_resource(
&self,
request: ReadResourceRequestParam,
_context: RequestContext<RoleServer>,
) -> Result<ReadResourceResult, ErrorData> {
match request.uri.as_str() {
"example://data/info" => {
Ok(ReadResourceResult {
contents: vec![
ResourceContents::text("範例資源內容".to_string())
.with_uri(request.uri)
.with_mime_type("text/plain"),
],
})
}
_ => Err(ErrorData::invalid_params("未知的資源")),
}
}
}
src/state.rs
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone)]
pub struct ServerState {
// 在此加入共享狀態
counter: Arc<RwLock<i32>>,
}
impl ServerState {
pub fn new() -> Self {
Self {
counter: Arc::new(RwLock::new(0)),
}
}
pub async fn increment(&self) -> i32 {
let mut counter = self.counter.write().await;
*counter += 1;
*counter
}
pub async fn get(&self) -> i32 {
*self.counter.read().await
}
}
src/tools/mod.rs
pub mod example;
pub use example::ExampleParams;
src/tools/example.rs
use rmcp::model::Parameters;
use serde::{Deserialize, Serialize};
use schemars::JsonSchema;
#[derive(Debug, Deserialize, JsonSchema)]
pub struct ExampleParams {
pub input: String,
}
pub async fn execute(params: Parameters<ExampleParams>) -> Result<String, String> {
let input = ¶ms.inner().input;
// 工具邏輯
Ok(format!("已處理:{}", input))
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_example_tool() {
let params = Parameters::new(ExampleParams {
input: "test".to_string(),
});
let result = execute(params).await.unwrap();
assert!(result.contains("test"));
}
}
src/prompts/mod.rs
// 提示實作可放在此處(如有需要)
src/resources/mod.rs
// 資源實作可放在此處(如有需要)
tests/integration_test.rs
use rmcp::{
model::*,
protocol::*,
server::{RequestContext, ServerHandler, RoleServer},
};
// 將以下替換為您的實際專案名稱(蛇形命名)
// 例如:若專案為 "my-mcp-server",則使用 my_mcp_server
use my_mcp_server::handler::McpHandler;
#[tokio::test]
async fn test_list_tools() {
let handler = McpHandler::new();
let context = RequestContext::default();
let result = handler.list_tools(None, context).await.unwrap();
assert!(!result.tools.is_empty());
assert!(result.tools.iter().any(|t| t.name == "example_tool"));
}
#[tokio::test]
async fn test_call_tool() {
let handler = McpHandler::new();
let context = RequestContext::default();
let request = CallToolRequestParam {
name: "example_tool".to_string(),
arguments: Some(serde_json::json!({
"input": "test"
})),
};
let result = handler.call_tool(request, context).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_list_prompts() {
let handler = McpHandler::new();
let context = RequestContext::default();
let result = handler.list_prompts(None, context).await.unwrap();
assert!(!result.prompts.is_empty());
}
#[tokio::test]
async fn test_list_resources() {
let handler = McpHandler::new();
let context = RequestContext::default();
let result = handler.list_resources(None, context).await.unwrap();
assert!(!result.resources.is_empty());
}
實作指南
- 使用 rmcp-macros:善用
#[tool]、#[tool_router]和#[tool_handler]巨集以簡化程式碼 - 型別安全:所有參數型別使用
schemars::JsonSchema - 錯誤處理:回傳
Result型別並附上適當的錯誤訊息 - Async/Await:所有 handler 必須是非同步的
- 狀態管理:使用
Arc<RwLock<T>>管理共享狀態 - 測試:包含工具的單元測試與 handler 的整合測試
- 日誌:使用
tracing巨集(info!、debug!、warn!、error!) - 文件:為所有公開項目加上文件註解
範例工具模式
簡單唯讀工具
#[derive(Debug, Deserialize, JsonSchema)]
pub struct GreetParams {
pub name: String,
}
#[tool(
name = "greet",
description = "依姓名問候使用者",
annotations(read_only_hint = true, idempotent_hint = true)
)]
async fn greet(params: Parameters<GreetParams>) -> String {
format!("你好,{}!", params.inner().name)
}
含錯誤處理的工具
#[derive(Debug, Deserialize, JsonSchema)]
pub struct DivideParams {
pub a: f64,
pub b: f64,
}
#[tool(name = "divide", description = "將兩個數字相除")]
async fn divide(params: Parameters<DivideParams>) -> Result<f64, String> {
let p = params.inner();
if p.b == 0.0 {
Err("不能除以零".to_string())
} else {
Ok(p.a / p.b)
}
}
含狀態的工具
#[tool(
name = "increment",
description = "將計數器加一",
annotations(destructive_hint = true)
)]
async fn increment(state: &ServerState) -> i32 {
state.increment().await
}
執行生成的伺服器
生成後:
cd {project-name}
cargo build
cargo test
cargo run
整合至 Claude Desktop:
{
"mcpServers": {
"{project-name}": {
"command": "path/to/{project-name}/target/release/{project-name}",
"args": []
}
}
}
現在根據使用者的需求生成完整的專案!






