Crates.io | turbomcp-macros |
lib.rs | turbomcp-macros |
version | 1.1.0-exp.3 |
created_at | 2025-08-26 17:18:53.685588+00 |
updated_at | 2025-08-29 19:50:30.379674+00 |
description | Procedural macros for ergonomic MCP tool and resource registration |
homepage | |
repository | https://github.com/Epistates/turbomcp |
max_upload_size | |
id | 1811557 |
size | 137,947 |
Zero-boilerplate procedural macros for ergonomic MCP server development with automatic schema generation and compile-time validation.
turbomcp-macros
provides the procedural macros that make TurboMCP development effortless. These macros eliminate boilerplate code while providing compile-time validation, automatic schema generation, and type-safe parameter handling.
┌─────────────────────────────────────────────┐
│ TurboMCP Macros │
├─────────────────────────────────────────────┤
│ Procedural Macro Processing │
│ ├── #[server] trait implementation │
│ ├── #[tool] function registration │
│ ├── #[resource] handler registration │
│ └── #[prompt] template registration │
├─────────────────────────────────────────────┤
│ Schema Generation Engine │
│ ├── Type introspection │
│ ├── JSON Schema creation │
│ ├── Parameter validation │
│ └── Documentation extraction │
├─────────────────────────────────────────────┤
│ Code Generation │
│ ├── Handler registration code │
│ ├── Parameter extraction logic │
│ ├── Error conversion helpers │
│ └── Schema metadata functions │
├─────────────────────────────────────────────┤
│ Compile-Time Validation │
│ ├── Type compatibility checking │
│ ├── Parameter validation │
│ ├── Context injection validation │
│ └── Schema correctness verification │
└─────────────────────────────────────────────┘
#[server]
- Server ImplementationAutomatically implements the MCP server trait for a struct:
use turbomcp::prelude::*;
#[derive(Clone)]
struct Calculator;
#[server]
impl Calculator {
#[tool("Add two numbers")]
async fn add(&self, a: f64, b: f64) -> McpResult<f64> {
Ok(a + b)
}
#[tool("Get server status")]
async fn status(&self, ctx: Context) -> McpResult<String> {
ctx.info("Status requested").await?;
Ok("Server running".to_string())
}
}
// Generated code includes:
// - Automatic trait implementation
// - Handler registration
// - Schema generation
// - Transport integration
Generated Capabilities:
MCP
trait implementationrun_stdio
, run_http
, etc.)#[tool]
- Tool RegistrationTransforms functions into MCP tools with automatic parameter handling:
#[tool("Calculate mathematical expressions")]
async fn calculate(
#[description("Mathematical expression to evaluate")]
expression: String,
#[description("Precision for floating point results")]
precision: Option<u32>,
ctx: Context
) -> McpResult<serde_json::Value> {
ctx.info(&format!("Calculating: {}", expression)).await?;
let precision = precision.unwrap_or(2);
// ... calculation logic
Ok(serde_json::json!({
"result": result,
"expression": expression,
"precision": precision
}))
}
Generated Features:
#[resource]
- Resource RegistrationCreates URI template-based resource handlers:
#[resource("file://{path}")]
async fn read_file(
#[description("File path to read")]
path: String,
#[description("Maximum file size in bytes")]
max_size: Option<usize>,
ctx: Context
) -> McpResult<String> {
let max_size = max_size.unwrap_or(1024 * 1024); // 1MB default
if std::fs::metadata(&path)?.len() > max_size as u64 {
return Err(McpError::InvalidInput("File too large".to_string()));
}
ctx.info(&format!("Reading file: {}", path)).await?;
tokio::fs::read_to_string(&path).await
.map_err(|e| McpError::Resource(e.to_string()))
}
URI Template Features:
#[prompt]
- Prompt Template RegistrationCreates prompt templates with parameter substitution:
#[prompt("code_review")]
async fn code_review_prompt(
#[description("Programming language")]
language: String,
#[description("Code to review")]
code: String,
#[description("Focus areas for review")]
focus: Option<Vec<String>>,
ctx: Context
) -> McpResult<String> {
let focus_areas = focus.unwrap_or_else(|| vec![
"security".to_string(),
"performance".to_string(),
"maintainability".to_string()
]);
ctx.info(&format!("Generating {} code review prompt", language)).await?;
Ok(format!(
"Please review the following {} code focusing on {}:\n\n```{}\n{}\n```",
language,
focus_areas.join(", "),
language,
code
))
}
The Context
parameter can appear anywhere in the function signature:
// Context first
#[tool("Process data")]
async fn process(ctx: Context, data: String) -> McpResult<String> {
ctx.info("Processing started").await?;
Ok(format!("Processed: {}", data))
}
// Context in middle
#[tool("Transform data")]
async fn transform(input: String, ctx: Context, format: String) -> McpResult<String> {
ctx.info(&format!("Transforming to {}", format)).await?;
// transformation logic
Ok(transformed)
}
// Context last
#[tool("Validate input")]
async fn validate(data: String, strict: bool, ctx: Context) -> McpResult<bool> {
ctx.info("Validating input").await?;
// validation logic
Ok(is_valid)
}
// No context
#[tool("Simple calculation")]
async fn add(a: f64, b: f64) -> McpResult<f64> {
Ok(a + b)
}
Use the #[description]
attribute for rich parameter documentation:
#[tool("Search documents")]
async fn search(
#[description("Search query string")]
query: String,
#[description("Maximum number of results to return")]
#[default(10)]
limit: Option<u32>,
#[description("Include archived documents in search")]
#[default(false)]
include_archived: Option<bool>,
#[description("Sort results by relevance or date")]
#[allowed("relevance", "date")]
sort_by: Option<String>,
) -> McpResult<SearchResults> {
// Implementation
}
Generated Schema:
{
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query string"
},
"limit": {
"type": "integer",
"description": "Maximum number of results to return",
"default": 10
},
"include_archived": {
"type": "boolean",
"description": "Include archived documents in search",
"default": false
},
"sort_by": {
"type": "string",
"description": "Sort results by relevance or date",
"enum": ["relevance", "date"]
}
},
"required": ["query"]
}
The macros automatically generate schemas for custom types:
#[derive(Serialize, Deserialize)]
struct User {
id: u64,
name: String,
email: Option<String>,
active: bool,
}
#[derive(Serialize, Deserialize)]
struct CreateUserRequest {
name: String,
email: String,
role: UserRole,
}
#[derive(Serialize, Deserialize)]
enum UserRole {
Admin,
User,
Guest,
}
#[tool("Create a new user")]
async fn create_user(request: CreateUserRequest) -> McpResult<User> {
// Schema automatically generated for both CreateUserRequest and User
// Enums become string unions in JSON Schema
// Optional fields marked appropriately
Ok(User {
id: generate_id(),
name: request.name,
email: Some(request.email),
active: true,
})
}
Ergonomic error creation macros:
use turbomcp::prelude::*;
#[tool("Divide numbers")]
async fn divide(a: f64, b: f64) -> McpResult<f64> {
if b == 0.0 {
return Err(mcp_error!("Division by zero: {} / {}", a, b));
}
Ok(a / b)
}
#[tool("Process file")]
async fn process_file(path: String) -> McpResult<String> {
let content = tokio::fs::read_to_string(&path).await
.map_err(|e| mcp_error!("Failed to read file {}: {}", path, e))?;
// Processing logic
Ok(processed_content)
}
The macros generate metadata access functions:
#[derive(Clone)]
struct MyServer;
#[server]
impl MyServer {
#[tool("Example tool")]
async fn example(&self, input: String) -> McpResult<String> {
Ok(input)
}
}
// Generated metadata functions
let (name, description, schema) = MyServer::example_tool_metadata();
assert_eq!(name, "example");
assert_eq!(description, "Example tool");
// schema contains the complete JSON Schema
// Test the generated function directly
let result = MyServer.test_tool_call("example", serde_json::json!({
"input": "test"
})).await?;
Attribute | Description | Example |
---|---|---|
#[description] |
Parameter description | #[description("User ID")] |
#[default] |
Default value for optional parameters | #[default(10)] |
#[allowed] |
Allowed string values (enum) | #[allowed("read", "write")] |
#[range] |
Numeric range validation | #[range(0, 100)] |
#[pattern] |
Regex pattern validation | #[pattern(r"^\d{3}-\d{2}-\d{4}$")] |
Attribute | Description | Example |
---|---|---|
URI template | Resource URI pattern | #[resource("file://{path}")] |
#[mime_type] |
Content MIME type | #[mime_type("text/plain")] |
#[binary] |
Binary resource flag | #[binary(true)] |
Input:
#[tool("Add numbers")]
async fn add(&self, a: f64, b: f64) -> McpResult<f64> {
Ok(a + b)
}
Generated (simplified):
// Metadata function
pub fn add_tool_metadata() -> (&'static str, &'static str, serde_json::Value) {
("add", "Add numbers", serde_json::json!({
"type": "object",
"properties": {
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["a", "b"]
}))
}
// Handler registration
async fn register_handlers(&self, registry: &mut HandlerRegistry) -> McpResult<()> {
registry.register_tool("add", |params| {
let a: f64 = extract_param(¶ms, "a")?;
let b: f64 = extract_param(¶ms, "b")?;
self.add(a, b).await
}).await?;
Ok(())
}
// Direct test function
pub async fn test_tool_call(&self, name: &str, params: serde_json::Value) -> McpResult<serde_json::Value> {
match name {
"add" => {
let a: f64 = extract_param(¶ms, "a")?;
let b: f64 = extract_param(¶ms, "b")?;
let result = self.add(a, b).await?;
Ok(serde_json::to_value(result)?)
},
_ => Err(McpError::InvalidInput(format!("Unknown tool: {}", name)))
}
}
The macros provide excellent IDE support:
The macros generate testing utilities:
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_calculator_tools() {
let calc = Calculator;
// Test add tool directly
let result = calc.test_tool_call("add", serde_json::json!({
"a": 5.0,
"b": 3.0
})).await.unwrap();
assert_eq!(result, serde_json::json!(8.0));
// Test schema generation
let (name, desc, schema) = Calculator::add_tool_metadata();
assert_eq!(name, "add");
assert_eq!(desc, "Add two numbers");
assert!(schema["properties"]["a"]["type"] == "number");
}
}
The macros generate efficient code:
# Build macros crate
cargo build
# Test macro expansion
cargo expand --package turbomcp-macros
# Run macro tests
cargo test
# See expanded macro code
cargo expand --bin my_server
# Debug specific macro
RUST_LOG=debug cargo build
Licensed under the MIT License.
Part of the TurboMCP high-performance Rust SDK for the Model Context Protocol.