Skip to content

Tools Resources Prompts

Jeffrie Budde edited this page Nov 25, 2025 · 1 revision

Tools, Resources, Prompts

How to define, register, and call MCP primitives. See Core Concepts for Context/State basics.

Tools (#[tool])

use fastrmcp::prelude::*;
use fastrmcp_macros::tool;
use fastrmcp_core::error::MCPError;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct Stats { mean: f64, count: usize }

// Option<T> parameters are optional; non-Option are required.
#[tool]
async fn summarize(numbers: Vec<f64>, precision: Option<u32>, ctx: Context) -> Result<Stats> {
    ctx.report_progress(1, 3, Some("validating")).await.ok();
    if numbers.is_empty() {
        return Err(MCPError::InvalidParams("numbers cannot be empty".into()));
    }
    let mean = numbers.iter().sum::<f64>() / numbers.len() as f64;
    let factor = 10f64.powi(precision.unwrap_or(2) as i32);
    let rounded = (mean * factor).round() / factor;
    ctx.log_info("ready").await.ok();
    Ok(Stats { mean: rounded, count: numbers.len() })
}
  • Macro auto-registers tool info + JSON schema from the signature and doc comments.
  • Return types: any serde-friendly type; strings/numbers/bools are coerced to ToolResult::text, complex types are JSON pretty-printed.
  • Pitfall: Parameter types must be serde-deserializable; add #[derive(Serialize, Deserialize)] to structs/enums.

Call Shape

{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"summarize","arguments":{"numbers":[1,2,3]}}}

Resources (#[resource("uri://{template}")])

use fastrmcp_macros::resource;

#[resource("file:///{path}")]
async fn read_file(path: String, ctx: Context) -> Result<String> {
    let content = tokio::fs::read_to_string(&path).await?;
    ctx.log_info(format!("read {path}")).await.ok();
    Ok(content)
}

#[resource("config://{section}/{key}")]
async fn config(section: String, key: String) -> Result<serde_json::Value> {
    Ok(serde_json::json!({ "section": section, "key": key, "value": "demo" }))
}
  • URI template vars become parameters; unmatched URIs yield MethodNotFound.
  • Context is optional; use it to access client info or send progress/logs.
  • Registry requires server info before registration; do not manually call ResourceRegistry::register_from_inventory without first setting server info (handled by FastMCP::new(...).run()).
  • Return ResourceContent implicitly via helper; returning String becomes text/plain with URI preserved.

Read Call Shape

{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"file:///tmp/demo.txt"}}

Prompts (#[prompt])

use fastrmcp_macros::prompt;

#[prompt]
async fn reviewer(file_name: String) -> Result<PromptResult> {
    Ok(PromptResult {
        description: Some(format!("Review {file_name}")),
        messages: vec![
            PromptMessage::system("You are a strict Rust reviewer."),
            PromptMessage::user(format!("Please review {file_name}")),
        ],
    })
}
  • Arguments are serde-deserializable and optional unless typed otherwise.
  • Return PromptResult with messages (Text/Image/Resource content); descriptions appear in prompt metadata.

Fetch Call Shape

{"jsonrpc":"2.0","id":4,"method":"prompts/get","params":{"name":"reviewer","arguments":{"file_name":"main.rs"}}}

Advanced Patterns

  • Context injection: Add Context to any handler signature; it is filled automatically.
  • State: Add State to access shared data (state.get::<Config>()?).
  • Custom return types: For binary resources, return ResourceContent::binary(...).
  • URI templates: Avoid overlapping patterns; conflicting templates cause ServerError -32002.
  • Validation: Prefer InvalidParams for user errors; reserve InternalError/ServerError for server faults.
  • Ordering: Tools/resources/prompts are available after initialize; ensure initialize runs before list/read/call in clients.

Related: Core Concepts • Middleware Cookbook for metadata patterns • Troubleshooting for schema/param issues.

Back to Home

Clone this wiki locally