Skip to content

feat: surface downstream tool metadata through the catalog (#27)#38

Merged
JumpTechCode merged 1 commit into
mainfrom
feat/tool-metadata-passthrough
Jun 17, 2026
Merged

feat: surface downstream tool metadata through the catalog (#27)#38
JumpTechCode merged 1 commit into
mainfrom
feat/tool-metadata-passthrough

Conversation

@JumpTechCode

Copy link
Copy Markdown
Collaborator

What

domain.Tool carried only Ref, Description, and InputSchema, so the edge's tools/list (handleList) dropped a downstream tool's outputSchema, title, annotations, and icons (issue #27). outputSchema matters for structured-output tools (clients validate structured results against it); title/annotations/icons drive client display and hinting.

This widens the tool model and threads the fields end to end:

registry.ToolInfo  →  aggregate.Tool  →  domain.Tool  →  edge mcp.Tool

Design: opaque in the middle, typed at the boundaries

The extra fields ride as opaque json.RawMessage through the middle layers, so domain stays free of MCP SDK types — consistent with how InputSchema is already carried, and preserving the domain-leaf invariant. The SDK's typed values are produced only at the two transport boundaries:

  • Ingest (registry.toolInfo) reduces the SDK's reported *mcp.Tool to raw JSON. Typed-nil values (a nil *ToolAnnotations, an empty []Icon) are guarded so an absent field stays nil rather than marshaling to "null".
  • Egress (edge.toMCPTool) reconstructs the SDK's typed Annotations/Icons from the raw JSON and passes OutputSchema through the SDK's any-typed field. Each optional field is set only when present, so an unset field is omitted on the wire rather than serialized as null.

The new fields are display/validation metadata only — the tools/call path is unchanged.

Testing

TDD, one failing test per layer first, then threaded through:

  • registry (toolinfo_test.go): the SDK→ToolInfo mapping carries all fields and round-trips annotations/icons; a separate test asserts absent metadata stays nil.
  • aggregate: Build carries the four fields aggregate.Tooldomain.Tool.
  • app (catalog_test.go): metadata flows through toListingBuild into the cached catalog.
  • edge (server_test.go): end-to-end — an in-process MCP client receives title, annotations (readOnlyHint/title), icons, and outputSchema over tools/list.

Widening Tool past gocritic's copy thresholds also turned several pre-existing range-by-value loops (domain.Catalog.Lookup, aggregate paging, policy.Filter/Sync) into index-based access.

make verify: build, vet, -race tests, coverage 95.0% (gate 80%), golangci-lint v2.12.2 0 issues, govulncheck clean.

Closes #27

🤖 Generated with Claude Code

domain.Tool carried only Ref, Description, and InputSchema, so the edge's
tools/list dropped a downstream tool's outputSchema, title, annotations, and
icons — outputSchema matters for structured-output tools (clients validate
results against it), and title/annotations/icons drive client display.

Widen the tool model and thread the fields end to end:

  registry.ToolInfo → aggregate.Tool → domain.Tool → edge mcp.Tool

The extra fields ride as opaque json.RawMessage through the middle layers, so
domain stays free of MCP SDK types (consistent with how InputSchema is already
carried). The SDK's typed values are produced only at the boundaries: the
registry reduces the SDK's reported tool to raw JSON (toolInfo), and the edge
reconstructs the SDK's typed Annotations/Icons and passes OutputSchema through
its any-typed field (toMCPTool). Absent metadata stays nil at both ends, so an
unset field is omitted on the wire rather than serialized as null.

The new fields are display/validation metadata only; the tools/call path is
unchanged. Tests cover each layer: the registry mapping (including absent-field
handling), aggregate carry-through, the app catalog/toListing path, and an
end-to-end edge test asserting a client receives all four fields over
tools/list. Widening Tool past gocritic's copy thresholds also turned several
range-by-value loops into index-based access.

Closes #27

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JumpTechCode
JumpTechCode merged commit dd8dcf0 into main Jun 17, 2026
5 checks passed
@JumpTechCode
JumpTechCode deleted the feat/tool-metadata-passthrough branch June 17, 2026 04:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

domain.Tool drops downstream tool metadata (outputSchema, title, annotations)

1 participant