pforge-macro

Crates.iopforge-macro
lib.rspforge-macro
version0.1.4
created_at2025-10-02 14:45:26.815182+00
updated_at2025-12-06 12:09:08.984901+00
descriptionZero-boilerplate MCP server framework with EXTREME TDD methodology
homepagehttps://github.com/paiml/pforge
repositoryhttps://github.com/paiml/pforge
max_upload_size
id1864479
size10,561
Noah Gift (noahgift)

documentation

https://docs.rs/pforge-runtime

README

pforge-macro

crates.io Documentation

Procedural macros for the pforge framework - ergonomic attribute macros for building MCP handlers.

Features

  • #[handler] - Derive Handler trait automatically
  • #[tool] - Generate complete MCP tool with JSON Schema
  • Type-Safe: Compile-time validation of handler signatures
  • Zero Boilerplate: Write handlers as plain async functions
  • Auto Schema: Automatic JSON Schema generation from Rust types

Installation

cargo add pforge-macro

Usage

Basic Handler

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)
        }))
    }
}

Tool Macro

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:

  1. Parameter Struct: Type-safe params with JSON Schema
  2. Handler Implementation: Full Handler trait
  3. Schema Function: JSON Schema for MCP protocol
  4. Registration Helper: Easy registry integration

Derive Macros

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"}))
    }
}

Attributes

#[handler]

Mark async functions as MCP handlers:

#[handler]
async fn my_handler(params: MyParams) -> Result<Value> {
    // Implementation
}

Requirements:

  • Must be async fn
  • First parameter must be deserializable from JSON
  • Must return Result<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:

  • Parameter struct with JsonSchema
  • Handler implementation
  • Schema function
  • Registration code

#[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
}

Advanced Examples

Stateful Handler

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)
    }
}

Validation

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"}))
}

Error Handling

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))
}

Generated Code Quality

All macro-generated code:

  • ✅ Follows Rust naming conventions
  • ✅ Includes proper error handling
  • ✅ Generates complete documentation
  • ✅ Passes clippy lints
  • ✅ Is fully type-safe

Compile-Time Checks

The macros perform validation at compile time:

  • Signature Validation: Ensures async fn with correct return type
  • Type Bounds: Verifies Serialize/Deserialize bounds
  • Attribute Validation: Checks required attributes are present
  • Name Conflicts: Prevents duplicate tool names

Documentation

License

MIT

Commit count: 0

cargo fmt