MCP Tools
Tools are the primary way AI agents interact with your server. Each tool has a name, a description, a JSON Schema describing its inputs, and a handler closure that executes when the tool is called.
Registering a tool
use rust_web_server::mcp::{McpServer, McpContent};
let mcp = McpServer::new("my-server", "1.0") .tool( "greet", // name (snake_case recommended) "Return a greeting for a name", // description shown to the AI r#"{ "type": "object", "properties": { "name": { "type": "string", "description": "The name to greet" } }, "required": ["name"] }"#, |args| { let name = rust_web_server::mcp::extract_arg(args, "name") .unwrap_or_else(|| "World".to_string()); Ok(McpContent::text(format!("Hello, {name}!"))) }, );Handler signature
Fn(&str) -> Result<McpContent, String>The handler receives the raw arguments JSON string from the MCP tools/call request. On success, return Ok(McpContent). On failure, return Err(String) — this is sent back to the AI as isError: true (a tool-level error, not a protocol error).
McpContent variants
use rust_web_server::mcp::McpContent;
// Plain text — sets mimeType to text/plainMcpContent::text("Operation completed successfully");
// JSON — sets mimeType to application/jsonMcpContent::json(r#"{"count": 42, "items": ["a", "b"]}"#);Extracting arguments
extract_arg pulls a string value from the arguments JSON by field name:
use rust_web_server::mcp::extract_arg;
// args = r#"{"query": "SELECT 1", "limit": 10}"#let query = extract_arg(args, "query"); // Some("SELECT 1")let limit = extract_arg(args, "limit"); // Some("10")let other = extract_arg(args, "missing"); // NoneFor numeric or boolean fields, parse the returned String:
let limit: usize = extract_arg(args, "limit") .and_then(|s| s.parse().ok()) .unwrap_or(100);Input schema format
The input_schema parameter is a JSON Schema object serialized as a string. Required fields:
// Minimal schema — no arguments"{}"
// Single required string argumentr#"{"type":"object","properties":{"sql":{"type":"string"}},"required":["sql"]}"#
// Multiple arguments, one optionalr#"{ "type": "object", "properties": { "sql": { "type": "string", "description": "SQL query to execute" }, "limit": { "type": "integer", "description": "Max rows to return", "default": 100 } }, "required": ["sql"]}"#Complete example: database query tool
use rust_web_server::mcp::{McpServer, McpContent, extract_arg};
// Assume `db` is your connection pool — Arc<DbPool> for thread safety.fn build_mcp(db: std::sync::Arc<crate::db::Pool>) -> McpServer { let db_tool = db.clone();
McpServer::new("my-api", "1.0") .tool( "query_users", "Query the users table by email or ID", r#"{ "type": "object", "properties": { "email": { "type": "string", "description": "Filter by email address" }, "id": { "type": "integer", "description": "Filter by user ID" } } }"#, move |args| { let email = extract_arg(args, "email"); let id: Option<i64> = extract_arg(args, "id") .and_then(|s| s.parse().ok());
let results = db_tool.query_users(email.as_deref(), id) .map_err(|e| e.to_string())?;
// Serialize to JSON and return let json = serde_json::to_string(&results) .map_err(|e| e.to_string())?; Ok(McpContent::json(json)) }, )}Error handling
Return Err(String) for recoverable tool errors (invalid input, resource not found, downstream failure). The AI receives an isError: true response and can decide how to proceed or report it to the user.
|args| { let id: u64 = extract_arg(args, "id") .ok_or_else(|| "Missing required argument: id".to_string())? .parse() .map_err(|_| "Argument 'id' must be a positive integer".to_string())?;
fetch_record(id).map_err(|e| format!("Database error: {e}")) .map(|r| McpContent::json(r.to_json()))}