Repository navigation
Structured tool output: advertise outputSchema and populate structuredContent #1278
Description
Problem
No tool sets outputSchema, and almost none set structuredContent (only get_panel_image and the Tempo trace view do). Every result is JSON serialized into content[0].text.
This is fine for a chat client but hurts code-mode / programmatic clients, where the model writes a script that calls tools and processes results:
- The model can't see the response shape before calling, so it either guesses field names or spends a call (and tokens) just to inspect a sample.
- Every script needs a boilerplate "join text content, check
isError,JSON.parse, fall back to string" helper. - Clients that generate typed declarations from tool definitions (e.g.
Promise<CallToolResult>) can only offer an untyped result.
Proposal
Derive an output schema from the handler's return type R in ConvertTool (tools.go), the same way the input schema is reflected from T, and populate structuredContent alongside the existing text content (keep text for backwards compatibility, as the spec recommends).
Return-type survey of handlers in tools/ today:
| Return type | Count (approx) | Treatment |
|---|---|---|
Named structs / pointers to structs (*QueryPrometheusResult, *SearchDashboardsResult, …) |
majority | reflect schema directly |
[]string, []*T |
~30 | spec requires outputSchema to be type: object and structuredContent to be an object → wrap as {"items": [...]} in structured content only |
string, any, model.Value, *mcp.CallToolResult |
~25 | no schema; leave as-is (handlers returning *mcp.CallToolResult can set their own) |
Risks / constraints
- We register via the low-level
Server.AddTool, so the go-sdk does not validate output against the schema. Clients that do validate (the spec says they SHOULD) will hard-fail a call whosestructuredContentdoesn't match. Types with customMarshalJSON,anyfields,json.RawMessage, or upstream models (models.*, Prometheusmodel.Value) are the likely mismatches. - So this should be opt-in per tool (e.g. a
WithStructuredOutput()ToolOption, or opt-out list), rolled out starting with the highest-traffic read tools, with a test that validates real fixture output against the advertised schema for every opted-in tool. - Adds schema bytes to
tools/list— relevant to Consolidate the tool surface into action-based read/write tool pairs #1092's token budget. Measure and consider keeping schemas compact (no descriptions on output fields unless useful).
Suggested first batch
query_prometheus, query_loki_logs, query_loki_stats, list_datasources, search_dashboards, list_prometheus_metric_names, list_prometheus_label_*, list_loki_label_*, search_tempo_traces, alerting_manage_rules (read).
Related
- Response envelopes are inconsistent today (bare arrays vs
{data, hints, metadata}vs{datasources, total, hasMore}). Designing output schemas is a good moment to converge on a consistent pagination/metadata shape (see Shared time-range and pagination param structs for consolidated tools #1099 for the input side).
Activity