Skip to content

feat: add structured output schemas to read-only tools - #2382

Draft
SamMorrowDrums wants to merge 8 commits into
mainfrom
sammorrowdrums/structured-output-schemas
Draft

feat: add structured output schemas to read-only tools#2382
SamMorrowDrums wants to merge 8 commits into
mainfrom
sammorrowdrums/structured-output-schemas

Conversation

@SamMorrowDrums

Copy link
Copy Markdown
Collaborator

Summary

Adds structured output (OutputSchema + StructuredContent) to read-only tools by leveraging the go-sdk's mcp.AddTool[In, Out]() generic registration path.

How it works

  1. TypedRegisterFunc — A new closure field on ServerTool that captures Go type parameters (In, Out) and calls mcp.AddTool[In, Out]() instead of the untyped s.AddTool(). Set automatically in NewServerToolWithContextHandler and NewServerTool.

  2. RegisterFunc preference — When TypedRegisterFunc is set, RegisterFunc uses it. This enables the SDK to auto-generate OutputSchema from the Out type and populate StructuredContent on tool results.

  3. Backwards compatibility — Tools still return MarshalledTextResult(...) for Content (text). The SDK adds StructuredContent alongside without overwriting existing content.

Tools with structured output

Tool Out Type
get_me MinimalUser
list_issues MinimalIssuesResponse
list_pull_requests ListPullRequestsResult
search_issues IssueSearchResult
search_pull_requests PullRequestSearchResult
search_code CodeSearchResult

Tools unchanged (Out=any, no structured output)

  • issue_read — multi-method tool (get, get_comments, get_sub_issues, get_labels) returning different shapes
  • pull_request_read — multi-method tool (8 methods) returning different shapes

Changes

  • pkg/inventory/server_tool.go — Added TypedRegisterFunc field, updated RegisterFunc to prefer it, set it in NewServerToolWithContextHandler and NewServerTool
  • pkg/github/minimal_types.go — Added wrapper output structs (ListPullRequestsResult, CodeSearchResult, IssueSearchResult, PullRequestSearchResult) and convertToCodeSearchResult
  • pkg/github/context_tools.goget_me returns MinimalUser as typed output
  • pkg/github/issues.golist_issues returns MinimalIssuesResponse, search_issues returns IssueSearchResult
  • pkg/github/pullrequests.golist_pull_requests returns ListPullRequestsResult, search_pull_requests returns PullRequestSearchResult
  • pkg/github/search.gosearch_code returns CodeSearchResult
  • pkg/github/search_utils.gosearchHandler now also returns *github.IssuesSearchResult for callers to convert
  • pkg/github/tools_validation_test.go — Tests verifying TypedRegisterFunc is set on all tools and structured output tools register without panics
  • pkg/github/pullrequests_test.go — Updated test to match new wrapped output format

SamMorrowDrums and others added 8 commits April 23, 2026 22:34
Replace the server instructions system with MCP skill resources that
follow the skill:// URI convention for discovery by MCP clients.

Each skill resource covers a domain of tools and provides YAML
frontmatter (name, description, allowed-tools) plus markdown guidance.

All tools remain available by default (all tools mode). Skills provide
contextual guidance that clients can discover via resources/list.

16 skills covering all toolsets: context, repos, issues, pull-requests,
code-security, actions, discussions, projects, notifications, gists,
users-orgs, security-advisories, labels, git, stargazers, copilot.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update all 16 skill definitions to use explicit backtick-formatted
tool names with '## Available Tools' sections instead of bold markdown
references. This makes tool names more reliably parseable by models
that consume the SKILL.md content via load_skill.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…kills

Replace 16 toolset-oriented skills (repos, issues, pull-requests, etc.)
with 27 workflow-oriented skills that map to real user intents:

PR workflows: create-pr, review-pr, self-review-pr, address-pr-feedback, merge-pr
Issue workflows: triage-issues, create-issue, manage-sub-issues
CI/CD: debug-ci, trigger-workflow
Security: security-audit, fix-dependabot, research-vulnerability
Code exploration: explore-repo, search-code, trace-history
Projects: manage-project
Notifications: handle-notifications
Releases: prepare-release
Repository management: manage-repo, manage-labels
Collaboration: contribute-oss, browse-discussions, delegate-to-copilot
Discovery: discover-github, share-snippet, get-context

Each skill focuses on a specific workflow with non-obvious guidance,
anti-patterns, and identity-aware instructions (e.g., reviewing someone
else's PR vs self-reviewing your own). Tools overlap across skills
based on workflow needs rather than API domain.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update all 27 skill descriptions to include both what the skill does
AND when to use it, following the agent skills specification guidance
that descriptions are the primary triggering mechanism. Descriptions
now explicitly list user intents that should activate each skill.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Skills now only include tools that are actually registered with the
MCP server, based on the active toolset configuration. Skills with
no available tools are skipped entirely. This prevents skills from
referencing non-existent tools (e.g., gist tools when the gists
toolset isn't enabled).

RegisterSkillResources now accepts a map of available tool names
built from the inventory at startup time.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When skills handle progressive disclosure, the server should expose
all tools via tools/list regardless of toolset configuration. The
client decides tool visibility through skill allowedTools, not the
server.

This replaces the previous filtering approach — now inv.AllTools()
registers every tool with the MCP server, and skills reference the
full tool surface.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All toolsets are now enabled by default (Default: true). This ensures
every tool is registered out of the box, which is required for skills
to work — skills handle progressive tool discovery, so the server
should expose the full surface. Users can still restrict with
--toolsets if needed.

Reverts the inv.AllTools() registration workaround from server.go
since the normal RegisterAll path now picks up everything.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add TypedRegisterFunc to ServerTool that uses mcp.AddTool[In, Out]()
for typed tool registration. The SDK auto-generates OutputSchema from
the Go Out type and populates StructuredContent on tool results,
while preserving existing TextContent for backwards compatibility.

Tools updated with typed structured output:
- get_me (MinimalUser)
- list_issues (MinimalIssuesResponse)
- list_pull_requests (ListPullRequestsResult)
- search_issues (IssueSearchResult)
- search_pull_requests (PullRequestSearchResult)
- search_code (CodeSearchResult)

Tools like issue_read and pull_request_read keep Out=any since they
are multi-method tools returning different shapes per method.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@olaservo olaservo mentioned this pull request Jul 26, 2026
13 tasks
olaservo added a commit to olaservo/github-mcp-server that referenced this pull request Aug 10, 2026
Adds feature-gated MCP `outputSchema` and `structuredContent`, targeting the
tools whose response shape varies by their `method` argument — the ones both
existing upstream attempts (github#2382, github#2468) explicitly set aside as
"multi-method tool returning different shapes".

Protocol revision 2026-07-28 (SEP-2106) is what makes these expressible.
2025-11-25 typed `outputSchema` as a closed object shape restricted to
`type: "object"` at the root, and `structuredContent` as a JSON object; the
draft widens both to any valid JSON Schema 2020-12 and any JSON value.

Two orthogonal gates:

  - `output_schemas` feature flag controls rollout.
  - The negotiated protocol version, read per request, controls which schema
    shapes are legal. Object-root schemas ship to every client; non-object
    roots and non-object structuredContent are withheld below 2026-07-28.

Unions use `anyOf`, never `oneOf`. `oneOf` requires exactly one branch to
match and provably fails here: actions_run_trigger has four structurally
identical branches, an empty array satisfies every array branch, and an
issue_read `get` on a sub-issue also satisfies the `get_parent` branch. A
test asserts no schema ever contains "oneOf".

Handlers are unchanged. Rather than thread typed values out of ~20 call
sites, structuredContent is mirrored from the serialized-JSON text block each
handler already emits — the relationship the spec itself describes. This is
byte-exact, so it cannot drift from the text content and preserves number
formatting a re-marshal would lose. It applies only to tools that declare a
schema, leaving the other ~120 tools byte-identical on the wire.

Because the schema lives on ServerTool and is copied onto a duplicate
mcp.Tool at registration, Tool.OutputSchema stays nil in the default surface
and none of the 123 committed toolsnaps change.

Tools covered: actions_get, actions_list, actions_run_trigger,
discussion_comment_write, issue_dependency_read (single shape, not a union),
issue_read, pull_request_read.

Also fixes three adjacent bugs found along the way:

  - GetSubIssues could emit the literal `null`: go-github declares
    `var subIssues []*SubIssue` and leaves it nil on an empty body, which
    marshals to null rather than []. Every sibling method normalises; this
    one did not.
  - sub_issue_write's `method` property had no `enum`, so its values existed
    only in prose while dispatch accepted exactly add/remove/reprioritize.
  - convertJSONTextResultToCSV cleared StructuredContent, which would have
    made csv_output silently disable output_schemas for every list_* tool.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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