Skip to content

Add notification subscriptions for issues, pull requests, and discussions - #3492

Draft
kaii9 wants to merge 1 commit into
github:mainfrom
kaii9:feat/subject-subscriptions
Draft

kaii9 wants to merge 1 commit into
github:mainfrom
kaii9:feat/subject-subscriptions

Conversation

@kaii9

@kaii9 kaii9 commented Oct 13, 2026

Copy link
Copy Markdown

Summary

Agents can manage an authenticated user's notification subscription for an issue, pull request, or discussion directly from its repository and number, without an existing notification thread or watching the entire repository.

This is a Draft while confirming GitHub's IGNORED behavior. Subscribe and unsubscribe are verified live; the API returns UNSUBSCRIBED for the tested ignore requests. See the standalone reproduction and questions below.

Why

Related to #3452. Existing subscription tools target a notification thread ID or an entire repository, leaving no direct way to follow one specific upstream issue, pull request, or discussion. Subscribe and unsubscribe are verified live; the ignored state has an observed upstream API limitation described below.

What changed

  • Add get_subject_subscription and manage_subject_subscription to the notifications toolset using typed inputs and outputs.
  • Resolve the concrete subject through GraphQL and update its subscription through updateSubscription.
  • Accept the three GraphQL subscription states, preserving GitHub's nullable viewer subscription state in reads. Subscribe/unsubscribe are verified live; when GitHub returns a different state, the tool reports the actual and requested states as an error.
  • Skip the mutation when the requested state already matches. Reject invalid arguments before obtaining the client and report missing resources, inaccessible subjects, GraphQL failures, and incomplete or mismatched mutation responses.
  • Add behavior and protocol tests, input/output schema snapshots, and generated README documentation.

MCP impact

  • No tool or API changes
  • Tool schema or behavior changed
  • New tool added

Both tools take owner, repo, subjectType (issue, pull_request, discussion), and number. The write tool additionally requires state (subscribed, unsubscribed, ignored). Responses include the subject ID, URL, type, number, current state, and whether the authenticated user can subscribe. Keeping the read tool separate allows subscription inspection in read-only mode.

Prompts tested (tool changes only)

Representative workflows:

Validation uses equivalent direct MCP requests rather than LLM prompt runs. Live reads and writes cover one issue, one pull request, and one discussion, and compare results with independent GitHub GraphQL queries. Subscribe, unsubscribe, repeated operations, error reporting for inconsistent ignored results, and restoration of the original states were checked. Mutation behavior is also covered through the real handler and a simulated GitHub HTTP boundary.

Security / limits

  • No security or limits impact
  • Auth / permissions considered
  • Data exposure, filtering, or token/size limits considered

updateSubscription requires the notifications OAuth scope. The write policy declares both repo and notifications, so OAuth can request repository access for the preliminary lookup of private subjects, while notifications-only PATs remain eligible for public targets. Challenges request only missing scopes. Reads follow the existing repository-read scope policy. Subscription state belongs to the authenticated user, so results carry private/trusted IFC labels even for public subjects. The tools return subscription metadata without issue bodies or comments. A state change may require one lookup and one mutation; an already-matching state requires only the lookup. The write tool follows the existing notification tools' destructive annotation and is marked idempotent.

Tool renaming

  • I am renaming tools as part of this PR
  • I am not renaming tools as part of this PR

Lint & tests

  • Linted locally with ./script/lint — 0 issues.
  • Tested locally with ./script/test (go test -race ./...) after the final scope-policy change; full race suite passed. Earlier development validation also passed with -count=1.

Also ran CI-mode schema/feature checks without test cache, built the binary, and ran live read-only MCP checks under legacy and modern protocol versions. Enabling csv_output leaves these non-list tools in JSON as designed. With notifications authorization granted, live subscribe/unsubscribe and repeated operations succeeded for issue #3452, PR #3454, and discussion #1257 in github/github-mcp-server. Original subscription states were restored and checked independently.

Observed upstream limit: GitHub accepted updateSubscription(state: IGNORED) but returned UNSUBSCRIBED for each of those three subjects. Independent direct GraphQL calls reproduced the same result; Issue's viewerThreadSubscriptionStatus was NONE, not IGNORING_THREAD. The tool reports the actual versus requested state as an error rather than claiming a successful ignore operation. The GraphQL enum remains accepted so a subject for which GitHub does return IGNORED can succeed. Maintainer confirmation of this GitHub API behavior is still needed.

API behavior to confirm

The following calls reproduce the behavior directly against GitHub GraphQL, without this MCP server or the Go bindings. Rechecked on October 14, 2026 (Asia/Shanghai), using an OAuth token with repo and notifications; original subscription states were restored afterward.

  1. Resolve the issue's node ID and record its original state:
query {
  repository(owner: "github", name: "github-mcp-server") {
    issue(number: 3452) {
      id
      viewerCanSubscribe
      viewerSubscription
      viewerThreadSubscriptionStatus
    }
  }
}
  1. Request IGNORED using that node ID:
mutation($id: ID!, $state: SubscriptionState!) {
  updateSubscription(input: {subscribableId: $id, state: $state}) {
    subscribable {
      id
      viewerSubscription
      ... on Issue {
        viewerThreadSubscriptionStatus
      }
    }
  }
}

Variables:

{"id": "<node ID from the first query>", "state": "IGNORED"}
  1. Repeat the first query to check persisted state. Expected viewerSubscription: IGNORED based on the documented enum; the mutation and independent read both returned UNSUBSCRIBED, and the issue's thread state was NONE. Restore the captured original state using the same mutation before finishing the reproduction. The operation can alter the account's subscription even when the requested state is not returned.

The same mutation and independent read yielded UNSUBSCRIBED for PR #3454 and discussion #1257. This is an observation for these subjects and credentials, not a claim that IGNORED never works across GitHub.

Could maintainers confirm whether IGNORED is supported through updateSubscription for these subject types, or whether another API/readback is needed? If this behavior is expected, would you prefer an initial implementation limited to subscribe/unsubscribe, with ignore support in a follow-up?

Docs

  • Not needed
  • Updated (README / docs / examples)

Generated README tool documentation; input and output schema snapshots included.

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.

1 participant