| Crates.io | pforge-macro |
| lib.rs | pforge-macro |
| version | 0.1.4 |
| created_at | 2025-10-02 14:45:26.815182+00 |
| updated_at | 2025-12-06 12:09:08.984901+00 |
| description | Zero-boilerplate MCP server framework with EXTREME TDD methodology |
| homepage | https://github.com/paiml/pforge |
| repository | https://github.com/paiml/pforge |
| max_upload_size | |
| id | 1864479 |
| size | 10,561 |
Procedural macros for the pforge framework - ergonomic attribute macros for building MCP handlers.
#[handler] - Derive Handler trait automatically#[tool] - Generate complete MCP tool with JSON Schemacargo add pforge-macro
use pforge_macro::handler;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
#[derive(Deserialize)]
struct GreetParams {
name: String,
}
#[handler]
async fn greet(params: GreetParams) -> Result<Value> {
Ok(json!({
"message": format!("Hello, {}!", params.name)
}))
}
Expands to:
struct GreetHandler;
#[async_trait::async_trait]
impl Handler for GreetHandler {
async fn handle(&self, params: Value) -> Result<Value> {
let params: GreetParams = serde_json::from_value(params)?;
Ok(json!({
"message": format!("Hello, {}", params.name)
}))
}
}
The #[tool] macro generates everything needed for an MCP tool:
use pforge_macro::tool;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[tool(
name = "calculator",
description = "Perform basic arithmetic"
)]
async fn calculate(
#[doc = "First operand"] a: f64,
#[doc = "Second operand"] b: f64,
#[doc = "Operation: add, sub, mul, div"] op: String,
) -> Result<f64> {
match op.as_str() {
"add" => Ok(a + b),
"sub" => Ok(a - b),
"mul" => Ok(a * b),
"div" if b != 0.0 => Ok(a / b),
_ => Err(Error::Validation("Invalid operation".into())),
}
}
This generates:
use pforge_macro::Handler;
use serde::{Deserialize, Serialize};
#[derive(Handler)]
struct MyHandler {
config: Config,
}
#[async_trait::async_trait]
impl MyHandler {
async fn handle(&self, params: Value) -> Result<Value> {
// Your logic here
Ok(json!({"status": "ok"}))
}
}
#[handler]Mark async functions as MCP handlers:
#[handler]
async fn my_handler(params: MyParams) -> Result<Value> {
// Implementation
}
Requirements:
async fnResult<Value> or Result<impl Serialize>#[tool]Full MCP tool generation:
#[tool(
name = "tool_name", // Required
description = "Description", // Required
timeout_ms = 5000, // Optional
)]
async fn my_tool(param1: Type1, param2: Type2) -> Result<ReturnType> {
// Implementation
}
Generates:
JsonSchema#[derive(Handler)]Derive Handler trait for custom types:
#[derive(Handler)]
#[handler(
validate = true, // Add validation middleware
log = true, // Add logging middleware
timeout_ms = 10000, // Set timeout
)]
struct CustomHandler {
// Fields
}
use pforge_macro::tool;
use std::sync::Arc;
use tokio::sync::RwLock;
struct Counter {
value: Arc<RwLock<i64>>,
}
#[tool(
name = "increment",
description = "Increment counter"
)]
impl Counter {
async fn increment(&self, amount: i64) -> Result<i64> {
let mut value = self.value.write().await;
*value += amount;
Ok(*value)
}
}
use pforge_macro::tool;
use validator::Validate;
#[derive(Deserialize, Validate)]
struct UserInput {
#[validate(email)]
email: String,
#[validate(range(min = 18, max = 120))]
age: u8,
}
#[tool(
name = "create_user",
description = "Create a new user"
)]
async fn create_user(input: UserInput) -> Result<Value> {
input.validate()?;
// Create user
Ok(json!({"id": "user_123"}))
}
use pforge_macro::tool;
use thiserror::Error;
#[derive(Error, Debug)]
enum MyError {
#[error("User not found: {0}")]
NotFound(String),
#[error("Invalid input: {0}")]
Invalid(String),
}
#[tool(
name = "get_user",
description = "Fetch user by ID"
)]
async fn get_user(id: String) -> Result<User, MyError> {
database::find_user(&id)
.await
.ok_or_else(|| MyError::NotFound(id))
}
All macro-generated code:
The macros perform validation at compile time:
MIT