Repository navigation
Tools Resources Prompts
Jeffrie Budde edited this page Nov 25, 2025
·
1 revision
How to define, register, and call MCP primitives. See Core Concepts for Context/State basics.
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.
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"summarize","arguments":{"numbers":[1,2,3]}}}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. -
Contextis optional; use it to access client info or send progress/logs. - Registry requires server info before registration; do not manually call
ResourceRegistry::register_from_inventorywithout first setting server info (handled byFastMCP::new(...).run()). - Return
ResourceContentimplicitly via helper; returningStringbecomestext/plainwith URI preserved.
{"jsonrpc":"2.0","id":3,"method":"resources/read","params":{"uri":"file:///tmp/demo.txt"}}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
PromptResultwithmessages(Text/Image/Resource content); descriptions appear in prompt metadata.
{"jsonrpc":"2.0","id":4,"method":"prompts/get","params":{"name":"reviewer","arguments":{"file_name":"main.rs"}}}-
Context injection: Add
Contextto any handler signature; it is filled automatically. -
State: Add
Stateto 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
InvalidParamsfor user errors; reserveInternalError/ServerErrorfor server faults. -
Ordering: Tools/resources/prompts are available after
initialize; ensureinitializeruns before list/read/call in clients.
Related: Core Concepts • Middleware Cookbook for metadata patterns • Troubleshooting for schema/param issues.