F3: Tool Execution API¶
| Field | Value |
|---|---|
| Feature ID | F3 |
| Title | Tool Execution API |
| Complexity | S |
| Priority | P0 |
| Dependencies | F2 (Tool Discovery API) |
| Status | Draft |
Overview¶
Defines the POST /tools/{name}/call endpoint that executes a named tool with
caller-supplied arguments and returns an MCP-compliant CallToolResult response.
Execution is delegated to a ToolCallHandler abstraction provided by the host
application.
Endpoint¶
Request Body¶
The request body contains the tool's input arguments as a JSON object:
If the body is not valid JSON, it is silently treated as an empty object ({}).
Precondition Checks¶
Preconditions are evaluated in order. The first failing check short-circuits the request.
| Order | Condition | HTTP Status | Response Body |
|---|---|---|---|
| 1 | allow_execute is false |
403 | {"error": "Tool execution is disabled."} |
| 2 | Tool {name} not found |
404 | {"error": "Tool not found: {name}"} |
| 3 | Request body is invalid JSON | — | Treat as {}, continue |
| 4 | auth_hook configured and rejects |
401 | {"error": "Unauthorized"} (see F4) |
| 5 | All checks pass | — | Call handler |
ToolCallHandler Abstraction¶
The handler is an asynchronous function provided by the host application. It receives the tool name and parsed arguments, with an optional third parameter for the HTTP request object, and returns a three-element tuple.
Signature (language-neutral):
The optional request parameter allows handlers to access HTTP context
(headers, identity, etc.) without breaking existing 2-parameter handlers.
Implementations should auto-detect whether the handler accepts 2 or 3
parameters (e.g. via inspect.signature in Python, Function.length in JS).
| Return Element | Type | Description |
|---|---|---|
content |
list[Content] |
List of MCP content objects (TextContent, ImageContent, etc.) |
is_error |
boolean |
true if the tool invocation represents a logical error |
trace_id |
string \| null |
Optional trace identifier; null or empty string means omit _meta |
Cross-Language Type Mapping¶
| Language | 2-param (basic) | 3-param (with request) |
|---|---|---|
| Python | async def(name: str, args: dict) -> tuple[list, bool, str \| None] |
async def(name: str, args: dict, request: Request) -> tuple[list, bool, str \| None] |
| TypeScript | (name: string, args: Record<string, unknown>) => Promise<[Content[], boolean, string?]> |
(name: string, args: Record<string, unknown>, req: Request) => Promise<[Content[], boolean, string?]> |
| Go | HandleCall(name string, args map[string]any) ([]Content, bool, string, error) |
HandleCall(ctx context.Context, name string, args map[string]any) ([]Content, bool, string, error) |
| Rust | async fn(name: &str, args: Value) -> Result<CallResult> |
async fn(name: &str, args: Value, req: &Request) -> Result<CallResult> |
Response Format¶
All responses use Content-Type: application/json and conform to the MCP
CallToolResult schema.
Success (200)¶
Returned when the handler completes without exception and is_error is false.
{
"content": [{ "type": "text", "text": "result here" }],
"isError": false,
"_meta": { "_trace_id": "abc-123" }
}
Handler Error (500)¶
Returned when the handler completes without exception but is_error is true.
Handler Exception (500)¶
Returned when the handler raises an unhandled exception. The exception is caught and logged server-side; its message is surfaced in the response.
Response Rules¶
_metaomission: The_metafield is omitted entirely (not set tonull) whentrace_idisnullor an empty string.- HTTP status mapping:
200— handler returned successfully withis_error=false.500— handler returned withis_error=true, or an unhandled exception occurred.- Exception safety: Handler exceptions must be caught, logged, and returned
as an
isError=trueresponse. They must never propagate as unhandled 500 errors with framework-default error pages. - Content format: The
contentarray contains MCP content objects. Supported types includeTextContent({"type": "text", "text": "..."}),ImageContent({"type": "image", "mimeType": "...", "data": "..."}), and other MCP content types. The frontend renders each type appropriately.
Security Considerations¶
- The
allow_executegate is enforced at the handler level, not merely in the UI. This prevents direct API calls from bypassing the restriction. - Tool
namefrom the URL path is validated against the known tool list before any execution occurs (precondition 2). - Auth hook rejection must return a generic
{"error": "Unauthorized"}without leaking internal details (see F4). - Handler exception messages are included in the response body. Implementations should ensure sensitive internal state is not exposed through exception text.
Test Criteria¶
| # | Scenario | Expected Result |
|---|---|---|
| 1 | Successful tool call | 200 with content array and _meta containing _trace_id |
| 2 | Handler returns is_error=true |
500 with isError: true in response body |
| 3 | Tool name not found | 404 with {"error": "Tool not found: {name}"} |
| 4 | allow_execute is false |
403 with {"error": "Tool execution is disabled."} |
| 5 | Invalid JSON request body | Body treated as {}; tool executes normally |
| 6 | Handler raises an exception | 500 with isError: true and exception message in content |
| 7 | trace_id is null or empty |
Response has no _meta field |
References¶
- PROTOCOL.md — Full protocol specification
- F2: Tool Discovery API — Tool lookup (dependency)
- F4: Auth Hook — Auth guard (precondition 4)
- MCP Specification — CallToolResult — Upstream schema