Skip to content

SpanDetail output schema is serialised twice, 13% of the MCP tool surface #9330

Description

@Abhinash-Singh

Requirement

The MCP tool surface is the fixed cost every agent session pays before fetching any
telemetry. Operators running smaller-context models need that surface to be as
small as it can be without losing information, so the budget goes to spans rather
than to schema boilerplate.

Problem

SpanDetail is serialised in full in two separate output schemas. MCP has no
shared-definitions mechanism (no $defs/$ref in the emitted schemas), so a type
used by more than one tool is repeated verbatim.

Measured against jaeger all-in-one at b3076cc with memory storage and
ai.mcp: {}, reading tools/list off the wire (compact JSON):

  • 9 tools, 15,048 bytes of tool definitions
  • Output schemas: 9,763 bytes — 64% of that
  • Input schemas: 3,254 bytes (21%); descriptions: 1,378 bytes (9%)

The repeated definition is 1,799 bytes, appearing at:

  • get_span_details.outputSchema.properties.spans.items
  • get_trace_errors.outputSchema.properties.spans.items

with three further duplicated types nested inside it, already counted within that
1,799: events.items (317 B), links.items (306 B), status (250 B).

Separately, the 162-byte trace_id input schema is shared by get_critical_path
and get_trace_errors.

Recoverable total: 1,961 bytes, 13% of the tool surface.

Credit to @Animesh-Parashar, who first surfaced the SpanDetail duplication in
#9135. One refinement on the figure quoted there: 3,598 bytes is the combined size
of both copies, but one definition has to exist regardless, so deduplication
recovers one copy — 1,799 bytes, not 3,598. Worth pinning down before it becomes
the justification for a change, since it halves the payoff.

A methodology note for anyone re-measuring: comparing whole schemas for
byte-identity finds almost none of this. SpanDetail is nested inside two
different output schemas rather than repeated at top level, so the detection has to
recurse into subschemas.

Proposal

Options, roughly in increasing order of effort:

  1. Do nothing, but document it. 13% is real but not dramatic, and it is a fixed
    cost rather than a per-call one. Response payloads are unbounded and currently
    ship twice (~2.2× overhead, see [Feature/🧑‍🎓 LFX] Evaluating and Optimizing the Jaeger AI Assistant: MCP Tools + Skills #9135), so they dominate the total budget. This
    may simply be the wrong thing to optimise first.
  2. Emit $defs + $ref in the output schemas if the Go SDK's schema generator
    can be persuaded to, and if the models consuming these reliably resolve $ref.
    That second condition is the risk: a model that does not resolve refs sees a
    less useful schema, which would trade context bytes for accuracy — the wrong
    direction.
  3. Narrow get_trace_errors' output. It returns the full SpanDetail per span;
    an error-focused tool may not need every field, in which case the duplication
    partly disappears as a side effect of a tighter contract.

I lean toward (1) or (3). (2) is the obvious fix but its benefit depends on model
behaviour we have not measured, and this is exactly the kind of change the
evaluation work in #9135 exists to decide empirically rather than by intuition.

Open questions

  • Does the Go SDK's schema generation support $defs/$ref emission today?
  • Is there a preference for keeping tool schemas fully self-contained, deliberately,
    so that no client needs ref resolution?
  • Is output-schema size worth optimising at all given JSON_SCHEMA_FOR_FUNC_DECL
    gates whether these reach the model in the first place?

Happy to send a PR for whichever direction maintainers prefer, though I would
rather see the direction settled here first than guess.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions