返回市场
MCP-属性

MCP-属性

作者:frozenlib29 星标更新:2025-10-07

项目介绍

mcp-attr

Crates.io Docs.rs Actions Status

一个用于声明式构建模型上下文协议服务器的库。

特性

mcp-attr 是一个旨在使人类和AI都能轻松创建 [模型上下文协议] 服务器的库。 为了实现这一目标,它具有以下特性:

  • 声明性描述
    • 使用如 #[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<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-26
  • 2024-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 工具表示它接收名为 lhsrhs 的整数。 这些信息由 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> 作为返回值。

对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]
  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::Errormcp_attr::Resultstd::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 在以下方面有所不同:

  • 可以存储 MCP 中使用的 JSON-RPC 错误
  • 具有区分错误消息是否为应发送给 MCP 客户端的公共信息或不应发送的私有信息的功能
    • (但在调试构建中,所有信息都会发送给 MCP 客户端)

提供了类似于 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> { }
  • "name"(可选):提示名称。如果未指定,则使用函数名称。
  • "description"(可选):AI 的函数描述。优先于文档注释。
  • "title"(可选):人类可读的提示标题。

实现以下方法:

  • [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> { }
  • "url_template"(可选):指示此方法处理的资源 URL 的 URI 模板([RFC 6570])。如果未指定,则处理所有 URL。
  • "name"(可选):资源名称。如果未指定,则使用函数名称。
  • "mime_type"(可选):资源的 MIME 类型。
  • "description"(可选):AI 的函数描述。优先于文档注释。
  • "title"(可选):人类可读的资源标题。

实现以下方法:

  • [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> { }
  • "name"(可选):工具名称。如果未指定,则使用函数名称。
  • "description"(可选):