Skip to content

feat(tools): add outputSchema and structuredContent via typed handler Out parameters#93

Open
emsearcy wants to merge 4 commits into
mainfrom
lfxv2-1996-add-structured-content-output-schema
Open

feat(tools): add outputSchema and structuredContent via typed handler Out parameters#93
emsearcy wants to merge 4 commits into
mainfrom
lfxv2-1996-add-structured-content-output-schema

Conversation

@emsearcy
Copy link
Copy Markdown
Contributor

@emsearcy emsearcy commented May 27, 2026

Summary

Promotes tool handler signatures from untyped ToolHandlerFor[In, any] to concrete output types so the MCP Go SDK automatically generates Tool.OutputSchema and populates StructuredContent on every successful response.

The existing TextContent fallback (pretty-printed JSON) is retained in the Content array so backward-compatible clients continue to receive a text response unchanged.

Tools Updated

Tool Output Type
search_b2b_orgs b2bOrgSearchResult
list_b2b_org_memberships b2bOrgMembershipListResult
search_members memberSearchResult
get_member_membership *ProjectMembershipResponse
list_project_tiers membershipTierListResult
get_project_tier membershipTierView
get_membership_key_contacts keyContactListResult
get_membership_key_contact keyContactView
search_projects projectSearchResult
get_project projectGetResult
search_committees / search_groups committeeSearchResult
get_committee / get_group committeeGetResult
get_committee_member / get_group_member *CommitteeMemberFullWithReadonlyAttributes
search_committee_members / search_group_members committeeMemberSearchResult

Skipped Tools

  • hello_world, user_info — no stable structured schema
  • query_lfx_lens, query_lfx_semantic_layer — freeform text output
  • Write tools, email, discord — no structured output schema

Behavioral Notes

  • get_project_tier and get_membership_key_contact now marshal the filtered view type instead of the raw service response, aligning with the rest of the member tools and stripping internal fields.
  • Inline anonymous structs lifted to named package-level types to satisfy the type parameter constraint.
  • list_project_tiers and get_membership_key_contacts wrap their slice results in an object type to conform to the 2025-11-25 spec requirement that outputSchema have type: "object" at the root.

Known Client Caveats

  • Claude Code: Silently drops all tools from a server when any tool includes outputSchema in tools/list. Validate separately.
  • Claude Desktop (Windows): May fail on complex outputSchema containing $defs/$ref. Our schemas are flat structs so this likely does not apply, but should be confirmed in Dev.
  • Claude.ai web + Claude Desktop: Reported "Error occurred during tool execution" on every call when outputSchema is present. Validate that tool calls succeed, not just that tools appear in the list.
  • Cursor: Validates structuredContent against outputSchema; returns -32602 on mismatch.
  • MCP Inspector / Gemini CLI: Full support.

Testing

  • make check passes
  • Integration tests pass (./scripts/test_server.sh)
  • Dev validation pending: confirm tools appear and tool calls succeed in both Claude Desktop and Claude Code

Jira: LFXV2-1996


🤖 Generated with GitHub Copilot (via OpenCode)

emsearcy added 3 commits May 26, 2026 16:43
Correct committee endpoint path, fix parameter name from 'slug'
to 'project_slug' in two sequence diagrams.

Assisted-by: github-copilot:claude-sonnet-4.6
Signed-off-by: Eric Searcy <eric@linuxfoundation.org>
Change tool handler signatures from the untyped
ToolHandlerFor[In, any] form to concrete output types so the MCP Go
SDK automatically generates Tool.OutputSchema and populates
StructuredContent on every successful response.

The existing TextContent fallback (pretty-printed JSON) is retained in
the Content array so backward-compatible clients continue to receive a
text response unchanged.

Tools updated:
- search_b2b_orgs        → b2bOrgSearchResult
- list_b2b_org_memberships → b2bOrgMembershipListResult
- search_members         → memberSearchResult
- get_member_membership  → *ProjectMembershipResponse
- list_project_tiers     → []membershipTierView
- get_project_tier       → membershipTierView
- get_membership_key_contacts → []keyContactView
- get_membership_key_contact  → keyContactView
- search_projects        → projectSearchResult
- get_project            → projectGetResult
- search_committees/search_groups → committeeSearchResult
- get_committee/get_group         → committeeGetResult
- get_committee_member/get_group_member → *CommitteeMemberFullWithReadonlyAttributes
- search_committee_members/search_group_members → committeeMemberSearchResult

Inline anonymous structs that were only used as local variables are
lifted to named package-level types to satisfy the type parameter
constraint.

Assisted-by: github-copilot:claude-sonnet-4.6
Signed-off-by: Eric Searcy <eric@linuxfoundation.org>
Error returns in handleListProjectTiers were returning nil for the
[]membershipTierView Out parameter. Use []membershipTierView{} to
match the pattern used by all other handlers in the file, so the SDK
serializes [] instead of null in structuredContent on error paths.

Assisted-by: github-copilot:claude-sonnet-4.6
Signed-off-by: Eric Searcy <eric@linuxfoundation.org>
Copilot AI review requested due to automatic review settings May 27, 2026 15:59
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds typed output parameters to selected MCP tool handlers so the SDK can generate outputSchema and populate structured responses while preserving existing text JSON content for backward compatibility.

Changes:

  • Adds named result structs for project, committee, member, and B2B organization search/get responses.
  • Updates read-only tool handlers from any outputs to concrete typed outputs.
  • Adjusts a few member tool responses to return filtered view types consistently.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/tools/project.go Adds typed structured outputs for project search and get tools.
internal/tools/member.go Adds typed structured outputs for member, tier, and key contact tools.
internal/tools/committee.go Adds typed structured outputs for committee/group search, get, and member tools.
internal/tools/b2b_org.go Adds typed structured outputs for B2B org search and membership list tools.
ARCHITECTURE.md Corrects documented parameter and endpoint examples.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/tools/member.go Outdated

// handleListProjectTiers implements the list_project_tiers tool logic.
func handleListProjectTiers(ctx context.Context, req *mcp.CallToolRequest, args ListProjectTiersArgs) (*mcp.CallToolResult, any, error) {
func handleListProjectTiers(ctx context.Context, req *mcp.CallToolRequest, args ListProjectTiersArgs) (*mcp.CallToolResult, []membershipTierView, error) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2fec443. Introduced membershipTierListResult{Tiers []membershipTierView} wrapper so the root is an object. The 2025-11-25 spec requires outputSchema to have type: "object" at the root and structuredContent to be a string-keyed map.

Comment thread internal/tools/member.go Outdated

// handleGetMembershipKeyContacts implements the get_membership_key_contacts tool logic.
func handleGetMembershipKeyContacts(ctx context.Context, req *mcp.CallToolRequest, args GetMembershipKeyContactsArgs) (*mcp.CallToolResult, any, error) {
func handleGetMembershipKeyContacts(ctx context.Context, req *mcp.CallToolRequest, args GetMembershipKeyContactsArgs) (*mcp.CallToolResult, []keyContactView, error) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2fec443. Introduced keyContactListResult{Contacts []keyContactView} wrapper so the root is an object, same rationale as the tiers fix above.

The 2025-11-25 MCP spec requires outputSchema to have type "object" at
the root level, and structuredContent to be a string-keyed object map.
Returning a raw slice violates this constraint.

Introduce two wrapper types:
- membershipTierListResult{Tiers []membershipTierView} for list_project_tiers
- keyContactListResult{Contacts []keyContactView} for get_membership_key_contacts

Update both handlers to build and return the wrapper, and marshal the
wrapper for the TextContent fallback so all representations stay in sync.

Assisted-by: github-copilot:claude-sonnet-4.6
Signed-off-by: Eric Searcy <eric@linuxfoundation.org>
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.

2 participants