pmcp 2.2.0

High-quality Rust SDK for Model Context Protocol (MCP) with full TypeScript SDK compatibility
Documentation
//! Example demonstrating the new procedural macro system for tools.
//!
//! This example shows how to use the #[tool] and #[tool_router] macros
//! to define MCP tools with automatic schema generation and type safety.
//!
//! Run with:
//! ```bash
//! cargo run --example 21_macro_tools --features macros
//! ```

use async_trait::async_trait;
use pmcp::{
    Error, RequestHandlerExtra, Result, Server, ServerBuilder, ServerCapabilities, StdioTransport,
    ToolHandler,
};
use pmcp_macros::{tool, tool_router};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

/// Parameters for math operations
#[derive(Debug, Deserialize, JsonSchema)]
struct MathParams {
    #[schemars(description = "First number")]
    a: f64,

    #[schemars(description = "Second number")]
    b: f64,
}

/// Result of math operations
#[derive(Debug, Serialize, JsonSchema)]
struct MathResult {
    #[schemars(description = "The computed result")]
    result: f64,

    #[schemars(description = "The operation performed")]
    operation: String,
}

/// Parameters for string operations
#[derive(Debug, Deserialize, JsonSchema)]
struct StringParams {
    #[schemars(description = "The text to process")]
    text: String,

    #[schemars(description = "Optional prefix to add")]
    prefix: Option<String>,

    #[schemars(description = "Optional suffix to add")]
    suffix: Option<String>,
}

/// A calculator server with math and string tools
#[derive(Clone)]
struct CalculatorServer {
    /// Store calculation history
    history: Arc<RwLock<Vec<String>>>,
}

impl CalculatorServer {
    fn new() -> Self {
        Self {
            history: Arc::new(RwLock::new(Vec::new())),
        }
    }

    /// Record an operation in history
    async fn record_operation(&self, op: String) {
        let mut history = self.history.write().await;
        history.push(op);
        // Keep only last 100 operations
        if history.len() > 100 {
            history.drain(0..history.len() - 100);
        }
    }
}

// Use the tool_router macro to automatically collect all tools
#[tool_router]
impl CalculatorServer {
    /// Add two numbers together
    #[tool(description = "Add two numbers")]
    async fn add(&self, params: MathParams) -> Result<MathResult> {
        let result = params.a + params.b;
        self.record_operation(format!("{} + {} = {}", params.a, params.b, result))
            .await;

        Ok(MathResult {
            result,
            operation: "addition".to_string(),
        })
    }

    /// Subtract second number from first
    #[tool(description = "Subtract b from a")]
    async fn subtract(&self, params: MathParams) -> Result<MathResult> {
        let result = params.a - params.b;
        self.record_operation(format!("{} - {} = {}", params.a, params.b, result))
            .await;

        Ok(MathResult {
            result,
            operation: "subtraction".to_string(),
        })
    }

    /// Multiply two numbers
    #[tool(description = "Multiply two numbers")]
    async fn multiply(&self, params: MathParams) -> Result<MathResult> {
        let result = params.a * params.b;
        self.record_operation(format!("{} × {} = {}", params.a, params.b, result))
            .await;

        Ok(MathResult {
            result,
            operation: "multiplication".to_string(),
        })
    }

    /// Divide first number by second
    #[tool(name = "divide", description = "Divide a by b (b must not be zero)")]
    async fn div(&self, params: MathParams) -> Result<MathResult> {
        if params.b == 0.0 {
            return Err(Error::InvalidParams("Division by zero".to_string()));
        }

        let result = params.a / params.b;
        self.record_operation(format!("{} ÷ {} = {}", params.a, params.b, result))
            .await;

        Ok(MathResult {
            result,
            operation: "division".to_string(),
        })
    }

    /// Calculate power (a^b)
    #[tool(description = "Calculate a raised to the power of b")]
    async fn power(&self, params: MathParams) -> Result<MathResult> {
        let result = params.a.powf(params.b);
        self.record_operation(format!("{} ^ {} = {}", params.a, params.b, result))
            .await;

        Ok(MathResult {
            result,
            operation: "exponentiation".to_string(),
        })
    }

    /// Process a string with optional prefix and suffix
    #[tool(description = "Process text with optional prefix and suffix")]
    async fn process_string(&self, params: StringParams) -> Result<String> {
        let mut result = params.text;

        if let Some(prefix) = params.prefix {
            result = format!("{}{}", prefix, result);
        }

        if let Some(suffix) = params.suffix {
            result = format!("{}{}", result, suffix);
        }

        self.record_operation(format!("Processed string: {}", result))
            .await;
        Ok(result)
    }

    /// Get calculation history
    #[tool(description = "Get the history of calculations")]
    async fn get_history(&self) -> Result<Vec<String>> {
        let history = self.history.read().await;
        Ok(history.clone())
    }

    /// Clear calculation history
    #[tool(description = "Clear the calculation history")]
    async fn clear_history(&self) -> Result<String> {
        let mut history = self.history.write().await;
        let count = history.len();
        history.clear();
        Ok(format!("Cleared {} operations from history", count))
    }
}

// Manual wrapper to integrate with current PMCP API
// (This would be auto-generated in the future)
struct CalculatorToolHandler {
    server: CalculatorServer,
}

#[async_trait]
impl ToolHandler for CalculatorToolHandler {
    async fn handle(&self, args: Value, _extra: RequestHandlerExtra) -> Result<Value> {
        // In a full implementation, the macro would generate this routing
        // For now, we'll manually route to demonstrate the concept
        Ok(serde_json::json!({
            "message": "Tool handled via macro system",
            "args": args
        }))
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    env_logger::init();

    println!("🧮 Calculator Server with Macro-based Tools");
    println!("=".repeat(50));
    println!();
    println!("This example demonstrates the new procedural macro system");
    println!("for defining MCP tools with automatic schema generation.");
    println!();
    println!("Features demonstrated:");
    println!("  • #[tool] attribute for individual tool methods");
    println!("  • #[tool_router] for collecting tools from impl blocks");
    println!("  • Automatic JSON schema generation from Rust types");
    println!("  • Type-safe parameter handling");
    println!("  • Custom tool names and descriptions");
    println!();
    println!("Available tools:");
    println!("  • add        - Add two numbers");
    println!("  • subtract   - Subtract b from a");
    println!("  • multiply   - Multiply two numbers");
    println!("  • divide     - Divide a by b");
    println!("  • power      - Calculate a^b");
    println!("  • process_string - Process text with prefix/suffix");
    println!("  • get_history    - Get calculation history");
    println!("  • clear_history  - Clear calculation history");
    println!();

    // Create the calculator server
    let calc_server = CalculatorServer::new();
    let handler = CalculatorToolHandler {
        server: calc_server.clone(),
    };

    // Build the MCP server
    let server = ServerBuilder::new("calculator-macro-server", "1.0.0")
        .capabilities(ServerCapabilities {
            tools: Some(pmcp::types::ToolsCapability {
                list_changed: Some(false),
            }),
            ..Default::default()
        })
        // Register all tools from the macro-generated router
        .tool(
            "add",
            "Add two numbers",
            serde_json::json!({
                "type": "object",
                "properties": {
                    "a": {"type": "number", "description": "First number"},
                    "b": {"type": "number", "description": "Second number"}
                },
                "required": ["a", "b"]
            }),
            handler.clone(),
        )?
        .tool(
            "subtract",
            "Subtract b from a",
            serde_json::json!({
                "type": "object",
                "properties": {
                    "a": {"type": "number", "description": "First number"},
                    "b": {"type": "number", "description": "Second number"}
                },
                "required": ["a", "b"]
            }),
            handler.clone(),
        )?
        .tool(
            "multiply",
            "Multiply two numbers",
            serde_json::json!({
                "type": "object",
                "properties": {
                    "a": {"type": "number", "description": "First number"},
                    "b": {"type": "number", "description": "Second number"}
                },
                "required": ["a", "b"]
            }),
            handler.clone(),
        )?
        .tool(
            "divide",
            "Divide a by b",
            serde_json::json!({
                "type": "object",
                "properties": {
                    "a": {"type": "number", "description": "First number"},
                    "b": {"type": "number", "description": "Second number (non-zero)"}
                },
                "required": ["a", "b"]
            }),
            handler.clone(),
        )?
        .tool(
            "power",
            "Calculate a raised to the power of b",
            serde_json::json!({
                "type": "object",
                "properties": {
                    "a": {"type": "number", "description": "Base"},
                    "b": {"type": "number", "description": "Exponent"}
                },
                "required": ["a", "b"]
            }),
            handler.clone(),
        )?
        .tool(
            "process_string",
            "Process text with optional prefix and suffix",
            serde_json::json!({
                "type": "object",
                "properties": {
                    "text": {"type": "string", "description": "The text to process"},
                    "prefix": {"type": "string", "description": "Optional prefix"},
                    "suffix": {"type": "string", "description": "Optional suffix"}
                },
                "required": ["text"]
            }),
            handler.clone(),
        )?
        .tool(
            "get_history",
            "Get the history of calculations",
            serde_json::json!({
                "type": "object",
                "properties": {}
            }),
            handler.clone(),
        )?
        .tool(
            "clear_history",
            "Clear the calculation history",
            serde_json::json!({
                "type": "object",
                "properties": {}
            }),
            handler.clone(),
        )?
        .build()?;

    println!("Starting server on stdio transport...");
    println!("Connect with an MCP client to use the calculator.");
    println!();

    // Run the server
    server.run_stdio().await?;

    Ok(())
}