Skip to content

Structured tool output: advertise outputSchema and populate structuredContent #1278

Open

Description

@sd2k

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 whose structuredContent doesn't match. Types with custom MarshalJSON, any fields, json.RawMessage, or upstream models (models.*, Prometheus model.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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesttoolsChanges to MCP tools

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions