Tools
Tools are functions that an LLM can call to perform actions. They are the primary mechanism for servers to expose executable functionality.
Registering a Tool
server->RegisterTool("get_weather",
ToolOptions{}.Description("Get current weather for a location"),
[](const RequestContext<CallToolRequestParams>& ctx) -> CallToolResult {
std::string location;
if (ctx.Params().arguments) {
auto* v = ctx.Params().arguments->Find("location");
if (v) location = v->GetString();
}
CallToolResult result;
result.content.push_back(
TextContent{"text", "The weather in " + location + " is sunny."});
return result;
});Tool with Annotations
ToolOptions opts;
opts.title = "Delete File";
opts.description = "Permanently delete a file by path";
opts.destructive = true; // marks as destructive_hint
opts.idempotent = false;
server->RegisterTool("delete_file", opts,
[](const RequestContext<CallToolRequestParams>& ctx) -> CallToolResult {
// ...
});McpServerTool (Reusable Tools)
For reusable tool logic, create an McpServerTool object explicitly:
auto tool = McpServerTool::Create("get_weather",
[](const RequestContext<CallToolRequestParams>& ctx) -> CallToolResult {
// ...
},
ToolOptions{}.Description("Get current weather"));
server->RegisterTool(tool);The lambda-based RegisterTool overload is shorthand for this — both forms are equivalent.
ToolOptions Fields
All ToolOptions fields:
| Field | Type | Default | Description |
|---|---|---|---|
name | optional<string> | — | The registered tool name comes from the first argument of RegisterTool (or the name argument of McpServerTool::Create); this field is currently not consumed |
title | optional<string> | — | Human-readable title |
description | optional<string> | — | Tool description |
input_schema | optional<JsonValue> | {"type":"object","properties":{}} | JSON Schema for input parameters |
output_schema | optional<JsonValue> | — | JSON Schema for structured output |
use_structured_content | bool | false | Opt into structured output (sets output_schema to {"type":"object"}) |
icons | vector<Icon> | — | Tool icons |
meta | optional<JsonValue> | — | Additional metadata |
Annotation fields (propagated to ToolAnnotations):
| Field | Type | Description |
|---|---|---|
destructive | optional<bool> | Marks the tool as destructive |
idempotent | optional<bool> | Calling multiple times has same effect as once |
read_only_hint | optional<bool> | Tool does not modify state |
open_world_hint | optional<bool> | Tool may interact with external systems |
Structured Content
Tools can return structured JSON data instead of text:
CallToolResult result;
result.content.push_back(TextContent{"text", "Processed."});
result.structured_content = JsonValue::Parse(R"({
"records_updated": 42,
"duration_ms": 153
})");The structured_content field is a 2026-07-28 protocol feature. Set use_structured_content = true on ToolOptions to opt in (sets output_schema to {"type": "object"}). You can also set output_schema directly for fine-grained schema control.
Input Schema
Set the tool's input_schema via ToolOptions::InputSchema() or directly:
auto schema = JsonValue::Parse(R"({
"type": "object",
"properties": {
"location": {"type": "string"},
"units": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["location"]
})");
server->RegisterTool("get_weather",
ToolOptions{}.InputSchema(std::move(schema)),
[](const RequestContext<CallToolRequestParams>& ctx) -> CallToolResult {
// ...
});Multi-Round Tool Requests (input_required)
Tool handlers can set input_required on the CallToolResult to start an MRTR (multi round-trip request) collection round with the client; the client gathers input via elicitation and retries the call with requestState:
CallToolResult result;
InputRequiredResult ir;
ElicitRequestParams params;
params.message = "Please provide the API key";
ir.input_requests["api_key"] = MakeInputRequestForElicitation(params);
result.input_required = std::move(ir);
return result; // the server auto-signs requestState (requires request_state_key)Once ServerOptions::request_state_key is configured, the server automatically signs request_state on input_required results before returning them; tampered or expired states are rejected on retry before reaching the handler (-32602, data.reason="invalid_request_state"). See MRTR for the full protocol flow.
Task Execution
Declaring a tool in task mode makes tools/call return a task handle (taskId) immediately while execution continues in the background; the client follows up with GetTask / PollTaskToCompletion (available in both the 2025 and 2026 eras; in the 2026 era task status notifications are routed via subscriptions — dropped when there are no subscribers — so the client polls for the terminal state):
ToolExecution execution;
execution.mode = ToolExecutionMode::Task;
ToolOptions opts;
opts.execution = execution;
opts.description = "A long-running computation";
server->RegisterTool("slow_compute", opts,
[](const RequestContext<CallToolRequestParams>& ctx) -> CallToolResult {
// Poll ctx.IsCancellationRequested() for cooperative cancellation
// ...
});This requires ServerOptions::task_store to be configured (the task persistence backend). tasks/update accepts inputResponses to answer input requests raised mid-execution (mutually exclusive with result). The task handle shape differs by era: flat top-level taskId/status in the 2026 era, nested task object retained for compatibility in the 2025 era (the client accepts both). Terminal-state semantics: a normal handler return (including is_error results) lands Completed; a thrown exception lands Failed with the error payload carried inline as error. See Extensions for extension declarations and wire semantics.