mcp-attr
A crate for creating Model Context Protocol servers using declarative descriptions with attributes and types.
Features
mcp-attr is a crate designed to make it easy for both humans and AI to create Model Context Protocol servers. To achieve this goal, it has the following features:
- Declarative Description:
- Use attributes like
#[mcp_server]to describe MCP servers with minimal code - Fewer lines of code make it easier for humans to understand and consume less context window for AI
- Following the DRY (Don't Repeat Yourself) principle helps prevent inconsistent code
- Use attributes like
- Leveraging the Type System:
- Expressing information sent to MCP clients through types reduces source code volume and improves readability
- Type errors help AI with coding
Quick Start
Installation
Add the following to your Cargo.toml:
[]
= "0.0.3"
= "1.43.0"
Basic Usage
use Mutex;
use ;
use Result;
async
;
Support Status
The following protocol versions, transports, and methods are supported.
Protocol Versions
2024-11-05
Transport
- stdio
SSE is not yet supported. However, transport is extensible, so custom transports can be implemented.
Methods
| Attribute | McpServer methods |
Model context protocol methods |
|---|---|---|
#[prompt] |
prompts_listprompts_get |
prompts/listprompts/get |
#[resource] |
resources_listresources_readresources_templates_list |
resources/listresources/readresources/templates/list |
#[tool] |
tools_listtools_call |
tools/listtools/call |
Usage
Starting the Server
MCP servers created with this crate run on the tokio async runtime.
Start the server by launching the async runtime with #[tokio::main] and passing a value implementing the McpServer trait to the serve_stdio function,
which starts a server using standard input/output as transport.
While you can implement the McpServer trait manually, you can implement it more efficiently in a declarative way by using the #[mcp_server] attribute.
use ;
use Result;
async
;
Most of the functions implementing MCP methods are asynchronous and can be executed concurrently.
Input and Output
How an MCP server receives data from an MCP client is expressed through function argument definitions.
For example, in the following example, the add tool indicates that it receives integers named lhs and rhs.
This information is sent from the MCP server to the MCP client, and the MCP client sends appropriate data to the server.
use ;
use Result;
;
The types that can be used for arguments vary by method, and must implement the following traits:
| Attribute | Trait for argument types | Return type |
|---|---|---|
#[prompt] |
FromStr |
GetPromptResult |
#[resource] |
FromStr |
ReadResourceResult |
#[tool] |
DeserializeOwned + JsonSchema |
CallToolResult |
Arguments can also use Option<T>, in which case they are communicated to the MCP client as optional arguments.
Return values must be types that can be converted to the type shown in the Return type column above, wrapped in Result.
For example, since CallToolResult implements From<String>, you can use Result<String> as the return value as shown in the example above.
Explanations for AI
For an MCP client to call MCP server methods, the AI needs to understand the meaning of the methods and arguments.
Adding documentation comments to methods and arguments sends this information to the MCP client, allowing the AI to understand their meaning.
use ;
use Result;
;
State Management
Since values implementing McpServer are shared among multiple concurrently executing methods, only &self is available. &mut self cannot be used.
To maintain state, you need to use thread-safe types with interior mutability like Mutex.
use Mutex;
use ;
use Result;
;
Error Handling
mcp_attr uses Result, Rust's standard error handling method.
The types mcp_attr::Error and mcp_attr::Result (an alias for std::result::Result<T, mcp_attr::Error>) are provided for error handling.
mcp_attr::Error is similar to anyhow::Error, capable of storing any error type implementing std::error::Error + Sync + Send + 'static, and implements conversion from other error types.
Therefore, in functions returning mcp_attr::Result, you can use the ? operator for error handling with expressions of type Result<T, impl std::error::Error + Sync + Send + 'static>.
However, it differs from anyhow::Error in the following ways:
- Can store JSON-RPC errors used in MCP
- Has functionality to distinguish whether error messages are public information to be sent to the MCP Client or private information not to be sent
- (However, in debug builds, all information is sent to the MCP Client)
The macros bail! and bail_public! are provided for error handling, similar to anyhow::bail!.
bail!takes a format string and arguments and raises an error treated as private information.bail_public!takes an error code, format string, and arguments and raises an error treated as public information.
Additionally, conversions from other error types are treated as private information.
use ;
use ;
;
Calling Client Features
MCP servers can call client features (such as roots/list) using RequestContext.
To use RequestContext in methods implemented using attributes, add a &RequestContext type variable to the method arguments.
use ;
use Result;
;
Attribute Descriptions
#[prompt]
async
- "name" (optional): Prompt name. If omitted, the function name is used.
Implements the following methods:
Function arguments become prompt arguments. Arguments must implement the following trait:
FromStr: Trait for restoring values from strings
Arguments can be given names using the #[arg("name")] attribute.
If not specified, the name used is the function argument name with leading _ removed.
Return value: Result<impl Into<GetPromptResult>>
use Result;
use ;
;
#[resource]
async
- "url_template" (optional): URI Template (RFC 6570) indicating the URL of resources this method handles. If omitted, handles all URLs.
- "name" (optional): Resource name. If omitted, the function name is used.
- "mime_type" (optional): MIME type of the resource.
Implements the following methods:
resources_list(can be manually implemented)resources_readresources_templates_list
Function arguments become URI Template variables. Arguments must implement the following trait:
FromStr: Trait for restoring values from strings
URI Templates are specified in RFC 6570 Level2. The following variables can be used in URI Templates:
{var}{+var}{#var}
Return value: Result<impl Into<ReadResourceResult>>
use Result;
use ;
;
The automatically implemented resources_list returns a list of URLs without variables specified in the #[resource] attribute.
If you need to return other URLs, you must manually implement resources_list.
If resources_list is manually implemented, it is not automatically implemented.
#[tool]
async
- "name" (optional): Tool name. If omitted, the function name is used.
Implements the following methods:
Function arguments become tool arguments. Arguments must implement all of the following traits:
DeserializeOwned: Trait for restoring values from JSONJsonSchema: Trait for generating JSON Schema (JSON Schema is sent to MCP Client so AI can understand argument structure)
Arguments can be given names using the #[arg("name")] attribute.
If not specified, the name used is the function argument name with leading _ removed.
Return value: Result<impl Into<CallToolResult>>
use Result;
use ;
;
Manual Implementation
You can also directly implement McpServer methods without using attributes.
Additionally, the following methods do not support implementation through attributes and must be implemented manually:
The following method can be overridden with manual implementation over the attribute-based implementation:
Testing
With the advent of AI Coding Agents, testing has become even more important. AI can hardly write correct code without tests, but with tests, it can write correct code through repeated testing and fixes.
mcp_attr includes McpClient for testing, which connects to MCP servers within the process.
use McpClient;
use ;
use ;
use Result;
;
async
License
This project is dual licensed under Apache-2.0/MIT. See the two LICENSE-* files for details.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.