56 Commits

Author SHA1 Message Date
Sam Morrow a7c3b494e6 test(http): cover authorization server override wiring
Verify the HTTP-only configuration surface, unchanged host-derived default, and explicit override propagation through OAuth metadata.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-20 16:44:51 +02:00
Anika Reiter 0ae533c163 feat(http): add --authorization-server flag to override OAuth AS URL
When deploying the MCP server behind an OAuth proxy (e.g. for GHES,
which does not natively support RFC 8414, RFC 7591, or PKCE), the
/.well-known/oauth-protected-resource endpoint currently always derives
the authorization_servers URL from GITHUB_HOST. There is no way to
point clients at a different authorization server without intercepting
that endpoint at the ingress/proxy layer.

The oauth.Config struct already has an AuthorizationServer field with
the conditional logic in place (pkg/http/oauth/oauth.go), but it was
never wired to any configuration surface.

This commit exposes it as:
- --authorization-server CLI flag on the http subcommand
- GITHUB_AUTHORIZATION_SERVER environment variable (via viper's
  existing GITHUB_ prefix + automatic env mapping)

When set, the value is passed through ServerConfig into oauth.Config,
and the protected resource metadata advertises it directly instead of
calling apiHost.AuthorizationServerURL().
2026-08-20 16:44:51 +02:00
Sam Morrow b3ecab4a01 Set the request-body limit to 5 MiB at both layers
Bounds the total HTTP request, so allow modest headroom over the MCP
SDK's 4 MiB default for JSON-RPC and tool-call envelope overhead rather
than spending the whole budget on tool content.

Because the limit now exceeds the SDK default, passing it to
StreamableHTTPOptions is load-bearing: without it the SDK would cap
requests at 4 MiB and the headroom would not exist. Covered by a test
that sends a request between the two limits.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 16:30:05 +02:00
Sam Morrow b0b41a5515 Align request-body limit with the MCP SDK default
The middleware default was an arbitrary 10 MiB, above the 4 MiB the SDK
already enforces, so it never changed which requests were accepted.
Alias mcp.DefaultMaxRequestBodyBytes instead, making the earlier
enforcement point behaviour-preserving by construction.

Also pass the effective limit to StreamableHTTPOptions. Previously the
SDK kept its own 4 MiB default, so a larger configured
MaxRequestBodyBytes was silently capped; both layers now agree.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 16:30:05 +02:00
Sam Morrow 0f0e191bb0 test: exercise MaxBytesError branches with unknown-length bodies
WithMCPParse and WithScopeChallenge tests for oversized requests were
using strings.NewReader, which gives httptest.NewRequest a known
Content-Length. That let WithMaxBodySize reject the request in its
fast path before the request ever reached the middleware's own
io.ReadAll/isMaxBytesError handling, leaving those branches untested.

Reuse the existing unknownLengthBody helper (body_limit_test.go) so
these tests actually reach the fallback read path and cover the
*http.MaxBytesError handling added in WithMCPParse and
WithScopeChallenge.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 16:30:05 +02:00
Sam Morrow e0096d87d5 Limit HTTP request bodies before MCP middleware parsing
Add WithMaxBodySize middleware that bounds the request body via
http.MaxBytesReader (with a fast Content-Length rejection when known),
registered first in RegisterMiddleware so it runs before any other
middleware or the MCP SDK reads or buffers the body.

WithMCPParse and WithScopeChallenge now return a clear 413 "request
body too large" response when their body read hits the limit, instead
of silently continuing.

Defaults to 10 MiB, overridable via ServerConfig.MaxRequestBodyBytes.

Fixes #3102

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 16:30:05 +02:00
Sam Morrow 3bad3bc651 fix(http): make server lockdown mode an upper bound over requests (#3112) 2026-08-19 15:20:19 +02:00
Sam Morrow 21c5a6f1dd fix(auth): scope tokens across GitHub clients
Use exact configured host authorities for every REST, GraphQL, and raw client so redirects cannot reattach credentials to foreign hosts or ports. Add adversarial redirect and lookalike coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 00:02:40 +02:00
Syed Anas Mohiuddin 198017fb54 Attach GitHub token only to configured GitHub hosts
BearerAuthTransport re-adds the Authorization header on every hop, which
defeats net/http's cross-host redirect stripping. Scope the credential to
the configured hosts so a redirect off them travels without the token.

An empty AllowedHosts preserves prior behavior; the three production
construction sites populate it from the configured REST, upload, GraphQL
and raw hosts.
2026-08-19 00:02:40 +02:00
Sam Morrow 505b88f8ac test(http): preserve fail-closed inventory regressions
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-18 23:43:30 +02:00
Sam Morrow a4f801e3d6 fix(http): fail startup on invalid static tools
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-18 23:43:30 +02:00
Mahmoud772122777 bb19060e63 Fix static tools validation fallback 2026-08-18 23:43:30 +02:00
Sam Morrow 8ec62491c6 Add confirmed repository deletion tool (#3076)
* feat(repos): add confirmed repository deletion

Add a destructive delete_repository tool that requires an exact owner/repo confirmation through multi-round-trip elicitation. Gate the tool to MCP protocol 2026-07-28 and newer across local and remote transports.

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

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* refactor(inventory): generalize tool availability guards

Gate protocol-restricted tools on required elicitation capabilities and enforce direct calls inside the registered handler so SDK result finalization remains intact.

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

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* feat(http): protect MRTR request state

Seal repository deletion targets for self-hosted HTTP with a stable AES-256-GCM key. Hide only delete_repository when no key is configured and expose an optional sealer interface for remote integrators.

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

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* fix(repos): expire deletion confirmations

Bind sealed repository deletion state to the immutable repository ID and a ten-minute expiry. Re-check identity before deletion so replay cannot affect a recreated repository.

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

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* fix(http): preserve tool and scope restrictions

Apply static allowlists before removing unavailable tools and fail closed on invalid configured tool names. Model independent OAuth requirements as conjunctive groups so repository deletion requires both delete_repo and repo.

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

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* fix(repos): require protected confirmation state

Give stdio a process-local request-state sealer and make deletion fail closed without one. Preserve legacy any-of OAuth behavior globally while documenting and enforcing delete_repository's conjunctive delete_repo and repo requirements.

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

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* fix(oauth): request repository deletion scope

Include delete_repo in the supported OAuth scope set used by stdio login, HTTP protected-resource metadata, and tool filtering.

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

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* fix(oauth): require deletion scope opt-in

Keep delete_repo in protected-resource discovery for step-up authorization while excluding it from the default stdio OAuth grant.

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

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* refactor(oauth): derive scope sets from catalog

Generate protected-resource supported scopes and the lower-risk default OAuth grant from one canonical scope definition list.

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

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* refactor(scopes): own OAuth scope catalog

Move supported and default OAuth scope policy into pkg/scopes so protected-resource metadata and stdio grants derive from the scope domain package.

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

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* fix(scopes): require workflow scope opt-in

Keep workflow and codespace in protected-resource discovery while excluding both from the default OAuth grant alongside delete_repo.

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

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

---------

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8
2026-08-18 14:50:38 +02:00
Sam Morrow 3085e59f66 Reject unsupported subscription streams (#3073)
* fix(http): reject unsupported subscription streams

Use the Mcp-Method header to reject subscriptions/listen with the spec-defined 404 Method Not Found response instead of opening an idle SSE stream. Preserve SDK validation for missing or mismatched headers.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 06d5dda1-4086-4996-8d18-152e45e611b0

* refactor(http): clarify subscription rejection

Document why header validation precedes the unsupported-method rejection and use named SDK error constants in tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 06d5dda1-4086-4996-8d18-152e45e611b0

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 06d5dda1-4086-4996-8d18-152e45e611b0
2026-08-17 13:42:34 +02:00
Sam Morrow 0c825b4233 fix(security): enforce HTTPS for gh-host/GITHUB_HOST to prevent cleartext credentials
GHES hosts accepted an http:// scheme, which was interpolated into every
REST/GraphQL/upload/raw/authorization URL. Authenticated requests would then
carry the bearer token/PAT over cleartext http, exposing it to network
interception and replay.

Add a central HTTPS check in parseAPIHost so no deployment can build
authenticated URLs over http, mirroring the existing GHEC behaviour. Permit
http only for loopback hosts (localhost, 127.0.0.1, ::1) so local development
against a dev server still works.

Closes github/copilot-mcp-core#1815

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-14 13:53:02 +02:00
Kelsey Myers f3cb662c25 Make search_issues semantic by default (#2964)
* Make search_issues semantic by default

* initialize description depending on the host

---------

Co-authored-by: Iulia B <iulia-b@github.com>
Co-authored-by: Iulia Bejan <64602043+iulia-b@users.noreply.github.com>
2026-08-06 16:51:13 +02:00
Sam Morrow ea8099d7b2 fix: don't advertise unsupported list-changed capabilities
The server exposes a static set of tools, prompts, and resources and never
mutates them at runtime, so it never emits list_changed notifications. When
capabilities are left unset, the go-sdk infers listChanged:true from the
presence of items and advertises tools/prompts/resources list-change support
we don't actually provide - and the 2026-07-28 spec (subscriptions/listen)
tightens expectations around this.

Declare empty tools/prompts/resources capabilities in NewMCPServer so both the
stdio and remote servers advertise honestly. The remote HTTP handler already
set these explicitly; that duplication is now removed in favour of the shared
default, leaving only the remote-specific schema cache.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 95b8432c-f280-472e-a242-d3ca6dc31f19
2026-07-30 15:22:06 +02:00
Connor Peet a217a7f43a Add MCP App form deferral opt-out
Allow clients to keep MCP App views enabled while making form-backed write tools execute directly when explicitly configured.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-22 19:02:50 +02:00
Sam Morrow ea4e3960b8 refactor(auth): isolate GitHub App auth to stdio startup
Keep PEM loading and installation-token provider construction at the CLI leaf, then pass a generic refreshing token provider through the existing HTTP transports. Rebase the feature onto current main and keep the HTTP command unchanged.

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

Copilot-Session: 646357dd-c89f-4973-9a5c-e6c5fc18818c
2026-07-22 18:51:35 +02:00
Sam Morrow 8ce77e3c16 feat(oauth): add stdio OAuth 2.1 login core library (1/4) (#2704)
* 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>
2026-06-26 11:59:35 +02:00
Kazuhiko Yamashita 909235ed3a feat(http): support custom listen address (#2655) 2026-06-15 21:13:57 +02:00
JoannaaKL fb7cbc8b85 Annotate read tools with ifc labels (#2671)
* 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.
2026-06-11 13:48:36 +02:00
Matt Holloway 561a4a7951 Strip _meta.ui when client lacks UI capability
Per the MCP Apps 2026-01-26 spec, servers SHOULD check client capabilities
before advertising UI-enabled tools. Extend the inventory strip gate to
remove _meta.ui not only when the feature flag is off, but also when the
request context explicitly reports the client lacks UI support
(HasUISupport returns supported=false, ok=true).

When the capability is unknown (ok=false, e.g. stdio paths), fall through
to the existing feature-flag gate so existing behaviour is preserved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-29 19:41:54 +02:00
JoannaaKL 7d46f8d8db I want to enable only ifc (#2565)
* I want to enable only ifc

* Fix tests
2026-05-28 19:42:10 +02:00
Sam Morrow 6e0af328bc deps: bump go-sdk to v1.6.1 and drop CrossOriginProtection workaround
Bumps github.com/modelcontextprotocol/go-sdk from v1.6.0 to v1.6.1 and
removes the CrossOriginProtection bypass we previously installed on the
StreamableHTTP handler.

As of go-sdk v1.6.0 the cross-origin check is opt-in: a nil
CrossOriginProtection on StreamableHTTPOptions means no check is run.
v1.6.1 also marks the field itself as deprecated (the SDK recommends
wrapping the handler with middleware instead, and the field will be
removed in v1.8.0).

This server authenticates via bearer tokens, not cookies, so the
Sec-Fetch-Site CSRF check is unnecessary and would block browser-based
MCP clients. Leaving CrossOriginProtection unset preserves that
behavior without depending on a deprecated API.

Supersedes #2541.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-28 15:10:29 +02:00
Sam Morrow 014fd17fa3 feat: gate issue_write and get_issue behind remote_mcp_issue_fields flag
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>
2026-05-28 13:11:29 +02:00
Sam Morrow b473a5afd1 feat(http): ignore proxy forwarding headers by default
X-Forwarded-Host and X-Forwarded-Proto were unconditionally honored when
constructing OAuth resource metadata URLs. In HTTP-mode deployments that
do not set --base-url and are not fronted by a proxy that strips these
headers, this lets an on-path client influence the URL advertised in
WWW-Authenticate and the /.well-known/oauth-protected-resource body.

This is a hardening change rather than a true vulnerability — exploiting
it requires HTTP without --base-url plus an attacker already positioned
to inject the header — but the unsafe default is worth closing.

Default behavior now derives host/scheme from r.Host and the TLS state.
Setups that rely on a trusted internal forwarder (e.g. an in-cluster
gateway that needs to preserve the originating hostname per request) can
opt back in with --trust-proxy-headers / GITHUB_TRUST_PROXY_HEADERS=1.
--base-url continues to take precedence in all cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-27 07:29:04 +02:00
Ross Tarrant f929c58c6b feat: Add CSV output format for default list tools under insiders mode (#2450)
* Add CSV output for list tools under insiders mode

* fix: resolve rebase feature flag conflicts

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

* Simplify feature-flag handling: collapse CSV dual-variant + skip filtering when no checker (#2516)

* refactor: generic toolset+name sort, clarify feature flag intent

Address review feedback on #2450:

- Collapse the three near-identical sort helpers in pkg/inventory/filters.go
  into a generic sortByToolsetThenName so adding new inventory item types
  doesn't require copying the comparator.
- Expand the doc comments on the three *WithoutFeatureFiltering helpers to
  spell out why they exist: HTTP mode builds a static (process-wide)
  inventory as an upper bound, but per-request feature flags from headers
  (X-MCP-Features, X-MCP-Insiders) are evaluated later, so feature-flagged
  variants must be preserved here.
- Strengthen the doc comment on ResolveFeatureFlags to make the contract
  explicit: user-supplied flags are validated against AllowedFeatureFlags,
  but insiders expansion deliberately is not — InsidersFeatureFlags may
  include server-controlled flags that are not user-toggleable.

CORS comments are intentionally left for the PR author.

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

* docs(feature-flags): clarify allowed and insiders sets are independent

Also add tests covering:
- a user-toggleable flag (FeatureFlagIssuesGranular) that insiders does
  not turn on automatically
- insiders mode not turning on user-only allowed flags

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

* refactor(inventory): collapse three *WithoutFeatureFiltering helpers into StaticUpperBound

The three parallel methods (AvailableToolsWithoutFeatureFiltering,
AvailableResourceTemplatesWithoutFeatureFiltering,
AvailablePromptsWithoutFeatureFiltering) were always called as a triple
in exactly two places: HTTP buildStaticInventory and its test mirror.
They exist because the dual-variant pattern (sibling tools with mirrored
FeatureFlagEnable / FeatureFlagDisable on the same name, e.g. CSV output)
makes feature filtering at static-build time impossible — both variants
must be kept and resolved per-request.

Replace the three with one method, Inventory.StaticUpperBound(ctx), that
returns (tools, resources, prompts) and carries the rationale in its
doc comment. Reduces API surface, eliminates the triplication, and makes
the single "skip feature filtering" concept obvious to readers.

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

* refactor: simplify feature-flag handling

Two related simplifications, both about treating insiders as a meta flag
that expands once at startup and then stops mattering:

- Collapse CSV's dual-variant pattern into a single tool whose handler
  performs a runtime feature-flag check via deps.IsFeatureEnabled. CSV
  is a pure response-format toggle, not a schema change, so it does not
  need the dual-name pattern that genuine schema variants (granular
  issues/PRs) still use.

- When no feature checker is installed, skip feature-flag filtering and
  return the full upper bound. The static HTTP inventory now uses plain
  AvailableTools/Resources/Prompts; the per-request inventory always
  installs a checker, so MCP registration (which serves a tool name once)
  always sees a deduplicated set. The bespoke StaticUpperBound helper and
  the isToolEnabledWithFeatureFlags split go away.

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

* ci(mcp-diff): add insiders + per-feature configs

The mcp-diff matrix now includes:
  - --insiders (and --insiders --read-only)
  - one config per github.AllowedFeatureFlags entry, generated by
    script/print-mcp-diff-configs so new user-controllable flags get
    diffed automatically without editing the workflow

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

* docs(insiders): explain feature-flag resolution for contributors

Adds a 'How feature flags are resolved' section covering:
  - Insiders is a meta flag, like 'all'/'default' for toolsets
  - User input -> allowlist filter -> insiders expansion ->
    server-side fallback (remote only)
  - AllowedFeatureFlags vs InsidersFeatureFlags are independent
  - How to add a new feature flag, including the
    TestGitHubPackageDoesNotReadInsidersMode guard

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

* refactor(inventory): make feature-flag gating a regular ToolFilter

Move tool feature-flag evaluation out of isToolEnabled and into a
ToolFilter installed at the head of the pipeline by Build() when
WithFeatureChecker received a non-nil checker. The 'no checker = no
filtering' contract is now expressed structurally (the filter isn't
installed) instead of by a runtime nil check inside the helper.

Resources and prompts have no filter pipeline, so they call the now-pure
featureFlagAllowed helper behind an explicit r.featureChecker != nil
guard at the iteration site.

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

* perf(inventory): cache extracted toolset IDs in sort comparator

Avoid evaluating the extractor closures up to three times per comparison.

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

---------

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

* fix: correct MCP features header in cors

* docs: regenerate README for CSV output toolset

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

* fix: remove duplicate MCPFeaturesHeader from CORS headers

* ci(mcp-diff): add streamable-http job with header-based configs

Adds a sibling mcp-diff-http job that exercises the streamable-http
transport against a shared HTTP server, with per-config settings supplied
via X-MCP-* request headers — mirroring how the remote server is invoked
in production (server-side defaults + per-user header overrides).

The config generator gains a -transport flag:
- stdio (default, unchanged behaviour)
- http-headers (emits headers-only configs targeting a shared server)

Two new combined entries layer multiple headers together as a smoke test
for header-merging regressions.

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

* docs: regenerate after merging main

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 <info@sam-morrow.com>
Co-authored-by: sammorrowdrums <sammorrowdrums@github.com>
2026-05-21 16:50:55 +02:00
Sam Morrow 0f0506d2fd refactor: remove dynamic toolsets and deprecated closure constructor (#2512)
Dynamic toolset discovery (the meta-tools enable_toolset, list_available_toolsets,
get_toolset_tools and the --dynamic-toolsets / GITHUB_DYNAMIC_TOOLSETS switch)
was a local-only feature never offered by the remote server. Removing it
deletes a meaningful chunk of branching, configuration surface and tests
for a path no longer in active use.

The deprecated closure-based NewServerToolWithDeps generic constructor was
only kept around for the dynamic tool registration path and is removed
together with it. Going forward there are exactly two constructors:

- NewServerTool — raw mcp.ToolHandler, no closure, no unmarshalling
- NewServerToolWithContextHandler[In, Out] — typed handler, deps via context

Inventory methods that only existed for the dynamic path
(ToolsForToolset, IsToolsetEnabled, EnableToolset, EnabledToolsetIDs)
are removed. ResolvedEnabledToolsets loses its dynamic flag.

Also strips dynamic references from the README, server configuration
docs, copilot-instructions, mcp-diff workflow, and conformance-test
script.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-20 10:51:47 +02:00
Matt Holloway 0e2fc38896 fix(mcp-apps): defer _meta.ui strip to per-request RegisterTools (#2446)
* fix(mcp-apps): defer _meta.ui strip to per-request RegisterTools

The MCP Apps `_meta.ui` strip lived in `Builder.Build()`, which calls
`checkFeatureFlag(context.Background())`. The HTTP feature checker
(`createHTTPFeatureChecker`) reads insiders mode from the request
context — a background context never has it set, so the FF reported
MCP Apps off and the strip ran eagerly at server startup. Per-request
inventory factories then served pre-stripped tools regardless of
whether the request actually arrived on the `/insiders` route.

Symptom: `github/github-mcp-server-remote` returns 0 tools with
`_meta.ui` over HTTP `/insiders`, despite the source unconditionally
setting it on `get_me`, `issue_write`, and `create_pull_request`.
VS Code only renders MCP App UIs because of its persistent tool cache
from earlier deploys. Reproducible locally with
`cmd/github-mcp-server http --insiders` plus a vanilla curl tools/list.

Fix: drop the strip from `Build()`. Apply it in `RegisterTools(ctx,…)`
where the per-request context is in scope and the HTTP feature checker
can correctly detect insiders mode (or the remote checker can correctly
read user identity for Statsig flag lookup).

The same root cause affects `github/github-mcp-server-remote` — its
`featureflags.NewComposedFeatureFlagChecker` reads
`requestctx.User(ctx)`, which background context lacks, so the
`remote_mcp_ui_apps` Statsig flag always returned false. The fix here
covers both downstreams since `RegisterTools` is the single entry
point for tool registration.

Stdio mode is unaffected: it uses a closure-captured insiders mode
flag (`createFeatureChecker`) that does not depend on context, and the
per-request strip in `RegisterTools` produces the same outcome.

Verified end-to-end against the deployed remote tool definitions:

  HTTP /insiders     → 3 tools with _meta.ui (was 0)
  HTTP /             → 0 tools with _meta.ui (correct)
  stdio --insiders   → 3 tools with _meta.ui (unchanged)
  stdio              → 0 tools with _meta.ui (correct)

Adds:
  - pkg/http: TestInsidersRoutePreservesUIMeta — pins the regression
  - pkg/inventory: updates the existing strip tests to use the new
    RegisterTools-as-strip-site contract via a captureRegisteredTools
    helper

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

* style: gofmt handler_test.go and registry_test.go

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

* lint: address revive (context-as-argument) and unused checkFeatureFlag

- Reorder captureRegisteredTools params to put context.Context first
- Remove dead Builder.checkFeatureFlag (was only called by Build's
  former MCP Apps strip, now done in RegisterTools via the Inventory
  receiver's checkFeatureFlag instead)

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-08 09:08:50 -07:00
Iulia Bejan 3a6a6f6682 Fix set_issue_fields mutation: use correct inline fragments for IssueFieldValue union (#2366)
Docker / build (push) Has been cancelled
GoReleaser Release / release (push) Has been cancelled
MCP Server Diff / mcp-diff (push) Has been cancelled
Publish to MCP Registry / publish (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
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
* Fix set_issue_fields mutation: use correct inline fragments for IssueFieldValue union

The mutation response struct used a single inline fragment
'... on IssueFieldDateValue' with a 'Name' field that doesn't exist
on that type (only IssueFieldSingleSelectValue has 'name'). This
caused GraphQL validation to fail with:

  Field 'name' doesn't exist on type 'IssueFieldDateValue'

Since GraphQL validates the entire document (including response
selection sets) before executing any operation, the mutation never
fired at all — no fields were ever set regardless of input.

Fix by adding correct inline fragments for all four union types:
- IssueFieldTextValue (value)
- IssueFieldSingleSelectValue (name)
- IssueFieldDateValue (value)
- IssueFieldNumberValue (value)

* Update test mock to match corrected inline fragments

* Update handler_test.go formatting
2026-04-22 15:42:25 +01:00
RossTarrant d0320b870d Allow browser-based MCP clients via CORS and cross-origin bypass 2026-04-21 16:31:01 +02:00
copilot-swe-agent[bot] 7fd6a92cef chore: revert unintended handler test formatting
Agent-Logs-Url: https://github.com/github/github-mcp-server/sessions/49811f97-33b0-476c-8811-419dee2a5318

Co-authored-by: omgitsads <4619+omgitsads@users.noreply.github.com>
2026-04-21 14:55:00 +02:00
copilot-swe-agent[bot] ebeefe0aa0 chore: run go mod tidy
Agent-Logs-Url: https://github.com/github/github-mcp-server/sessions/49811f97-33b0-476c-8811-419dee2a5318

Co-authored-by: omgitsads <4619+omgitsads@users.noreply.github.com>
2026-04-21 14:55:00 +02:00
Adam Holt 28171abb0c Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-21 14:55:00 +02:00
Iryna Kulakova 91d646597e Remove NormalizeContentType middleware workaround
The go-sdk bump (27f29c1) includes the proper fix upstream, making
the middleware unnecessary.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 14:55:00 +02:00
Iryna Kulakova 88de5b75a0 Fix Content-Type rejection for application/json; charset=utf-8
Add NormalizeContentType middleware that strips optional parameters
(e.g. charset=utf-8) from application/json Content-Type headers before
the request reaches the Go SDK's StreamableHTTP handler, which performs
strict string matching.

Per RFC 8259, the charset parameter is redundant for JSON but must be
accepted per HTTP semantics.

Fixes #2333

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-21 14:55:00 +02:00
Sam Morrow a24c0be254 refactor: migrate MCP Apps from insiders mode to feature flag
Rebase PR #2282 onto main (post-#2332) and unify feature flag
allowlists into a single source of truth.

- Add MCPAppsFeatureFlag, AllowedFeatureFlags, InsidersFeatureFlags,
  and ResolveFeatureFlags in feature_flags.go
- AllowedFeatureFlags includes all user-controllable flags (MCP Apps +
  granular), InsidersFeatureFlags only includes MCPAppsFeatureFlag
- HeaderAllowedFeatureFlags() now delegates to AllowedFeatureFlags
- Builder uses feature checker instead of insidersMode bool
- Remove InsidersOnly field from ServerTool and WithInsidersMode from
  Builder
- HTTP feature checker uses ResolveFeatureFlags for per-request
  resolution with insiders expansion
- Tool handlers check MCPAppsFeatureFlag via IsFeatureEnabled instead
  of InsidersMode

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-16 11:41:19 +02:00
Matt Holloway efcaead5b5 feat(http): update knownFeatureFlags to use HeaderAllowedFeatureFlags() and add tests for feature flag validation 2026-04-15 17:36:32 +02:00
Matt Holloway 3cf4124dcf feat(http): implement HeaderAllowedFeatureFlags for X-MCP-Features header validation 2026-04-15 17:36:32 +02:00
Sam Morrow 372c874f30 feat(http): enforce static CLI flags as upper bound for per-request filtering
The HTTP server now respects the same static CLI flags as the stdio
server: --toolsets, --tools, --exclude-tools, --read-only,
--dynamic-toolsets, and --insiders.

A static inventory is built once at startup from these flags, producing
a pre-filtered tool/resource/prompt universe. Per-request headers
(X-MCP-Toolsets, X-MCP-Tools, etc.) can only narrow within these
bounds, never expand beyond them. When no static flags are set, the
existing behavior is preserved — headers have full access to all
toolsets.

Fixes #2156

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-13 14:50:24 +02:00
Matt Holloway dd239d8443 Initial OSS logging adapter for http (#2008)
* initial logging stack for http

* add metrics adapter

* fix linter issues

* make log fields generic

* Update pkg/github/server_test.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Remove unused SlogMetrics adapter

The slog-based metrics adapter was never used — OSS always uses
NoopMetrics and the remote server has its own DataDog-backed adapter.

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

* Update pkg/github/dependencies.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fmt

* change to use slog

* address feedback

* rename noop adapter to noop sink

* Update pkg/http/server.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* [WIP] [WIP] Address feedback on OSS logging adapter for http implementation (#2264)

* Initial plan

* Fix BaseDeps.Logger and BaseDeps.Metrics to return safe defaults when Obsv is nil

Agent-Logs-Url: https://github.com/github/github-mcp-server/sessions/53221b0b-abb4-4138-a147-3ce9e13b379a

Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com>

* Fix nil metrics in server.go by passing metrics.NewNoopMetrics() to NewExporters

Agent-Logs-Url: https://github.com/github/github-mcp-server/sessions/53221b0b-abb4-4138-a147-3ce9e13b379a

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>
Co-authored-by: Matt Holloway <mattdholloway@github.com>

* replace nil with stubs

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2026-03-31 13:10:22 +01:00
copilot-swe-agent[bot] 74507a00ab Use translation strings for server name/title override
Instead of new CLI flags (--server-name, --server-title), reuse the
existing string override mechanism that already supports tool title/
description overrides throughout the codebase.

Users can now configure the server name and title via:
  - GITHUB_MCP_SERVER_NAME / GITHUB_MCP_SERVER_TITLE env vars
  - "SERVER_NAME" / "SERVER_TITLE" keys in github-mcp-server-config.json

This is consistent with how all other user-visible strings are
overridden (e.g. GITHUB_MCP_TOOL_GET_ME_USER_TITLE). No new struct
fields or CLI flags are needed.

Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
2026-03-16 15:03:13 +01:00
copilot-swe-agent[bot] 0fda6f1509 Add configurable server name and title via env/flag
Allows users running multiple GitHub MCP Server instances (e.g., for
github.com and GitHub Enterprise Server) to override the server name and
title in the MCP initialization response.

- Add --server-name / GITHUB_SERVER_NAME flag+env to override name
- Add --server-title / GITHUB_SERVER_TITLE flag+env to override title
- Defaults remain "github-mcp-server" and "GitHub MCP Server"
- Applies to both stdio and HTTP server modes
- Add tests for default and custom name/title

Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
2026-03-16 15:03:13 +01:00
Atharva Patil 48a2a05651 Use configured --gh-host as oauth authorization server (#2046)
When configured with a `--gh-host` argument, construct the OAuth Authorization Server URL from this host, rather than defaulting to `https://github.com/login/oauth`

Co-authored-by: Adam Holt < 4619+omgitsads@users.noreply.github.com>
Co-authored-by: atharva1051 <53966412+atharva1051@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-02-24 10:52:19 +01:00
tommaso-moro c38802ac80 rename to --exclude-tools 2026-02-18 17:13:41 +01:00
tommaso-moro 9c8f96f6bf add header support in http entry point 2026-02-18 17:13:41 +01:00
Adam Holt 08231a2aeb Add support for custom middleware in the correct order. (#2026)
* Add support for custom middleware in the correct order.

* Switch this up to be more clear on what it's doing
2026-02-18 14:43:59 +01:00
Adam Holt efe9d40b58 Token scopes context (#1997)
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (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
* Move scope storage into its own context key, separately from token info.

This allows us to provide scopes seperately in the remote server, where
we have scopes before we do the auth.

* Skip token extraction if token info already exists in context.

This is to avoid redundant token extraction in remote setup where token info may have already been extracted earlier in the request lifecycle.

* Check for existing scopes in context before fetching from GitHub API in scope challenge middleware

* Return error type for unknown tools in inventory builder and handle it in HTTP handler
2026-02-16 14:10:28 +01:00
Oleksandr Redko 505d5dc33a refactor: modernize code with modernize and intrange 2026-02-12 12:58:49 +01:00