mcp attr
  
用于声明性构建模型上下文协议服务器的库。
特性
mcp-attr是一个机箱,旨在使人类和人工智能都能轻松创建 模型上下文协议 服务器。 为了实现这一目标,它具有以下特点:
- 声明性描述:
- 使用以下属性 #[mcp_server] 用最少的代码描述MCP服务器 - 更少的代码行使人类更容易理解和使用更少的AI上下文窗口
- DRY(不要重复自己)原则:
- 声明性描述确保代码遵循DRY原则 - 防止AI编写不一致的代码
- 利用类型系统:
- 通过类型表示发送给MCP客户端的信息可以减少源代码量并提高可读性 - 类型错误有助于AI进行编码
rustfmt友好的:
- 仅使用可通过以下方式格式化的属性宏 rustfmt - 确保AI生成的代码能够可靠地格式化
快速开始
安装
将以下内容添加到您的 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);
struct ServerData {
/// Server state
count: u32,
}
#[mcp_server]
impl McpServer for ExampleServer {
/// Description sent to MCP client
#[tool]
async fn add_count(&self, message: String) -> Result {
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 {
Ok(format!("Content of {name}.txt"))
}
#[prompt]
async fn example_prompt(&self) -> Result {
Ok("Hello!")
}
}支持状态
协议版本
2025-03-262024-11-05
运输
- 标准
尚未支持SSE。然而,传输是可扩展的,因此可以实现自定义传输。
方法
| 属性 | McpServer 方法 | 模型上下文协议方法 |
|---|
用法
启动服务器
使用此机箱创建的MCP服务器在tokio异步运行时上运行。
通过启动异步运行时启动服务器 #[tokio::main] 并传递一个值来实现 McpServer 特质到 serve_stdio 功能, 其使用标准输入/输出作为传输来启动服务器。
虽然你可以实现 McpServer trait可以手动实现,您可以通过使用 [#[mcp_server]](https://docs.rs/mcp-attr/latest/mcp_attr/server/attr.mcp_server.html) 属性。
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 {
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 {
Ok(format!("{}", lhs + rhs))
}
}可用于参数的类型因方法而异,并且必须实现以下特征:
| 属性 | 参数类型的特性 | 返回类型 |
|---|---|---|
[#[prompt]](#prompt) | FromStr | GetPromptResult |
[#[resource]](#resource) | FromStr | ReadResourceResult |
[#[tool]](#tool) | DeserializeOwned + JsonSchema | CallToolResult |
参数也可以使用 Option,在这种情况下,它们作为可选参数传递给MCP客户端。
返回值必须是可以转换为中所示类型的类型 Return type 上面的柱子,包裹着 Result. 例如,由于 CallToolResult 实现 From,您可以使用 Result 如上例所示的返回值。
AI的解释
对于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 description
#[tool]
async fn concat(&self,
/// Description of argument a (for AI)
a: u32,
/// Description of argument b (for AI)
b: u32,
) -> Result {
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);
struct ServerData {
count: u32,
}
#[mcp_server]
impl McpServer for ExampleServer {
#[tool]
async fn add_count(&self) -> Result {
let mut state = self.0.lock().unwrap();
state.count += 1;
Ok(format!("count: {}", state.count))
}
}错误处理
mcp_attr使用 ResultRust的标准错误处理方法。
类型 mcp_attr::Error 和 mcp_attr::Result (别名 std::result::Result)用于错误处理。
mcp_attr::Error 类似于 anyhow::Error,能够存储任何错误类型实现 std::error::Error + Sync + Send + 'static,并实现从其他错误类型的转换。 因此,在函数返回 mcp_attr::Result,您可以使用 ? 用于处理类型表达式错误的运算符 Result.
然而,它与 anyhow::Error 通过以下方式:
- 可以存储MCP中使用的JSON-RPC错误
- 具有区分错误消息是发送给MCP客户端的公共信息还是不发送的私人信息的功能
- (但是,在调试版本中,所有信息都会发送到MCP客户端)
宏 bail! 和 bail_public! 用于错误处理,类似于 anyhow::bail!.
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 {
let something_wrong = false;
if something_wrong {
bail_public!(ErrorCode::INTERNAL_ERROR, "Error message");
}
if something_wrong {
bail!("Error message");
}
let a = a.parse::()?;
Ok(format!("Success {a}"))
}
}调用客户端功能
使用 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 {
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;
/// This server provides file operations and utilities.
/// It can handle various file formats and perform data transformations.
#[mcp_server]
impl McpServer for ExampleServer {
#[tool]
async fn hello(&self) -> Result {
Ok("Hello, world!".to_string())
}
}如果 instructions 该方法是手动实现的,使用手动实现,不执行从文档注释自动生成指令。
完工支持(#[complete])
您可以使用以下命令为提示和资源参数添加完成功能 #[complete(function)] 属性。
完成功能必须有签名:
- 方法形式(
.method_name):async fn func_name(&self, p: &CompleteRequestParams, cx: &RequestContext) -> Result - 全局函数形式(
method_name):async fn func_name(p: &CompleteRequestParams, cx: &RequestContext) -> Result
当 #[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 {
Ok(format!("Hello, {name}!"))
}
#[resource("files://{path}")]
async fn get_file(&self, #[complete(.complete_paths)] path: String) -> Result {
Ok(format!("File: {path}"))
}
// #[complete_fn] can be written inside #[mcp_server] block
#[complete_fn]
async fn complete_paths(&self, _value: &str) -> Result> {
Ok(vec!["home".to_string(), "usr".to_string()])
}
// When RequestContext is needed
#[complete_fn]
async fn complete_names(&self, _value: &str, _cx: &RequestContext) -> Result> {
Ok(vec!["Alice", "Bob"])
}
}这 #[complete_fn] 属性允许省略 cx: &RequestContext 参数。当不需要RequestContext时,您可以省略它以获得更简单的完成函数。
完成仅适用于 #[prompt] 和 #[resource] 争论,不是为了 #[tool] 论据。
属性描述
#[prompt]
#[prompt("name", description = "..", title = "..")]
async fn func_name(&self) -> Result { }- “name”(可选):提示名称。如果省略,则使用函数名称。
- “description”(可选):AI的功能描述。优先于文档注释。
- “title”(可选):人类可读的提示标题。
实现以下方法:
函数参数变为提示参数。参数必须实现以下特性:
FromStr:用于从字符串中恢复值的特性
可以使用以下命令为参数命名 #[arg("name")] 属性。 如果未指定,则使用的名称是带前导的函数参数名称 _ 远离的。
参数可以使用完成功能 #[complete(function)] 属性。 看 完工支持 了解详情。
返回值: Result>
use mcp_attr::Result;
use mcp_attr::server::{mcp_server, McpServer};
struct ExampleServer;
#[mcp_server]
impl McpServer for ExampleServer {
/// Function description (for AI)
#[prompt]
async fn hello(&self) -> Result {
Ok("Hello, world!")
}
#[prompt]
async fn echo(&self,
/// Argument description (for AI)
a: String,
/// Argument description (for AI)
#[arg("x")]
b: String,
) -> Result {
Ok(format!("Hello, {a} {b}!"))
}
}#[resource]
#[resource("url_template", name = "..", mime_type = "..", description = "..", title = "..")]
async fn func_name(&self) -> Result { }- “url_template”(可选):URI模板(RFC 6570)指示此方法处理的资源的URL。如果省略,则处理所有URL。
- “name”(可选):资源名称。如果省略,则使用函数名称。
- “mime_type”(可选):资源的mime类型。
- “description”(可选):AI的功能描述。优先于文档注释。
- “title”(可选):人类可读的资源标题。
实现以下方法:
函数参数变为URI模板变量。参数必须实现以下特性:
FromStr:用于从字符串中恢复值的特性
参数可以使用完成功能 #[complete(function)] 属性。 看 完工支持 了解详情。
URI模板在中指定 RFC 6570 Level2.以下变量可用于URI模板:
{var}{+var}{#var}
返回值: Result>
use mcp_attr::Result;
use mcp_attr::server::{mcp_server, McpServer};
struct ExampleServer;
#[mcp_server]
impl McpServer for ExampleServer {
/// Function description (for AI)
#[resource("my_app://x/y.txt")]
async fn file_one(&self) -> Result {
Ok(format!("one file"))
}
#[resource("my_app://{a}/{+b}")]
async fn file_ab(&self, a: String, b: String) -> Result {
Ok(format!("{a} and {b}"))
}
#[resource]
async fn file_any(&self, url: String) -> Result {
Ok(format!("any file"))
}
}自动执行 resources_list 返回一个没有在中指定变量的URL列表 #[resource] 属性。 如果需要返回其他URL,则必须手动实现 resources_list. 如果 resources_list 如果是手动实现的,则不会自动实现。
#[tool]
#[tool(
"name",
description = "..",
title = "..",
non_destructive,
idempotent,
read_only,
closed_world,
)]
async fn func_name(&self) -> Result { }- “name”(可选):工具名称。如果省略,则使用函数名称。
- “description”(可选):AI的功能描述。优先于文档注释。
- “title”(可选):人类可读的工具标题。
- “non_destructive”(可选):工具仅执行附加更新(MCP规范:
destructive = false) - “幂等”(可选):使用相同的参数重复调用该工具没有额外效果(MCP规范:
idempotent = true) - “只读”(可选):工具不修改其环境(MCP规范:
read_only = true) - “closed_world”(可选):工具的交互域已关闭(MCP规范:
open_world = false)
实现以下方法:
函数参数变成工具参数。参数必须实现以下所有特征:
DeserializeOwned:用于从JSON恢复值的特性JsonSchema:用于生成JSON模式的特性(JSON模式被发送到MCP客户端,以便AI能够理解参数结构)
可以使用以下命令为参数命名 #[arg("name")] 属性。 如果未指定,则使用的名称是带前导的函数参数名称 _ 远离的。
返回值: Result>
use mcp_attr::Result;
use mcp_attr::server::{mcp_server, McpServer};
struct ExampleServer;
#[mcp_server]
impl McpServer for ExampleServer {
/// Function description (for AI)
#[tool]
async fn echo(&self,
/// Argument description (for AI)
a: String,
/// Argument description (for AI)
#[arg("x")]
b: String,
) -> Result {
Ok(format!("Hello, {a} {b}!"))
}
}手动执行
您还可以直接实现 McpServer 不使用属性的方法。
以下方法不支持通过属性实现,必须手动实现:
以下方法可以通过手动实现覆盖基于属性的实现:
completion_complete(自动生成时#[complete]使用属性)resources_listinstructions
测试
随着AI编码代理的出现,测试变得更加重要。 没有测试,人工智能很难写出正确的代码,但有了测试,它可以通过反复的测试和修复来写出正确的编码。
mcp_attr包括 McpClient 用于测试,它连接到流程中的MCP服务器。
use mcp_attr::client::McpClient;
use mcp_attr::server::{mcp_server, McpServer};
use mcp_attr::schema::{GetPromptRequestParams, GetPromptResult};
use mcp_attr::Result;
struct ExampleServer;
#[mcp_server]
impl McpServer for ExampleServer {
#[prompt]
async fn hello(&self) -> Result {
Ok("Hello, world!")
}
}
#[tokio::test]
async fn test_hello() -> Result {
let client = McpClient::with_server(ExampleServer).await?;
let a = client
.prompts_get(GetPromptRequestParams::new("hello"))
.await?;
let e: GetPromptResult = "Hello, world!".into();
assert_eq!(a, e);
Ok(())
}许可证
该项目在Apache-2.0/MIT下获得双重许可。有关详细信息,请参阅两个LICENSE-\*文件。
贡献
除非您另有明确说明,否则根据Apache-2.0许可证的定义,您有意提交以包含在作品中的任何贡献都应如上所述获得双重许可,无需任何额外的条款或条件。
