一个用于声明式构建模型上下文协议服务器的库。
mcp-attr 是一个旨在使人类和AI都能轻松创建 [模型上下文协议] 服务器的库。 为了实现这一目标,它具有以下特性:
#[mcp_server] 属性来用最少的代码描述 MCP 服务器rustfmt友好:
rustfmt 格式的属性宏在你的 Cargo.toml 中添加以下内容:
[dependencies]
mcp-attr = "0.0.7"
tokio = "1.43.0"
use std::sync::Mutex;
use mcp_attr::server::{mcp_server, McpServer, serve_stdio};
use mcp_attr::Result;
#[tokio::main]
async fn main() -> Result<()> {
serve_stdio(ExampleServer(Mutex::new(ServerData { count: 0 }))).await?;
Ok(())
}
struct ExampleServer(Mutex<ServerData>);
struct ServerData {
/// 服务器状态
count: u32,
}
#[mcp_server]
impl McpServer for ExampleServer {
/// 发送给 MCP 客户端的描述
#[tool]
async fn add_count(&self, message: String) -> Result<String> {
let mut state = self.0.lock().unwrap();
state.count += 1;
Ok(format!("Echo: {message} {}", state.count))
}
#[resource("my_app://files/{name}.txt")]
async fn read_file(&self, name: String) -> Result<String> {
Ok(format!("Content of {name}.txt"))
}
#[prompt]
async fn example_prompt(&self) -> Result<&str> {
Ok("Hello!")
}
}
2025-03-262024-11-05尚未支持 SSE。然而,传输是可扩展的,因此可以实现自定义传输方式。
| 属性 | McpServer 方法 | 模型上下文协议方法 |
|---|---|---|
#[prompt] | [prompts_list]<br>[prompts_get] | [prompts/list]<br>[prompts/get] |
#[resource] | [resources_list]<br>[resources_read]<br>[resources_templates_list] | [resources/list]<br>[resources/read]<br>[resources/templates/list] |
#[tool] | [tools_list]<br>[tools_call] | [tools/list]<br>[tools/call] |
使用此库创建的 MCP 服务器运行在 tokio 异步运行时上。
通过使用 #[tokio::main] 启动异步运行时,并将实现了 McpServer 特性的值传递给 serve_stdio 函数来启动服务器,
该函数使用标准输入输出作为传输方式启动服务器。
虽然你可以手动实现 McpServer 特性,但可以通过使用 #[mcp_server] 属性以声明的方式更高效地实现它。
use mcp_attr::server::{mcp_server, McpServer, serve_stdio};
use mcp_attr::Result;
#[tokio::main]
async fn main() -> Result<()> {
serve_stdio(ExampleServer).await?;
Ok(())
}
struct ExampleServer;
#[mcp_server]
impl McpServer for ExampleServer {
#[tool]
async fn hello(&self) -> Result<&str> {
Ok("Hello, world!")
}
}
大多数实现 MCP 方法的函数都是异步的,并且可以并发执行。
MCP 服务器如何从 MCP 客户端接收数据是通过函数参数定义表达的。
例如,在下面的例子中,add 工具表示它接收名为 lhs 和 rhs 的整数。
这些信息由 MCP 服务器发送到 MCP 客户端,而 MCP 客户端向服务器发送适当的数据。
use mcp_attr::server::{mcp_server, McpServer};
use mcp_attr::Result;
struct ExampleServer;
#[mcp_server]
impl McpServer for ExampleServer {
#[tool]
async fn add(&self, lhs: u32, rhs: u32) -> Result<String> {
Ok(format!("{}", lhs + rhs))
}
}
可用于参数的类型因方法而异,必须实现以下特性:
| 属性 | 参数类型的特性 | 返回类型 |
|---|---|---|
#[prompt] | [FromStr] | [GetPromptResult] |
#[resource] | [FromStr] | [ReadResourceResult] |
#[tool] | [DeserializeOwned] + [JsonSchema] | [CallToolResult] |
参数也可以使用 Option<T>,在这种情况下,它们被传达给 MCP 客户端作为可选参数。
返回值必须是可以转换为上述 返回类型 列表中的类型的类型,并包装在 Result 中。
例如,由于 CallToolResult 实现了 From<String>,你可以像上面的例子那样使用 Result<String> 作为返回值。
为了让 MCP 客户端调用 MCP 服务器的方法,AI 需要理解这些方法及其参数的意义。
通过在方法和参数上添加文档注释,将这些信息发送给 MCP 客户端,使 AI 能够理解其意义。
你还可以使用 description 属性参数指定描述。当同时指定了文档注释和描述属性时,描述属性优先。
use mcp_attr::server::{mcp_server, McpServer};
use mcp_attr::Result;
struct ExampleServer;
#[mcp_server]
impl McpServer for ExampleServer {
/// 工具描述
#[tool]
async fn concat(&self,
/// 参数 a 的描述(针对AI)
a: u32,
/// 参数 b 的描述(针对AI)
b: u32,
) -> Result<String> {
Ok(format!("{a},{b}"))
}
}
由于实现 McpServer 的值在多个并发执行的方法之间共享,只有 &self 可用。不能使用 &mut self。
为了维护状态,你需要使用具有内部可变性的线程安全类型,如 Mutex。
use std::sync::Mutex;
use mcp_attr::server::{mcp_server, McpServer};
use mcp_attr::Result;
struct ExampleServer(Mutex<ServerData>);
struct ServerData {
count: u32,
}
#[mcp_server]
impl McpServer for ExampleServer {
#[tool]
async fn add_count(&self) -> Result<String> {
let mut state = self.0.lock().unwrap();
state.count += 1;
Ok(format!("count: {}", state.count))
}
}
mcp_attr 使用 Result,这是 Rust 的标准错误处理方法。
提供了 mcp_attr::Error 和 mcp_attr::Result(std::result::Result<T, mcp_attr::Error> 的别名)来进行错误处理。
mcp_attr::Error 类似于 anyhow::Error,能够存储任何实现了 std::error::Error + Sync + Send + 'static 的错误类型,并实现了从其他错误类型的转换。
因此,在返回 mcp_attr::Result 的函数中,你可以使用 ? 操作符对类型为 Result<T, impl std::error::Error + Sync + Send + 'static> 的表达式进行错误处理。
然而,它与 anyhow::Error 在以下方面有所不同:
提供了类似于 anyhow::bail! 的错误处理宏 bail! 和 bail_public!。
bail! 接受格式字符串和参数,并引发被视为私有信息的错误。bail_public! 接受错误码、格式字符串和参数,并引发被视为公共信息的错误。此外,从其他错误类型的转换被视为私有信息。
use mcp_attr::server::{mcp_server, McpServer};
use mcp_attr::{bail, bail_public, Result, ErrorCode};
struct ExampleServer;
#[mcp_server]
impl McpServer for ExampleServer {
#[prompt]
async fn add(&self, a: String) -> Result<String> {
let something_wrong = false;
if something_wrong {
bail_public!(ErrorCode::INTERNAL_ERROR, "错误消息");
}
if something_wrong {
bail!("错误消息");
}
let a = a.parse::<i32>()?;
Ok(format!("成功 {a}"))
}
}
MCP 服务器可以使用 RequestContext 调用客户端功能(如 roots/list)。
要在使用属性实现的方法中使用 RequestContext,请在方法参数中添加一个 &RequestContext 类型变量。
use mcp_attr::server::{mcp_server, McpServer, RequestContext};
use mcp_attr::Result;
struct ExampleServer;
#[mcp_server]
impl McpServer for ExampleServer {
#[prompt]
async fn echo_roots(&self, context: &RequestContext) -> Result<String> {
let roots = context.roots_list().await?;
Ok(format!("{:?}", roots))
}
}
instructions 方法会自动从 impl McpServer 块上的文档注释生成。如果你写了一些描述服务器的文档注释,它们会被发送给 MCP 客户端作为指令。
use mcp_attr::server::{mcp_server, McpServer};
use mcp_attr::Result;
struct ExampleServer;
/// 此服务器提供文件操作和实用工具。
/// 它可以处理各种文件格式并执行数据转换。
#[mcp_server]
impl McpServer for ExampleServer {
#[tool]
async fn hello(&self) -> Result<String> {
Ok("Hello, world!".to_string())
}
}
如果手动实现了 instructions 方法,则使用手动实现,不会执行从文档注释自动生成指令的操作。
#[complete])你可以使用 #[complete(function)] 属性为提示和资源参数添加补全功能。
补全函数必须具有以下签名:
.method_name):async fn func_name(&self, p: &CompleteRequestParams, cx: &RequestContext) -> Result<CompleteResult>method_name):async fn func_name(p: &CompleteRequestParams, cx: &RequestContext) -> Result<CompleteResult>当使用 #[complete] 属性时,会自动生成 completion_complete 方法。手动实现会覆盖自动生成。
为了简化补全函数的开发,你可以使用 #[complete_fn] 属性自动将简单的签名转换为所需的签名:
use mcp_attr::server::{mcp_server, McpServer, RequestContext, complete_fn};
use mcp_attr::Result;
struct ExampleServer;
#[mcp_server]
impl McpServer for ExampleServer {
#[prompt]
async fn greet(&self, #[complete(.complete_names)] name: String) -> Result<String> {
Ok(format!("Hello, {name}!"))
}
#[resource("files://{path}")]
async fn get_file(&self, #[complete(.complete_paths)] path: String) -> Result<String> {
Ok(format!("File: {path}"))
}
// #[complete_fn] 可以写在 #[mcp_server] 块内
#[complete_fn]
async fn complete_paths(&self, _value: &str) -> Result<Vec<String>> {
Ok(vec!["home".to_string(), "usr".to_string()])
}
// 当需要 RequestContext 时
#[complete_fn]
async fn complete_names(&self, _value: &str, _cx: &RequestContext) -> Result<Vec<&'static str>> {
Ok(vec!["Alice", "Bob"])
}
}
#[complete_fn] 属性允许省略 cx: &RequestContext 参数。当不需要 RequestContext 时,可以省略它以简化补全函数。
补全是为 #[prompt] 和 #[resource] 参数提供的,而不是为 #[tool] 参数提供的。
#[prompt]#[prompt("name", description = "..", title = "..")]
async fn func_name(&self) -> Result<GetPromptResult> { }
实现以下方法:
prompts_list]prompts_get]函数参数成为提示参数。参数必须实现以下特性:
FromStr]:从字符串恢复值的特性参数可以使用 #[arg("name")] 属性命名。
如果没有指定,则使用去掉开头 _ 的函数参数名称。
参数可以使用 #[complete(function)] 属性添加补全功能。
详情见 补全支持。
返回值:Result<impl Into<GetPromptResult>>
use mcp_attr::Result;
use mcp_attr::server::{mcp_server, McpServer};
struct ExampleServer;
#[mcp_server]
impl McpServer for ExampleServer {
/// 函数描述(针对AI)
#[prompt]
async fn hello(&self) -> Result<&str> {
Ok("Hello, world!")
}
#[prompt]
async fn echo(&self,
/// 参数描述(针对AI)
a: String,
/// 参数描述(针对AI)
#[arg("x")]
b: String,
) -> Result<String> {
Ok(format!("Hello, {a} {b}!"))
}
}
#[resource]#[resource("url_template", name = "..", mime_type = "..", description = "..", title = "..")]
async fn func_name(&self) -> Result<ReadResourceResult> { }
实现以下方法:
resources_list](可以手动实现)resources_read]resources_templates_list]函数参数成为 URI 模板变量。参数必须实现以下特性:
FromStr]:从字符串恢复值的特性参数可以使用 #[complete(function)] 属性添加补全功能。
详情见 补全支持。
URI 模板遵循 [RFC 6570] Level2。可以在 URI 模板中使用以下变量:
{var}{+var}{#var}返回值:Result<impl Into<ReadResourceResult>>
use mcp_attr::Result;
use mcp_attr::server::{mcp_server, McpServer};
struct ExampleServer;
#[mcp_server]
impl McpServer for ExampleServer {
/// 函数描述(针对AI)
#[resource("my_app://x/y.txt")]
async fn file_one(&self) -> Result<String> {
Ok(format!("one file"))
}
#[resource("my_app://{a}/{+b}")]
async fn file_ab(&self, a: String, b: String) -> Result<String> {
Ok(format!("{a} and {b}"))
}
#[resource]
async fn file_any(&self, url: String) -> Result<String> {
Ok(format!("any file"))
}
}
自动实现的 resources_list 返回没有在 #[resource] 属性中指定变量的 URL 列表。
如果你需要返回其他 URL,则必须手动实现 resources_list。
如果手动实现了 resources_list,则不会自动实现。
#[tool]#[tool(
"name",
description = "..",
title = "..",
non_destructive,
idempotent,
read_only,
closed_world,
)]
async fn func_name(&self) -> Result<CallToolResult> { }