CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Build and Test Go Project / build (macos-latest) (push) Has been cancelled
Build and Test Go Project / build (ubuntu-latest) (push) Has been cancelled
Build and Test Go Project / build (windows-latest) (push) Has been cancelled
GoReleaser Release / release (push) Has been cancelled
MCP Server Diff / mcp-diff (push) Has been cancelled
MCP Server Diff / mcp-diff-http (push) Has been cancelled
Publish to MCP Registry / publish (push) Has been cancelled
* Add reaction tools for issues and pull requests
Implement three granular-only tools for adding emoji reactions:
- add_issue_reaction: Add reaction to an issue
- add_issue_comment_reaction: Add reaction to an issue comment
- add_pull_request_review_comment_reaction: Add reaction to a PR review comment
All tools are feature-flagged with FeatureFlagEnable set to enable them
only when clients request granular toolsets. Tools use go-github's
Reactions service and return minimal ID response on success (HTTP 201).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Make reaction tools available in both granular and non-granular modes
Remove feature flag gates from reaction tools so they're available to all
clients regardless of granular toolset preference. Reaction tools are
naturally atomic operations and work equally well in both modes.
Updates test expectations to exclude reaction tools from granular-only
test assertions, since they're now always available.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update toolsnaps with aligned reaction tool descriptions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Align reaction tool descriptions with codebase style
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Improve reaction tool responses and wording
Return reaction URLs in minimal responses and clarify issue tools apply to pull requests where applicable.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Expose reactions through default comment tools
Keep the standalone reaction tools behind granular feature flags to avoid expanding the default tool count. Add optional reaction support to the existing issue comment and pull request comment reply tools, requiring at least one of body or reaction.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Clarify PR review comment IDs for reactions
Document that PR review comment reaction inputs require the numeric review comment ID, not the GraphQL review thread node ID returned by review thread APIs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Support issue comment reactions in add_issue_comment
Add optional comment_id support so the default add_issue_comment tool can react to a specific issue or pull request comment without exposing a separate default reaction tool. Keep body creation tied to issue_number and require reaction targets to provide either issue_number or comment_id.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Harden combined comment reaction tools
Apply reactions before creating comments or replies so retrying a failed combined call cannot duplicate the non-idempotent comment operation.
Also reject issue comment IDs without a reaction target to avoid silently ignoring the field.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Group reaction tools with granular registrations
Keep standalone reaction tool registrations next to the granular issue and pull request tools they belong with, so the default compound tool sections stay focused.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Align granular reaction tool constructors
Rename the standalone reaction tool constructors to match the granular tool naming convention and clarify issue comment reaction IDs for pull request comments.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Require issue number for comment reactions
Make issue_number required on the consolidated add_issue_comment tool even when reacting to a specific issue comment, keeping the default tool input shape explicit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address issue comment feedback
Return structured GitHub API errors for issue comment creation failures and assert MCP error results in tests.
Also reject comment_id with body on the consolidated comment tool to avoid ambiguous comment-plus-comment-reaction requests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Validate issue comment reaction target
Use the required issue_number to verify issue comment reaction targets before creating the reaction, and remove overlapping add_issue_comment test coverage.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Validate positive comment IDs
Reject non-positive issue and pull request comment IDs in handlers and add the missing schema minimum for pull request review comment IDs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Sam Morrow <sammorrowdrums@github.com>
Co-authored-by: Sam Morrow <info@sam-morrow.com>
* Fall back to REST DELETE when the PATCH can't carry an issue field clear
The dotcom REST update endpoint uses set semantics for issue_field_values:
sending {"issue_field_values": [...]} overwrites the whole list, and
anything not in the new list is treated as a deletion. UpdateIssue
already exploits this via merge-and-filter — delete:true on a field
removes it from the kept list and the server clears it as a side effect.
That breaks for one specific case: when the kept list ends up empty
(e.g. you're deleting the only remaining field value, or every field in
one call), go-github's omitempty tag on
`IssueRequest.IssueFieldValues` strips the empty slice from the JSON
body. The dotcom REST handler's top-level
`if data.include?(ISSUE_FIELD_VALUES)` guard then short-circuits — the
key isn't in the payload, so the whole block is skipped and the field
keeps its old value. The MCP tool returns success regardless, so a
coding agent would happily report the field as cleared.
Detect this case in UpdateIssue and fall back to the dedicated REST
DELETE endpoint per field: DELETE /repos/{owner}/{repo}/issues/{number}/
issue-field-values/{field_id}. The endpoint is idempotent, takes the
integer field ID (no GraphQL node ID needed), and is the same one the
public OpenAPI documents.
Empirical confirmation on github/github-mcp-server#2756:
- Set Priority=P1 via REST
- Before the fix: MCP issue_write delete:true returns success, PATCH body
on the wire is literally {}, Priority remains P1 (silent no-op).
- After the fix: MCP issue_write delete:true returns success, PATCH body
is still {}, but the follow-up DELETE clears the field. Priority is
cleared as expected.
Three new tests in issues_delete_test.go cover:
- The omitempty contract (so we know if go-github ever drops the tag)
- The 1-of-1 fallback path (PATCH body has no issue_field_values, DELETE
fires)
- The N-1 set-semantics path (kept list is non-empty, PATCH alone
handles it, no DELETE call)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review: handle delete-absent-field and DELETE partial failures
Two issues surfaced in code review of the original delete fix:
1. Asking to delete a field that isn't set on the issue used to be a
silent no-op (PATCH body is {} after omitempty stripping, server
skips the issue_field_values block, tool returns success). The fix
turned it into a hard error because the fallback DELETE loop fires
unconditionally for every field ID in fieldIDsToDelete, and the
dotcom DELETE endpoint returns 404 when the field has no value to
delete. This breaks idempotent 'ensure field X is cleared' callers
that may re-run the same delete on a field that's already been
cleared.
Fix: before queueing the fallback DELETEs, filter
fallbackDeleteFieldIDs down to IDs that actually appeared in the
existing field values. This preserves the pre-fix silent-no-op
behaviour and avoids a guaranteed 404.
2. The DELETE loop returned on the first error. If a caller asks to
clear three fields and the second fails (transient 5xx, rate limit,
etc.), the first is gone, the third is never attempted, and the
user gets a generic error with no indication of which field failed
or which had already succeeded. This is unrecoverable from the
caller side.
Fix: continue on per-field errors, accumulate the failed and
succeeded IDs, and return a single aggregated error naming both
sets so callers can retry the right fields.
Tests:
- Test_UpdateIssue_DeleteAbsentFieldIsNoOp: existing field values is
empty, fieldIDsToDelete=[101]. Asserts no DELETE call fires (the
mock returns 404 if it does, which would fail the test) and the
tool returns success.
- Test_UpdateIssue_DeleteFallbackContinuesOnPartialFailure: three
fields exist, all three are deleted. Mock returns 500 for the
middle DELETE, 204 for the others. Asserts all three DELETEs
fired (the middle failure didn't short-circuit the third) and the
error result names failed=[202] and cleared=[101 303].
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Slim down code comments in delete-fix region
Trim the doc comments on UpdateIssue's omitempty-trap region + test
file to the bare minimum needed to understand the non-obvious behaviour
(why the DELETE fallback exists, why we filter to existing IDs, why
errors are aggregated). Drop restated context and step-by-step
explanations.
No behaviour change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add issue dependency read/write MCP tools
Add two feature-flagged tools for issue blocked-by / blocking
relationships, gated behind the issue_dependencies flag so they stay off
the default tool surface (auto-enabled for insiders only):
- issue_dependency_read: get_blocked_by / get_blocking via the
Issue.blockedBy / Issue.blocking GraphQL connections, cursor-paginated.
- issue_dependency_write: add / remove x blocked_by / blocking via the
addBlockedBy / removeBlockedBy mutations. Accepts issue numbers and
resolves them to node IDs in a single aliased query; "blocking" is the
inverse of "blocked_by" with the subject/related roles swapped.
Closes the MCP gap behind the gh CLI dependency verbs (cli/cli#13057).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fail fast on self-dependency in issue_dependency_write
Reject a write where the subject and related issue are identical before
resolving node IDs or issuing a mutation, avoiding two API round-trips.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Revert addition of `any of` for required ui_get scopes
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(ui): render success view when an MCP App tool executed up-front
The create_pull_request / issue_write / update_pull_request Views decided
form-vs-success from in-app submit state only, ignoring the tool-result the
host delivers on render. Per the MCP Apps 2026-01-26 spec the host renders a
View whenever the tool carries _meta.ui.resourceUri — independent of whether
the server deferred or executed. So when the server executed up-front (e.g.
show_ui=false, or parameters the form can't represent) the View still showed
its "Create pull request" input form over an already-created PR, which reads
as a bug (it even shows a PR number).
Drive the Views off the result instead: a new shared completedToolResult()
helper returns parsed data only for a genuine completed success, and returns
null for the awaiting_user_submission deferral sentinel, errors, or no result.
Each write View now shows its success card when that completed result is
present, so the form is only ever shown while the action is genuinely deferred.
Reconciles the show/defer state machine at the View (decision layer that the
host result feeds). See github/copilot-mcp-core#1864.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(ui): scope tool-result to the current invocation
Address review feedback: the write Views derive their success card from
`toolResult`, but it wasn't cleared when a new invocation arrived (only the
in-app `successPR`/`successIssue` was reset on `toolInput` change). A completed
result from a previous invocation could briefly render a stale success card
over the next, still-deferred form.
Clear `toolResult` whenever a new `tool-input` notification arrives. The spec
guarantees `tool-input` precedes that invocation's `tool-result`, so this scopes
the result to the current invocation centrally in the hook — fixing all three
Views without per-app invocation keys.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(mcp-apps): remove show_ui — it can't suppress app rendering
show_ui promised "skip the form and execute directly", but it can't deliver:
the host renders an MCP App for any tool that carries _meta.ui.resourceUri, and
the 2026-01-26 MCP Apps spec has no per-call/per-result way to opt out of
rendering. show_ui only flipped the server's defer decision, so show_ui=false
created the PR/issue up-front yet the host still rendered the app — exactly the
contradiction this work set out to fix. And show_ui is only ever exposed to
clients that support UI, i.e. precisely the clients that always render the app.
Remove it entirely:
- Drop the show_ui schema property, the form-param allowlist entry, and the
showUI term from the defer predicate in create_pull_request and issue_write.
The gate is now FF && clientSupportsUI && !_ui_submitted && !hasNonFormParams.
- Delete the now-unused UI-only schema-property strip machinery in
pkg/inventory (uiOnlySchemaProperties, stripUIOnlySchemaProperties,
stripSchemaProperties) and the exported ConditionalSchemaPropertyDescriptions,
which existed solely to surface show_ui to UI-capable clients. _meta.ui
stripping is untouched.
- Drop the conditional-property annotation from the docs generator.
- Update toolsnaps, generated docs, and tests.
With the up-front-execution Views now rendering the result (success card), the
remaining contract is simple: when MCP Apps are enabled the form is the path,
and the form is only shown while the action is genuinely deferred.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(mcp-apps): centralize the show/defer decision (single source of truth)
The defer-to-form predicate was triplicated across create_pull_request,
update_pull_request, and issue_write, each with its own near-identical
*HasNonFormParams function. As more MCP App tools are added this duplication
would grow and the copies could silently drift.
Extract one shared gate in ui_capability.go:
- shouldDeferToForm(ctx, deps, req, args, formParams) — the single show/defer
decision (MCP Apps enabled, client supports UI, not a form submission, and no
non-form params).
- hasNonFormParams(args, formParams) — one generic helper replacing the three
per-tool functions.
- uiSubmitted(args) — small shared predicate.
Each handler is now a one-line `if shouldDeferToForm(...) { return awaiting }`.
The per-tool form-parameter allowlists and the user-facing messages stay
per-tool (that is the genuine per-tool config). Pure refactor — behavior
unchanged; existing tests now exercise the generic helper.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace mapstructure.WeakDecode with RequiredParam/RequiredInt checks so
missing arguments like owner return a clear validation error instead of
running the GraphQL query with zero values.
Fixes#2718
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* feat(oauth): add stdio OAuth 2.1 stdio login
Introduce internal/oauth, a self-contained library that performs the
user-facing GitHub OAuth login the stdio server uses to obtain a token
without a pre-provisioned PAT. It is independent of MCP: client concerns
(elicitation) sit behind the Prompter interface so the flows are testable
without a live session.
What it provides:
- Authorization-code + PKCE flow with a local loopback callback server,
state/CSRF validation, and XSS-safe result pages.
- Device-authorization flow as a fallback (headless, containers).
- A Manager that selects the most secure available channel
(browser auto-open -> URL elicitation -> last-resort user action),
runs a single flow at a time, and exposes a refreshing token source.
Both GitHub OAuth Apps and GitHub Apps are supported without special
casing: the token is modeled as an x/oauth2 refreshing TokenSource, so
expiring GitHub App user tokens are renewed transparently (the gap that
made a stored-token approach silently die after ~8h).
When a client lacks secure URL elicitation and the flow falls back to a
tool-response message, the message advises the user that their agent/CLI/
IDE does not appear to support URL elicitation and suggests requesting it
for improved security.
Tests exercise real protocol behavior against an httptest GitHub stand-in:
PKCE challenge/verifier, GitHub App refresh-on-expiry, device polling,
URL elicitation, declined prompts, the last-resort action with advisory,
and single-flight concurrency.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(oauth): reap browser launcher and keep native callback on loopback
Address code review:
- openBrowser: reap the launcher process asynchronously so it does not
linger as a zombie for the lifetime of the server.
- listenCallback: take an explicit bindAll flag and bind to all interfaces
only inside a container (where the published port arrives via eth0).
A native run, even with a fixed callback port, now stays on 127.0.0.1
instead of 0.0.0.0.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(oauth): fail fast when a fixed callback port is unavailable
A fixed --oauth-callback-port is registered with the OAuth app and chosen
deliberately, so a bind failure means another process holds the port and
could intercept the authorization redirect. Treat that as fatal instead of
silently downgrading to the device flow, which would mask the conflict.
Also warn, when binding the callback inside a container, that the listener
is on all interfaces and should be published to loopback only so the
authorization code is not exposed on the container network.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(oauth): surface refresh failures, bound refresh, prefer device flow when headless
Addresses pre-merge review of the OAuth stdio core:
- Log a one-time warning when token refresh fails instead of silently
returning an empty access token, so a forced re-login isn't a surprise.
- Bound each background token refresh with a 30s HTTP client timeout so a
stalled GitHub token endpoint can't block tool calls indefinitely.
- On a headless host (no display server) with a random callback port, fall
back to the device-code flow — the only channel reachable from a browser
on another machine — instead of dead-ending on an unreachable localhost
redirect. A generic browser-open failure still offers the manual URL.
- Mark the callback bind failure with a sentinel so the fixed-port-busy
fatal path can't misreport an unrelated error as a port conflict.
- Export NormalizeHost so callers can recognize the default github.com host
(consumed by the build-time baked-in credential guard).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(oauth): wire stdio OAuth 2.1 login into the server (2/4) (#2710)
* feat(oauth): wire stdio OAuth 2.1 login into the server
Connect the internal/oauth core library to the stdio MCP server so users
can authenticate with an OAuth App or GitHub App client ID instead of a
static personal access token.
- BearerAuthTransport gains a TokenProvider that is consulted per request,
letting the lazily-acquired, auto-refreshing OAuth token take effect
without rebuilding the client.
- createGitHubClients uses BearerAuthTransport (and skips go-github's
WithAuthToken, which would pin a static token) when a TokenProvider is set.
- RunStdioServer starts without a token and installs receiving middleware
that runs the authorization flow on the first tool call, surfacing the
auth URL or device code via elicitation (or a tool result as a fallback).
- Tool filtering uses the requested OAuth scopes; the default supported set
hides nothing, while a narrower --oauth-scopes both narrows the grant and
filters tools accordingly.
- A sessionPrompter adapts the MCP server session to oauth.Prompter, keeping
the authorization URL off the model's context.
- New stdio flags: --oauth-client-id/-client-secret/-scopes/-callback-port.
This is stdio-only and deliberately does not touch MCP-HTTP auth.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(oauth): address review — omit empty bearer header, guard token/oauth
- BearerAuthTransport omits the Authorization header entirely when the token
is empty (pre-authorization) rather than sending an empty "Bearer " value.
- RunStdioServer rejects the ambiguous combination of a static Token and an
OAuthManager up front, enforcing the documented mutual exclusivity.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(oauth): clarify SupportedScopes is the stdio default and tool filter
Document that stdio OAuth login requests these scopes by default and then
filters the exposed tools to the scopes actually granted, so a tool whose
required scope is absent from this list is hidden under default OAuth even
though a PAT carrying that scope would expose it. Keep the list in sync with
tool scope requirements when scopes change.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Distinguish undeliverable auth prompts from user declines
An elicitation prompt that the client cannot deliver (a transport or
protocol failure) was treated the same as a user actively declining: any
display error cancelled the flow. That conflated a system failure with a
deliberate "no", so a client that advertised URL elicitation but failed
to deliver it would hard-fail the login instead of degrading.
Add an ErrPromptUnavailable sentinel alongside ErrPromptDeclined and have
the MCP adapter return it when Elicit fails at the transport level. The
manager now falls back to the manual user-action channel on an
undeliverable prompt (keeping the background flow alive so the user can
still authorize out of band), while a genuine decline still aborts. A
context-cancelled prompt is checked first so an ending flow is never
misread as a transport failure.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* build(oauth): bake in default OAuth credentials for official releases (3/4) (#2711)
* build(oauth): bake in default OAuth credentials via build-time ldflags
Inject the public OAuth client credentials (stored as the OAUTH_CLIENT_ID
and OAUTH_CLIENT_SECRET repo secrets) at build time via -ldflags so
official binaries and images ship a working default app for zero-config
login. Security relies on PKCE, not on the secret. Local/dev builds leave
the values empty and continue to require an explicit token or
--oauth-client-id.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(oauth): recognize github.com host aliases for the baked-in client
Match the default host via oauth.NormalizeHost instead of only an empty
host string, so an explicit GITHUB_HOST=github.com (or api.github.com)
still counts as the default and keeps zero-config baked-in login working.
GHES and ghe.com users continue to bring their own --oauth-client-id.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(oauth): document stdio OAuth login; make PAT optional in install config (#2717)
Add a dedicated Local Server OAuth Login guide (docs/oauth-login.md) covering
the PKCE/device flows, display channels and the URL-elicitation security
advisory, scope-based tool filtering, the fixed-port Docker recipe and its
loopback/port-safety behavior, bringing your own OAuth or GitHub App, and the
GitHub Enterprise Server / ghe.com requirement to register an app on that host
(custom --gh-host directs login at that instance's authorization server).
Reflect that the local server now logs in with OAuth by default on github.com:
- README: make the stdio Docker install badges OAuth-first (fixed callback port
8085 published to loopback), drop the PAT prompt, and reframe the PAT as an
optional alternative with a pointer to the new guide.
- server.json: make GITHUB_PERSONAL_ACCESS_TOKEN optional and publish the OAuth
callback port so the registry default works without a token.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ui_get backs a synchronous UI picker (label/assignee/etc. dropdowns in the
MCP App issue/PR write surfaces). Each handler paginated GitHub API results
in an unbounded loop (PerPage 100, looping until NextPage==0 /
HasNextPage==false). On very large repos/orgs this fans out into dozens of
sequential round-trips, and a single slow page inflates the whole call —
production telemetry showed a tail spiking to ~20 minutes.
Bound every ui_get pagination loop to uiGetMaxPages (10 pages, ~1000 items)
and add an additive "has_more" flag indicating results were truncated.
Truncation is acceptable here because the picker pairs it with typeahead, so
responsiveness matters more than completeness.
Affected methods: labels, assignees, milestones, branches, reviewers
(both the collaborators and teams loops). uiGetIssueTypes and issue_fields
are single-request and left unchanged.
has_more is response-only and backward-compatible: no existing keys are
removed or renamed, and the tool input schema is unchanged. HTTP-client
timeouts are intentionally a separate follow-up.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Sam Morrow <info@sam-morrow.com>
The issue_write form-gating logic uses issueWriteFormParams to decide whether
to render the MCP App form or execute directly: a call carrying any parameter
outside that allowlist bypasses the form. The form prefills and re-submits
labels, assignees, milestone and type, but those four were absent from the
allowlist, so passing any of them skipped the confirmation form even though the
form fully supports them.
Add labels, assignees, milestone and type to issueWriteFormParams so the form
is shown when they are present. The allowlist now covers every input-schema
property, leaving issueWriteHasNonFormParams as a forward-compatibility safety
net for properties added without form support.
Also correct the now-inaccurate show_ui guidance on issue_write and
create_pull_request: both descriptions claimed the form "does not collect"
fields it actually collects (labels/assignees/milestone/type/issue_fields, and
reviewers respectively).
Update unit tests and regenerate toolsnaps and docs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add an upward parent read to issue_read, the counterpart to the existing
downward get_sub_issues. Uses the GraphQL Issue.parent field and returns a
null parent when the issue is not a sub-issue. Kept always-on (not feature
gated) to mirror get_sub_issues; the dependency tools remain flag-gated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Sam Morrow <info@sam-morrow.com>
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Docker / build (push) Has been cancelled
Build and Test Go Project / build (macos-latest) (push) Has been cancelled
Build and Test Go Project / build (ubuntu-latest) (push) Has been cancelled
Build and Test Go Project / build (windows-latest) (push) Has been cancelled
GoReleaser Release / release (push) Has been cancelled
MCP Server Diff / mcp-diff (push) Has been cancelled
MCP Server Diff / mcp-diff-http (push) Has been cancelled
Publish to MCP Registry / publish (push) Has been cancelled
* Add repo-scoped support to list_issue_types tool
* Render multi-scope tools as "any of" in generated docs
* Clarify issue type field description for repo-scoped list_issue_types
* Fix enum values to match those expected by gql
* Update test
* Update doc and snaps
* Normalize lower case confidence values
---------
Co-authored-by: Ross Tarrant <rosstarrant@github.com>
* PoC full flow (hello world example)
* add avatar resource domain
* add postmessage logic and richer UI
* add create issue ui
* update ui for issue creatioon
* fix
* ignore banner
* update docs after rebase
* update toolsnap for get_me
* new UI changes
* update docs
* update workflows that need ui build
* add UI diff
* fix build ui step for windows runners to use git bash
* fix UI diff
* refactor issue creation UI
* add AvatarWithFallback component and update UserCard to use it; enhance CreateIssueApp to manage existing issue data
* fix formatting of button labels
* add create pull request functionality with UI support and insiders
* update docs
* add test for insiders mode handling in ServerTool schema
* remove `show_ui` param for now
* make insiders mode metadata stripping generic
* remove ui diff
* fix CI
* remove redundant mention of old app name
* add node types to fix ide issues for ts code
* remove unused TriangleDownIcon import
* update @primer/behaviors and electron-to-chromium versions in package-lock.json
* add check to ensure base and head are not the same when creating a new PR
* remove old show_ui
* fix gitignore for dist so builds dont break
* add tests for insiders mode handling and metadata stripping in ServerTool
* remove unused state and components from CreatePRApp
* fix ui build
* update docker build to fix npm issue
* remove reference to show_ui
* allow insiders to work for non-ui features
* formalise insiders inventory support
* update docs
* fix overflow issues and replace pull request dropdown with matching UI from dotcom
* fix createpullrequest test
* consolidate fetching tools under `ui_get` tool to remove toolset deps
* fix issue data prefill in issue_write form
* fix link component when updating issue
* fix avatar URL
* fix broken issue update logic
* remove dbg
* fix for new GetFlags
* revert to original required fields for create_pull_request
* fix for UI form submission
* Simplify MCP App UIs for basic branch
Remove advanced features to be kept in mcp-ui-apps-advanced:
- Strip labels, assignees, milestones, issue types, repo picker from issue-write
- Strip repo picker, branch selectors from pr-write
- Delete ui_get tool (ui_tools.go, ui_tools_test.go, ui_get.snap)
- Remove UIGet registration from tools.go
Basic forms retain: title, body, submit with _ui_submitted,
draft/regular split button (PR), MarkdownEditor, and SuccessView.
* Fix header spacing in issue-write and pr-write UIs
Add proper spacing between icon, title text, and repo name in the
header bar for both issue-write and create-pull-request forms.
* fix UI spacing
* Revert "Simplify MCP App UIs for basic branch"
This reverts commit 24174b91e222e45b47913ff6b760db809f48660b.
* Undo dependency downgrades in ui/package-lock.json
* Update ui/src/apps/pr-write/App.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update ui/src/apps/issue-write/App.tsx
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Implement pagination for uiGetBranches (#2012)
* Initial plan
* Implement pagination for uiGetBranches function
Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com>
* update to new insiders feature flag func
* ensure transient state is reset on successive tool calls
* Mark ui_get as app-only visibility
ui_get backs only the MCP App views and has no business in the agent's
tool list. Per the MCP Apps 2026-01-26 spec, omitting _meta.ui.visibility
defaults to ["model","app"], which exposes the tool to the model. Declare
visibility ["app"] so the host hides it from tools/list while the views can
still invoke it via tools/call.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update ui_get toolsnap for app-only visibility
Regenerated via UPDATE_TOOLSNAPS to capture the new _meta.ui.visibility.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Assert ui_get declares app-only visibility
Locks in the _meta.ui.visibility ["app"] contract so a future edit can't
silently re-expose the UI data tool to the model.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add ui_get to insiders feature docs
Regenerated docs/feature-flags.md and docs/insiders-features.md to include
the ui_get tool entry.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address ui_get review feedback
- Paginate the labels GraphQL query (cursor-based) so repos with more than
100 labels return a complete list instead of silently truncating.
- Emit an empty due_on for milestones without a due date instead of
formatting the zero time as "0001-01-01".
- Use NewGitHubAPIErrorResponse in uiGetIssueTypes to preserve GitHub
response context, matching the other REST-backed methods.
- Extend tests to cover the labels (GraphQL), milestones (including the
no-due-date case) and issue_types methods, plus the issue_types error path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix repo reset and stale base-branch in MCP App views
- Re-initialize selectedRepo from toolInput inside the reset-on-invocation
effect instead of a separate effect. The two effects both depended on
toolInput and ran in declaration order, so the reset wiped the just-
initialized repo and the picker never reflected the invocation's owner/repo.
- Set the default base branch with a functional update in pr-write so a base
prefilled from toolInput.base (or chosen by the user) isn't overwritten by
a stale baseBranch value captured before the branches request resolved.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Fix issue-write repo owner mapping and clear stale UI state on reset
- issue-write: derive owner/name from full_name since search_repositories
minimal output omits the owner object (mirrors pr-write)
- pr-write/issue-write: clear available branch/label/assignee/milestone/type
lists and filters in the toolInput reset effect so prefill effects can't
match against the previous repo's stale data
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Merge remote-tracking branch 'origin/main' into mcp-ui-apps-advanced
* feat: add pull request editing functionality with reviewers support
* feat: implement interactive form handling for issue and pull request creation and updates
* Close response body per page in ui_get pagination loops
Avoids leaking HTTP connections when paging through assignees,
milestones, branches, collaborators, and teams.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Cache pr-edit.html in build-ui action
The build-ui cache only saved get-me/issue-write/pr-write HTML, so once a
cache entry was stored it restored an incomplete ui_dist on later runs and
skipped the rebuild, leaving pr-edit.html absent and panicking the tests.
Add pr-edit.html to the cached paths and bump the cache key to v2 to evict
the incomplete entries.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: tommaso-moro <tommaso-moro@github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add explicit show_ui parameter to UI-enabled write tools
Today the server decides whether to route issue_write and create_pull_request
through the MCP App form using two implicit signals: _ui_submitted (set by
the form on submit) and a heuristic that bypasses the form when the call
carries any parameter the form cannot represent (labels, assignees,
issue_fields, state, reviewers, etc.). The model had no first-class,
documented way to say "execute directly, do not show a form".
Add a show_ui boolean parameter to the input schema of IssueWrite,
LegacyIssueWrite, and CreatePullRequest. It defaults to true and is
visible only to clients that advertise MCP App UI support: the strip
happens per-request in inventory.ToolsForRegistration via a new
stripUIOnlySchemaProperties helper, gated by the same predicate that
already strips _meta.ui (shouldStripMCPAppsMetadata). The two strips share
one decision so the schema and metadata stay in lock-step.
Form-routing predicate becomes:
MCPApps FF on && client supports UI &&
!_ui_submitted && show_ui && !hasNonFormParams
show_ui=false is a new explicit way for the model to opt out. The existing
non-form-param auto-bypass stays as a safety net, and the React forms keep
sending _ui_submitted=true on submit unchanged. get_me is out of scope
because its UI is pure client-side card rendering with no server-side
gating to replace.
The current strip gate ("strip when FF is off OR capability explicitly
absent") mirrors today's _meta.ui behavior exactly, including the
"capability unknown" case. For stdio that means UI-capable schemas are
exposed to any FF-enabled client. The handler-side clientSupportsUI check
still gates form execution at call time, so it is functionally a no-op for
non-UI stdio clients. A separate follow-up will tighten the gate to
"strip on unknown too" and wire an InitializedHandler in stdio to
re-register the un-stripped surface only after a UI-capable client has
advertised; the two changes must ship together to avoid breaking stdio.
docs/feature-flags.md and docs/insiders-features.md include an unrelated
"reviewers" description update picked up by script/generate-docs from
commit 2bd162ac ("fix: support team pull request reviewers"), which
updated the source schema but did not regenerate docs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Clarify where show_ui appears in generated docs
The code comments next to the show_ui schema entries (and the
uiOnlySchemaProperties allowlist) said the property is documented in
"toolsnaps / README". README is generated from the stripped (non-UI)
schema, so show_ui is not actually in it — it only appears in toolsnaps
and the feature-flag / insiders docs. Reword the comments to match
reality.
Comment-only change; no behavior or test impact.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Guard issue_write/create_pull_request schemas against UI-gating desync
The form-routing logic depends on a hand-maintained classification of
each schema property into form-resendable vs known-non-form. A new
property added without updating the classification would silently shift
UI gating behavior (e.g. a form-incompatible param wouldn't trigger the
safety-net bypass).
Add Test_issueWriteSchemaClassification and Test_createPullRequestSchemaClassification
that enumerate each tool's InputSchema.Properties and require every
property to be classified as exactly one of:
- form-resendable (member of issueWriteFormParams / pullRequestWriteFormParams)
- known-non-form (test-local allowlist)
A future schema addition without classification fails the test with a
message pointing at the exact set the contributor needs to update.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Mark conditional schema parameters in generated docs
Previously `show_ui` was listed in docs/feature-flags.md and
docs/insiders-features.md alongside ordinary parameters with no
indication that it is hidden from clients without MCP App UI support.
A reader scanning the parameter list would assume it is always available.
Add a programmatic conditional-property mechanism:
- `inventory.ConditionalSchemaPropertyDescriptions()` exposes a
map[propertyName]conditionDescription derived from the same
uiOnlySchemaProperties allowlist that drives the per-request strip
in ToolsForRegistration. Single source of truth.
- The doc generator (writeToolDoc) consults this map and appends
"conditional — <description>" to the parameter's parenthesised
type/required suffix.
Example rendered output:
- `show_ui`: Whether to render the MCP App form... (boolean, optional,
conditional — only visible to clients that advertise MCP App UI support)
A small test (TestConditionalSchemaPropertyDescriptions) ensures every
entry in uiOnlySchemaProperties has a description, so a future stripped
property addition can't silently lose its doc marker.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Sam Morrow <sammorrowdrums@github.com>
Previously, omitting the `private` parameter on create_repository
defaulted the new repository to public, an insecure default that could
unintentionally expose code, configuration, and history. Omission now
defaults to a private repository; public repositories are only created
when `private` is explicitly set to false.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: add get_file_blame tool
* feat: implement cursor-based pagination for get_file_blame tool
* resolve annotated tags to their target commit in get_file_blame
* Regenerate get_file_blame toolsnap and docs after merge with main
The cursor-pagination parameter description changed on main; regenerate the
toolsnap and README so docs-check and toolsnap tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: gate get_file_blame behind file_blame feature flag
The git blame tool adds a new tool to the inventory, which carries a
context-footprint cost for every client. Gate it behind a new
file_blame feature flag (user opt-in via --features / X-MCP-Features)
that is also auto-enabled in insiders mode, so it is not advertised by
default.
Regenerated README, feature-flags.md and insiders-features.md docs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Sam Morrow <sammorrowdrums@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: Add get_commits method to pull_request_read
* Add nil check and additional test case
---------
Co-authored-by: Sam Morrow <info@sam-morrow.com>
* Annotate read tools with ifc labels
* Dont automatically enable IFCLabels in insiders mode
* ifc: don't label unpublished repo advisories as public
Repository security advisory listings can include draft/triage/closed
advisories (via the state filter), which are not world-readable even on a
public repository. Deriving confidentiality from repo visibility alone
under-classified those results as public.
LabelRepositorySecurityAdvisory now takes an allPublished flag and only
returns a public label when the repo is public AND every returned advisory
is published; otherwise it is private. list_repository_security_advisories
computes allPublished from the response state; the org-wide listing stays
private-untrusted. Adds unit + handler regression tests covering the
draft-advisory-on-public-repo case.
Addresses PR review feedback.
* ifc: fix confidentiality under-classification in releases, collaborators, get_me
Audit for the same bug class as the repo-advisory fix (confidentiality
derived from a coarse signal that misses access-restricted items) found
three more under-classifications:
- Releases (list_releases, get_latest_release, get_release_by_tag): draft
releases are visible only to push-access users and are not world-readable
even on a public repo. New LabelRelease(isPrivate, hasDraft) returns public
only for a non-draft release on a public repo; handlers compute hasDraft
from the response (Draft flag / per-item scan).
- list_repository_collaborators: a collaborator roster requires push access
to list, so it is never world-readable, not even on a public repo. New
LabelCollaboratorRoster() is always PrivateTrusted (mirrors LabelTeam),
replacing the repo-visibility-derived label.
- get_me: the result includes private_gists / total_private_repos /
owned_private_repos, which are not part of the public profile. LabelGetMe
is now PrivateTrusted instead of PublicTrusted.
Verified the remaining public-capable labels are sound: Actions logs are
world-readable on public repos; branches/tags are public metadata; gist,
project, search, and starred-repo labels read per-item visibility and join.
Adds ifc unit tests for the new/changed labels and a get_release_by_tag
handler regression test (draft on public repo -> private); updates the
get_me handler test to assert private.
* ifc: document why list results use one joined label, not per-item
Explain on LabelSearchIssues (and cross-ref from LabelGistList) that a tool
result is delivered as one opaque payload and the IFC engine makes one
allow/deny decision per flow at egress, so the only sound bound for a list is
the meet of every item's label. Per-item labels would only be load-bearing if
the engine could partition a result and route items to different sinks; until
then they would invite unsafe declassification of a public item that arrived
alongside private data. Doc-only change.
* Using issues suggestions feature flag
* Gate set_issue_fields confidence behind update_issue_confidence flag
The GitHub GraphQL API does not yet accept the per-field confidence input
on setIssueFieldValue mutations. Hide it from the user-facing schema and
drop it from the mutation payload unless the new update_issue_confidence
feature flag is enabled so users do not try to use it before the API
supports it.
* adding back confidence
* Update set_issue_fields confidence schema and toolsnap
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Docker / build (push) Has been cancelled
Build and Test Go Project / build (macos-latest) (push) Has been cancelled
Build and Test Go Project / build (ubuntu-latest) (push) Has been cancelled
Build and Test Go Project / build (windows-latest) (push) Has been cancelled
GoReleaser Release / release (push) Has been cancelled
MCP Server Diff / mcp-diff (push) Has been cancelled
MCP Server Diff / mcp-diff-http (push) Has been cancelled
Publish to MCP Registry / publish (push) Has been cancelled
Add an optional confidence integer parameter (0–100) to update_issue_type,
update_issue_labels, and set_issue_fields MCP tools. The confidence score
is passed through to the REST/GraphQL API on mutation calls.
- Rename structs to WithIntent (labelWithIntent, issueTypeWithIntent)
- Add confidence schema property (integer, min 0, max 100) with prompt
guidance describing what different confidence levels represent
- Update tool descriptions to encourage including confidence scores
- Pass confidence in the API request body alongside rationale/suggest
Closesgithub/plan-track-agentic-toolkit#219
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the get_commit tool's two boolean flags (include_diff,
include_patch) with a single detail enum: none / stats / full_patch.
Why:
- The two-boolean shape had an awkward dependency
("include_patch only applies when include_diff is true") and an
impossible state (include_patch=true, include_diff=false) that was
silently ignored.
- A single discriminator collapses three meaningful response shapes
into one orthogonal choice, makes the most expensive option
("full_patch") self-describing, and eliminates the "diff vs patch"
naming confusion.
Behavior:
- Default ("stats") matches the previous default
(include_diff=true, include_patch=false): per-file metadata with no
patch text. Existing callers using defaults are unaffected.
- "none" omits Stats and Files entirely (was include_diff=false).
- "full_patch" is the new opt-in level that adds the unified diff to
each MinimalCommitFile.
Breaking change: callers that previously passed include_diff or
include_patch must switch to detail. Callers using the defaults are
unaffected.
Changes:
- Added Patch field to MinimalCommitFile.
- Added commitDetail type, parseCommitDetail, and migrated
convertToMinimalCommit to take a commitDetail.
- Updated get_commit schema, list_commits caller (commitDetailNone),
unit tests, toolsnap, and README.
Co-authored-by: Sam Morrow <sammorrowdrums@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Skip MCP App form when issue/PR write carries non-form params
When MCP Apps are enabled and the client supports UI, issue_write and
create_pull_request route the call to an interactive form. The form only
collects a subset of fields and rebuilds the submit payload from scratch,
so any parameter it cannot represent was silently dropped — e.g. labels,
assignees, milestone, type, state and issue_fields (priority) for
issue_write.
Skip the form and execute directly whenever the call carries a parameter
outside the set the form collects and re-sends. This generalizes the
previous state-only guard and is robust to future parameter additions
(an unrecognized param now bypasses the form rather than being lost).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Forward original tool params on MCP App form submit
The issue-write and pr-write forms rebuilt their submit payload from
scratch, so any parameter the form does not render was dropped on submit.
Spread the original toolInput first and override only the edited fields,
so unsupported params (e.g. issue_fields, labels, state) are preserved
when the user submits the form.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds two new methods to the consolidated projects_write tool:
- create_project: creates a new GitHub ProjectsV2 for a user or org
- create_iteration_field: adds an iteration field to an existing project
Changes addressing review feedback:
- Validate owner_type is exactly 'user' or 'org' in create_project
- Use resolveProjectNodeID (GraphQL) instead of getProjectNodeID (REST)
to avoid HTTP response body leaks
- Add omitempty to Iterations JSON tag
- Rename iterations item field startDate to start_date for consistency
- Validate iteration elements instead of silently skipping invalid ones
- Use explicit response structs with snake_case JSON tags
- Add test for auto-detected owner_type in create_iteration_field
- Use stubExporters() in test deps for nil-safety
Co-authored-by: João Doria de Souza <jdoria@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Explicitly set prefersBorder on every UI resource — false for the
get_me profile card, true for the issue/PR write forms — since
hosts' defaults vary.
* Declare an empty csp on issue_write_ui and pr_write_ui to document
that they need no external origins.
* Point spec link comment at the stable 2026-01-26 location.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Build and Test Go Project / build (macos-latest) (push) Has been cancelled
Build and Test Go Project / build (ubuntu-latest) (push) Has been cancelled
Build and Test Go Project / build (windows-latest) (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Docker / build (push) Has been cancelled
GoReleaser Release / release (push) Has been cancelled
MCP Server Diff / mcp-diff (push) Has been cancelled
MCP Server Diff / mcp-diff-http (push) Has been cancelled
Publish to MCP Registry / publish (push) Has been cancelled
The remote/HTTP server never called RegisterUIResources, so when the
remote_mcp_ui_apps feature flag was enabled per-request, tools like
issue_write and create_pull_request would advertise a ui:// resource URI
in their _meta.ui block but the resource itself was not registered. The
client's follow-up resources/read call then failed with -32002 'Resource
not found' (the error surfaced as 'Error loading MCP App: MPC -32002:
Resource not found' in VS Code).
The stdio bootstrap also gated registration on featureChecker called
with context.Background(), which can't see per-request flag overrides.
Move RegisterUIResources into pkg/github.NewMCPServer (the shared
constructor used by both stdio and HTTP), gated only on
UIAssetsAvailable(). The resources are inert static HTML; the inventory
still strips _meta.ui from tools per-request via stripMCPAppsMetadata,
so the URI is only advertised to clients when the flag is on for that
request.
Fixes#2467
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the AST-based ReadOnlyHint scan introduced in #2486 out of
pkg/github's test file and into a new exported package, pkg/toolvalidation,
so downstream consumers (notably github/github-mcp-server-remote, which
uses this repo as a library) can apply the same guardrail to their own
tool registrations with a one-line test:
violations, err := toolvalidation.ScanReadOnlyHint(pkgDir)
Changes:
- New pkg/toolvalidation/readonlyhint.go with ScanReadOnlyHint,
FormatReadOnlyHintViolations, and the ReadOnlyHintViolation type.
- Dedicated unit tests for the scanner using in-memory fixtures
(compliant, missing-hint, missing-annotations, non-literal,
aliased import, positional fields, file without mcp import).
- pkg/github/tools_static_validation_test.go shrunk to a thin wrapper
that calls ScanReadOnlyHint against its own package directory; the
existing behavior for pkg/github is preserved.
No production-code, schema, or toolsnap changes.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Resolve each file's local alias for github.com/modelcontextprotocol/go-sdk/mcp
via file.Imports rather than hard-coding the "mcp" qualifier, so the check
also covers files that import the SDK under a non-default alias.
- Detect positional (unkeyed) composite literals and report a dedicated
diagnostic instead of producing misleading "missing field" violations.
- Drop the brittle 'expected to discover at least one mcp.Tool literal'
assertion: if registrations move behind constructors/factories the AST
walker legitimately finds nothing.
- Use strconv.Unquote to decode tool-name string literals (handles escapes
in interpreted strings); fall back to the raw lexeme on parse error.
Adds a source-level (AST) validation test that walks every non-test Go file in pkg/github and fails if any mcp.Tool composite literal omits Annotations.ReadOnlyHint.
The existing TestAllToolsHaveRequiredMetadata can only assert that Annotations is non-nil at runtime: Go cannot distinguish an unset bool field from one explicitly set to false. The new test closes that gap so future read-intent tools cannot silently default to ReadOnlyHint=false, which has caused downstream agents to prompt for human approval on safe read operations.
All 97 current mcp.Tool registrations pass. Fault-injected by removing ReadOnlyHint from issue_read and confirmed the test reports the exact file, line, tool name, and reason.
Refs github/github-mcp-server#2483
The generic map decoding path for project field values treated 'name'
(ProjectV2FieldOption) and 'title' (ProjectV2FieldIteration) as plain
strings, but the GitHub API returns them as ProjectV2TextContent objects
with raw/html fields. As a result, single-select option names and
iteration titles could be returned empty when values reached the
minimal converter as map[string]any instead of typed structs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Return compact project item content and field values from project item tools to avoid verbose issue and pull request payloads.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mergeIssueFieldValues built the merged slice by iterating a Go map,
which produces non-deterministic ordering and caused a flake in
Test_UpdateIssue/partial_update_with_issue_fields_reconciled_by_names
(introduced in #2551). Switch to an order-preserving merge: emit
incoming entries first in their original order, then any existing
entries (in their original order) whose field IDs weren't seen in
incoming. Semantics (incoming wins, existing preserved) unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Companion to Test_GetIssue_FieldValues: when remote_mcp_issue_fields is
enabled, the GraphQL nodes() round-trip populates the enriched
field_values while the raw REST issue_field_values stays cleared.
Addresses the Copilot review suggestion on #2558.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the shared buildIssueWrite(includeIssueFields) helper with two
fully duplicated tool definitions. When the FeatureFlagIssueFields flag
is retired, LegacyIssueWrite can be deleted as a single function with no
merge thinking required.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ports the gating from PR #2553 onto main (the original merge landed on a
stack base that did not make it to main).
Changes:
- pkg/inventory: FeatureFlagDisable becomes []string (any-listed-on → hide).
FeatureFlagEnable stays as a single string. This avoids the AND-of-enable
semantics from the earlier proposal, which encoded dependencies rather
than rollout knobs and had no real call site. Disable-OR is the case
that does need the slice (LegacyIssueWrite below).
- pkg/github/issues.go: split IssueWrite into IssueWrite (flag-enabled,
exposes issue_fields) and LegacyIssueWrite (flag-disabled, omits it).
Both register as 'issue_write'; mutually exclusive flag annotations
pick exactly one at runtime. Refactored into a shared buildIssueWrite
helper instead of duplicating the ~250-line tool definition.
- pkg/github/issues.go: GetIssue field_values enrichment now requires
the flag at runtime. The verbose REST IssueFieldValues is always
cleared from the response.
- Existing single-flag Disable call sites converted to slices.
- New toolsnap variant issue_write_ff_remote_mcp_issue_fields.snap; the
canonical issue_write.snap is owned by LegacyIssueWrite.
- README + flag docs regenerated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>