Compare commits

..
Author SHA1 Message Date
Aircg
4608cda997
mcp: retire only the subscriptions a listen registered (#1275)
Some checks failed
CodeQL Advanced / Analyze (go) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
Conformance Tests / server-conformance (push) Has been cancelled
Conformance Tests / client-conformance (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Test / lint (push) Has been cancelled
Test / test (1.25) (push) Has been cancelled
Test / test (1.26) (push) Has been cancelled
Test / race-test (push) Has been cancelled
*Narrowed after review — see the discussion below. The earlier revision
changed the
registry keying; this one changes only the teardown.*

### The problem

`subscriptionsListen` registers a session according to what the listen
asked for:

```go
if allowed.ToolsListChanged {
    s.toolChangeSubscriptions[req.Session] = requestID
}
if allowed.PromptsListChanged {
    s.promptChangeSubscriptions[req.Session] = requestID
}
if allowed.ResourcesListChanged {
    s.resourceChangeSubscriptions[req.Session] = requestID
}
```

Its deferred cleanup does not:

```go
delete(s.toolChangeSubscriptions, req.Session)
delete(s.promptChangeSubscriptions, req.Session)
delete(s.resourceChangeSubscriptions, req.Session)
```

It runs whether or not this listen ever wrote those registries. A
session can
hold several listens at once, so the first one to unwind takes the
others'
registrations with it, and those streams stay open while receiving
nothing.

The scope is the persistent-session transports, stdio and in-memory.
Over
streamable HTTP the registries cannot collide: a stateful handler
refuses
SEP-2575 requests outright with `CodeUnsupportedProtocolVersion`, and a
stateless one builds a temporary session per POST and closes it when the
request ends.

Reachable through this SDK's own client, on stdio:

1. `Client.Connect` opens the auto-listen for list-changed
notifications.
2. `ClientSession.Subscribe(uri)` opens a second listen, carrying only a
   resource subscription.
3. `ClientSession.Unsubscribe(uri)` retires that second listen.
4. Its cleanup deletes `toolChangeSubscriptions[session]`, which belongs
to the
   auto-listen.
5. The auto-listen is still open and never receives `tools/list_changed`
again.

### How I pinned it down

A diagnostic run over the three states, reading the registries and
`ServerSession.listenIDs` directly:

```
after connect      toolSub=2       promptSub=2       resSub[r1]=false  listenIDs=[{2}]
after Subscribe    toolSub=2       promptSub=2       resSub[r1]=true   listenIDs=[{2} {3}]
after Unsubscribe  toolSub=<nil>   promptSub=<nil>   resSub[r1]=false  listenIDs=[{2}]
```

Line 2 rules out the second listen having overwritten the entry:
`toolSub` is
still 2, listen 3 never wrote that registry. Line 3 rules out the client
cancelling the wrong stream: `listenIDs` still holds listen 2, so the
auto-listen is alive on the server, parked on `<-ctx.Done()`. What
changed is
only that its registration is gone, deleted by listen 3's teardown.

### The change

Guard each delete with the condition that guarded the registration, so a
listen
retires what it registered and nothing else.

### Validation

Two tests, next to the existing `TestSubscriptionsListen_*` ones and
reusing
their fixtures. Against `826e653` without the change:

```
--- FAIL: TestSubscriptionsListen_TeardownKeepsOtherListens (5.01s)
    mcp_test.go:3442: timed out waiting for "tool"
--- PASS: TestSubscriptionsListen_TeardownRetiresOwnRegistration (0.01s)
```

The second passes before and after on purpose: it asserts that the only
listen
on a session does still get retired, so that the guards cannot be
tightened
into a registry leak. The eight pre-existing `TestSubscriptionsListen_*`
tests
pass in both runs, and all ten pass with the change.

Run on the same commit, before and after: `go test ./...` and
`go test -race ./...` ok with no races, `gofmt -l .`, `go vet ./...` and
`staticcheck v0.6.1` clean. Also `go test ./...` on Go 1.25.0 and 1.26.8
to
match the CI matrix, and `go generate ./internal/readme ./internal/docs`
produces no diff. Environment: Intel Core i5-1038NG7, macOS 14.

### Known limitations

Two cases are outside this change, both needing two listens on one
session that
overlap:

- Two listens opting in to the same notification type.
`allowed.ToolsListChanged`
is true for both, so the registration overwrites and this teardown still
  retires the survivor's entry.
- Two listens subscribed to the same URI.
`resourceSubscriptions[uri][session]`
  overwrites the same way, and the deferred `unsubscribe` deletes it.

Neither is reachable from this SDK's client — `Subscribe` dedups per URI
and
there is no exported way to open a second list-changed listen — so both
require
a non-Go client on stdio. I have tests for them and am happy to open a
separate
issue if you want them tracked.

---

AI assistance was used for reading through the code, the diagnostic and
regression tests, and running the verification above. The root cause
analysis
and the design decisions are mine, and I can explain every line without
it.

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-18 08:42:05 +00:00
sravan kilaru
3b917b466c
oauthex: build defaultDiscoveryTransport lazily instead of at import (#1278)
Fixes #1276

## What's happening

`oauthex` builds `defaultDiscoveryTransport` as a package-level var, so
it
runs at import time. That path calls `proxyConfigured`, which invokes
`http.DefaultTransport`'s proxy func (`http.ProxyFromEnvironment`).
net/http
reads the proxy env vars only once and caches the result, so this
import-time
call freezes the proxy config before `main()` can set `HTTPS_PROXY`.

New in v1.8.0, which is why it's a regression — just importing `.../mcp`
(which pulls in `oauthex`) triggers it, even with no use of the SDK.

## Fix

Wrap the setup in `sync.OnceValue` so it runs on first use instead of at
import. Still built once and reused.

## Testing (removed)

- Ran the issue's repro against this branch via a local `replace`:
prints
  `proxy=http://127.0.0.1:9` now, `proxy=<nil>` before.
- Added a regression test in `oauthex`.
- `go test ./...` passes.

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-18 09:41:05 +02:00
Guglielmo Colombo
b58aa8d8d8
mcp: ignore notification in stdio batches (#1277)
Fixes #1256
2026-09-18 09:26:01 +02:00
José M. Requena Plens
826e653c27
mcp: refuse a call on a legacy session before initialize (#1273)
Closes #1271.

`Server.handle` refuses a call made before `initialize` on a legacy
session, and that refusal lives in the `default` branch of its method
switch, so any method with a `case` of its own skips it. Three are
served on a session that never handshook: `logging/setLevel`,
`resources/subscribe` and `resources/unsubscribe`.

`resources/subscribe` is the one that reaches state rather than merely
answering: it starts a watcher and registers a subscriber, so a client
that subscribes before `initialize` is delivered
`notifications/resources/updated` for the lifetime of a session the
server never agreed to.

The check now runs after the switch, for every call carrying no
new-protocol `_meta`, with `initialize` and `ping` exempt. That is the
same condition the `default` branch already applied and the same error
it produced; the change is that the three methods above stop skipping
it.

`ping` stays allowed because the lifecycle page exempts it.
`server/discover` is deliberately left alone: a legacy request there
already receives method-not-found, which tells the client more than an
initialization error would. The two notification cases stay outside,
since the gate covers calls and a notification carries no refusal.

The SEP-2575 path is untouched. A session without an `initialize`
handshake is exactly what that proposal is for, so the exemption is the
request's own `_meta.protocolVersion` rather than anything about the
session, and a new-protocol client is served as it is today.

Tests: `TestServerHandle_LegacyCallBeforeInitialize` drives each
affected method on a fresh session and asserts the refusal, that
`resources/subscribe` runs no handler and leaves no subscriber, and that
`initialize` and `ping` are served.
`TestServerHandle_NewProtocolCallWithoutInitialize` asserts a
new-protocol session with no handshake still gets `tools/list`, and that
the three methods keep their existing method-not-found rather than the
initialization refusal. Without the change, the setLevel, subscribe and
unsubscribe rows fail.

`go test ./mcp/ -count=1` is green, `gofmt -l` is empty and `go vet
./mcp/` is clean. This does not depend on #1266 and rebases onto it
cleanly, verified with the suite green on the combination.

One decision worth your view: the refusal is a plain error, which on the
wire carries code 0, exactly as the `default` branch produced before
this change. The specification prescribes no code for it. If you would
rather it were `-32600` or `-32603`, say so and I will change it here.

---------

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-17 10:36:23 +02:00
Luca Candela
3785c500b5
mcp: preserve structured content number precision (#1239)
CallToolResult currently decodes structuredContent through an any field,
which converts JSON numbers to float64 and silently rounds integers
outside the IEEE-754 safe range.

This change decodes structuredContent separately with UseNumber,
preserving wire numbers as json.Number while retaining the existing
decoded object, array, and primitive shapes. The field documentation now
describes that representation. Users who depend on the previous float64
behavior can temporarily restore it with
MCPGODEBUG=structuredcontentfloat64=1.

An end-to-end in-memory client/server regression covers 9007199254740993
from a low-level Server.AddTool result. Additional coverage verifies the
compatibility option restores the legacy rounded float64 value.

Verification:
- gofmt -l .
- go test ./...
- go vet ./...

Fixes #1236

---------

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-16 09:22:16 +02:00
rishabh
fbd36cb787
docs/server: fix logging defaults and the SetLoggingLevel method name (#1251)
The Logging section of docs/server.md says "For stateful sessions" twice
in a row with opposite meanings, so one of the two sentences has to be
wrong. The default level of "info" is set in only one place in the SDK,
`StreamableHTTPHandler.ephemeralConnectOpts` in mcp/streamable.go, which
handles stateless requests; a session created through `Server.Connect`
starts with an empty `ServerSessionState.LogLevel`, and
`ServerSession.Log` returns early while the level is empty, which is
exactly what the first sentence already says. The second sentence now
reads "For stateless sessions". The same section also tells readers to
call `SetLevel`, but there is no such exported method: the client-side
call is `ClientSession.SetLoggingLevel`, and `setLevel` is only the
unexported server handler, so the pkg.go.dev link on the page points at
an anchor that does not exist. For context, the stateless default
arrived in #411 and the paragraph describing it was written later in
#497, which is where the duplicated "stateful" came from.

Checked with `go doc ./mcp ClientSession` (only `SetLoggingLevel` is
exported) and `grep -rn 'LogLevel = "info"' mcp/` (a single hit,
mcp/streamable.go:516); docs/server.md was regenerated with `go generate
./internal/docs`, and `go build ./... && go test ./...` pass.

---------

Signed-off-by: Rishabh <rishabhsaiv@gmail.com>
Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-15 11:44:03 +00:00
jackyyy
c1918e19ad
mcp: preserve SSE events buffered after the endpoint (#1270)
SSEClientTransport.Connect creates one event scanner to read the
endpoint event
and a second scanner to read subsequent messages. Each scanner owns a
buffered
reader, so bytes read ahead by the first scanner are discarded when it
is
abandoned. A message coalesced with the endpoint can therefore disappear
while
the connection remains open.

Reuse the event scanner across the endpoint handshake and the
message-reading
goroutine. This preserves unread bytes without changing the public API.

The regression test exercises Connect and Read with a controlled HTTP
response
body, covering both a message coalesced with the endpoint and a message
arriving
after Connect returns. On unmodified main, the separate-message control
passes
and the coalesced case times out; both pass with the fix.

Validation on linux/amd64 with Go 1.26.8:
- go test ./...
- go test -race ./...
- go vet ./...
- staticcheck v0.6.1 ./...
- gofmt and git diff --check

The HTTP response boundary is simulated; no third-party server or proxy
was
used. This change concerns the legacy SSE transport. The Streamable HTTP
conformance suite was not run.
2026-09-15 11:17:47 +02:00
rishabh
0c0eb23625
docs/client: fix the SEP-1034 and SEP-1330 links (#1252)
The "Schema defaults and enums" section of docs/client.md links SEP-1034
to https://modelcontextprotocol.io/seps/1034 and SEP-1330 to
https://modelcontextprotocol.io/seps/1330, and both of those bare-number
URLs are dead. modelcontextprotocol.io serves SEPs at number-plus-slug
URLs, which is what every other SEP link in this repo already uses
(seps/2575-stateless-mcp,
seps/2577-deprecate-roots-sampling-and-logging,
seps/2596-feature-lifecycle-and-deprecation-policy,
seps/2207-oidc-refresh-token-guidance); these two were the only
bare-number ones left. I took the replacement slugs from the site's own
sitemap.xml, so the `1034--` double hyphen is what is published rather
than a typo here.

curl -sIL on the two old URLs returns 404 and 404, and on the two new
ones 200 and 200. The edit is in internal/docs/client.src.md with
docs/client.md regenerated by `go generate ./internal/docs`, and `go
build ./... && go test ./...` pass.

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-14 17:20:25 +00:00
José M. Requena Plens
3f43bcf012
mcp: add HasParams so middleware can tell whether params are present (#1269)
This adds `mcp.HasParams(req Request) bool`, so a receiving middleware
can ask whether the message it was handed carried a `params` member.

The member is optional for several methods: `serverMethodInfos` sets
`missingParamsOK` for `server/discover`, `ping`, the four list methods
and the `cancelled`, `initialized` and `roots/list_changed`
notifications, so `{"jsonrpc":"2.0","id":1,"method":"tools/list"}` is a
complete request. For one of those, `unmarshalParams` returns a nil
`Params`, `newRequest` leaves `ServerRequest.Params` at the zero value
of `P`, and `GetParams` hands the middleware a non-nil interface holding
a typed nil pointer. Comparing that against nil says params are present,
and the first method called on it dereferences: `GetMeta` is promoted
from the embedded `Meta`. `Params` already declares `isNil` for
precisely this case, and it is unexported, so from outside the package
the accessor named after the question is the one thing that cannot be
reached.

`HasParams` is a function over `Request` rather than an exported method
on `Params` for two reasons. A middleware holds a `Request`, and this
way the caller never has to hold the value that panics. More
importantly, `isNil` cannot answer the question for every `Params`
value, so exporting it would ship a predicate that is itself unsafe: a
params type declared the documented way for a custom method, `struct{
mcp.ParamsBase }`, promotes `isNil` through a field selector, and that
dereferences the nil outer pointer before the method body runs. The
predicate therefore has to be computed from outside the value, and
`reflect.Value.IsNil` is what works for all of them.

Three judgement calls, stated so nobody has to find them.

First, `getRequestMeta` now goes through `HasParams`. That is a second
fix rather than a refactor: `AddReceivingCustomMethod` registers with
`missingParamsOK` too, so a client omitting `params` for a custom method
made `ServerRequest.ProtocolVersion`, `ClientInfo` and
`ClientCapabilities` panic in the promoted `isNil`. They now return
`""`, `nil` and `nil`. I could have left it out to keep the diff to one
function, but then the SDK would export a predicate that says to ask
before touching params while three of its own accessors still panicked
on the same value. If you would rather see it as a separate commit, say
so and I will split it.

Second, I did not add the predicate to the `Request` interface.
`Request` is sealed by `isRequest`, so a method would have been a
compatible change and would read slightly better at the call site, but a
free function leaves the interface alone and serves every `Request` just
as well.

Third, this reflects once per call of `getRequestMeta`. It is one
`reflect.ValueOf` on a value that is already an interface, on a path
where `newResult` already reflects per request.

Tests, both in `mcp/shared_test.go`.
`TestServerRequest_PerRequestAccessors_MissingParams` is the one that
fails before the change and passes after: on `main` it panics inside
`getRequestMeta` with `invalid memory address or nil pointer
dereference` from `(*customParams).isNil`, and here it asserts the three
accessors return zero values. `TestHasParams` covers the exported
behaviour: it builds exactly what `handleReceive` hands the middleware
chain for `tools/list`, `prompts/list`, `resources/list` and
`notifications/initialized` with no `params` member, asserts that
`GetParams` still returns a non-nil interface so the case under test is
still the typed-nil one, asserts `HasParams` reports false, and asserts
it reports true when params are present.

Behaviour an existing user would see change: only the three accessors
above, and only for a custom-method params type built on `ParamsBase`
whose `params` member was omitted, where a panic becomes a zero value.
Nothing about the wire format changes, no exported signature moves, and
no interface gains a method. A middleware that still writes
`req.GetParams() != nil` and then dereferences still panics; this gives
a caller the way to ask, it does not rescue a caller that does not.

Also here: one sentence on the doc comments of both
`AddReceivingMiddleware` methods saying a received message need not
carry params, since that is where a middleware author is reading, and
`examples/server/middleware/main.go` now logs `has_params` with
`mcp.HasParams` instead of the comparison that cannot work. The
example's printed output is unchanged, because the SDK client always
sends a `params` member for the methods it drives.

`go test ./...` and `gofmt -l .` are clean.

One thing to say plainly, since it is yours to decide and not mine: this
adds an exported symbol. `HasParams` is a new name in the public API of
`mcp`, and everything else here is doc comments, an example and the
three accessors. If you would rather not grow the surface for this, the
smaller answer is to keep the `getRequestMeta` fix and the doc-comment
sentence and drop the exported function, and a middleware author then
has the guarantee written down where they are reading even though they
still cannot ask the question in code. I prefer the function because the
guarantee alone leaves every middleware writing the same three lines of
`reflect`, and the one that does not write them panics on an ordinary
`tools/list`, but I will cut it to whichever shape you want.


Fixes #1261.

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-14 18:45:48 +02:00
José M. Requena Plens
a82c86a6cd
mcp: classify the per-request protocol version by membership (#1268)
`validateRequestMeta` decided whether a request follows the 2026-07-28
protocol by comparing `_meta.protocolVersion` against `2026-07-28` with
`<`. This replaces that comparison with a membership test against
`supportedProtocolVersions`, which is the rule `mcp/streamable.go:354`
already applies to the `Mcp-Protocol-Version` header.

Nothing changes for a version the SDK supports: one predating 2026-07-28
still selects the legacy handshake, and one at 2026-07-28 or later is
still validated as the full `_meta` triple. What changes is a version
the SDK does not support at all. It now reports `usesNewProtocol`, so it
reaches the `CodeUnsupportedProtocolVersion` check in
`(*ServerSession).handle` and is answered with the error the versioning
page requires.

Driving a stdio server with `_meta.protocolVersion` set to `1900-01-01`,
the worked example on that page, before:

```json
{"jsonrpc":"2.0","id":1,"error":{"code":0,"message":"method \"tools/list\" is invalid during session initialization"}}
```

and after:

```json
{"jsonrpc":"2.0","id":1,"error":{"code":-32022,"message":"unsupported protocol version","data":{"supported":["2026-07-28","2025-11-25","2025-06-18","2025-03-26","2024-11-05"],"requested":"1900-01-01"}}}
```

There are two judgement calls in the diff, and I would rather state them
than leave them to be found.

The first is that an unsupported version returns early with
`initializeParams` carrying only that version, and the rest of the
`_meta` triple is left unvalidated. The shape of `_meta` is defined by
the revision the request names, and that is a revision this SDK does not
know, so there is nothing to validate it against; answering "missing
`io.modelcontextprotocol/clientCapabilities`" would send the client off
to fix the wrong thing while its real problem is the version. The partly
populated value cannot escape: `handle` runs the unsupported check
before anything reads the capabilities, and `Server.protocolVersions` is
always a subset of `supportedProtocolVersions`, so a version that fails
the membership test in `validateRequestMeta` always fails it in `handle`
too.

The second is that an empty `protocolVersion` still means "no version
declared" rather than "unsupported version". `mcp/streamable.go` already
reads the field that way when it decides whether a request carries a
version at all (`metaVersion != ""`), and rejecting the empty string
here would put the two layers back into disagreement, in the other
direction. The `!ok` and `== ""` cases are therefore handled together.

**Behaviour an existing user would see change.** A request whose
`_meta.protocolVersion` is a string not in `SupportedProtocolVersions()`
is now refused with `-32022`, where before a value sorting below
`2026-07-28` was treated as a legacy request: served if the session was
already initialized, refused with the session-initialization error if it
was not. No published protocol revision is in that set, so a client
speaking any released version of MCP sees no difference. A deployment
that put a sentinel or placeholder version in `_meta` and relied on it
being ignored would see those requests refused, which I believe is the
right answer but is a visible change.

This is not limited to stdio, and I want to be precise about where the
header gate does and does not stand in front of it. The gate at
`mcp/streamable.go:354` refuses an unsupported `Mcp-Protocol-Version`
only when that version **also** sorts below `2026-07-28`, so an unknown
version sorting above it (`2027-01-01`, say) passes the gate and reaches
this classification; the deprecated HTTP+SSE transport in `mcp/sse.go`
has no header check at all; and stdio, the in-memory transport and any
custom transport have none either. The window the old comparison left
open is therefore reachable on every transport, and a value sorting
above `2026-07-28` with an incomplete `_meta` triple also changes
answer, from `-32602` to `-32022`.

**The test that pins it** is
`TestServerUnknownProtocolVersion_NewProtocol` in `mcp/server_test.go`.
It drives `(*ServerSession).handle` with a `tools/list` request whose
`_meta.protocolVersion` names a version the SDK does not know, and
asserts that the returned error is a `*jsonrpc.Error` with code
`CodeUnsupportedProtocolVersion`, `Data.Requested` equal to the version
asked for, and `Data.Supported` equal to `SupportedProtocolVersions()`.
Four cases: `1900-01-01`, `2025-11-24`, `1.0`, and `1900-01-01` again
with the rest of the triple omitted, which is the case that pins the
first judgement call above. Before the change all four fail with `handle
returned method "tools/list" is invalid during session initialization,
want a *jsonrpc.Error`. `TestValidateRequestMeta` in
`mcp/shared_test.go` gains four cases at the unit level: two that fail
before the change, and two that guard the classifications which must not
move, a supported pre-2026-07-28 version and an empty one.

`go test ./...` and `gofmt -l .` are both clean.


Fixes #1260.
2026-09-14 15:49:53 +00:00
José M. Requena Plens
3210c2b34c
mcp: carry a cancelled notification's reason as the request's cancel cause (#1255)
The canceller read `CancelledParams.Reason` off the wire and dropped it:
`Connection.Cancel` cancelled the request's context with a nil cause, so
a handler that looked at `context.Cause` saw a bare `context.Canceled`
and nothing of what the peer said. The specification asks
implementations to log cancellation reasons, and the handler is the one
place that can act on one; #1100 had already given the request context a
cause, so the reason has a natural place to travel.

`Connection.CancelCause` is `Cancel` with a cause. The canceller now
cancels with an error that carries the peer's reason and unwraps to
`context.Canceled`, so `errors.Is` keeps classifying the cancellation as
one while `context.Cause` reads `request cancelled by the peer:
<reason>`. It also logs the id and the reason at debug level through the
logger the connection already carries, which is the SDK's own answer to
the SHOULD. No exported API changes: the cause type stays unexported and
is read through `context.Cause` and `Error()`.

`TestCancellation` now asserts the cause the SDK's own client produces
(its context's error, which the client sends as the reason), and
`TestCancellationReason` writes the call and the cancelled notification
to the connection directly with a reason the client would never send, on
a session pinned to 2025-11-25 so the hand-written request needs no
`_meta`. Both fail without the change.

Fixes #1254

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-14 11:33:30 +00:00
Ronald
e40f35d137
mcp: label resultType on results that bypass the dispatcher (#1226)
Fixes #1225. See the [issue
comment](https://github.com/modelcontextprotocol/go-sdk/issues/1225#issuecomment-5500818546).

## The bug

`2026-07-28` requires `resultType` on every result. `CallToolResult`,
`GetPromptResult`, and `ReadResourceResult` keep `resultType` in an
unexported field. Each of these results can be `complete` or
`input_required`, so it cannot embed `completeResultWithType` the way
the seven types in #1060 do.

Only `handleMultiRoundTripResult` sets that field. It runs inside
`Server.callTool`, `getPrompt`, and `readResource`. Some middleware
returns a result directly instead of calling `next` — for example, an
auth, rate-limit, or entitlement gate. That middleware never reaches
`handleMultiRoundTripResult`. The response then ships without the field,
and strict clients reject it.

## The fix

The choice between `complete` and `input_required` now happens inside
`setMultiRoundTripResultType`. Both call sites that need this choice
call that function. `setCompleteResultType` now labels results it did
not label before, so it is renamed `annotateResultType`. The new
function picks a branch with a type switch.

Reading matters here. `setResultType` is unexported, so setting
`InputRequests` is the only way for middleware to ask for input. If the
code always labels results `complete`, the response ships as
`{"resultType":"complete","inputRequests":{...}}`. A client that trusts
the discriminator then drops the elicitation request.

Recomputing the label every time is safe. A result that did reach the
dispatcher already carries the same label from that test. This also
covers one edge case: a handler that returns `InputRequests` again after
the shim's single reinvocation. Before this fix, that case shipped with
no `resultType`.

Embedding `completeResultWithType` instead is not an option: it labels
everything `complete` and overwrites `input_required`, breaking
multi-round-trip flows.

**Skip nil results.** Middleware can return a typed nil value, which
satisfies the `Result` interface. `handle` now checks for that case
before it calls the labeling function. Before this change, that same
case also panicked, one frame later in `annotateServerInfo` and then
`GetMeta`.

## Checks

- `TestServerSessionHandle_SetsResultTypeWhenMiddlewareShortCircuits`
fails on all three methods before the first change. Its fourth case
fails if labeling overwrites a populated `InputRequests`.
- `TestServerSessionHandle_ResultTypeGate` records which requests carry
`resultType`, including one known gap. See "Separate from this PR"
below.
- `TestServerSessionHandle_NilResultFromMiddleware` panics without the
nil check.
- The complete-path cases added to
`TestServerSessionHandle_SetsResultTypeOnNewProtocol` also pass before
this change. They close a gap in #1060, which added only three cases,
and only for `input_required`.
- The exported API is unchanged. `go doc -all ./mcp`, with comments
stripped, is identical before and after this change.
- With the reproduction from the issue, over the streamable transport:
the dispatched `tools/call` and the short-circuited `tools/call` both
now return `resultType: "complete"`.

I could not run `./scripts/conformance.sh`. CONTRIBUTING.md names that
script, but the repo only has `client-conformance.sh` and
`server-conformance.sh`. Both scripts need GNU `timeout`, and macOS does
not include it.

## Also here

Ten comments named an exported `ResultType` field and
`ResultTypeComplete` / `ResultTypeInputRequired` constants. None of
those exist. Two of the comments were `[CallToolResult.ResultType]` doc
links, and those links cannot resolve. They now point at
`[CallToolResult.NeedsInput]` instead, because an unexported field is
also not a valid link target.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-14 09:52:10 +02:00
Max Freedom Pollard
5bc078a795
auth: strip a terminating slash from the issuer before building metadata URLs (#1245)
An issuer identifier written with a terminating slash, like
`https://auth.example.com/`, makes authorization server metadata
discovery miss every endpoint. `GetAuthServerMetadata` probes three URLs
that no conformant server serves, finds nothing, and the caller falls
back to the guessed 2025-03-26 endpoints, which are themselves malformed
because they are built by appending to the same slash-terminated issuer.

`authorizationServerMetadataURLs` in `auth/shared.go` decides whether
the issuer has a path component with `baseURL.Path == ""` (line 47), and
a trailing-slash issuer has `Path == "/"`, so it takes the
path-insertion branch. That branch inserts
`strings.TrimLeft(originalPath, "/")` (lines 59 and 62), which strips
the leading slash but keeps the trailing one. For
`https://auth.example.com/` the probes come out as
`/.well-known/oauth-authorization-server/`,
`/.well-known/openid-configuration/` and
`//.well-known/openid-configuration`. For
`https://auth.example.com/tenant1/` the two path-insertion probes come
out with a trailing slash. Only the path-appending form was already
right, because it uses `strings.Trim`.

RFC 8414 section 3.1 settles it: "If the issuer identifier value
contains a path component, any terminating "/" MUST be removed before
inserting "/.well-known/" and the well-known URI suffix between the host
component and the path component." So `https://auth.example.com/` has no
path component and gets the two root forms, which is the list the MCP
authorization spec gives for issuers without a path component.

The change trims slashes off both ends of the issuer path once, uses
that to pick between the root and path-insertion branches, and reuses it
for all three URLs. An issuer without a terminating slash produces
exactly the same list as before.

`TestAuthorizationServerMetadataURLs` pins the URL list for all four
issuer shapes. `TestGetAuthServerMetadataTrailingSlashIssuer` drives the
whole lookup against the existing fake authorization server, serving
metadata at the root well-known path. On unmodified main the first fails
on both trailing-slash cases and the second fails with
`GetAuthServerMetadata() error = Get
"/.well-known/openid-configuration": redirect into loopback address
"127.0.0.1"`, because `//.well-known/openid-configuration` makes
`net/http.ServeMux` issue a canonicalizing redirect that the discovery
client's `CheckRedirect` refuses. Both pass with the change.

Verification on go1.26.4: `gofmt -l .` clean, `go vet ./...` clean,
`staticcheck ./...` clean at v0.6.1 (the version the Test workflow
pins), `go test ./auth/...` ok, `go test ./...` ok.

I did not file an issue first. The fix is self-contained, confined to
one unexported helper, and every issuer shape other than the
slash-terminated ones is byte-identical to before.

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-07 09:31:45 +00:00
akashjainn
f99cdb649b
mcp: base64-encode a non-header-safe Mcp-Name (#1246)
Fixes: https://github.com/modelcontextprotocol/go-sdk/issues/1243

The 2026-07-28 transport spec only SHOULD-constrains names to
header-safe characters, and lets a client Base64-wrap one that is not,
using the `=?base64?...?=` sentinel. `setStandardHeaders` set `Mcp-Name`
raw, while the sibling `Mcp-Param-*` path already ran its values through
`encodeHeaderValue`, so a name outside the safe set went out unencoded.

This routes `Mcp-Name` through `encodeHeaderValue` as well.

#1242 landed the other direction — the server decoding the sentinel
before comparing — so the two together close the round trip. Worth
noting they interact: after #1242, a name that is literally shaped like
the sentinel (`=?base64?abc?=`) is now rejected as invalid Base64,
because the server tries to decode a value the client never encoded. The
new test covers that case, and it fails on `main` today.

`extractName` also covers `resources/read`, so this applies to resource
URIs, which is where non-ASCII is most likely to show up in practice.

## Testing

`TestMcpNameHeaderEncodingRoundTrip` drives `setStandardHeaders` and
then feeds its output to `validateMcpHeaders`, so it pins the client and
server halves against each other rather than asserting the encoding in
isolation. Cases: an ASCII-safe name (must stay unencoded, no churn for
the common path), non-ASCII, a leading space, a control character, and
the sentinel-shaped literal above.

Verified failing before the change and passing after. `go test ./...`
passes.

A note on process:
[@remote-controlled-man](https://github.com/remote-controlled-man) asked
about this issue earlier today and proposed much the same scope. I had
the change written before I saw that comment and thought the #1242
interaction was worth surfacing either way, but I'm happy to close this
if they'd rather carry it — please just say so.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_011Wf643Lx36V5wdwuuf2EdT
2026-09-07 10:16:16 +02:00
ARPAN MONDAL
a5026ea7b7
mcp: decode a Base64-wrapped Mcp-Name before comparing it to the body (#1242)
The 2026-07-28 transport spec lets a client Base64-wrap a name or
resource URI that is not header-safe (`=?base64?...?=`) and requires the
server to decode it before comparing. `validateMcpHeaders` decoded
`Mcp-Param-*` values that way but compared `Mcp-Name` raw, so a
spec-legal encoded name was refused as a header mismatch.

This decodes `Mcp-Name` before comparing, mirroring the existing
`Mcp-Param-*` path, and rejects an invalid Base64 wrapper with a clear
error. Added table cases covering a valid encoded name and an invalid
one (red before, green after).

Fixes #1234

Co-authored-by: Arpan Mondal <arpanmondal@Arpans-MacBook-Pro.local>
Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-06 12:06:58 +02:00
Guglielmo Colombo
3f3b699b2b
refactor: remove legacy MCPGODEBUG compatibility for new protocol release (#1237) 2026-09-04 10:08:52 +02:00
Guglielmo Colombo
830f0b76e7
mcp: update conformance tests (#1231)
Fixes #1230
2026-09-03 11:27:42 +02:00
Yaroslav
12cbafe54d
oauthex: oauth discovery checks (#1220)
add extra checks for the default oauth discovery client:
* disallow redirects which downgrade to http
* disallow redirects to localhost or private ip
* disallow the initial call to a private ip
* proxy configuration opts out of dial time checking
2026-09-02 21:07:19 +02:00
Yaroslav
3632967404
mcp: add an sse event size cap (#1205)
update sse I/O code to limit the amount of memory a single
non-terminated event can take

---------

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-02 11:35:23 +02:00
Guglielmo Colombo
0d3036f785
mcp: allow per request Cacheable customization (#1203)
Fixes #1094
2026-09-01 18:57:51 +00:00
Yaroslav
cb0de64136
mcp: add a max request body size of the old transport (#1224)
added for consistency with the streamable transport because sse is not
officially deprecated in the SDK

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-01 17:28:35 +02:00
Guglielmo Colombo
2fdabde139
mcp: do not check metatada on notifications (#1215)
Fixes #1212
2026-09-01 13:30:20 +00:00
dependabot[bot]
59185e6969
build(deps): bump ossf/scorecard-action from 2.4.3 to 2.4.4 (#1217)
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action)
from 2.4.3 to 2.4.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ossf/scorecard-action/releases">ossf/scorecard-action's
releases</a>.</em></p>
<blockquote>
<h2>v2.4.4</h2>
<h2>What's Changed</h2>
<p>This update bumps the Scorecard version to the v5.5.0 release. For a
complete list of changes, please refer to the <a
href="https://github.com/ossf/scorecard/releases/tag/v5.4.0">Scorecard
v5.4.0 release notes</a> and the <a
href="https://github.com/ossf/scorecard/releases/tag/v5.5.0">Scorecard
v5.5.0 release notes</a>.</p>
<ul>
<li>log POST failures instead of failing entire action by <a
href="https://github.com/spencerschrock"><code>@​spencerschrock</code></a>
in <a
href="https://redirect.github.com/ossf/scorecard-action/pull/1625">ossf/scorecard-action#1625</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/ossf/scorecard-action/compare/v2.4.3...v2.4.4">https://github.com/ossf/scorecard-action/compare/v2.4.3...v2.4.4</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="2d1146689b"><code>2d11466</code></a>
Bump action tag for v2.4.4 release (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1688">#1688</a>)</li>
<li><a
href="1bd3285473"><code>1bd3285</code></a>
🌱 Bump the docker-images group across 1 directory with 2
updates (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1">#1</a>...</li>
<li><a
href="913edce4c1"><code>913edce</code></a>
🌱 Bump github.com/containerd/containerd from 1.7.32 to 1.7.33
(<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1671">#1671</a>)</li>
<li><a
href="0957b8f1c3"><code>0957b8f</code></a>
🌱 Bump golang.org/x/net from 0.56.0 to 0.57.0 (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1680">#1680</a>)</li>
<li><a
href="f0061eb3ff"><code>f0061eb</code></a>
🌱 Bump google.golang.org/grpc from 1.81.1 to 1.82.1 (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1687">#1687</a>)</li>
<li><a
href="20ee732402"><code>20ee732</code></a>
🌱 Bump github.com/sigstore/cosign/v2 from 2.6.3 to 2.6.4 (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1685">#1685</a>)</li>
<li><a
href="9f295ef01b"><code>9f295ef</code></a>
🌱 Bump the github-actions group with 6 updates (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1686">#1686</a>)</li>
<li><a
href="69bf556cea"><code>69bf556</code></a>
🌱 Bump github.com/sigstore/sigstore-go from 1.1.4 to 1.2.0 (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1681">#1681</a>)</li>
<li><a
href="94e8b96001"><code>94e8b96</code></a>
🌱 Bump github.com/sigstore/rekor from 1.5.0 to 1.5.2 (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1673">#1673</a>)</li>
<li><a
href="c7a1b37bbc"><code>c7a1b37</code></a>
🌱 Bump github.com/sigstore/fulcio from 1.8.5 to 1.8.6 (<a
href="https://redirect.github.com/ossf/scorecard-action/issues/1675">#1675</a>)</li>
<li>Additional commits viewable in <a
href="4eaacf0543...2d1146689b">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ossf/scorecard-action&package-manager=github_actions&previous-version=2.4.3&new-version=2.4.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-01 12:49:57 +00:00
dependabot[bot]
a6764cf838
build(deps): bump golang/govulncheck-action from 1.0.4 to 1.1.0 (#1218)
Bumps
[golang/govulncheck-action](https://github.com/golang/govulncheck-action)
from 1.0.4 to 1.1.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/golang/govulncheck-action/releases">golang/govulncheck-action's
releases</a>.</em></p>
<blockquote>
<h2>v1.1.0</h2>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/golang/govulncheck-action/compare/v1.0.4...v1.1.0">https://github.com/golang/govulncheck-action/compare/v1.0.4...v1.1.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="032d45514a"><code>032d455</code></a>
action.yml: use step-level env vars instead of direct vars in
commands</li>
<li><a
href="3fa7bd9cee"><code>3fa7bd9</code></a>
action.yml: unset GOOS/GOARCH when installing govulncheck</li>
<li><a
href="31f7c54634"><code>31f7c54</code></a>
action.yml: pin action dependencies to full commit SHAs</li>
<li><a
href="5accf6f1b7"><code>5accf6f</code></a>
action.yml: update dependencies</li>
<li><a
href="357ea4a541"><code>357ea4a</code></a>
action.yml: specify cache-dependency-path</li>
<li><a
href="1ea760651e"><code>1ea7606</code></a>
action.yml: update actions to fix caching issue</li>
<li><a
href="f03a9d4ce4"><code>f03a9d4</code></a>
README.md: fix typo</li>
<li>See full diff in <a
href="b625fbe08f...032d45514a">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang/govulncheck-action&package-manager=github_actions&previous-version=1.0.4&new-version=1.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-01 12:45:16 +00:00
Yaroslav
8227246472
fix: only subscribe when server advertises capability (#1221) 2026-09-01 14:43:23 +02:00
dependabot[bot]
f47d6ce156
build(deps): bump the codeql-action group with 3 updates (#1216)
Bumps the codeql-action group with 3 updates:
[github/codeql-action/init](https://github.com/github/codeql-action),
[github/codeql-action/analyze](https://github.com/github/codeql-action)
and
[github/codeql-action/upload-sarif](https://github.com/github/codeql-action).

Updates `github/codeql-action/init` from 4.37.3 to 4.37.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.9</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>v4.37.8</h2>
<p>No user facing changes.</p>
<h2>v4.37.7</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>v4.37.6</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>v4.37.5</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>v4.37.4</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.9 - 26 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>4.37.8 - 21 Aug 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="cdf488f595"><code>cdf488f</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4107">#4107</a>
from github/update-v4.37.9-920ba7cd1</li>
<li><a
href="7243f38558"><code>7243f38</code></a>
Update changelog for v4.37.9</li>
<li><a
href="920ba7cd15"><code>920ba7c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4106">#4106</a>
from github/update-bundle/codeql-bundle-v2.26.4</li>
<li><a
href="ecfa6e1681"><code>ecfa6e1</code></a>
Add changelog note</li>
<li><a
href="adcdf4a70d"><code>adcdf4a</code></a>
Update default bundle to codeql-bundle-v2.26.4</li>
<li><a
href="486fec2a3e"><code>486fec2</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4099">#4099</a>
from github/update-supported-enterprise-server-versions</li>
<li><a
href="134624c67b"><code>134624c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4101">#4101</a>
from github/dependabot/npm_and_yarn/npm-minor-457d82...</li>
<li><a
href="ff43db8f98"><code>ff43db8</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4103">#4103</a>
from github/mergeback/v4.37.8-to-main-db488dde</li>
<li><a
href="4605e03a74"><code>4605e03</code></a>
Rebuild</li>
<li><a
href="099c869cad"><code>099c869</code></a>
Update changelog and version after v4.37.8</li>
<li>Additional commits viewable in <a
href="e4fba868fa...cdf488f595">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/analyze` from 4.37.3 to 4.37.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/analyze's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.9</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>v4.37.8</h2>
<p>No user facing changes.</p>
<h2>v4.37.7</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>v4.37.6</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>v4.37.5</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>v4.37.4</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/analyze's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.9 - 26 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>4.37.8 - 21 Aug 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="cdf488f595"><code>cdf488f</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4107">#4107</a>
from github/update-v4.37.9-920ba7cd1</li>
<li><a
href="7243f38558"><code>7243f38</code></a>
Update changelog for v4.37.9</li>
<li><a
href="920ba7cd15"><code>920ba7c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4106">#4106</a>
from github/update-bundle/codeql-bundle-v2.26.4</li>
<li><a
href="ecfa6e1681"><code>ecfa6e1</code></a>
Add changelog note</li>
<li><a
href="adcdf4a70d"><code>adcdf4a</code></a>
Update default bundle to codeql-bundle-v2.26.4</li>
<li><a
href="486fec2a3e"><code>486fec2</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4099">#4099</a>
from github/update-supported-enterprise-server-versions</li>
<li><a
href="134624c67b"><code>134624c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4101">#4101</a>
from github/dependabot/npm_and_yarn/npm-minor-457d82...</li>
<li><a
href="ff43db8f98"><code>ff43db8</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4103">#4103</a>
from github/mergeback/v4.37.8-to-main-db488dde</li>
<li><a
href="4605e03a74"><code>4605e03</code></a>
Rebuild</li>
<li><a
href="099c869cad"><code>099c869</code></a>
Update changelog and version after v4.37.8</li>
<li>Additional commits viewable in <a
href="e4fba868fa...cdf488f595">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/upload-sarif` from 4.37.3 to 4.37.9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/upload-sarif's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.9</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>v4.37.8</h2>
<p>No user facing changes.</p>
<h2>v4.37.7</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>v4.37.6</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>v4.37.5</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>v4.37.4</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/upload-sarif's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.9 - 26 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>4.37.8 - 21 Aug 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="cdf488f595"><code>cdf488f</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4107">#4107</a>
from github/update-v4.37.9-920ba7cd1</li>
<li><a
href="7243f38558"><code>7243f38</code></a>
Update changelog for v4.37.9</li>
<li><a
href="920ba7cd15"><code>920ba7c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4106">#4106</a>
from github/update-bundle/codeql-bundle-v2.26.4</li>
<li><a
href="ecfa6e1681"><code>ecfa6e1</code></a>
Add changelog note</li>
<li><a
href="adcdf4a70d"><code>adcdf4a</code></a>
Update default bundle to codeql-bundle-v2.26.4</li>
<li><a
href="486fec2a3e"><code>486fec2</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4099">#4099</a>
from github/update-supported-enterprise-server-versions</li>
<li><a
href="134624c67b"><code>134624c</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4101">#4101</a>
from github/dependabot/npm_and_yarn/npm-minor-457d82...</li>
<li><a
href="ff43db8f98"><code>ff43db8</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4103">#4103</a>
from github/mergeback/v4.37.8-to-main-db488dde</li>
<li><a
href="4605e03a74"><code>4605e03</code></a>
Rebuild</li>
<li><a
href="099c869cad"><code>099c869</code></a>
Update changelog and version after v4.37.8</li>
<li>Additional commits viewable in <a
href="e4fba868fa...cdf488f595">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-09-01 10:12:12 +00:00
Qinghong Zhu
ce3a92a0a8
mcp: fix ListChanged doc comments on server capabilities (#1219)
The doc comments on `ToolCapabilities.ListChanged` and
`ResourceCapabilities.ListChanged` say the flag reports whether **the
client** supports list-change notifications. Both types only ever appear
inside `ServerCapabilities`, though, and the spec defines the field the
other way around: whether **this server** supports notifications for
changes to the list. `PromptCapabilities` in the same file already words
it correctly.

The "client" wording looks like it was copied from `RootCapabilities`,
the one listChanged capability that genuinely belongs to the client.
Docs only, no behavior change.
2026-09-01 10:09:19 +02:00
Haoxin Yang
69c17de411
mcp: don't mutate caller params on multi-round-trip retries (#1145)
Fixes #1144.

`clientMultiRoundTripMiddleware` calls
`setMultiRoundTripRetryParams(req, ...)`, which writes `InputResponses`
and `RequestState` through the caller's params pointer and never clears
them. A `*CallToolParams` (or `GetPromptParams` / `ReadResourceParams`)
struct reused across calls then carries the first call's answers into
the next call: a server gating on `len(req.Params.InputResponses) == 0`
— the idiom in this repo's own tests and examples — treats the fresh
call as already answered, so the client's `ElicitationHandler` never
fires and an elicitation-gated (e.g. destructive) tool runs without
confirmation.

This change makes the retry loop carry the fulfilled responses and
echoed state on a shallow copy of the params
(`multiRoundTripRetryRequest`) instead of mutating the original request.
Caller-owned params are now left untouched by `CallTool`, `GetPrompt`,
and `ReadResource`. The server-side shim
(`serverMultiRoundTripMiddleware`) is unchanged: the params it mutates
are SDK-owned, decoded from the wire.

Tests: `TestMultiRoundTrip_AutoRetryDoesNotMutateCallerParams`
(CallTool: params unchanged after the call, and reusing the same struct
fulfills the input requests again instead of silently replaying the
first call's answers — it failed with `elicitations after reuse = 1,
want 2` before the fix), plus GetPrompt/ReadResource analogues asserting
params are left clean. All three fail on main and pass with this change;
`go test ./...` and `go vet ./...` are clean.

Note on observable behavior: callers can no longer read the fulfilled
responses off their own params struct after a call returns. That side
effect was undocumented, and relying on it is exactly what makes the
replay hazard above possible.

---------

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-08-31 13:53:34 +02:00
Minghui Wang
d19bfebab9
mcp: preserve single-request batch responses (#1214)
## What does this PR do?

Preserve JSON-RPC batch response arrays when a batch contains a single
request.

## Why is this needed?

A single-request legacy batch must be returned as a one-element JSON
array, but the JSON response path currently unwraps it into a single
JSON object.

## Changes

- Preserve the original batch state on the logical stream
- Keep single-request batch responses as arrays
- Add a regression test for legacy initialization batches

## Testing

- `go test ./mcp -count=1`
- `go test ./... -count=1`
- `git diff --check`

Fixes #1211
2026-08-31 12:00:32 +02:00
Guglielmo Colombo
21c18c6229
mcp: allow restricting the protocol versions a server supports (#1199)
Fixes #945
2026-08-28 16:06:10 +02:00
Yaroslav
5a89b63764
oauthex: bound dcr read to 1mb (#1206) 2026-08-28 09:43:05 +02:00
Bobo
165121e53e
jsonrpc2: wrap write error in server-closing error (#1186)
The "server is closing" error produced when a connection shuts down
because its write side failed formatted the underlying write error with
`%v`, so the cause - typically `io.EOF` when a stdio host closes its
pipe - was not part of the error chain. A caller that wants to
distinguish a clean host disconnect from a real failure via
`errors.Is(err, io.EOF)` therefore always got `false`.

This changes both `s.writeErr` wrappings to use `%w`, preserving the
write error in the error chain while keeping the same message text
("server is closing: EOF").

A regression test exercises the write-failure branch of `shuttingDown`
and asserts both `errors.Is(err, ErrServerClosing)` and `errors.Is(err,
io.EOF)`.

Fixes #1098

---------

Co-authored-by: Bo Zhang <187063395+Tethys0@users.noreply.github.com>
Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-08-27 14:12:53 +00:00
Yaroslav
b55bcbd610
internal/json: json nesting depth check (#1200)
Added a lightweight JSON depth-checking pass before the actual parsing.
2026-08-27 10:47:08 +02:00
Recoordinate
2732839dba
mcp: prune completed listen request IDs from the session (#1190)
### Problem

`ServerSession` tracks the request IDs of in-flight
`subscriptions/listen` streams so `Close` can cancel the parked handlers
and avoid the jsonrpc2 drain deadlock. The ID is appended when the
listen starts, but it is never removed when the listen handler returns.

Every completed listen stream (peer cancel, unsubscribe, stream break)
therefore leaves one stale entry in the session for its entire lifetime.
On a long-lived session where a client repeatedly subscribes and
unsubscribes, the slice grows without bound: one leaked entry per cycle.

`Close` calls `jsonrpc2.Connection.Cancel` on each recorded ID, and a
stale ID is a no-op there today, but the bookkeeping is meant to hold
only in-flight listens and currently never shrinks.

### Change

Drop the request ID from the session's bookkeeping when the listen
handler returns, matching the lifecycle of the in-flight handler that
owns it.

### Tests

Three tests reproduce the leak, failing on `main` and passing here:

- `TestListenPrunedAfterSingleCompletion`
- `TestListenIDsDoNotAccumulate`
- `TestListenIDsLeakViaPublicSubscribeUnsubscribe`

The third exercises the leak through the public
`Subscribe`/`Unsubscribe` API. `go test ./mcp/` passes.

---------

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-08-25 17:15:09 +02:00
Scott Leggett
c68ad9a4e6
mcp: tolerate 404 on subscriptions/listen in stateless mode (#1193)
When connecting to remote stateless servers (such as GitHub's MCP
server), optional streams like subscriptions/listen may be rejected by
middleware with a plain-text HTTP 404 before reaching a JSON-RPC
handler.

Previously, checkResponse misclassified these 404s as lost stateful
sessions (ErrSessionMissing) and tore down the transport connection
because the error didn't wrap jsonrpc2.ErrRejected.

This change aligns with the MCP 2026-07-28 Streamable HTTP
specification:

1. Conditionally check for an active session ID before returning
ErrSessionMissing, since a 404 on a stateless connection cannot mean a
session was lost.

2. Wrap subscriptions/listen failures with ErrRejected (matching
server/discover) so the jsonrpc2 layer doesn't permanently break the
transport when encountering non-compliant plain-text 404s.

---

I have manually tested this change against GitHub's hosted MCP server
and confirmed that it fixes
https://github.com/github/github-mcp-server/issues/3129
2026-08-25 14:27:37 +02:00
杨成锴
32404652e6
auth: validate unadvertised RFC 9207 iss instead of rejecting it (#1187)
## Summary

Fixes #1152.

Sentry, Linear, and Notion omit
`authorization_response_iss_parameter_supported` from their
authorization-server metadata. Sentry still sends a correct `iss` in the
authorization response, so sign-in fails after the browser step: the
user has already consented and an authorization code has been issued,
but `validateIssuerResponse` rejects the exchange solely because the
server did not advertise RFC 9207 support.

RFC 9207 §2.4 says clients MUST extract and compare `iss` when the
parameter is present. §2.4 also notes that clients SHOULD discard an
unadvertised `iss`, but that is a recommendation and local policy may
accept servers that send `iss` without advertising. Validating a
matching `iss` is interoperable with those hosted MCP servers and still
catches mix-up attacks: a wrong `iss` is rejected whether or not support
was advertised.

When `iss` is present, this change always compares it to the expected
issuer (simple string equality). A match is accepted even if the server
did not advertise support. A mismatch still returns `authorization
response issuer %q does not match expected issuer %q`. Absence of `iss`
remains an error only when the server advertised support.

## Test plan

- [x] `go test ./auth/ -run TestValidateIssuerResponse -count=1`
- [x] `go test ./auth/...`

New `TestValidateIssuerResponse` cases:

- `UnadvertisedIssCorrect`: `iss == expectedIssuer`, `issSupported ==
false` → no error
- `UnadvertisedIssWrong`: `iss == "https://attacker.example.com"`,
`issSupported == false` → error containing `does not match expected
issuer`

Existing advertised+match / advertised+mismatch / missing-iss cases are
unchanged.

---------

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-08-25 09:26:17 +00:00
Anton Dziatkovskii
810e6cb751
docs: make the protocol.md table of contents resolve (#1196)
Hi — I am an AI agent (Claude), working as Anton Dzyatkovsky's synthetic
co-founder. Same author as #1159 (dead MRTR links in `client.md`), same
genre, but this one has a cause worth naming rather than five links
worth patching.

**AI disclosure** (per
[AI_POLICY.md](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/AI_POLICY.md)):
this contribution — the investigation, the diff, the test and this
description — was written by Claude Code running autonomously; Anton is
the accountable human and reachable on this thread. Every measurement
quoted below was produced by running the thing, not inferred: the id
tables are scraped from the two rendered surfaces, the test's slug
function was checked against 31 of GitHub's real ids, and the failure
output is a real run against the pre-fix tree. Any reply from this
account is written the same way unless it says otherwise. I edited only
`internal/docs/**` and regenerated, per `AGENTS.md`.

## What is broken

Five table-of-contents entries in `docs/protocol.md` point at anchors
that no heading produces. They are dead on **both** surfaces — GitHub's
render of the file, and the published site.

| ToC entry links to | heading actually gets |
|---|---|
| `#discovery-(server/discover)` | `discovery-serverdiscover` |
| `#per-request-meta-keys` | `per-request-_meta-keys` |
| `#per-response-meta-keys` | `per-response-_meta-keys` |
| `#subscriptions-(subscriptions/listen)` |
`subscriptions-subscriptionslisten` |
| `#sse-transport-(legacy)` | `sse-transport-legacy` |

Right-hand column is scraped, not derived: `user-content-*` ids from
GitHub's rendered `docs/protocol.md`, and `<h* id=...>` from
`https://go.sdk.modelcontextprotocol.io/protocol`. Four of the five are
dead on the live site too; `sse-transport-legacy` is not in the
currently deployed build, so I could only confirm that one on GitHub.

## Why

`%toc` and the renderer slug the same heading by different rules, and
they disagree on exactly the punctuation these headings use:

* **weave** keeps `(`, `)` and `/`, and drops `_`
* **GitHub and python-markdown's `toc`** drop `(`, `)` and `/`, and keep
`_`

So the two agree on plain words and part ways the moment a heading
carries a method name in parentheses or a `_meta` field. This is a
class, not five typos: any future heading with those characters gets a
dead ToC entry, silently — the docs regenerate fine, `docs-check`
passes, and only a reader clicking the ToC finds out.

Someone already hit this, by hand: `internal/docs/server.src.md` links
to `protocol.md#subscriptions-subscriptionslisten`, i.e. the real
renderer id, not the one the ToC uses. That cross-link is the odd one
out today; the ToC is what is wrong.

## The fix

The characters have to leave the headings, because weave computes the
ToC and it is not in this repo. Each of the five sections already names
its RPC or field in its first sentence, so the heading loses nothing:

| was | becomes | the name is still in the first sentence |
|---|---|---|
| Discovery (``server/discover``) | **Discovery** | "the
`server/discover` RPC lets a client discover…" |
| Per-request ``_meta`` keys | **Per-request metadata keys** | "carries
these keys inside its `_meta` map" |
| Per-response ``_meta`` keys | **Per-response metadata keys** |
"servers SHOULD identify themselves on every response" |
| Subscriptions (``subscriptions/listen``) | **Subscriptions** |
"`subscriptions/listen` replaces the legacy…" |
| SSE Transport (legacy) | **Legacy SSE Transport** | "Before the
streamable transport, the 2024-11-05 spec…" |

Plus the one hand-written cross-link in `server.src.md`, updated to
`protocol.md#subscriptions`.

**The cost, stated plainly:** renaming a heading changes its id, so any
external deep link to `#discovery-serverdiscover` or
`#per-request-_meta-keys` breaks. Those ids are only reachable today by
someone who scrolled and copied the permalink — the ToC never produced
them — so I think the trade is worth it, but it is your call. **The
alternative is to fix the slug in weave**
(`golang.org/x/example/internal/cmd/weave`) and leave the headings
alone. I am happy to take it there instead if you would rather keep the
ids; say the word and I will close this.

## The guard

`internal/docs/toc_test.go` — reads the generated docs and checks every
`%toc` entry against the id its heading will actually get. No network,
no new dependency.

The slug function in it is validated, not asserted: run over all 31
headings of the current `docs/protocol.md`, it reproduces **31 of 31**
of the ids GitHub actually emitted for that file (and 30 of 31 on the
live site — the miss is the not-yet-deployed SSE section above).

Behaviour on the pre-fix tree, which is the point of it:

```
--- FAIL: TestTOCAnchorsResolve
    protocol.md: table-of-contents entry "Discovery (`server/discover`)" points at #discovery-(server/discover),
      which no heading produces (want #discovery-serverdiscover); drop (, ), / and _ from the heading
    ... and the other four, each named
```

All five, by name, with the id they should have had. On this branch it
passes.

## Runs

`go generate ./...` → no diff (idempotent, so `docs-check` stays green)
· `go vet ./...` clean · `go build ./...` clean · `go test ./...` → 14
packages ok, 0 failures, 26 without tests.

Docs and one new test only; no SDK code touched.

Co-authored-by: Anton Dziatkovskii <194927794+tonydzi@users.noreply.github.com>
2026-08-25 09:26:21 +02:00
Guglielmo Colombo
b8c53fedd6
mcp: add docs for tier 1 assessment (#1195)
Closes open points in
[comment](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/3220#issuecomment-5373089321)
2026-08-24 12:00:59 +02:00
Guglielmo Colombo
6f0a645c20
mcp: add ServerSession.NotifyElicitationComplete (#1194)
Adds:

```go
func (ss *ServerSession) NotifyElicitationComplete(ctx context.Context, params *ElicitationCompleteParams) error
```

As according to
[spec](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.ts#L2493-L2501)
2026-08-24 10:14:28 +02:00
Lee
3d6450fccf
mcp: bound streamable Close DELETE so a silent peer cannot hang teardown (#1184)
Close issued a synchronous session-termination DELETE on the connection
context and cancelled that context only after `Do` returned. A reachable
but silent peer could stall `Close()` until the TCP timeout.

Give the DELETE its own short timeout.

RED on current main: `TestCloseHangsOnBlackHoledDelete` failed with
`Close() blocked on a black-holing DELETE`.

GREEN: `go test ./mcp/ -count=1 -timeout 120s -run
'TestStreamableClient|TestCloseHangs|TestCallCancellation_FastReturn'`

Fixes #1183

Made with [Cursor](https://cursor.com)

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-08-21 12:31:12 +02:00
jansen
b074c7f3af
internal/jsonrpc2: test WireError.Is and fix nil target panic (#1079)
# Problem
WireError.Is backs errors.Is checks used across the SDK against
sentinels such as ErrMethodNotFound, ErrRejected, and ErrClientClosing,
but had no direct test. Add a table-driven test for it.

While doing so, errors.Is(err, (*WireError)(nil)) was found to panic on
a nil pointer dereference, because Is read w.Code without a nil check.
Guard against a nil *WireError target so the comparison returns false.

  ```go
  errors.Is(err, (*WireError)(nil)) // panic: nil pointer dereference
 ```

# Approach


Is read w.Code after a successful type assertion without checking
whether w itself was nil. The fix guards against a nil *WireError target
so the comparison returns false instead of panicking.
# Test plan

  - go test ./internal/jsonrpc2/ passes.
- The new typed nil WireError case panics without the one-line fix and
passes with it, confirming the test exercises the guarded path.

Co-authored-by: Yaroslav <yarolegovich@gmail.com>
2026-08-21 10:56:31 +02:00
PratikDhanave
ce0b198202
mcp: prevent MemoryEventStore.After panic on index past the end (#1132)
## Summary

`MemoryEventStore.After` guarded only the lower bound of the requested
index:

```go
start := index + 1
if dl.first > start { // too old → ErrEventsPurged
    return nil, fmt.Errorf(... ErrEventsPurged)
}
return slices.Clone(dl.data[start-dl.first:]), nil
```

There was no *upper*-bound guard, so an `index` greater than the highest
stored event made the final slice expression panic with `slice bounds
out of range`. The boundary `start-dl.first == len(dl.data)` (resume
from the last event seen) is a valid empty replay, so only
strictly-greater indices faulted.

This is reachable from untrusted input: `parseEventID` accepts any
non-negative index from the client-supplied `Last-Event-ID` header, and
`serveGET` passes it straight to `eventStore.After`. Direct callers of
the exported `EventStore.After` API crash outright.

Fixes #1131

## Change

Return an empty replay for an index at or beyond the latest stored event
— there is nothing after it — consistent with the already-valid `index
== last` case. `ErrEventsPurged` would be misleading here, since those
events were never sent rather than purged.

## Testing

Extended `TestMemoryEventStoreAfter` with indices past the end of a
stream (`3`, `100`, and one past a second stream), asserting an empty
replay with no error.

Verified the new cases **panic on the pre-fix code** (`panic: runtime
error: slice bounds out of range [3:2]`) and **pass with the fix**. `go
vet ./mcp/` and the full `go test ./mcp/` suite pass.

---------

Co-authored-by: Yaroslav Shevchuk <yarolegovich@gmail.com>
2026-08-21 10:53:36 +02:00
jansen
fe27fa9151
internal/jsonrpc2: plumb cancellation cause through request context (#1103)
Closes #1100.

## What

`handleAsync` in `internal/jsonrpc2/conn.go` reported *why* an in-flight
request had been canceled by inspecting the connection-level
`s.writeErr`, guarded by a TODO waiting on golang/go#51365:

```go
// Only deliver to the Handler if not already canceled.
if err := req.ctx.Err(); err != nil {
    c.updateInFlight(func(s *inFlightState) {
        if s.writeErr != nil {
            // Assume that req.ctx was canceled due to s.writeErr.
            // TODO(#51365): use a Context API to plumb this through req.ctx.
            err = fmt.Errorf("%w: %v", ErrServerClosing, s.writeErr)
        }
    })
    ...
}
```

That API shipped as `context.WithCancelCause` / `context.Cause` in Go
1.21, and this module already targets Go 1.25, so the TODO is now
actionable.

This PR carries the cancellation cause on each request's context instead
of inferring it from global state:

- `incomingRequest.cancel` becomes a `context.CancelCauseFunc` (built
via `context.WithCancelCause`).
- The write-failure and read-side teardowns cancel each in-flight
request with a cause wrapping `ErrServerClosing`.
- The remaining cancel sites (peer cancel via `Cancel`, normal
completion) pass `nil`.
- `handleAsync` reads `context.Cause(req.ctx)` instead of looking up
`s.writeErr`.

## Why

Besides resolving the TODO, this fixes a latent mis-attribution.
`s.writeErr` is connection-level state, not per-request: a request
canceled for an unrelated reason (e.g. a peer cancel notification via
`Cancel`) was reported as `ErrServerClosing` whenever `s.writeErr`
happened to be non-nil at that moment. Tying the cause to the specific
request's context removes that coupling.

`ctx.Err()` still returns `context.Canceled` in all cases, so external
behavior is unchanged; only the reported cause becomes accurate and
per-request.

## Test plan

- `go build ./...`
- `go test ./internal/... ./mcp/` (including the longer `mcp`
integration tests) — all pass.
- Verified `context.WithCancelCause` semantics: with a cause, `Err()`
stays `context.Canceled` while `Cause()` returns the wrapped
`ErrServerClosing`; with `nil`, both resolve to `context.Canceled` —
matching prior behavior.

## Note

Per the discussion in #1100, the read-side teardown also carries a
descriptive cause (wrapping `ErrServerClosing`) rather than a bare
`context.Canceled`.

---------

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
Co-authored-by: Yaroslav <yarolegovich@gmail.com>
2026-08-21 10:16:28 +02:00
gwyszynski-sa
b0408f2821
mcp: prefer the negotiated protocol version over the request context (#1163)
setMCPHeaders resolved Mcp-Protocol-Version from the request context
before
c.initializedResult. #1107 established the opposite order and said so in
its
description; the code it replaced also consulted the context last. The
two
branches were transposed by the switch-to-if rewrite in #1115, whose
subject
was an unrelated nil-pointer check.

The context branch serves the pre-negotiation server/discover probe,
which
sets its own value in Client.Connect. Once a version has been negotiated
it is
the authoritative one for that connection, so initializedResult should
win.
_meta.protocolVersion from the outgoing message stays on top, so #1109
stays
fixed.

The existing table built its request with http.NewRequest, so
protocolVersionFromContext returned "" in every case and the two
orderings
were indistinguishable, which is why the transposition went unnoticed.
Adds a
ctxVersion dimension and three cases pinning the precedence.

Fixes #1162

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-08-18 07:42:58 +00:00
Guglielmo Colombo
1fcaf0c393
mcp: resolve deadlock on serverSession.Close() with subscriptionsListen call (#1166)
Fixes #1160
2026-08-17 12:21:13 +00:00
King Star
ba1496a9aa
mcp: keep request log levels scoped to each request (#1168)
Fixes a request-scoping bug in the new-protocol logging path.
`ServerSession.handle` decoded
`_meta[io.modelcontextprotocol/logLevel]`, but stored it in
`ServerSessionState.LogLevel`. Concurrent new-protocol requests on the
same session could therefore clear or inherit another request's log
threshold before the original handler emitted logs.

This keeps the new-protocol log level on the handler context instead.
`ServerSession.Log` and `LoggingHandler.Enabled` read that context value
first, while legacy `logging/setLevel` behavior still uses session
state.

Tests:

- `go test ./mcp -run
TestServerLogLevelDoesNotLeakBetweenNewProtocolRequests -count=1`
- `go test -race ./mcp -run
TestServerLogLevelDoesNotLeakBetweenNewProtocolRequests -count=1`
- `go test ./mcp -run TestEndToEnd -count=1`
- `go test ./mcp -run TestValidateRequestMeta -count=1`
- `go test ./mcp -run
TestStreamableStateless_NewProtocolSession_NoFakeInit -count=1`
- `go test ./... -skip
TestSSELocalhostProtection|TestStreamableLocalhostProtection`

A full `go test ./...` run in my local environment still fails the two
localhost protection cases for `0.0.0.0 via localhost rejects evil.com`;
those same two tests also fail when run directly and are unrelated to
this logging change.

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-08-17 14:00:41 +02:00
Recoordinate
0eb1600eef
mcp: avoid panicking when a resource handler returns nil (#1176)
### What

`readResource` dereferenced the handler's result before the `if res ==
nil`
check that was meant to guard it. A `ResourceHandler` returning `(nil,
nil)`
therefore panicked instead of returning an error:

- modern clients: `handleMultiRoundTripResult` calls `res.setResultType`
on the
  nil result, and
- older clients: `res.setDefaultCacheableValues()` runs on the nil
result.

Either way the `if res == nil` guard a few lines below was dead code.

### Why it matters

Request handlers run in their own goroutine and nothing recovers, so the
panic
does not just fail the one request. It terminates the whole server
process and
every concurrent session.

The sibling handlers already avoid this: `callTool` and `getPrompt` both
guard
their result with `if err == nil && res != nil` before touching it, so a
`(nil, nil)` return flows up and becomes a normal error response.

### Change

Move the nil check in `readResource` ahead of
`handleMultiRoundTripResult` and
`setDefaultCacheableValues`, so a nil result returns the existing
descriptive
error (`read handler returned nil information`) instead of panicking.
Added a
regression test that drives a resource handler returning `(nil, nil)`
through
the server and asserts a clean error.
2026-08-17 10:44:53 +02:00
Tony Dzi (Anton Dziatkovskii)
64e454e35c
docs: fix four dead links to the Multi Round-Trip Requests section (#1159)
### What was broken

Four links in `docs/client.md` point at a section that does not exist.

Three of them sit right where a reader needs them — at the end of the
**Roots**, **Sampling**
and **Elicitation** sections, on the sentence explaining that from
protocol version
`2026-07-28` these requests are delivered via the Multi Round-Trip
Requests pattern:

```
[Multi Round-Trip Requests](protocol.md#multi-round-trip-requests-mrtr)
```

`protocol.md` has no MRTR section. Following the link drops you at the
top of a page that
never mentions the pattern — while the explanation you were promised is
one screen further
down the *same* page you were already on (`client.md` → *Multi
Round-Trip Requests*).

The fourth one is in that MRTR section itself, sending you to the server
side:

```
[server-side documentation](server.md#multi-round-trip-requests-mrtr).
```

`server.md` does have the section, but its heading is `## Multi
Round-Trip Requests`, so the
slug is `#multi-round-trip-requests` — the trailing `-mrtr` is stale.

### How I checked

Against the published site, not just the source:

```
$ curl -sL https://go.sdk.modelcontextprotocol.io/protocol | grep -o 'id="[^"]*round[^"]*"'
            # (no output)
$ curl -sL https://go.sdk.modelcontextprotocol.io/client  | grep -o 'id="[^"]*round[^"]*"'
id="multi-round-trip-requests"
$ curl -sL https://go.sdk.modelcontextprotocol.io/server  | grep -o 'id="[^"]*round[^"]*"'
id="multi-round-trip-requests"
```

So `/protocol` carries no anchor containing `round` at all, and both
live targets use the
plain slug.

`git log -S` says the links arrived with b9e6fc5 ("mcp: add
documentation for new protocol
version", #1019, 2026-06-23) and that `protocol.src.md` has never
contained a
`Multi Round-Trip Requests` heading — the section landed in
`client.src.md` and
`server.src.md` instead, and these four references were never repointed.

I also ran a small link/anchor checker over `docs/`: 4 bad anchors
before, 0 after. (Two
"broken links" it also reports in `server.md` are its own false
positives — Go generics
inside a fence, `jsonschema.For[WeatherInput](opts)`, read as markdown
link syntax. Not
touched.)

### What it is now

The three per-feature links become same-page references, matching how
`client.md` already
links its own sections (`(see [Capabilities](#capabilities))`):

```
[Multi Round-Trip Requests](#multi-round-trip-requests)
```

and the cross-page one drops the stale suffix:

```
[server-side documentation](server.md#multi-round-trip-requests).
```

Change is in `internal/docs/client.src.md`; `docs/client.md` regenerated
with
`go generate ./...`, both committed together. Locally `go generate
./...` leaves the tree
clean afterwards, which is what `docs-check` asserts. Docs-only, four
link targets, no prose
and no code paths touched.

---
Assisted-by: Claude Opus 5 — this PR was drafted and verified by Anton's
AI cofounder running
on his account; every command and output above is from a real run on
this machine, not a
reconstruction.

Co-authored-by: tonydzi <dzyatkovskiy.a@gmail.com>
2026-08-13 10:49:56 +02:00
Guglielmo Colombo
4282916668
mcp: avoid Client.Connect leaks (#1158)
Fixes #1154
2026-08-10 08:52:50 +00:00
Recoordinate
e9a85e285f
mcp: fix typo in protocol version comment (#1156)
`protcol` should be `protocol`.

The comment sits above the `Mcp-Protocol-Version` header lookup, and the
surrounding code uses `protocolVersion` and
`protocolVersionFromContext`, so this is the only place in the package
where the word is misspelled.
2026-08-10 09:10:06 +02:00
Guglielmo Colombo
039119057f
mcp: allow asynchronous cancel notification (#1151)
Fixes #1150
2026-08-07 10:43:17 +02:00
Anton Dziatkovskii
71dfbc0d3e
docs: quick start go get leaves the module unbuildable (#1148)
### What was broken

`docs/quick_start.md` → *Installation* says:

```
go get github.com/modelcontextprotocol/go-sdk
```

The module root holds no importable package (only `copyright_test.go`),
so `go get` on the
module path records the requirement as `// indirect` and writes none of
the `go.sum` entries
the `mcp` package needs. A reader who runs that command and then pastes
the server snippet
from the next section hits six errors on the first build — before
writing a line of their own.

### How I checked

Followed the page verbatim on Go 1.26.4, in an empty directory:

```
$ go mod init example.com/qs
$ go get github.com/modelcontextprotocol/go-sdk
go: added github.com/modelcontextprotocol/go-sdk v1.7.0
```

`go.mod` now reads `require github.com/modelcontextprotocol/go-sdk
v1.7.0 // indirect`.
Then the two snippets from *Getting started*, saved as `server/main.go`
and `client/main.go`:

```
$ go build ./...
missing go.sum entry for module providing package github.com/google/jsonschema-go/jsonschema (imported by github.com/modelcontextprotocol/go-sdk/mcp)
missing go.sum entry for module providing package golang.org/x/oauth2 (imported by github.com/modelcontextprotocol/go-sdk/mcp)
missing go.sum entry for module providing package github.com/segmentio/encoding/json (imported by github.com/modelcontextprotocol/go-sdk/internal/json)
missing go.sum entry for module providing package github.com/yosida95/uritemplate/v3 (imported by github.com/modelcontextprotocol/go-sdk/mcp)
missing go.sum entry for module providing package golang.org/x/sync/errgroup (imported by github.com/modelcontextprotocol/go-sdk/mcp)
missing go.sum entry for module providing package golang.org/x/time/rate (imported by github.com/modelcontextprotocol/go-sdk/mcp)
```

Same directory, same snippets, asking for the package instead:

```
$ go get github.com/modelcontextprotocol/go-sdk/mcp
$ go build -o myserver ./server && go build -o myclient ./client
$ ./myclient
{"greeting":"Hi you"}
```

`go mod tidy` after the fact fixes it too — that is the escape hatch the
note mentions,
but it is not what the page tells you to do.

### What it is now

```
go get github.com/modelcontextprotocol/go-sdk/mcp
```

plus four lines saying why the module path alone is not enough, so the
next person who
copies the module path from `go.mod` or from a search result knows what
they are looking at.

Change is in `internal/docs/quick_start.src.md`; `docs/quick_start.md`
regenerated with
`go generate ./internal/docs`, both committed together. Docs-only, no
code paths touched.

---
Assisted-by: Claude Opus 5 — this PR was drafted and verified by Anton's
AI cofounder running
on his account; every command and output above is from a real run on
this machine, not a
reconstruction.

---------

Co-authored-by: Palo-Alto-AI-Research-Lab <Palo-Alto-AI-Research-Lab@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 16:27:46 +02:00
dev_Hakaze
958dfcc532
mcp: limit SSE discover supportedVersions to SSE-defined revisions (#1121)
## Summary

`NewSSEHandler` sessions were advertising `2026-07-28` in
`server/discover` `supportedVersions`. That revision only defines stdio
and Streamable HTTP, not the deprecated HTTP+SSE binding, so
Modern-first clients could negotiate a revision the transport cannot
honour.

## Changes

- Implement `ProtocolVersionSupporter` on `SSEServerTransport` so
`filterSupportedVersions` excludes `>= 2026-07-28` (matching the
streamable transport's version gate pattern).
- In `Server.discover`, only set session `InitializeParams` when the
requested version is actually supported by the transport. Without this,
a discover probe that returns only legacy versions still marked the
session initialized, and the client's fallback `initialize` failed with
`duplicate "initialize" received`.
- Tests: unit coverage for filtered SSE versions, plus an end-to-end
Modern client → SSE fallback-to-initialize path.

Fixes #1112

## Test plan

- [x] `go test ./...`
- [x] `go test ./mcp/ -run
'TestSSE|TestInMemory_E2E_Discover|TestStreamableStateful_AcceptsDiscover|TestStreamableHTTP_E2E_Discover|TestClientConnectDiscover'`

Assistance: human-reviewed AI assistance used for implementation.

---------

Co-authored-by: arimu1 <19286898+arimu1@users.noreply.github.com>
2026-08-05 15:36:22 +02:00
Suraj B
d462fbd303
Disable scheduled nightly runs on forks (#1083)
.github/workflows/nightly.yml: disable scheduled nightly runs on the
forked repos

User will still be able to run the workflow manually

Fixes #1082

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-08-05 13:50:58 +02:00
Guglielmo Colombo
6b1518cb0b
mcp: return json-rpc error on stateful server serving protocolVersion20260728 requests (#1143)
Fixes #1130
2026-08-05 12:16:39 +02:00
Anton Dziatkovskii
725694100a
docs: fix the streamable client snippet to use (*Client).Connect (#1142)
### What was broken

`docs/protocol.md` → *Streamable Transport* → client side shows:

```go
client, err := mcp.Connect(ctx, transport, &mcp.ClientOptions{...})
```

There is no top-level `mcp.Connect`. The package has
`(*Client).Connect`, whose
third parameter is `*ClientSessionOptions`, not `*ClientOptions`. So the
one
snippet a reader reaches for when wiring up the client side of
streamable HTTP
does not build.

It is also the only outlier in the docs: `quick_start.md`, `client.md`,
`server.md`, `troubleshooting.md` and `protocol.md` itself (lines 470,
538) all
already use `client.Connect(ctx, transport, nil)`.

### How I checked

Pasted the snippet verbatim into a fresh module requiring the released
SDK
(`github.com/modelcontextprotocol/go-sdk v1.7.0`):

```
$ go build ./...
./broken.go:15:21: undefined: mcp.Connect
```

Then ran the proposed replacement end to end against a real streamable
server
(`mcp.NewStreamableHTTPHandler` behind `httptest.NewServer`), connecting
and
calling a tool over the session:

```
BUILD OK
tool result: {"greeting":"Hi you"}
```

### What it is now

```go
transport := &mcp.StreamableClientTransport{
	Endpoint: "http://localhost:8080/mcp",
}
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v1.0.0"}, nil)
session, err := client.Connect(ctx, transport, nil)
```

Change is in `internal/docs/protocol.src.md`; `docs/protocol.md`
regenerated with
`go generate ./internal/docs`, both committed together. Docs-only, no
code paths
touched.

---
Assisted-by: Claude Opus 5 — this PR was drafted and verified by Anton's
AI
cofounder running on his account; the commands and outputs above are
from real
runs, not reconstructions.

Co-authored-by: Anton Dzyatkovsky <dzyatkovskiy.a@gmail.com>
2026-08-04 14:39:05 +02:00
Guglielmo Colombo
c4a4ce4543
mcp: fix error propagation to prevent session closing (#1118)
Fixes #1117
2026-08-04 11:02:36 +02:00
dev_Hakaze
1acd66e9d7
mcp: export ClientSessionOptions.ProtocolVersion (#1127)
## Summary

`ClientSessionOptions.protocolVersion` was unexported and documented as
test-only, so external consumers of `Client.Connect` could not request a
specific protocol revision.

This exports the field as `ProtocolVersion` with the same semantics:

- empty → request the latest supported version (unchanged default)
- non-empty → send that version in initialize/discover
- existing negotiate-down path is unchanged

## Usage

```go
cs, err := client.Connect(ctx, transport, &mcp.ClientSessionOptions{
    ProtocolVersion: "2025-11-25",
})
```

## Testing

- `go test ./mcp/ -count=1` (pass)

Fixes #1113

Co-authored-by: arimu1 <19286898+arimu1@users.noreply.github.com>
Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-08-03 15:29:52 +02:00
Guglielmo Colombo
0d0cdbc943
mcp: prevent leaks of ServerSession on StatefulServer (#1137)
Fixes #1136
2026-08-03 13:54:24 +02:00
dependabot[bot]
ec29d60bb9
build(deps): bump actions/setup-go from 6.5.0 to 7.0.0 (#1125)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.5.0
to 7.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-go/releases">actions/setup-go's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Migrate to ESM and upgrade dependencies by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
in <a
href="https://redirect.github.com/actions/setup-go/pull/763">actions/setup-go#763</a></li>
<li>chore(deps): bump <code>@​actions/cache</code> to 6.2.0 by <a
href="https://github.com/philip-gai"><code>@​philip-gai</code></a> in <a
href="https://redirect.github.com/actions/setup-go/pull/771">actions/setup-go#771</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/philip-gai"><code>@​philip-gai</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-go/pull/771">actions/setup-go#771</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-go/compare/v6...v7.0.0">https://github.com/actions/setup-go/compare/v6...v7.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="b7ad1dad31"><code>b7ad1da</code></a>
chore(deps): bump <code>@​actions/cache</code> to 6.2.0 (<a
href="https://redirect.github.com/actions/setup-go/issues/771">#771</a>)</li>
<li><a
href="0778a10ce4"><code>0778a10</code></a>
Migrate to ESM and upgrade dependencies (<a
href="https://redirect.github.com/actions/setup-go/issues/763">#763</a>)</li>
<li>See full diff in <a
href="924ae3a1cd...b7ad1dad31">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-08-03 09:43:31 +02:00
dependabot[bot]
02610a2075
build(deps): bump actions/checkout from 7.0.0 to 7.0.1 (#1126)
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0
to 7.0.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/releases">actions/checkout's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>skip running unsafe pr check if input is default by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2518">actions/checkout#2518</a></li>
<li>trim only ascii whitespace for branch by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2521">actions/checkout#2521</a></li>
<li>escape values passed to --unset by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2530">actions/checkout#2530</a></li>
<li>Various dependency updates</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/checkout/compare/v7...v7.0.1">https://github.com/actions/checkout/compare/v7...v7.0.1</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/checkout/blob/main/CHANGELOG.md">actions/checkout's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v7.0.1</h2>
<ul>
<li>Skip running unsafe pr check if input is default by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2518">actions/checkout#2518</a></li>
<li>Trim only ascii whitespace for branch by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2521">actions/checkout#2521</a></li>
<li>Escape values passed to --unset by <a
href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2530">actions/checkout#2530</a></li>
<li>Various dependency updates</li>
</ul>
<h2>v7.0.0</h2>
<ul>
<li>Block checking out fork PR for pull_request_target and workflow_run
by <a href="https://github.com/aiqiaoy"><code>@​aiqiaoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2454">actions/checkout#2454</a></li>
<li>Various dependency updates</li>
</ul>
<h2>v6.0.3</h2>
<ul>
<li>Fix checkout init for SHA-256 repositories by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2439">actions/checkout#2439</a></li>
<li>fix: expand merge commit SHA regex and add SHA-256 test cases by <a
href="https://github.com/yaananth"><code>@​yaananth</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2414">actions/checkout#2414</a></li>
</ul>
<h2>v6.0.2</h2>
<ul>
<li>Fix tag handling: preserve annotations and explicit fetch-tags by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2356">actions/checkout#2356</a></li>
</ul>
<h2>v6.0.1</h2>
<ul>
<li>Add worktree support for persist-credentials includeIf by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2327">actions/checkout#2327</a></li>
</ul>
<h2>v6.0.0</h2>
<ul>
<li>Persist creds to a separate file by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2286">actions/checkout#2286</a></li>
<li>Update README to include Node.js 24 support details and requirements
by <a href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2248">actions/checkout#2248</a></li>
</ul>
<h2>v5.0.1</h2>
<ul>
<li>Port v6 cleanup to v5 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2301">actions/checkout#2301</a></li>
</ul>
<h2>v5.0.0</h2>
<ul>
<li>Update actions checkout to use node 24 by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2226">actions/checkout#2226</a></li>
</ul>
<h2>v4.3.1</h2>
<ul>
<li>Port v6 cleanup to v4 by <a
href="https://github.com/ericsciple"><code>@​ericsciple</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2305">actions/checkout#2305</a></li>
</ul>
<h2>v4.3.0</h2>
<ul>
<li>docs: update README.md by <a
href="https://github.com/motss"><code>@​motss</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1971">actions/checkout#1971</a></li>
<li>Add internal repos for checking out multiple repositories by <a
href="https://github.com/mouismail"><code>@​mouismail</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1977">actions/checkout#1977</a></li>
<li>Documentation update - add recommended permissions to Readme by <a
href="https://github.com/benwells"><code>@​benwells</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2043">actions/checkout#2043</a></li>
<li>Adjust positioning of user email note and permissions heading by <a
href="https://github.com/joshmgross"><code>@​joshmgross</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2044">actions/checkout#2044</a></li>
<li>Update README.md by <a
href="https://github.com/nebuk89"><code>@​nebuk89</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2194">actions/checkout#2194</a></li>
<li>Update CODEOWNERS for actions by <a
href="https://github.com/TingluoHuang"><code>@​TingluoHuang</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/2224">actions/checkout#2224</a></li>
<li>Update package dependencies by <a
href="https://github.com/salmanmkc"><code>@​salmanmkc</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/2236">actions/checkout#2236</a></li>
</ul>
<h2>v4.2.2</h2>
<ul>
<li><code>url-helper.ts</code> now leverages well-known environment
variables by <a href="https://github.com/jww3"><code>@​jww3</code></a>
in <a
href="https://redirect.github.com/actions/checkout/pull/1941">actions/checkout#1941</a></li>
<li>Expand unit test coverage for <code>isGhes</code> by <a
href="https://github.com/jww3"><code>@​jww3</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1946">actions/checkout#1946</a></li>
</ul>
<h2>v4.2.1</h2>
<ul>
<li>Check out other refs/* by commit if provided, fall back to ref by <a
href="https://github.com/orhantoy"><code>@​orhantoy</code></a> in <a
href="https://redirect.github.com/actions/checkout/pull/1924">actions/checkout#1924</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="3d3c42e5aa"><code>3d3c42e</code></a>
prep v7.0.1 release (<a
href="https://redirect.github.com/actions/checkout/issues/2531">#2531</a>)</li>
<li><a
href="28802689a1"><code>2880268</code></a>
escape values passed to --unset (<a
href="https://redirect.github.com/actions/checkout/issues/2530">#2530</a>)</li>
<li><a
href="12cd2235ef"><code>12cd223</code></a>
trim only ascii whitespace for branch (<a
href="https://redirect.github.com/actions/checkout/issues/2521">#2521</a>)</li>
<li><a
href="62661c4e71"><code>62661c4</code></a>
skip running unsafe pr check if input is default (<a
href="https://redirect.github.com/actions/checkout/issues/2518">#2518</a>)</li>
<li><a
href="e8d4307400"><code>e8d4307</code></a>
Bump the minor-actions-dependencies group with 2 updates (<a
href="https://redirect.github.com/actions/checkout/issues/2499">#2499</a>)</li>
<li><a
href="631c942040"><code>631c942</code></a>
eslint 9 (<a
href="https://redirect.github.com/actions/checkout/issues/2474">#2474</a>)</li>
<li><a
href="4f1f4aec02"><code>4f1f4ae</code></a>
Bump actions/upload-artifact from 4 to 7 (<a
href="https://redirect.github.com/actions/checkout/issues/2476">#2476</a>)</li>
<li><a
href="ba097532fb"><code>ba09753</code></a>
Bump actions/checkout from 6 to 7 (<a
href="https://redirect.github.com/actions/checkout/issues/2488">#2488</a>)</li>
<li><a
href="b9e0990d21"><code>b9e0990</code></a>
Bump docker/login-action from 3.3.0 to 4.2.0 (<a
href="https://redirect.github.com/actions/checkout/issues/2479">#2479</a>)</li>
<li><a
href="e8cb398be4"><code>e8cb398</code></a>
Bump docker/build-push-action from 6.5.0 to 7.2.0 (<a
href="https://redirect.github.com/actions/checkout/issues/2478">#2478</a>)</li>
<li>Additional commits viewable in <a
href="9c091bb21b...3d3c42e5aa">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-08-03 09:37:58 +02:00
dependabot[bot]
7b3bbc0f32
build(deps): bump actions/setup-python from 6.3.0 to 7.0.0 (#1124)
Bumps [actions/setup-python](https://github.com/actions/setup-python)
from 6.3.0 to 7.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-python/releases">actions/setup-python's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<h3>Enhancements</h3>
<ul>
<li>Migrate to ESM and upgrade dependencies by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
in <a
href="https://redirect.github.com/actions/setup-python/pull/1330">actions/setup-python#1330</a></li>
<li>Pin SHA commits and update docs with latest versions by <a
href="https://github.com/HarithaVattikuti"><code>@​HarithaVattikuti</code></a>
in <a
href="https://redirect.github.com/actions/setup-python/pull/1338">actions/setup-python#1338</a></li>
<li>Remove the pip-install input by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-python/pull/1336">actions/setup-python#1336</a></li>
</ul>
<h3>Bug Fix</h3>
<ul>
<li>Fix to Classify stderr warning messages as warnings instead of
errors in annotations by <a
href="https://github.com/lmvysakh"><code>@​lmvysakh</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/1335">actions/setup-python#1335</a></li>
<li>Validate and retry manifest fetch to prevent silent failures by <a
href="https://github.com/priyagupta108"><code>@​priyagupta108</code></a>
in <a
href="https://redirect.github.com/actions/setup-python/pull/1332">actions/setup-python#1332</a></li>
</ul>
<h3>Dependency Upgrade</h3>
<ul>
<li>Bump certifi from 2020.6.20 to 2024.7.4 in
/<strong>tests</strong>/data by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/1328">actions/setup-python#1328</a></li>
<li>Remove EOL Python versions and Bumps numpy text fixture by <a
href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a>
in <a
href="https://redirect.github.com/actions/setup-python/pull/1333">actions/setup-python#1333</a></li>
<li>Upgrade <code>@​actions/cache</code> to 6.2.0 by <a
href="https://github.com/philip-gai"><code>@​philip-gai</code></a> in <a
href="https://redirect.github.com/actions/setup-python/pull/1337">actions/setup-python#1337</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/lmvysakh"><code>@​lmvysakh</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-python/pull/1335">actions/setup-python#1335</a></li>
<li><a
href="https://github.com/philip-gai"><code>@​philip-gai</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-python/pull/1337">actions/setup-python#1337</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-python/compare/v6...v7.0.0">https://github.com/actions/setup-python/compare/v6...v7.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="5fda3b95a4"><code>5fda3b9</code></a>
Pin SHA commits and update docs with latest versions (<a
href="https://redirect.github.com/actions/setup-python/issues/1338">#1338</a>)</li>
<li><a
href="4ab7e95f05"><code>4ab7e95</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/setup-python/issues/1337">#1337</a>
from actions/philip-gai/bump-actions-cache-6-2-0</li>
<li><a
href="0f3a009f47"><code>0f3a009</code></a>
Remove the pip-install input (<a
href="https://redirect.github.com/actions/setup-python/issues/1336">#1336</a>)</li>
<li><a
href="f8cf4291c8"><code>f8cf429</code></a>
Migrate to ESM and upgrade dependencies (<a
href="https://redirect.github.com/actions/setup-python/issues/1330">#1330</a>)</li>
<li><a
href="54baeea5b3"><code>54baeea</code></a>
Validate and retry manifest fetch to prevent silent failures (<a
href="https://redirect.github.com/actions/setup-python/issues/1332">#1332</a>)</li>
<li><a
href="c7092773a3"><code>c709277</code></a>
Annotation code fix (<a
href="https://redirect.github.com/actions/setup-python/issues/1335">#1335</a>)</li>
<li><a
href="6849080452"><code>6849080</code></a>
remove EOL Python versions and Bumps numpy text fixture (<a
href="https://redirect.github.com/actions/setup-python/issues/1333">#1333</a>)</li>
<li><a
href="0903b469fb"><code>0903b46</code></a>
Bump certifi from 2020.6.20 to 2024.7.4 in /<strong>tests</strong>/data
(<a
href="https://redirect.github.com/actions/setup-python/issues/1328">#1328</a>)</li>
<li>See full diff in <a
href="ece7cb06ca...5fda3b95a4">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-03 07:34:48 +00:00
dependabot[bot]
5d1040061b
build(deps): bump actions/setup-node from 6.0.0 to 7.0.0 (#1123)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from
6.0.0 to 7.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-node/releases">actions/setup-node's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.0</h2>
<h2>What's Changed</h2>
<h3>Enhancements:</h3>
<ul>
<li>Add cache-primary-key and cache-matched-key as outputs by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1577">actions/setup-node#1577</a></li>
<li>Migrate to ESM and upgrade dependencies by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1574">actions/setup-node#1574</a></li>
</ul>
<h3>Bug fixes:</h3>
<ul>
<li>Remove dummy NODE_AUTH_TOKEN export by <a
href="https://github.com/gowridurgad"><code>@​gowridurgad</code></a> in
<a
href="https://redirect.github.com/actions/setup-node/pull/1558">actions/setup-node#1558</a></li>
<li>Only use <code>mirrorToken</code> in <code>getManifest</code> if
it's provided by <a
href="https://github.com/deiga"><code>@​deiga</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1548">actions/setup-node#1548</a></li>
</ul>
<h3>Documentation updates:</h3>
<ul>
<li>Add documentation for publishing to npm with Trusted Publisher
(OIDC) by <a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1536">actions/setup-node#1536</a></li>
<li>docs: Update restore-only cache documentation by <a
href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1550">actions/setup-node#1550</a></li>
<li>docs: Update caching recommendations to mitigate cache poisoning
risks by <a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1567">actions/setup-node#1567</a></li>
</ul>
<h3>Dependency update:</h3>
<ul>
<li>Upgrade <code>@​actions/cache</code> to 5.1.0, log cache write
denied by <a
href="https://github.com/jasongin"><code>@​jasongin</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1569">actions/setup-node#1569</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/chiranjib-swain"><code>@​chiranjib-swain</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1536">actions/setup-node#1536</a></li>
<li><a href="https://github.com/deiga"><code>@​deiga</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1548">actions/setup-node#1548</a></li>
<li><a href="https://github.com/jasongin"><code>@​jasongin</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1569">actions/setup-node#1569</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v6...v7.0.0">https://github.com/actions/setup-node/compare/v6...v7.0.0</a></p>
<h2>v6.5.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Update <code>@​actions/cache</code> to 5.1.0 and add security
overrides for undici and fast-xml-parser by <a
href="https://github.com/HarithaVattikuti"><code>@​HarithaVattikuti</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1579">actions/setup-node#1579</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v6.4.0...v6.5.0">https://github.com/actions/setup-node/compare/v6.4.0...v6.5.0</a></p>
<h2>v6.4.0</h2>
<h2>What's Changed</h2>
<h3>Dependency updates:</h3>
<ul>
<li>Upgrade <a
href="https://github.com/actions"><code>@​actions</code></a>
dependencies by <a
href="https://github.com/Copilot"><code>@​Copilot</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1525">actions/setup-node#1525</a></li>
<li>Update Node.js versions in versions.yml and bump package to v6.4.0
by <a
href="https://github.com/priya-kinthali"><code>@​priya-kinthali</code></a>
in <a
href="https://redirect.github.com/actions/setup-node/pull/1533">actions/setup-node#1533</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Copilot"><code>@​Copilot</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-node/pull/1525">actions/setup-node#1525</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-node/compare/v6...v6.4.0">https://github.com/actions/setup-node/compare/v6...v6.4.0</a></p>
<h2>v6.3.0</h2>
<h2>What's Changed</h2>
<h3>Enhancements:</h3>
<ul>
<li>Support parsing <code>devEngines</code> field by <a
href="https://github.com/susnux"><code>@​susnux</code></a> in <a
href="https://redirect.github.com/actions/setup-node/pull/1283">actions/setup-node#1283</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="8207627860"><code>8207627</code></a>
Migrate to ESM and upgrade dependencies (<a
href="https://redirect.github.com/actions/setup-node/issues/1574">#1574</a>)</li>
<li><a
href="04be95cf35"><code>04be95c</code></a>
Add cache-primary-key and cache-matched-key as outputs (<a
href="https://redirect.github.com/actions/setup-node/issues/1577">#1577</a>)</li>
<li><a
href="7c2c68d20d"><code>7c2c68d</code></a>
docs: Update caching recommendations to mitigate cache poisoning risks
(<a
href="https://redirect.github.com/actions/setup-node/issues/1567">#1567</a>)</li>
<li><a
href="6a61c0375d"><code>6a61c03</code></a>
Merge pull request <a
href="https://redirect.github.com/actions/setup-node/issues/1569">#1569</a>
from jasongin/update-actions-cache-5.1.0</li>
<li><a
href="30eb73b41d"><code>30eb73b</code></a>
Resolve high-severity audit issues</li>
<li><a
href="4e1a87a501"><code>4e1a87a</code></a>
Update dist</li>
<li><a
href="360237f0c0"><code>360237f</code></a>
Strict equality</li>
<li><a
href="4f8aac5beb"><code>4f8aac5</code></a>
Bump <code>@​actions/cache</code> to 5.1.0, log cache write denied</li>
<li><a
href="f4a67bbeca"><code>f4a67bb</code></a>
Only use <code>mirrorToken</code> in <code>getManifest</code> if it's
provided (<a
href="https://redirect.github.com/actions/setup-node/issues/1548">#1548</a>)</li>
<li><a
href="0355742c94"><code>0355742</code></a>
Remove dummy NODE_AUTH_TOKEN export (<a
href="https://redirect.github.com/actions/setup-node/issues/1558">#1558</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/setup-node/compare/v6...820762786026740c76f36085b0efc47a31fe5020">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-node&package-manager=github_actions&previous-version=6.0.0&new-version=7.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-08-03 07:28:48 +00:00
dependabot[bot]
d32302ee7a
build(deps): bump the codeql-action group with 3 updates (#1122)
Bumps the codeql-action group with 3 updates:
[github/codeql-action/init](https://github.com/github/codeql-action),
[github/codeql-action/analyze](https://github.com/github/codeql-action)
and
[github/codeql-action/upload-sarif](https://github.com/github/codeql-action).

Updates `github/codeql-action/init` from 4.37.0 to 4.37.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/init's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.3</h2>
<p>No user facing changes.</p>
<h2>v4.37.2</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>v4.37.1</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/init's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="e4fba868fa"><code>e4fba86</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4031">#4031</a>
from github/update-v4.37.3-72f6a9da0</li>
<li><a
href="fb50ab5d62"><code>fb50ab5</code></a>
Update changelog for v4.37.3</li>
<li><a
href="72f6a9da0d"><code>72f6a9d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4030">#4030</a>
from github/mbg/fix/no-proxy</li>
<li><a
href="3b5ee58597"><code>3b5ee58</code></a>
Use default <code>request</code> options instead of
<code>undefined</code></li>
<li><a
href="bfb6be4b5e"><code>bfb6be4</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4028">#4028</a>
from github/mergeback/v4.37.2-to-main-e0647621</li>
<li><a
href="526ab84f98"><code>526ab84</code></a>
Rebuild</li>
<li><a
href="d6217b9b8c"><code>d6217b9</code></a>
Update changelog and version after v4.37.2</li>
<li><a
href="e0647621c2"><code>e064762</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4027">#4027</a>
from github/update-v4.37.2-385bcdc5a</li>
<li><a
href="e0faed8391"><code>e0faed8</code></a>
Add a couple of change notes</li>
<li><a
href="73aad0eaa9"><code>73aad0e</code></a>
Update changelog for v4.37.2</li>
<li>Additional commits viewable in <a
href="99df26d4f1...e4fba868fa">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/analyze` from 4.37.0 to 4.37.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/analyze's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.3</h2>
<p>No user facing changes.</p>
<h2>v4.37.2</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>v4.37.1</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/analyze's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="e4fba868fa"><code>e4fba86</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4031">#4031</a>
from github/update-v4.37.3-72f6a9da0</li>
<li><a
href="fb50ab5d62"><code>fb50ab5</code></a>
Update changelog for v4.37.3</li>
<li><a
href="72f6a9da0d"><code>72f6a9d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4030">#4030</a>
from github/mbg/fix/no-proxy</li>
<li><a
href="3b5ee58597"><code>3b5ee58</code></a>
Use default <code>request</code> options instead of
<code>undefined</code></li>
<li><a
href="bfb6be4b5e"><code>bfb6be4</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4028">#4028</a>
from github/mergeback/v4.37.2-to-main-e0647621</li>
<li><a
href="526ab84f98"><code>526ab84</code></a>
Rebuild</li>
<li><a
href="d6217b9b8c"><code>d6217b9</code></a>
Update changelog and version after v4.37.2</li>
<li><a
href="e0647621c2"><code>e064762</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4027">#4027</a>
from github/update-v4.37.2-385bcdc5a</li>
<li><a
href="e0faed8391"><code>e0faed8</code></a>
Add a couple of change notes</li>
<li><a
href="73aad0eaa9"><code>73aad0e</code></a>
Update changelog for v4.37.2</li>
<li>Additional commits viewable in <a
href="99df26d4f1...e4fba868fa">compare
view</a></li>
</ul>
</details>
<br />

Updates `github/codeql-action/upload-sarif` from 4.37.0 to 4.37.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/upload-sarif's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.3</h2>
<p>No user facing changes.</p>
<h2>v4.37.2</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>v4.37.1</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/upload-sarif's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<h2>4.36.1 - 02 Jun 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.0 - 22 May 2026</h2>
<ul>
<li><em>Breaking change</em>: Bump the minimum required CodeQL bundle
version to 2.19.4. <a
href="https://redirect.github.com/github/codeql-action/pull/3894">#3894</a></li>
<li>Add support for SHA-256 Git object IDs. <a
href="https://redirect.github.com/github/codeql-action/pull/3893">#3893</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="e4fba868fa"><code>e4fba86</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4031">#4031</a>
from github/update-v4.37.3-72f6a9da0</li>
<li><a
href="fb50ab5d62"><code>fb50ab5</code></a>
Update changelog for v4.37.3</li>
<li><a
href="72f6a9da0d"><code>72f6a9d</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4030">#4030</a>
from github/mbg/fix/no-proxy</li>
<li><a
href="3b5ee58597"><code>3b5ee58</code></a>
Use default <code>request</code> options instead of
<code>undefined</code></li>
<li><a
href="bfb6be4b5e"><code>bfb6be4</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4028">#4028</a>
from github/mergeback/v4.37.2-to-main-e0647621</li>
<li><a
href="526ab84f98"><code>526ab84</code></a>
Rebuild</li>
<li><a
href="d6217b9b8c"><code>d6217b9</code></a>
Update changelog and version after v4.37.2</li>
<li><a
href="e0647621c2"><code>e064762</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4027">#4027</a>
from github/update-v4.37.2-385bcdc5a</li>
<li><a
href="e0faed8391"><code>e0faed8</code></a>
Add a couple of change notes</li>
<li><a
href="73aad0eaa9"><code>73aad0e</code></a>
Update changelog for v4.37.2</li>
<li>Additional commits viewable in <a
href="99df26d4f1...e4fba868fa">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-03 09:26:57 +02:00
Thomas Bechtold
0c004ee48a
auth: add ScopeFilter hook to AuthorizationCodeHandlerConfig (#1044)
## Summary

The authorization-code handler requests every scope advertised in the
protected resource's metadata `scopes_supported` (or named in the
`WWW-Authenticate` challenge), and offers the caller no way to adjust
that set. Some servers advertise a scope a client should *not* request:

- Google's Gmail MCP server advertises `gmail.metadata` in
`scopes_supported`.
- The Gmail API refuses the search `q` parameter on any token that
carries `gmail.metadata` — **even when `gmail.readonly` is also
granted** (a long-standing documented Gmail restriction, see
googleapis/google-api-python-client#582).

Because the handler requests the full advertised set, `gmail.metadata`
ends up in the grant and breaks search, with no client-side remedy.

## Change

Add an optional `ScopeFilter func(discovered []string) []string`
callback to `AuthorizationCodeHandlerConfig`. When non-nil, it receives
the discovered scopes and returns the set to request, giving the client
full control to narrow, extend, or reorder them. It runs **before** the
`offline_access` (SEP-2207) and step-up union (SEP-2350) logic, so
refresh-token support and scope accumulation are unaffected.

(Started as a static `Scopes []string` allowlist; switched to a callback
per review feedback for more flexibility.)

## Test

`TestAuthorize_ScopeFilter` drives a full authorize flow and asserts the
requested `scope` parameter for a nil filter (unchanged), a filter that
drops a scope, and one that replaces the set entirely. `go test
./auth/...` (incl. `-race`), `go vet`, and `gofmt` are clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-07-31 10:35:49 +02:00
n0liu
7743b71b1c
mcp: fix ListTools panic and null normalization on malformed tools (#1120)
Fixes #1119.

`ClientSession.ListTools` post-processes the server response through
`filterValidTools`, which has two problems on malformed input:

1. **Nil-pointer panic on `"tools":[null]`.** A `nil` `*Tool` element
reaches `validateParamHeaderAnnotations(tool)`, which dereferences
`tool.InputSchema`, crashing the client on untrusted server input.
2. **`"tools":null` is normalized into a non-nil empty slice.**
`make([]*Tool, 0, len(tools))` is always non-nil, so a nil `Tools` slice
from a malformed response is silently laundered into valid-looking data.

Reproduction:

```go
filterValidTools(nil, []*Tool{nil}) // panics: nil pointer dereference
filterValidTools(nil, nil)          // returns non-nil slice (len 0), want nil
```

Fix (`mcp/streamable_headers.go`):

- Skip (log and exclude) nil tool elements instead of dereferencing
them, so a malformed `"tools":[null]` can no longer crash the client.
This is consistent with the function’s existing contract of excluding
invalid tools.
- Return early with `nil` for a nil input slice so a `"tools":null`
response is preserved as nil rather than normalized into an empty slice.

Added `TestFilterValidToolsNilHandling` covering both defects plus the
mixed valid/nil case. `go test ./mcp/` passes; `gofmt` and `go vet` are
clean.

Note on scope: this keeps the change minimal and within
`filterValidTools`’s existing signature. If maintainers would rather
surface a hard protocol error for a malformed `"tools":null` /
`"tools":[null]` response at the `ListTools` level (rather than
tolerating and filtering it), I’m happy to follow up with that larger
change.
2026-07-30 10:06:14 +02:00
Yaroslav
bc72835f62
mcp: fix potential nil-pointer dereference on message send (#1115) 2026-07-27 17:20:53 +02:00
Yaroslav
cc4db25347
chore: update conformance (#1114)
Update to the latest conformance tests version.
2026-07-27 17:19:11 +02:00
George McCarron
e761950d97
mcp: avoid double-copy of payload in writeEvent (#1106)
`writeEvent` built the SSE frame with `fmt.Fprintf(&b, "data: %s\n\n",
string(evt.Data))`, which copies the payload into a fresh string and
then again into the buffer, growing it via repeated `bytes.growSlice`
doublings. For large MCP responses (e.g. ~162KB `tools/list`) under a
burst of concurrent writes this ~3x amplifies allocations and peak heap.

Write the payload directly into a pre-grown buffer instead. Output
framing is byte-identical. Benchmarks (162KB payload): 517KB->172KB per
op, 9->4 allocs/op, ~3x faster.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Yaroslav <yarolegovich@gmail.com>
2026-07-24 10:07:06 +02:00
Jan Steinke
2781b8f7e3
mcp: derive Mcp-Protocol-Version header from the outgoing message (#1107)
`streamableClientConn.setMCPHeaders` sets `Mcp-Protocol-Version` only
from `c.initializedResult`, which is populated exclusively through the
unexported `clientConnection.sessionUpdated` hook. `Client.Connect`
reaches that hook via a type assertion on the `Connection` returned by
the `Transport`:

```go
  if hc, ok := cs.mcpConn.(clientConnection); ok {
      hc.sessionUpdated(cs.state)
  }
```

That assertion silently fails whenever the `Connection` is wrapped by
another type that exposes only the exported `Connection` interface.
`c.initializedResult` then stays `nil` for the life of the session, and
every `SEP-2575` (>= `2026-07-28`) request sent through that connection
is missing the header. Servers that validate the header per spec reject
the request with `400 Bad Request`, closing the connection.

Every SEP-2575 request already carries its own `_meta.protocolVersion`
field via `injectRequestMeta`. `setMCPHeaders` now reads the protocol
version from the outgoing message first, falling back to
`c.initializedResult` and then the request context.

This is currently breaking the GitHub MCP with the 1.7-pre releases of
this module.

Fixes #1109

Co-authored-by: eu.anthropic.claude-sonnet-5 via Crush <charmcrush@users.noreply.github.com>
2026-07-24 09:21:59 +02:00
Guglielmo Colombo
827f90ba0c
mcp: allow opt-in to cancel handler ctx on aborted POST (#1099)
Fixes #1093
2026-07-17 13:40:39 +02:00
Guglielmo Colombo
5cb8339210
mcp: update return meta in DiscoverResult (SEP-2575) (#1097)
This PR implements the latest changes introduced by
https://github.com/modelcontextprotocol/modelcontextprotocol/pull/3002
2026-07-16 16:10:12 +02:00
Guglielmo Colombo
fa548d7401
docs: update docs (#1089) 2026-07-16 11:11:22 +02:00
Guglielmo Colombo
8baa4ccebe
mcp: add return type to subscriptions/listen (#1088)
This PR adds the expected return type to `subscriptions/listen` rpc
2026-07-15 14:37:19 +02:00
Guglielmo Colombo
9576afc114
mcp: wrap unmarshalParams with jsonrpc error (#1087)
Fixes #976
2026-07-14 14:26:18 +00:00
Guglielmo Colombo
0a8953d71c
ci: fix nightly runs (#1086)
This PR fixes the failing nightly runs
https://github.com/modelcontextprotocol/go-sdk/actions/workflows/nightly.yml
2026-07-14 16:24:47 +02:00
Guglielmo Colombo
7fde7d3452
mcp: reject calls to methodDiscover for <20260728 requests (#1084)
This PR adds a gate for requests towards methodDiscover if it is coming
from a client that does not support the 20260728 protocol version
2026-07-14 12:13:40 +02:00
Guglielmo Colombo
8f54783331
docs: update troubleshoot.md to include resultType (#1081)
This PR adds the resultType in the troubleshoot doc after PR #1060
2026-07-13 14:47:53 +00:00
Scott Leggett
beb3d1ab7a
auth: support OAuth2 session persistence (#1058)
This change adds NewTokenSource and InitialTokenSource fields to
AuthorizationCodeHandlerConfig. NewTokenSource enables wrapping the
underlying token source to intercept token refreshes. InitialTokenSource
enables configuring the token source used by the
AuthorizationCodeHandler on initialization. Together these features
facilitate persisting OAuth2 sessions across restarts.

Includes a package example demonstrating the persistence pattern.

Fixes: #901.

See #901 for the previous discussion behind this design.

---------

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
Co-authored-by: Jonathan Amsterdam <jba@users.noreply.github.com>
2026-07-13 15:10:05 +02:00
Guglielmo Colombo
c79a30ae0a
mcp: add param check for CompleteResult (#1080)
Fixes #1071
2026-07-13 12:14:21 +00:00
Yashas Gunderia
dd79ca3d44
mcp: include resultType on new-protocol responses (#1060)
## Summary

- Add `resultType: "complete"` to non-MRTR server result responses for
2026-07-28 requests.
- Cover `server/discover`, list methods, and completion responses, and
update expected conformance/example output.

## Why

Fixes #1032. The 2026-07-28 schema requires `resultType` on result
objects, but only the MRTR-capable results were setting it.

## Validation

- `go test ./mcp -run
'Test(ServerSessionHandle|CompleteResult|ClientConnectDiscover|InMemory_E2E_Discover)'
-count=1` — passed\n- `go test ./mcp -count=1` — passed\n- `go test
./...` — passed\n- `go vet ./...` — passed

---------

Co-authored-by: ychampion <ychampion@users.noreply.github.com>
Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-07-13 11:51:19 +00:00
Guglielmo Colombo
40f034322f
mcp: infer elicit mode from ElicitParams (#1078)
Fixes #1072
2026-07-13 11:49:15 +02:00
Guglielmo Colombo
ce393acb95
mcp: do not ApplyDefault when res.Content==nil (#1069)
Fixes #1062
2026-07-10 11:26:29 +02:00
78 changed files with 7974 additions and 603 deletions

View file

@ -36,13 +36,13 @@ jobs:
build-mode: autobuild
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
category: "/language:${{matrix.language}}"

View file

@ -14,25 +14,25 @@ permissions:
contents: read
env:
CONFORMANCE_VERSION: "0.2.0-alpha.9"
CONFORMANCE_VERSION: "0.2.0-alpha.11"
jobs:
server-conformance:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "^1.26"
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
- name: Build everything-server
run: go build -o everything-server ./conformance/everything-server
- name: Run conformance tests (stateful, legacy spec versions)
- name: Run conformance tests (2025-11-25, stateful)
run: |
./everything-server -http=":3001" -stateless=false &
SERVER_PID=$!
@ -40,10 +40,9 @@ jobs:
timeout 30 bash -c 'until curl -s http://localhost:3001/mcp > /dev/null; do sleep 0.5; done'
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" server \
--url http://localhost:3001/mcp \
--suite active \
--spec-version 2025-11-25 \
--requirements 2025-11-25 \
--expected-failures ./conformance/baseline.yml
- name: Run conformance tests (stateless, 2026-07-28, full suite)
- name: Run conformance tests (2026-07-28, stateless)
run: |
./everything-server -http=":3001" -stateless &
SERVER_PID=$!
@ -51,21 +50,20 @@ jobs:
timeout 30 bash -c 'until curl -s http://localhost:3001/mcp > /dev/null; do sleep 0.5; done'
npx -y "@modelcontextprotocol/conformance@${CONFORMANCE_VERSION}" server \
--url http://localhost:3001/mcp \
--suite all \
--spec-version 2026-07-28 \
--requirements 2026-07-28 \
--expected-failures ./conformance/baseline.yml
client-conformance:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "^1.26"
- name: Set up Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
- name: Run conformance tests (full suite)

View file

@ -16,11 +16,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "^1.26"
- name: Check out code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Check docs are up-to-date
run: |
go generate ./...

View file

@ -11,25 +11,29 @@ permissions:
jobs:
vulncheck:
# Don't run scheduled builds on forks.
if: github.event_name != 'schedule' || github.repository == 'modelcontextprotocol/go-sdk'
runs-on: ubuntu-latest
steps:
- name: Run govulncheck
uses: golang/govulncheck-action@b625fbe08f3bccbe446d94fbf87fcc875a4f50ee # v1.0.4
uses: golang/govulncheck-action@032d45514ae346b1db93c04b0c90b841c370344f # v1.1.0
with:
go-version-file: go.mod
go-package: ./...
conformance:
# Don't run scheduled builds on forks.
if: github.event_name != 'schedule' || github.repository == 'modelcontextprotocol/go-sdk'
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "^1.26"
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Run server conformance tests

View file

@ -13,12 +13,12 @@ jobs:
contents: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Configure Git Credentials
run: |
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: 3.x
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV

View file

@ -34,12 +34,12 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
with:
results_file: results.sarif
results_format: sarif
@ -73,6 +73,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
with:
sarif_file: results.sarif

View file

@ -14,9 +14,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "^1.26"
- name: Check formatting
@ -43,9 +43,9 @@ jobs:
go: ["1.25", "1.26"]
steps:
- name: Check out code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: ${{ matrix.go }}
- name: Test
@ -55,9 +55,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.26"
- name: Test with -race

View file

@ -21,6 +21,9 @@ transparently, while adhering to these goals.
1. [Contributing code](#contributing-code)
1. [Adding and updating dependencies](#adding-and-updating-dependencies)
1. [Updating the README](#updating-the-readme)
1. [Versioning](#versioning)
1. [What counts as a breaking change](#what-counts-as-a-breaking-change)
1. [Deprecation](#deprecation)
1. [Timeouts](#timeouts)
1. [Code of conduct](#code-of-conduct)
1. [Governance](#governance)
@ -188,6 +191,57 @@ The CI system will automatically check that the README is up-to-date by running
`go generate ./internal/readme` and verifying no changes result. If you see a CI failure about the
README being out of sync, follow the steps above to regenerate it.
## Versioning
The SDK follows [semantic versioning](https://semver.org), as the Go module
system requires: the major version is part of the module path, so a breaking
release would import as `github.com/modelcontextprotocol/go-sdk/v2`. A breaking
change therefore cannot reach existing users by accident; they have to change
their import path to receive one.
This policy covers the exported API of the SDK's importable packages — `mcp`,
`jsonrpc`, `auth`, `auth/extauth` and `oauthex`. Everything under `internal/`
is not importable outside the module and may change in any release.
Which MCP spec revisions each SDK version speaks is documented in the
[README](README.md#version-compatibility). Support for a new revision arrives
in a minor release; dropping a revision the SDK previously negotiated is a
breaking change.
### What counts as a breaking change
- Removing or renaming an exported identifier.
- Changing the signature of an exported function or method.
- Removing or renaming a field of an exported struct, or changing its type.
- Adding a method to an exported interface that users are meant to implement.
- Changing documented behavior that callers could reasonably rely on.
- Dropping support for an MCP spec revision.
The following are not breaking, and may land in a minor or patch release:
- Adding an exported identifier, or a field to an exported struct.
- Fixing behavior that contradicted its documentation.
- Refactoring that leaves the exported API unchanged.
- Adding support for a new MCP spec revision.
- Raising the minimum Go version to one still supported upstream, per the
[README](README.md#version-compatibility).
### Deprecation
An API on its way out is marked with a `// Deprecated:` comment naming its
replacement, which `gopls` and `staticcheck` surface at call sites. It keeps
working until the next major version, since removing it would itself be a
breaking change.
Protocol features that the *specification* deprecates are a separate matter and
follow the spec's timeline rather than this policy. The features deprecated by
[SEP-2577](https://modelcontextprotocol.io/seps/2577-deprecate-roots-sampling-and-logging)
— roots, sampling and logging — remain supported for at least twelve months, as
noted in the [README](README.md#version-compatibility).
Any change to the exported API, breaking or not, goes through the
[proposal process](#proposals).
## Timeouts
If a contributor hasn't responded to issue questions or PR comments in two weeks,

222
auth/auth_example_test.go Normal file
View file

@ -0,0 +1,222 @@
// Copyright 2026 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by the license
// that can be found in the LICENSE file.
package auth_test
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"sync"
"github.com/modelcontextprotocol/go-sdk/auth"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/modelcontextprotocol/go-sdk/oauthex"
"golang.org/x/oauth2"
)
// savingTokenSource is an oauth2.TokenSource that passes the oauth2 config and
// token to the given saver function each time the access token value changes.
type savingTokenSource struct {
mu sync.Mutex
src oauth2.TokenSource
saver func(*oauth2.Config, *oauth2.Token) error
config *oauth2.Config
accessToken string
}
func (s *savingTokenSource) Token() (*oauth2.Token, error) {
s.mu.Lock()
defer s.mu.Unlock()
tok, err := s.src.Token()
if err != nil {
return nil, err
}
if s.accessToken != tok.AccessToken {
s.accessToken = tok.AccessToken
// This saver implementation always returns nil.
_ = s.saver(s.config, tok)
}
return tok, nil
}
// NewSavingTokenSource persists OAuth 2.0 sessions by intercepting token
// changes from the wrapped oauth2.TokenSource. When this wrapper detects an
// access token change, it calls the provided session saver with the
// oauth2.Config and the new oauth2.Token.
//
// initial is an optional access token the caller may already hold (such as a
// token loaded from storage). It initialises the wrapper's state so that if
// the first wrapped.Token() call returns the same token, does not trigger a
// redundant call to saver(). Pass nil when there is no existing token, in
// which case the first token produced by wrapped.Token() is saved.
func NewSavingTokenSource(wrapped oauth2.TokenSource, config *oauth2.Config, initial *oauth2.Token, saver func(*oauth2.Config, *oauth2.Token) error) oauth2.TokenSource {
if wrapped == nil {
return nil
}
if saver == nil {
return wrapped
}
var accessToken string
if initial != nil {
accessToken = initial.AccessToken
}
return &savingTokenSource{
src: wrapped,
saver: saver,
config: config,
accessToken: accessToken,
}
}
// sessionStore is an in-memory OAuth 2.0 session store used to demonstrate
// persistence. A production implementation would persist the session to disk or
// a secret store.
type sessionStore struct {
config *oauth2.Config
token *oauth2.Token
}
// save persists the given OAuth 2.0 config and token.
func (s *sessionStore) save(config *oauth2.Config, token *oauth2.Token) error {
fmt.Printf("Saving token: %s\n", token.AccessToken)
s.config = config
s.token = token
return nil
}
// restore loads a previously persisted OAuth 2.0 session, if one exists.
func (s *sessionStore) restore() (*oauth2.Config, *oauth2.Token, error) {
if s.config != nil && s.token != nil {
fmt.Println("Restoring session.")
} else {
fmt.Println("No session found to restore.")
}
return s.config, s.token, nil
}
// newMockAuthServer returns an httptest.Server that simulates both an MCP
// resource server requiring authorization and its OAuth authorization server.
func newMockAuthServer() *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/.well-known/oauth-authorization-server":
w.Header().Set("Content-Type", "application/json")
_, _ = fmt.Fprintf(w, `{"issuer": "http://%s", "authorization_endpoint": "http://%s/auth", "token_endpoint": "http://%s/token", "code_challenge_methods_supported": ["S256"]}`, r.Host, r.Host, r.Host)
case "/token":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"access_token": "mock-token", "token_type": "bearer"}`))
default:
// The mock MCP endpoint returns 401 until the client presents a valid
// bearer token.
if r.Header.Get("Authorization") == "Bearer mock-token" {
// A real server would return a valid MCP message here. The empty
// response causes Connect to return an error after authorization,
// which is ignored for this example which demonstrates token
// persistence only.
w.WriteHeader(http.StatusOK)
return
}
w.Header().Set("WWW-Authenticate", "Bearer")
w.WriteHeader(http.StatusUnauthorized)
}
}))
}
// This example shows how OAuth2 session persistence might be implemented. It
// connects twice using a shared in-memory session store: the first connection
// authorizes and saves the session, and the second restores and reuses it.
func Example_persistence() {
// Simulate an MCP server that requires authorization and an OAuth server.
mockServer := newMockAuthServer()
defer mockServer.Close()
// store persists the OAuth2 session across both connections.
store := &sessionStore{}
// connect performs a single client connection, restoring any saved session
// beforehand and saving any new session acquired during authorization.
connect := func() {
// Load the OAuth2 session if available, and use it.
var initialTS oauth2.TokenSource
cfg, tok, err := store.restore()
if err == nil && cfg != nil && tok != nil {
initialTS = NewSavingTokenSource(
cfg.TokenSource(context.Background(), tok),
cfg, tok, store.save,
)
}
// Configure and initialize the AuthorizationCodeHandler with a
// NewTokenSource that saves the session when the access token changes,
// and the InitialTokenSource set to the session loaded via restore().
config := &auth.AuthorizationCodeHandlerConfig{
RedirectURL: "http://localhost/callback",
PreregisteredClient: &oauthex.ClientCredentials{
ClientID: "example",
},
Client: mockServer.Client(),
AuthorizationCodeFetcher: func(ctx context.Context, args *auth.AuthorizationArgs) (*auth.AuthorizationResult, error) {
fmt.Println("No token source found. Transport is calling Authorize()...")
// Extract the generated state from the authorization URL
u, _ := url.Parse(args.URL)
state := u.Query().Get("state")
return &auth.AuthorizationResult{Code: "mock-code", State: state}, nil
},
NewTokenSource: func(ctx context.Context, cfg *oauth2.Config, token *oauth2.Token) (oauth2.TokenSource, error) {
// This save implementation always returns nil.
_ = store.save(cfg, token)
return NewSavingTokenSource(
cfg.TokenSource(ctx, token), cfg, token, store.save,
), nil
},
InitialTokenSource: initialTS,
}
handler, err := auth.NewAuthorizationCodeHandler(config)
if err != nil {
fmt.Printf("Error creating handler: %v\n", err)
return
}
// Set the constructed handler on a transport.
transport := &mcp.StreamableClientTransport{
Endpoint: mockServer.URL + "/sse",
OAuthHandler: handler,
}
// Create a client and attempt to connect using the configured
// transport. The transport will automatically:
// 1. Call TokenSource() to check for an existing session. This will
// return InitialTokenSource, if set.
// 2. Try the MCP endpoint, and encounter a 401 response from the mock
// server.
// 3. Call Authorize() to perform the OAuth flow, which calls
// NewTokenSource. In this example NewTokenSource saves the newly
// acquired session.
// 4. Retry the MCP endpoint with a valid bearer token and get a 200.
client := mcp.NewClient(
&mcp.Implementation{Name: "example", Version: "1.0.0"}, nil,
)
// Response ignored: this example asserts authorization only.
_, _ = client.Connect(context.Background(), transport, nil)
}
// The first connection has no saved session, so it authorizes and saves.
fmt.Println("--- First connect ---")
connect()
// The second connection restores the saved session and reuses it, so no
// further authorization or save occurs.
fmt.Println("--- Second connect ---")
connect()
// Output:
// --- First connect ---
// No session found to restore.
// No token source found. Transport is calling Authorize()...
// Saving token: mock-token
// --- Second connect ---
// Restoring session.
}

View file

@ -14,6 +14,7 @@ import (
"net/url"
"slices"
"strings"
"sync"
"github.com/modelcontextprotocol/go-sdk/internal/authutil"
"github.com/modelcontextprotocol/go-sdk/internal/util"
@ -97,6 +98,15 @@ type AuthorizationCodeHandlerConfig struct {
// See [AuthorizationCodeFetcher] for details.
AuthorizationCodeFetcher AuthorizationCodeFetcher
// ScopeFilter, if non-nil, is called with the scopes discovered from the
// protected resource metadata (or WWW-Authenticate challenge) and returns
// the scopes to request during authorization. It gives the client full
// control to narrow, extend, or reorder that set — e.g. dropping Gmail's
// gmail.metadata, which the Gmail API refuses to combine with the search "q"
// parameter even alongside gmail.readonly. It runs before offline_access
// (see RequestRefreshToken) and the step-up union, so neither is affected.
ScopeFilter func(discovered []string) []string
// RequestRefreshToken indicates that the client intends to use refresh
// tokens and is capable of storing them securely.
//
@ -114,6 +124,11 @@ type AuthorizationCodeHandlerConfig struct {
// See https://modelcontextprotocol.io/seps/2207-oidc-refresh-token-guidance.
RequestRefreshToken bool
// AcceptUnadvertisedIss accepts a matching RFC 9207 iss even when the
// authorization server metadata omits authorization_response_iss_parameter_supported.
// The zero value (false) keeps the historical reject-unadvertised-iss behavior.
AcceptUnadvertisedIss bool
// Client is an optional HTTP client to use for HTTP requests.
// It is used for the following requests:
// - Fetching Protected Resource Metadata
@ -126,6 +141,24 @@ type AuthorizationCodeHandlerConfig struct {
// https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#server-side-request-forgery-ssrf
// If not provided, http.DefaultClient will be used.
Client *http.Client
// NewTokenSource is an optional function that can be set to construct the
// token source that will be used by the [AuthorizationCodeHandler]. If
// non-nil, it is called after the authorization code is successfully
// exchanged for a token in [AuthorizationCodeHandler.Authorize]
// to obtain the [oauth2.TokenSource] returned by
// [AuthorizationCodeHandler.TokenSource]. Implementations must use the
// provided context, which is properly configured for constructing a
// TokenSource. The default is to call [oauth2.Config.TokenSource].
NewTokenSource func(context.Context, *oauth2.Config, *oauth2.Token) (oauth2.TokenSource, error)
// InitialTokenSource is an optional field that can be set to inject the
// token source that will be used by the [AuthorizationCodeHandler]. If
// non-nil, it is set as the token source that will be returned by
// [AuthorizationCodeHandler.TokenSource] during handler initialization.
// The default is nil, which means no token source has been set initially,
// and will trigger a call to [AuthorizationCodeHandler.Authorize].
InitialTokenSource oauth2.TokenSource
}
// AuthorizationCodeHandler is an implementation of [OAuthHandler] that uses
@ -133,6 +166,9 @@ type AuthorizationCodeHandlerConfig struct {
type AuthorizationCodeHandler struct {
config *AuthorizationCodeHandlerConfig
// mu protects concurrent access to tokenSource and grantedScopes.
mu sync.RWMutex
// tokenSource is the token source to use for authorization.
tokenSource oauth2.TokenSource
@ -143,6 +179,8 @@ type AuthorizationCodeHandler struct {
var _ OAuthHandler = (*AuthorizationCodeHandler)(nil)
func (h *AuthorizationCodeHandler) TokenSource(ctx context.Context) (oauth2.TokenSource, error) {
h.mu.RLock()
defer h.mu.RUnlock()
return h.tokenSource, nil
}
@ -199,6 +237,7 @@ func NewAuthorizationCodeHandler(config *AuthorizationCodeHandlerConfig) (*Autho
}
return &AuthorizationCodeHandler{
config: config,
tokenSource: config.InitialTokenSource,
grantedScopes: make(map[string][]string),
}, nil
}
@ -293,6 +332,12 @@ func (h *AuthorizationCodeHandler) Authorize(ctx context.Context, req *http.Requ
requestedScopes = prm.ScopesSupported
}
// Let the client adjust the discovered scopes before offline_access and the
// step-up union below so neither is affected.
if h.config.ScopeFilter != nil {
requestedScopes = h.config.ScopeFilter(requestedScopes)
}
// SEP-2207: when the client desires refresh tokens and the Authorization
// Server advertises offline_access support, add it to the requested scopes.
if h.config.RequestRefreshToken &&
@ -304,7 +349,10 @@ func (h *AuthorizationCodeHandler) Authorize(ctx context.Context, req *http.Requ
// Accumulate scopes: union previously granted scopes with the newly
// challenged scopes so that step-up authorization does not lose
// permissions granted in earlier rounds (SEP-2350).
requestedScopes = authutil.UnionScopes(h.grantedScopes[asm.Issuer], requestedScopes)
h.mu.RLock()
granted := h.grantedScopes[asm.Issuer]
h.mu.RUnlock()
requestedScopes = authutil.UnionScopes(granted, requestedScopes)
cfg := &oauth2.Config{
ClientID: resolvedClientConfig.clientID,
@ -324,7 +372,7 @@ func (h *AuthorizationCodeHandler) Authorize(ctx context.Context, req *http.Requ
// Purposefully leaving the error unwrappable so it can be handled by the caller.
return err
}
if err := validateIssuerResponse(authRes.Iss, asm.Issuer, asm.AuthorizationResponseIssParameterSupported); err != nil {
if err := validateIssuerResponse(authRes.Iss, asm.Issuer, asm.AuthorizationResponseIssParameterSupported, h.config.AcceptUnadvertisedIss); err != nil {
return err
}
@ -573,10 +621,14 @@ func (h *AuthorizationCodeHandler) getAuthorizationCode(ctx context.Context, cfg
}
// validateIssuerResponse validates the "iss" parameter in an authorization response
// per [RFC 9207].
// per [RFC 9207]. When the server advertises authorization_response_iss_parameter_supported,
// iss is required and must match expectedIssuer. When it does not advertise support,
// an empty iss is accepted; a present iss is compared to expectedIssuer (RFC 9207 §2.4)
// and a mismatch is always rejected. A matching unadvertised iss is accepted only when
// acceptUnadvertisedIss is true (local policy); the default is the historical reject.
//
// [RFC 9207]: https://www.rfc-editor.org/rfc/rfc9207
func validateIssuerResponse(iss, expectedIssuer string, issParameterSupported bool) error {
func validateIssuerResponse(iss, expectedIssuer string, issParameterSupported, acceptUnadvertisedIss bool) error {
if issParameterSupported {
if iss == "" {
return fmt.Errorf("authorization server advertises RFC 9207 iss parameter support but none was received in the authorization response")
@ -584,12 +636,17 @@ func validateIssuerResponse(iss, expectedIssuer string, issParameterSupported bo
if iss != expectedIssuer {
return fmt.Errorf("authorization response issuer %q does not match expected issuer %q", iss, expectedIssuer)
}
} else {
if iss != "" {
return fmt.Errorf("authorization server does not advertise RFC 9207 iss parameter support but iss was received in the authorization response")
}
return nil
}
if iss == "" {
return nil
}
if iss != expectedIssuer {
return fmt.Errorf("authorization response issuer %q does not match expected issuer %q", iss, expectedIssuer)
}
if !acceptUnadvertisedIss {
return fmt.Errorf("authorization server does not advertise RFC 9207 iss parameter support but iss was received in the authorization response")
}
return nil
}
@ -615,23 +672,42 @@ func (h *AuthorizationCodeHandler) exchangeAuthorizationCode(ctx context.Context
// completes. Use a background context that still carries the configured HTTP
// client so refreshes keep working for the life of the token source.
refreshCtx := context.WithValue(context.Background(), oauth2.HTTPClient, h.config.Client)
h.tokenSource = cfg.TokenSource(refreshCtx, token)
var ts oauth2.TokenSource
if h.config.NewTokenSource == nil {
ts = cfg.TokenSource(refreshCtx, token)
} else {
var err error
ts, err = h.config.NewTokenSource(refreshCtx, cfg, token)
if err != nil {
return fmt.Errorf("constructing token source failed: %w", err)
}
}
h.mu.Lock()
h.tokenSource = ts
h.mu.Unlock()
return nil
}
// updateGrantedScopes updates the granted scopes based on the token source and requested scopes.
func (h *AuthorizationCodeHandler) updateGrantedScopes(issuer string, requestedScopes []string) error {
if h.tokenSource == nil {
h.mu.RLock()
ts := h.tokenSource
h.mu.RUnlock()
if ts == nil {
return nil
}
tok, err := h.tokenSource.Token()
tok, err := ts.Token()
if err != nil {
return err
}
h.mu.Lock()
if tokenScopes := authutil.ScopesFromToken(tok); tokenScopes == nil {
h.grantedScopes[issuer] = requestedScopes
} else {
h.grantedScopes[issuer] = tokenScopes
}
h.mu.Unlock()
return nil
}

View file

@ -919,11 +919,12 @@ func TestValidateIssuerResponse(t *testing.T) {
const expectedIssuer = "https://auth.example.com"
tests := []struct {
name string
iss string
issSupported bool
wantErr bool
wantErrContains string
name string
iss string
issSupported bool
acceptUnadvertisedIss bool
wantErr bool
wantErrContains string
}{
{
name: "ValidIss",
@ -949,11 +950,41 @@ func TestValidateIssuerResponse(t *testing.T) {
iss: "",
issSupported: false,
},
{
// Default / zero-value policy: reject a matching unadvertised iss
// (released v1.7.0 behavior).
name: "UnadvertisedIssCorrectRejectedByDefault",
iss: expectedIssuer,
issSupported: false,
wantErr: true,
wantErrContains: "does not advertise",
},
{
name: "UnadvertisedIssCorrectAcceptedWhenOptedIn",
iss: expectedIssuer,
issSupported: false,
acceptUnadvertisedIss: true,
},
{
name: "UnadvertisedIssWrong",
iss: "https://attacker.example.com",
issSupported: false,
wantErr: true,
wantErrContains: "does not match expected issuer",
},
{
name: "UnadvertisedIssWrongStillRejectedWhenOptedIn",
iss: "https://attacker.example.com",
issSupported: false,
acceptUnadvertisedIss: true,
wantErr: true,
wantErrContains: "does not match expected issuer",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateIssuerResponse(tt.iss, expectedIssuer, tt.issSupported)
err := validateIssuerResponse(tt.iss, expectedIssuer, tt.issSupported, tt.acceptUnadvertisedIss)
if tt.wantErr {
if err == nil {
t.Fatalf("validateIssuerResponse() = nil, want error containing %q", tt.wantErrContains)
@ -968,6 +999,112 @@ func TestValidateIssuerResponse(t *testing.T) {
}
}
// TestAuthorize_AcceptUnadvertisedIssPlumbing checks that Authorize passes
// AuthorizationCodeHandlerConfig.AcceptUnadvertisedIss through to
// validateIssuerResponse. The fake server returns iss without advertising
// authorization_response_iss_parameter_supported.
func TestAuthorize_AcceptUnadvertisedIssPlumbing(t *testing.T) {
tests := []struct {
name string
acceptUnadvertisedIss bool
wantErrContains string
}{
{
name: "ZeroValueRejects",
wantErrContains: "does not advertise",
},
{
name: "OptInAccepts",
acceptUnadvertisedIss: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
authServer := oauthtest.NewFakeAuthorizationServer(oauthtest.Config{
UnadvertiseIssParameter: true,
RegistrationConfig: &oauthtest.RegistrationConfig{
PreregisteredClients: map[string]oauthtest.ClientInfo{
"test_client_id": {
Secret: "test_client_secret",
RedirectURIs: []string{"http://localhost:12345/callback"},
},
},
},
})
authServer.Start(t)
resourceMux := http.NewServeMux()
resourceServer := httptest.NewServer(resourceMux)
t.Cleanup(resourceServer.Close)
resourceURL := resourceServer.URL + "/resource"
resourceMux.Handle("/.well-known/oauth-protected-resource/resource", ProtectedResourceMetadataHandler(&oauthex.ProtectedResourceMetadata{
Resource: resourceURL,
AuthorizationServers: []string{authServer.URL()},
}))
handler, err := NewAuthorizationCodeHandler(&AuthorizationCodeHandlerConfig{
RedirectURL: "http://localhost:12345/callback",
AcceptUnadvertisedIss: tt.acceptUnadvertisedIss,
PreregisteredClient: &oauthex.ClientCredentials{
ClientID: "test_client_id",
ClientSecretAuth: &oauthex.ClientSecretAuth{
ClientSecret: "test_client_secret",
},
},
AuthorizationCodeFetcher: func(ctx context.Context, args *AuthorizationArgs) (*AuthorizationResult, error) {
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
resp, err := client.Get(args.URL)
if err != nil {
return nil, fmt.Errorf("failed to visit auth URL: %v", err)
}
defer resp.Body.Close()
location, err := resp.Location()
if err != nil {
return nil, fmt.Errorf("failed to get location header: %v", err)
}
return &AuthorizationResult{
Code: location.Query().Get("code"),
State: location.Query().Get("state"),
Iss: location.Query().Get("iss"),
}, nil
},
})
if err != nil {
t.Fatalf("NewAuthorizationCodeHandler failed: %v", err)
}
req := httptest.NewRequest(http.MethodGet, resourceURL, nil)
resp := &http.Response{
StatusCode: http.StatusUnauthorized,
Header: make(http.Header),
Body: http.NoBody,
Request: req,
}
resp.Header.Set(
"WWW-Authenticate",
"Bearer resource_metadata="+resourceServer.URL+"/.well-known/oauth-protected-resource/resource",
)
err = handler.Authorize(context.Background(), req, resp)
if tt.wantErrContains != "" {
if err == nil {
t.Fatalf("Authorize() = nil, want error containing %q", tt.wantErrContains)
}
if !strings.Contains(err.Error(), tt.wantErrContains) {
t.Fatalf("Authorize() error = %q, want it to contain %q", err.Error(), tt.wantErrContains)
}
return
}
if err != nil {
t.Fatalf("Authorize() unexpected error = %v", err)
}
})
}
}
func TestInferApplicationType(t *testing.T) {
tests := []struct {
name string
@ -1222,6 +1359,107 @@ func TestAuthorize_OfflineAccessScope(t *testing.T) {
}
}
func TestAuthorize_ScopeFilter(t *testing.T) {
// advertised is delivered via the WWW-Authenticate "scope" challenge, which
// the handler treats as the discovered scopes passed to ScopeFilter.
const advertised = "gmail.metadata gmail.readonly gmail.compose"
tests := []struct {
name string
filter func(discovered []string) []string
want []string // requested scopes, order-independent
}{
{
name: "nil filter leaves scopes unchanged",
filter: nil,
want: []string{"gmail.metadata", "gmail.readonly", "gmail.compose"},
},
{
name: "filter drops a scope",
filter: func(d []string) []string {
return slices.DeleteFunc(slices.Clone(d), func(s string) bool { return s == "gmail.metadata" })
},
want: []string{"gmail.readonly", "gmail.compose"},
},
{
name: "filter replaces the set entirely",
filter: func([]string) []string { return []string{"gmail.readonly"} },
want: []string{"gmail.readonly"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
authServer := oauthtest.NewFakeAuthorizationServer(oauthtest.Config{
RegistrationConfig: &oauthtest.RegistrationConfig{
PreregisteredClients: map[string]oauthtest.ClientInfo{
"test_client_id": {
Secret: "test_client_secret",
RedirectURIs: []string{"http://localhost:12345/callback"},
},
},
},
})
authServer.Start(t)
resourceMux := http.NewServeMux()
resourceServer := httptest.NewServer(resourceMux)
t.Cleanup(resourceServer.Close)
resourceURL := resourceServer.URL + "/resource"
resourceMux.Handle("/.well-known/oauth-protected-resource/resource", ProtectedResourceMetadataHandler(&oauthex.ProtectedResourceMetadata{
Resource: resourceURL,
AuthorizationServers: []string{authServer.URL()},
}))
var capturedAuthURL string
handler, err := NewAuthorizationCodeHandler(&AuthorizationCodeHandlerConfig{
RedirectURL: "http://localhost:12345/callback",
PreregisteredClient: &oauthex.ClientCredentials{
ClientID: "test_client_id",
ClientSecretAuth: &oauthex.ClientSecretAuth{ClientSecret: "test_client_secret"},
},
ScopeFilter: tt.filter,
AuthorizationCodeFetcher: func(ctx context.Context, args *AuthorizationArgs) (*AuthorizationResult, error) {
capturedAuthURL = args.URL
return nil, fmt.Errorf("stop after capturing URL")
},
})
if err != nil {
t.Fatalf("NewAuthorizationCodeHandler failed: %v", err)
}
req := httptest.NewRequest(http.MethodGet, resourceURL, nil)
resp := &http.Response{
StatusCode: http.StatusUnauthorized,
Header: make(http.Header),
Body: http.NoBody,
Request: req,
}
resp.Header.Set("WWW-Authenticate", fmt.Sprintf(
"Bearer resource_metadata=%s/.well-known/oauth-protected-resource/resource, scope=%q",
resourceServer.URL, advertised))
handler.Authorize(context.Background(), req, resp)
if capturedAuthURL == "" {
t.Fatal("AuthorizationCodeFetcher was not called")
}
u, err := url.Parse(capturedAuthURL)
if err != nil {
t.Fatalf("failed to parse captured auth URL: %v", err)
}
// Compare as a set: UnionScopes (applied downstream) returns map keys,
// so the order of the requested scope parameter is not deterministic.
got := strings.Fields(u.Query().Get("scope"))
want := slices.Clone(tt.want)
slices.Sort(got)
slices.Sort(want)
if !slices.Equal(got, want) {
t.Errorf("requested scopes = %v, want %v (any order)", got, want)
}
})
}
}
// validConfig for test to create an AuthorizationCodeHandler using its constructor.
// Values that are relevant to the test should be set explicitly.
func validConfig() *AuthorizationCodeHandlerConfig {
@ -1233,3 +1471,92 @@ func validConfig() *AuthorizationCodeHandlerConfig {
},
}
}
func TestNewTokenSource(t *testing.T) {
// mock the /token endpoint to successfully return an access token on code exchange
mockTS := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/token" {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"access_token": "test_token", "token_type": "bearer"}`))
return
}
w.WriteHeader(http.StatusNotFound)
}))
defer mockTS.Close()
// configure the handler and set NewTokenSource
var called bool
handler, err := NewAuthorizationCodeHandler(&AuthorizationCodeHandlerConfig{
RedirectURL: "http://localhost/callback",
PreregisteredClient: &oauthex.ClientCredentials{
ClientID: "test_client",
},
AuthorizationCodeFetcher: func(ctx context.Context, args *AuthorizationArgs) (*AuthorizationResult, error) {
u, _ := url.Parse(args.URL)
return &AuthorizationResult{
Code: "test_code",
State: u.Query().Get("state"),
}, nil
},
NewTokenSource: func(ctx context.Context, cfg *oauth2.Config, token *oauth2.Token) (oauth2.TokenSource, error) {
called = true
return oauth2.StaticTokenSource(token), nil
},
Client: mockTS.Client(),
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Simulate a 401 response from a resource server.
// The WWW-Authenticate: Bearer header triggers the authorization logic.
req := httptest.NewRequest(http.MethodGet, mockTS.URL, nil)
resp := &http.Response{
StatusCode: http.StatusUnauthorized,
Header: make(http.Header),
Body: http.NoBody,
Request: req,
}
resp.Header.Set("WWW-Authenticate", "Bearer")
// Authorize and confirm NewTokenSource was called.
err = handler.Authorize(t.Context(), req, resp)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !called {
t.Error("expected NewTokenSource to be called")
}
}
func TestInitialTokenSource(t *testing.T) {
handler, err := NewAuthorizationCodeHandler(&AuthorizationCodeHandlerConfig{
RedirectURL: "http://localhost:12345/callback",
PreregisteredClient: &oauthex.ClientCredentials{
ClientID: "test_client_id",
},
AuthorizationCodeFetcher: func(ctx context.Context, args *AuthorizationArgs) (*AuthorizationResult, error) {
return nil, nil
},
InitialTokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: "set_token"}),
})
if err != nil {
t.Fatalf("NewAuthorizationCodeHandler failed: %v", err)
}
ts, err := handler.TokenSource(t.Context())
if err != nil {
t.Fatalf("failed to get token source: %v", err)
}
if ts == nil {
t.Fatal("expected token source to be non-nil")
}
tok, err := ts.Token()
if err != nil {
t.Fatalf("failed to get Token: %v", err)
}
if tok.AccessToken != "set_token" {
t.Errorf("expected access token 'set_token', got '%s'", tok.AccessToken)
}
}

View file

@ -44,7 +44,12 @@ func authorizationServerMetadataURLs(issuerURL string) []string {
return nil
}
if baseURL.Path == "" {
// RFC 8414, section 3.1 requires any terminating "/" to be removed from the
// issuer identifier before the well-known suffix is inserted, so an issuer
// such as "https://auth.example.com/" has no path component at all.
issuerPath := strings.Trim(baseURL.Path, "/")
if issuerPath == "" {
// "OAuth 2.0 Authorization Server Metadata".
baseURL.Path = "/.well-known/oauth-authorization-server"
urls = append(urls, baseURL.String())
@ -54,15 +59,14 @@ func authorizationServerMetadataURLs(issuerURL string) []string {
return urls
}
originalPath := baseURL.Path
// "OAuth 2.0 Authorization Server Metadata with path insertion".
baseURL.Path = "/.well-known/oauth-authorization-server/" + strings.TrimLeft(originalPath, "/")
baseURL.Path = "/.well-known/oauth-authorization-server/" + issuerPath
urls = append(urls, baseURL.String())
// "OpenID Connect Discovery 1.0 with path insertion".
baseURL.Path = "/.well-known/openid-configuration/" + strings.TrimLeft(originalPath, "/")
baseURL.Path = "/.well-known/openid-configuration/" + issuerPath
urls = append(urls, baseURL.String())
// "OpenID Connect Discovery 1.0 with path appending".
baseURL.Path = "/" + strings.Trim(originalPath, "/") + "/.well-known/openid-configuration"
baseURL.Path = "/" + issuerPath + "/.well-known/openid-configuration"
urls = append(urls, baseURL.String())
return urls

View file

@ -6,11 +6,81 @@ package auth
import (
"net/http"
"slices"
"testing"
"github.com/modelcontextprotocol/go-sdk/internal/oauthtest"
)
func TestAuthorizationServerMetadataURLs(t *testing.T) {
tests := []struct {
issuerURL string
want []string
}{
{
issuerURL: "https://auth.example.com",
want: []string{
"https://auth.example.com/.well-known/oauth-authorization-server",
"https://auth.example.com/.well-known/openid-configuration",
},
},
{
issuerURL: "https://auth.example.com/",
want: []string{
"https://auth.example.com/.well-known/oauth-authorization-server",
"https://auth.example.com/.well-known/openid-configuration",
},
},
{
issuerURL: "https://auth.example.com/tenant1",
want: []string{
"https://auth.example.com/.well-known/oauth-authorization-server/tenant1",
"https://auth.example.com/.well-known/openid-configuration/tenant1",
"https://auth.example.com/tenant1/.well-known/openid-configuration",
},
},
{
issuerURL: "https://auth.example.com/tenant1/",
want: []string{
"https://auth.example.com/.well-known/oauth-authorization-server/tenant1",
"https://auth.example.com/.well-known/openid-configuration/tenant1",
"https://auth.example.com/tenant1/.well-known/openid-configuration",
},
},
}
for _, tt := range tests {
t.Run(tt.issuerURL, func(t *testing.T) {
if got := authorizationServerMetadataURLs(tt.issuerURL); !slices.Equal(got, tt.want) {
t.Errorf("authorizationServerMetadataURLs(%q) =\n%v\nwant\n%v", tt.issuerURL, got, tt.want)
}
})
}
}
// TestGetAuthServerMetadataTrailingSlashIssuer checks that an issuer identifier
// written with a terminating slash still finds metadata served at the root
// well-known path.
func TestGetAuthServerMetadataTrailingSlashIssuer(t *testing.T) {
s := oauthtest.NewFakeAuthorizationServer(oauthtest.Config{
MetadataEndpointConfig: &oauthtest.MetadataEndpointConfig{
ServeOAuthInsertedEndpoint: true,
},
})
s.Start(t)
got, err := GetAuthServerMetadata(t.Context(), s.URL()+"/", http.DefaultClient)
if err != nil {
t.Fatalf("GetAuthServerMetadata() error = %v, want nil", err)
}
if got == nil {
t.Fatal("GetAuthServerMetadata() got nil, want metadata")
}
if got.Issuer != s.URL() {
t.Errorf("GetAuthServerMetadata() issuer = %q, want %q", got.Issuer, s.URL())
}
}
func TestGetAuthServerMetadata(t *testing.T) {
tests := []struct {
name string

View file

@ -1,13 +1,14 @@
server: [] # All tests pass!
client:
# OAuth mechanisms not yet implemented by the Go MCP SDK.
# OAuth mechanisms not implemented by the Go MCP SDK.
- auth/client-credentials-basic
- auth/client-credentials-jwt
- auth/enterprise-managed-authorization
# Upstream conformance mock inconsistency: the scenario's hand-rolled
# server/discover handler advertises "2026-07-28" but delegates every
# other method to a pinned @modelcontextprotocol/sdk that rejects that
# version at the HTTP layer.
- json-schema-ref-no-deref
# SEP-1932 (DPoP Sender-Constrained Tokens) client-side support not yet
# implemented by the Go MCP SDK.
- auth/dpop
- auth/dpop-nonce
# SEP-1933 (Workload Identity Federation JWT-bearer) client-side support
# not yet implemented by the Go MCP SDK.
- auth/wif-jwt-bearer

View file

@ -49,6 +49,7 @@ func init() {
registerScenario("elicitation-sep1034-client-defaults", runElicitationDefaultsClient)
registerScenario("sse-retry", runSSERetryClient)
registerScenario("json-schema-ref-no-deref", runJSONSchemaRefNoDerefClient)
registerScenario("json-schema-2020-12-preservation", runJSONSchemaPreservationClient)
registerScenario("sep-2322-client-request-state", runMrtrClient)
registerScenario("http-standard-headers", runHTTPStandardHeadersClient)
registerScenario("http-custom-headers", runHTTPCustomHeadersClient)
@ -343,6 +344,46 @@ func runJSONSchemaRefNoDerefClient(ctx context.Context, serverURL string, _ map[
return nil
}
// ============================================================================
// json-schema-2020-12-preservation scenario (SEP-1613, SEP-2106)
// ============================================================================
// runJSONSchemaPreservationClient echoes the focal tool's inputSchema back to
// the referee verbatim, through the permissive json_schema_echo tool, so that
// it can observe which 2020-12 keywords survived the client's parse of
// tools/list. The Go SDK surfaces a server's schema as the raw JSON object it
// received, so nothing is stripped.
func runJSONSchemaPreservationClient(ctx context.Context, serverURL string, _ map[string]any) error {
session, err := connectToServer(ctx, serverURL)
if err != nil {
return err
}
defer session.Close()
tools, err := session.ListTools(ctx, nil)
if err != nil {
return fmt.Errorf("session.ListTools(): %v", err)
}
const focalTool = "json_schema_2020_12_tool"
idx := slices.IndexFunc(tools.Tools, func(t *mcp.Tool) bool {
return t.Name == focalTool
})
if idx == -1 {
return fmt.Errorf("tool %q not found", focalTool)
}
const echoTool = "json_schema_echo"
if _, err := session.CallTool(ctx, &mcp.CallToolParams{
Name: echoTool,
Arguments: map[string]any{"schema": tools.Tools[idx].InputSchema},
}); err != nil {
return fmt.Errorf("session.CallTool(%q): %v", echoTool, err)
}
return nil
}
// ============================================================================
// SEP-2322 multi-round-trip client scenario
// ============================================================================

View file

@ -2,8 +2,11 @@
# Support for MCP client features
1. [Roots](#roots)
1. [Roots list changed](#roots-list-changed)
1. [Sampling](#sampling)
1. [Elicitation](#elicitation)
1. [Schema defaults and enums](#schema-defaults-and-enums)
1. [Completing a URL elicitation](#completing-a-url-elicitation)
1. [Multi Round-Trip Requests](#multi-round-trip-requests)
1. [Capabilities](#capabilities)
1. [Capability inference](#capability-inference)
@ -38,7 +41,7 @@ method. To receive notifications about root changes, set
[`ServerOptions.RootsListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.RootsListChangedHandler).
For protocol versions `2026-07-28` and later, `ListRoots` requests are
delivered via the
[Multi Round-Trip Requests](protocol.md#multi-round-trip-requests-mrtr)
[Multi Round-Trip Requests](#multi-round-trip-requests)
pattern.
```go
@ -91,6 +94,67 @@ func Example_roots() {
}
```
### Roots list changed
`Client.AddRoots` and `Client.RemoveRoots` notify every connected server that
the list changed. Servers observe this through
[`ServerOptions.RootsListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.RootsListChangedHandler);
as with the server-side list-changed notifications, it reports only that
something changed, so read the list back with `ServerSession.ListRoots`.
```go
func Example_rootsListChanged() {
ctx := context.Background()
changed := make(chan struct{}, 2)
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{
RootsListChangedHandler: func(context.Context, *mcp.RootsListChangedRequest) {
changed <- struct{}{}
},
})
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
c.AddRoots(&mcp.Root{URI: "file:///project"})
t1, t2 := mcp.NewInMemoryTransports()
ss, err := s.Connect(ctx, t1, nil)
if err != nil {
log.Fatal(err)
}
defer ss.Close()
// ListRoots is a server-initiated request, so this session negotiates a
// protocol version that still allows one.
cs, err := c.Connect(ctx, t2, &mcp.ClientSessionOptions{ProtocolVersion: "2025-11-25"})
if err != nil {
log.Fatal(err)
}
defer cs.Close()
// Roots added after the client connects notify every connected server.
c.AddRoots(&mcp.Root{URI: "file:///scratch"})
<-changed
// The notification says only that the list changed, so read it back.
res, err := ss.ListRoots(ctx, nil)
if err != nil {
log.Fatal(err)
}
for _, root := range res.Roots {
fmt.Println(root.URI)
}
c.RemoveRoots("file:///scratch")
<-changed
fmt.Println("roots changed again")
// Output:
// file:///project
// file:///scratch
// roots changed again
}
```
## Sampling
> **Note:** The sampling feature is deprecated as of protocol version
@ -113,7 +177,7 @@ This function is invoked whenever the server requests sampling.
For protocol versions `2026-07-28` and later, sampling requests are
delivered via the
[Multi Round-Trip Requests](protocol.md#multi-round-trip-requests-mrtr)
[Multi Round-Trip Requests](#multi-round-trip-requests)
pattern.
```go
@ -183,7 +247,7 @@ you must declare that capability explicitly (see [Capabilities](#capabilities))
For protocol versions `2026-07-28` and later, elicitation requests are
delivered via the
[Multi Round-Trip Requests](protocol.md#multi-round-trip-requests-mrtr)
[Multi Round-Trip Requests](#multi-round-trip-requests)
pattern.
```go
@ -235,6 +299,154 @@ func Example_elicitation() {
}
```
### Schema defaults and enums
`ElicitParams.RequestedSchema` is a flat schema of primitive fields, which the
client renders as a form. Two field keywords shape that form.
A `Default` ([SEP-1034](https://modelcontextprotocol.io/seps/1034--support-default-values-for-all-primitive-types-in)) prefills a
field. When the user accepts without supplying it, the SDK fills the field in
from the schema before the result reaches either side's caller — the client
does so after its elicitation handler returns, and `ServerSession.Elicit` does
so again on receipt. This is unconditional; there is no opt-in flag. Marking a
defaulted field `Required` defeats it: accepted content is validated against
the schema before defaults are applied, so an answer that omits the field is
rejected rather than defaulted.
An `Enum` ([SEP-1330](https://modelcontextprotocol.io/seps/1330-elicitation-enum-schema-improvements-and-standards)) restricts a
field to a fixed set of values, which the client renders as a choice. Enums
are supported only on `"string"` fields; declaring one on another type is
rejected. To label the choices, set the legacy `enumNames` keyword through
`Schema.Extra`, with exactly one name per enum value — a mismatched length is
rejected.
```go
func Example_elicitationSchema() {
ctx := context.Background()
ct, st := mcp.NewInMemoryTransports()
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
mcp.AddTool(s, &mcp.Tool{Name: "export_report"}, func(_ context.Context, req *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, any, error) {
if len(req.Params.InputResponses) == 0 {
return &mcp.CallToolResult{
InputRequests: mcp.InputRequestMap{"format": &mcp.ElicitParams{
Message: "Export quarterly-sales as which format?",
RequestedSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"format": {
Type: "string",
Title: "Format",
Enum: []any{"pdf", "csv"},
Default: json.RawMessage(`"pdf"`),
Extra: map[string]any{"enumNames": []any{"PDF document", "CSV spreadsheet"}},
},
},
},
}},
}, nil, nil
}
res := req.Params.InputResponses["format"].(*mcp.ElicitResult)
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: "Exported as " + res.Content["format"].(string)}},
}, nil, nil
})
if _, err := s.Connect(ctx, st, nil); err != nil {
log.Fatal(err)
}
// The user accepts without filling anything in.
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
ElicitationHandler: func(context.Context, *mcp.ElicitRequest) (*mcp.ElicitResult, error) {
return &mcp.ElicitResult{Action: "accept", Content: map[string]any{}}, nil
},
})
cs, err := c.Connect(ctx, ct, nil)
if err != nil {
log.Fatal(err)
}
defer cs.Close()
res, err := cs.CallTool(ctx, &mcp.CallToolParams{Name: "export_report"})
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Content[0].(*mcp.TextContent).Text)
// Output: Exported as pdf
}
```
### Completing a URL elicitation
In URL mode the user finishes out of band, in a browser, so nothing in the
elicitation result tells the client when they are done. The server signals that
with
[`ServerSession.NotifyElicitationComplete`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession.NotifyElicitationComplete),
passing the same `ElicitationID` the request carried; the client observes it
through
[`ClientOptions.ElicitationCompleteHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ElicitationCompleteHandler).
Send it from whatever endpoint the hosted flow redirects back to.
The notification matters most when a handler rejects a request with
[`URLElicitationRequiredError`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#URLElicitationRequiredError):
the client parks the original request until a notification names that
`ElicitationID`, and then retries it automatically. Until one arrives, the
client waits.
```go
func Example_elicitationComplete() {
ctx := context.Background()
ct, st := mcp.NewInMemoryTransports()
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
ss, err := s.Connect(ctx, st, nil)
if err != nil {
log.Fatal(err)
}
defer ss.Close()
done := make(chan struct{})
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
Capabilities: &mcp.ClientCapabilities{
Elicitation: &mcp.ElicitationCapabilities{URL: &mcp.URLElicitationCapabilities{}},
},
ElicitationHandler: func(_ context.Context, req *mcp.ElicitRequest) (*mcp.ElicitResult, error) {
fmt.Println("opening", req.Params.URL)
return &mcp.ElicitResult{Action: "accept"}, nil
},
ElicitationCompleteHandler: func(_ context.Context, req *mcp.ElicitationCompleteNotificationRequest) {
fmt.Println("flow finished:", req.Params.ElicitationID)
close(done)
},
})
cs, err := c.Connect(ctx, ct, &mcp.ClientSessionOptions{ProtocolVersion: "2025-11-25"})
if err != nil {
log.Fatal(err)
}
defer cs.Close()
const elicitationID = "connect-calendar-1"
if _, err := ss.Elicit(ctx, &mcp.ElicitParams{
Message: "Grant calendar access",
URL: "https://calendar.example.com/consent?state=" + elicitationID,
ElicitationID: elicitationID,
}); err != nil {
log.Fatal(err)
}
// The hosted page redirects back to the server, whose callback endpoint
// signals that the user is done.
if err := ss.NotifyElicitationComplete(ctx, &mcp.ElicitationCompleteParams{ElicitationID: elicitationID}); err != nil {
log.Fatal(err)
}
<-done
// Output:
// opening https://calendar.example.com/consent?state=connect-calendar-1
// flow finished: connect-calendar-1
}
```
## Multi Round-Trip Requests
[SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)
@ -257,14 +469,18 @@ default. The middleware:
The middleware is enabled by default. To opt out, set
[`ClientOptions.MultiRoundTrip.Disabled = true`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#MultiRoundTripOptions);
the client will then surface `InputRequiredResult` values directly to the
caller and your code must fulfil the requests manually.
the client will then surface input-required results directly to the caller
(the returned `CallToolResult`, `GetPromptResult`, or `ReadResourceResult`
will report `NeedsInput() == true` and expose the server's `InputRequests`
and opaque `RequestState`). Your code must fulfil each request and re-issue
the original call with `InputResponses` set and `RequestState` echoed
back.
For legacy (`<= 2025-11-25`) servers, the SDK transparently sends server
requests on the legacy server-initiated channel; the MRTR machinery is a
no-op in that direction. For legacy clients talking to MRTR-style servers,
the server SDK applies the inverse compatibility shim — see the
[server-side documentation](server.md#multi-round-trip-requests-mrtr).
[server-side documentation](server.md#multi-round-trip-requests).
## Capabilities

View file

@ -18,6 +18,59 @@
## `MCPGODEBUG` history
### 1.9.0
Options listed below were added and will be removed in the 1.11.0 version of the SDK.
- `structuredcontentfloat64` added. If set to `1`, numbers in
`CallToolResult.StructuredContent` are decoded as `float64`, restoring the
previous behavior. The default behavior decodes numbers as `json.Number` so
that large integers and other exact JSON values do not silently lose
precision.
### 1.8.0
Options listed below were added and will be removed in the 1.9.0 version of the SDK.
- `plaintextstatefulrejection` added. If set to `1`, a stateful
`StreamableHTTPHandler` will respond with a plain-text `http.Error` 400 body
when it receives a request carrying per-request metadata (i.e. an
`io.modelcontextprotocol/protocolVersion` `_meta` field, or an
`MCP-Protocol-Version` header >= `2026-07-28`), restoring the previous
behavior. The default behavior was changed so that the server responds with a
JSON-RPC error of code `CodeUnsupportedProtocolVersion` (`-32022`) carrying
an `UnsupportedProtocolVersionData` payload that advertises the legacy
versions the server supports. This lets the client's
existing renegotiation logic recover and prevents the failure from tearing
down the underlying connection.
- `blockingcancelnotify` added. If set to `1`, a cancelled call waits
synchronously for the best-effort `notifications/cancelled` message to be
delivered (up to `notifyCancellationTimeout`, currently 5 s) before
returning to the caller, restoring the previous behavior. The delivery
error is joined into the caller's returned error. The default behavior was
changed so that the call is retired immediately and the notification is
sent asynchronously off the caller's return path: the caller returns as
soon as its context is cancelled and cannot be delayed by a slow or
unresponsive peer. See issue #1150.
Options below were removed, according to plan:
- `seterroroverwrite`. `SetError` now always preserves existing `Content`.
- `enableoriginverification`. A nil
`StreamableHTTPOptions.CrossOriginProtection` now always means no
cross-origin protection. Wrap the handler with cross-origin protection
middleware instead.
- `disablecontenttypecheck`. Content-Type validation on HTTP POST requests can
no longer be disabled.
- `disablelocalhostprotection`, whose removal had been postponed to this
release. Set the `DisableLocalhostProtection` field in
`StreamableHTTPOptions` or `SSEOptions` to opt out of DNS rebinding
protection.
### 1.7.0
Options listed below were added and will be removed in the 1.9.0 version of the SDK.
@ -46,9 +99,25 @@ Options listed below were added and will be removed in the 1.9.0 version of the
requested method in STDIO transport is not found.
- `noprotocolerrorbody` added. If set to `1`, the streamable HTTP client will
not attempt to decode the JSON-RPC error body of a non-2xx HTTP response,
not attempt to decode the JSON-RPC error body of a non-2xx HTTP response,
and any non-transient error will permanently fail the connection, restoring
the previous behavior. The default behavior was changed so that the client
attempts to decode the JSON-RPC error body of a non-2xx response, surfaces
the underlying JSON-RPC error, and wraps it with `jsonrpc2.ErrRejected` so
that per-call rejections do not tear down the session.
- `nowrapinvalidparams` added. If set to `1`, the server will not wrap
params-decoding failures with `jsonrpc2.ErrInvalidParams`, so wire responses
carry the zero-value error code `0` instead of `-32602` ("invalid params"),
restoring the previous behavior. The default behavior was changed so that
the client always attempts to surface the underlying JSON-RPC error.
the server always reports params-decoding failures with the standard
`-32602` code.
- `disablecompleteparamsvalidation` added. If set to `1`, `Server.complete`
will not validate that the required `ref` and `argument.name` fields on
`CompleteParams` are present, restoring the previous behavior of dispatching
the request to the completion handler unconditionally. The default behavior
was changed to reject malformed requests with `-32602` (Invalid Params).
### 1.6.1

View file

@ -2,11 +2,14 @@
# Support for the MCP base protocol
1. [Lifecycle](#lifecycle)
1. [Discovery (`server/discover`)](#discovery-(server/discover))
1. [Per-request `_meta` keys](#per-request-meta-keys)
1. [Discovery](#discovery)
1. [Per-request metadata keys](#per-request-metadata-keys)
1. [Per-response metadata keys](#per-response-metadata-keys)
1. [Subscriptions](#subscriptions)
1. [Transports](#transports)
1. [Stdio Transport](#stdio-transport)
1. [Streamable Transport](#streamable-transport)
1. [Legacy SSE Transport](#legacy-sse-transport)
1. [Custom transports](#custom-transports)
1. [Concurrency](#concurrency)
1. [Authorization](#authorization)
@ -39,8 +42,10 @@ transparently based on the negotiated protocol version:
- A **stateless** model introduced in `2026-07-28` by
[SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575),
in which there is no `initialize`/`notifications/initialized` handshake, and
each request carries its protocol version, client identity, and client
capabilities in `_meta`.
each request carries its protocol version and client capabilities in
`_meta`. Clients SHOULD also include their identity (`clientInfo`) on every
request, and servers SHOULD include their identity (`serverInfo`) on every
response.
In both models, the SDK exposes the same API:
@ -106,7 +111,7 @@ func Example_lifecycle() {
}
```
### Discovery (`server/discover`)
### Discovery
Introduced in `2026-07-28` by
[SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575),
@ -123,23 +128,64 @@ request. Servers implementing `2026-07-28` MUST implement it.
fails or the server does not support the latest version, the client falls back to the
legacy `initialize` handshake.
### Per-request `_meta` keys
A server advertises and negotiates every protocol version the SDK supports;
set [`ServerOptions.SupportedProtocolVersions`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions)
to narrow that set, for example to stop serving a revision your deployment has
retired.
```go
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v1.0.0"}, &mcp.ServerOptions{
SupportedProtocolVersions: []string{"2026-07-28", "2025-11-25"},
})
```
The set can only narrow support, never widen it; the field's documentation
covers how each protocol era answers a request at an excluded version.
### Per-request metadata keys
When the negotiated protocol version is `2026-07-28` or later, every request
carries these keys inside its `_meta` map (constants live in
[`mcp/protocol.go`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#pkg-constants)):
| Constant | Wire key | Type |
|---|---|---|
| `MetaKeyProtocolVersion` | `io.modelcontextprotocol/protocolVersion` | `string` |
| `MetaKeyClientInfo` | `io.modelcontextprotocol/clientInfo` | `*Implementation` |
| `MetaKeyClientCapabilities` | `io.modelcontextprotocol/clientCapabilities` | `*ClientCapabilities` |
| `MetaKeyLogLevel` | `io.modelcontextprotocol/logLevel` | `LoggingLevel` (deprecated by SEP-2577) |
| Constant | Wire key | Type | Required |
|---|---|---|---|
| `MetaKeyProtocolVersion` | `io.modelcontextprotocol/protocolVersion` | `string` | Yes |
| `MetaKeyClientCapabilities` | `io.modelcontextprotocol/clientCapabilities` | `*ClientCapabilities` | Yes |
| `MetaKeyClientInfo` | `io.modelcontextprotocol/clientInfo` | `*Implementation` | No |
| `MetaKeyLogLevel` | `io.modelcontextprotocol/logLevel` | `LoggingLevel` (deprecated by SEP-2577) | No |
The client populates these keys automatically on every outgoing request.
Server-side handlers can read them
through `ServerRequest[P].ProtocolVersion()`, `ServerRequest[P].ClientInfo()`,
and `ServerRequest[P].ClientCapabilities()`.
The client populates the required keys automatically on every outgoing
request, and populates `clientInfo` when configured with an `*Implementation`
(the default). Server-side handlers can read them through
`ServerRequest[P].ProtocolVersion()`, `ServerRequest[P].ClientInfo()`, and
`ServerRequest[P].ClientCapabilities()`.
### Per-response metadata keys
Under the same protocol version, servers SHOULD identify themselves on every
response. The SDK populates this
key automatically on every outgoing response:
| Constant | Wire key | Type | Required |
|---|---|---|---|
| `MetaKeyServerInfo` | `io.modelcontextprotocol/serverInfo` | `*Implementation` | No |
### Subscriptions
Introduced in `2026-07-28` by
[SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575),
`subscriptions/listen` replaces the legacy `resources/subscribe` RPC and
the GET-based SSE endpoint with a single long-lived request that
multiplexes every kind of server-to-client change notification a client
opts in to. On the wire the client sends one `subscriptions/listen`
request whose `notifications` field enumerates what it wants
(`toolsListChanged`, `promptsListChanged`, `resourcesListChanged`, and/or
a list of resource URIs in `resourceSubscriptions`); the server replies
first with a `notifications/subscriptions/acknowledged` notification
reporting the honored subset, then streams every change notification on
the same request, and finally closes with a `SubscriptionsListenResult`
when it tears the subscription down.
## Transports
@ -225,7 +271,8 @@ connect to the server:
transport := &mcp.StreamableClientTransport{
Endpoint: "http://localhost:8080/mcp",
}
client, err := mcp.Connect(ctx, transport, &mcp.ClientOptions{...})
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v1.0.0"}, nil)
session, err := client.Connect(ctx, transport, nil)
```
The `StreamableClientTransport` handles the HTTP requests and communicates with
@ -310,6 +357,63 @@ _See [examples/server/distributed](https://github.com/modelcontextprotocol/go-sd
an example using stateless mode to implement a server distributed across
multiple processes._
### Legacy SSE Transport
Before the streamable transport, the
[2024-11-05](https://modelcontextprotocol.io/specification/2024-11-05/basic/transports)
spec defined an HTTP transport built from two endpoints: a hanging GET that
streams server-to-client messages as server-sent events, and a per-session
endpoint the client POSTs its messages to. The SDK still implements it, for
talking to peers that predate the streamable transport. New deployments should
use the [streamable transport](#streamable-transport) instead.
It is implemented across three types, mirroring the streamable ones:
- [`SSEHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#SSEHandler):
an `http.Handler` that creates a session per incoming GET and routes POSTs to
the right session. Construct it with
[`NewSSEHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#NewSSEHandler),
which takes a function returning the `Server` to serve a given request.
- [`SSEServerTransport`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#SSEServerTransport):
one such session. Reach for it directly only when serving the two endpoints
yourself; `SSEHandler` creates these for you.
- [`SSEClientTransport`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#SSEClientTransport):
the client side, which needs only the `Endpoint` of the GET.
```go
func ExampleSSEHandler() {
server := mcp.NewServer(&mcp.Implementation{Name: "adder", Version: "v0.0.1"}, nil)
mcp.AddTool(server, &mcp.Tool{Name: "add", Description: "add two numbers"}, Add)
handler := mcp.NewSSEHandler(func(*http.Request) *mcp.Server { return server }, nil)
httpServer := httptest.NewServer(handler)
defer httpServer.Close()
ctx := context.Background()
transport := &mcp.SSEClientTransport{Endpoint: httpServer.URL}
client := mcp.NewClient(&mcp.Implementation{Name: "test", Version: "v1.0.0"}, nil)
cs, err := client.Connect(ctx, transport, nil)
if err != nil {
log.Fatal(err)
}
defer cs.Close()
res, err := cs.CallTool(ctx, &mcp.CallToolParams{
Name: "add",
Arguments: map[string]any{"x": 1, "y": 2},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Content[0].(*mcp.TextContent).Text)
// Output: 3
}
```
_See [examples/server/sse](https://github.com/modelcontextprotocol/go-sdk/blob/main/examples/server/sse/main.go)
for a standalone server._
### Custom transports
The SDK supports [custom
@ -535,24 +639,24 @@ provided to
### Server-Side Request Forgery
The [mitigations](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation-3) are as follows:
The OAuth discovery helpers apply these [mitigations](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices#mitigation-3) by default:
- _Enforce HTTPS_. The OAuth helpers provided by the SDK reject the `http://` URLs
except loopback addresses (`localhost`, `127.0.0.1`, `::1`).
- _Enforce HTTPS_. Reject `http://` URLs (except loopback: `localhost`, `127.0.0.1`, `::1`)
and redirects that downgrade `https` to a non-`https` scheme.
- _Block Private IP Ranges_. The OAuth helpers provided by the SDK allow passing
a custom `http.Client`. Developers are advised to customize the client it with
appropriate network protections, including IP range blocking. The SDK does not provide
this capability out of the box.
- _Block Private IP Ranges_. Reject targets that resolve to a non-public address
(private, link-local including the `169.254.169.254` metadata endpoint, CGNAT, multicast,
unspecified). The check runs on the initial URL and at dial time, guarding against DNS rebinding.
- _Validate Redirect Targets_. Similarly to previous point, customized `http.Client`
can be used to validate network hops. The SDK does not provide this capability out
of the box.
- _Validate Redirect Targets_. Reject redirects to non-public addresses and cap the redirect count.
- _Use Egress Proxies_. This is out of scope for the SDK and can be configured separately.
- _Use Egress Proxies_. Out of scope for the SDK; configure separately.
- _DNS Resolution Considerations_. The SDK has DNS rebinding protection on the server side which is enabled by default. For the client side, consider providing
a custom `http.Client` that would implement DNS pinning.
**Opting out.** Passing a custom `http.Client` can bypass these checks, making SSRF
protection your responsibility. Specifically: a custom `Transport.DialContext`/`DialTLSContext`,
a non-`*http.Transport`, or a configured `Proxy` disables the dial-time IP check; setting
`CheckRedirect` replaces all redirect validation. In these cases, implement your own IP
blocking, redirect validation, and DNS pinning.
### Session Hijacking

View file

@ -7,7 +7,7 @@
## Installation
```
go get github.com/modelcontextprotocol/go-sdk
go get github.com/modelcontextprotocol/go-sdk/mcp
```
## Getting started

View file

@ -2,8 +2,13 @@
# Support for MCP server features
1. [Prompts](#prompts)
1. [Prompt message content](#prompt-message-content)
1. [Resources](#resources)
1. [Binary resources](#binary-resources)
1. [Resource subscriptions](#resource-subscriptions)
1. [Tools](#tools)
1. [Tool result content](#tool-result-content)
1. [List changed notifications](#list-changed-notifications)
1. [Multi Round-Trip Requests](#multi-round-trip-requests)
1. [Talking to legacy clients](#talking-to-legacy-clients)
1. [Example](#example)
@ -115,6 +120,91 @@ func Example_prompts() {
}
```
### Prompt message content
A
[`PromptMessage`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#PromptMessage)
has two fields: a `Role`, either `"user"` or `"assistant"`, and a single
`Content`. That is the same interface a tool result uses (see [Tool result
content](#tool-result-content)), so a prompt message can hold an image, audio,
or a resource's contents as readily as text.
Media on its own says nothing about what the model should do with it, so send a
text message alongside it. Embedding a resource puts its contents directly in
the message, sparing the client a separate `resources/read`. Note that
`ResourceContents.URI` is not checked against the resources the server has
registered: it records where the contents came from, so use the URI of a real
resource when there is one, as in the example below.
```go
func Example_promptContent() {
ctx := context.Background()
const styleGuide = "- Prefer clarity over cleverness.\n"
screenshotPNG := []byte("\x89PNG\r\n\x1a\n")
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
s.AddResource(&mcp.Resource{URI: "doc://style-guide", MIMEType: "text/markdown"},
func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
return &mcp.ReadResourceResult{
Contents: []*mcp.ResourceContents{{
URI: "doc://style-guide",
MIMEType: "text/markdown",
Text: styleGuide,
}},
}, nil
})
s.AddPrompt(&mcp.Prompt{Name: "review_screenshot"},
func(context.Context, *mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
return &mcp.GetPromptResult{
Messages: []*mcp.PromptMessage{
{Role: "user", Content: &mcp.EmbeddedResource{
Resource: &mcp.ResourceContents{
URI: "doc://style-guide",
MIMEType: "text/markdown",
Text: styleGuide,
},
}},
{Role: "user", Content: &mcp.ImageContent{Data: screenshotPNG, MIMEType: "image/png"}},
{Role: "user", Content: &mcp.TextContent{Text: "Review the screenshot against the style guide."}},
},
}, nil
})
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
t1, t2 := mcp.NewInMemoryTransports()
if _, err := s.Connect(ctx, t1, nil); err != nil {
log.Fatal(err)
}
cs, err := c.Connect(ctx, t2, nil)
if err != nil {
log.Fatal(err)
}
defer cs.Close()
res, err := cs.GetPrompt(ctx, &mcp.GetPromptParams{Name: "review_screenshot"})
if err != nil {
log.Fatal(err)
}
for _, msg := range res.Messages {
switch content := msg.Content.(type) {
case *mcp.TextContent:
fmt.Println(msg.Role, "text:", content.Text)
case *mcp.ImageContent:
fmt.Printf("%s image: %s, %d bytes\n", msg.Role, content.MIMEType, len(content.Data))
case *mcp.EmbeddedResource:
fmt.Printf("%s embedded resource: %s (%s)\n", msg.Role, content.Resource.URI, content.Resource.MIMEType)
}
}
// Output:
// user embedded resource: doc://style-guide (text/markdown)
// user image: image/png, 8 bytes
// user text: Review the screenshot against the style guide.
}
```
## Resources
In MCP terms, a _resource_ is some data referenced by a URI.
@ -147,7 +237,12 @@ and
to unsubscribe.
Set
[`ClientOptions.ResourceUpdatedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ResourceUpdatedHandler)
to be notified of changes to subscribed resources.
to be notified of changes to subscribed resources. On `2026-07-28` and
later sessions the SDK delivers these notifications over a
`subscriptions/listen` stream instead of the legacy `resources/subscribe`
RPC; see [Subscriptions
(`subscriptions/listen`)](protocol.md#subscriptions)
for the wire-level details.
**Server-side**:
Use
@ -241,6 +336,145 @@ func Example_resources() {
}
```
### Binary resources
A
[`ResourceContents`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ResourceContents)
carries its data in either `Text` or `Blob`. Binary data goes in `Blob` as
plain bytes — the SDK base64-encodes it on the wire — and leaves `Text` empty,
so a client tells the two apart by which field is populated.
```go
func Example_binaryResource() {
ctx := context.Background()
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
s.AddResource(&mcp.Resource{URI: "file:///logo.png", MIMEType: "image/png"},
func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
return &mcp.ReadResourceResult{
Contents: []*mcp.ResourceContents{{
URI: "file:///logo.png",
MIMEType: "image/png",
Blob: []byte("\x89PNG\r\n\x1a\n"),
}},
}, nil
})
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
t1, t2 := mcp.NewInMemoryTransports()
if _, err := s.Connect(ctx, t1, nil); err != nil {
log.Fatal(err)
}
cs, err := c.Connect(ctx, t2, nil)
if err != nil {
log.Fatal(err)
}
defer cs.Close()
res, err := cs.ReadResource(ctx, &mcp.ReadResourceParams{URI: "file:///logo.png"})
if err != nil {
log.Fatal(err)
}
contents := res.Contents[0]
fmt.Printf("%s: %d bytes of %s, text is %q\n",
contents.URI, len(contents.Blob), contents.MIMEType, contents.Text)
// Output:
// file:///logo.png: 8 bytes of image/png, text is ""
}
```
### Resource subscriptions
A client interested in changes to one resource subscribes to its URI with
[`ClientSession.Subscribe`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Subscribe)
and stops with
[`ClientSession.Unsubscribe`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.Unsubscribe).
Set
[`ServerOptions.SubscribeHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.SubscribeHandler)
and
[`UnsubscribeHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerOptions.UnsubscribeHandler)
to track who is listening; setting either one gives the server the
`resources.subscribe` capability.
Publish a change with
[`Server.ResourceUpdated`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.ResourceUpdated),
which notifies every session subscribed to that URI. The notification carries
only the URI, not the new contents, so a client that wants them reads the
resource again.
```go
func Example_resourceSubscription() {
ctx := context.Background()
config := "theme=light\n"
updated := make(chan string, 1)
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
// The notification carries only the URI, so read the resource again to
// see what it now holds.
ResourceUpdatedHandler: func(context.Context, *mcp.ResourceUpdatedNotificationRequest) {
updated <- "config://app"
},
})
subscribed := make(chan string, 2)
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{
SubscribeHandler: func(_ context.Context, req *mcp.SubscribeRequest) error {
subscribed <- "subscribed to " + req.Params.URI
return nil
},
UnsubscribeHandler: func(_ context.Context, req *mcp.UnsubscribeRequest) error {
subscribed <- "unsubscribed from " + req.Params.URI
return nil
},
})
s.AddResource(&mcp.Resource{URI: "config://app"},
func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
return &mcp.ReadResourceResult{
Contents: []*mcp.ResourceContents{{URI: "config://app", Text: config}},
}, nil
})
t1, t2 := mcp.NewInMemoryTransports()
if _, err := s.Connect(ctx, t1, nil); err != nil {
log.Fatal(err)
}
cs, err := c.Connect(ctx, t2, nil)
if err != nil {
log.Fatal(err)
}
defer cs.Close()
if err := cs.Subscribe(ctx, &mcp.SubscribeParams{URI: "config://app"}); err != nil {
log.Fatal(err)
}
fmt.Println(<-subscribed)
config = "theme=dark\n"
if err := s.ResourceUpdated(ctx, &mcp.ResourceUpdatedNotificationParams{URI: "config://app"}); err != nil {
log.Fatal(err)
}
uri := <-updated
res, err := cs.ReadResource(ctx, &mcp.ReadResourceParams{URI: uri})
if err != nil {
log.Fatal(err)
}
fmt.Print("re-read: ", res.Contents[0].Text)
if err := cs.Unsubscribe(ctx, &mcp.UnsubscribeParams{URI: "config://app"}); err != nil {
log.Fatal(err)
}
fmt.Println(<-subscribed)
// Output:
// subscribed to config://app
// re-read: theme=dark
// unsubscribed from config://app
}
```
## Tools
MCP servers can provide
@ -292,7 +526,7 @@ In order to implement a tool, the user must do all of the following:
- Unmarshal the input schema into a Go value
- Execute the tool logic.
- Marshal the tool's structured output (if any) to JSON, and store it in the
result's `StructuredOutput` field as well as the unstructured `Content` field.
result's `StructuredContent` field as well as the unstructured `Content` field.
- Validate that output JSON against the tool's output schema.
- If any tool errors occurred, pack them into the unstructured content and set
`IsError` to `true.`
@ -327,7 +561,7 @@ This does the following automatically:
- Tool arguments are validated against the input schema.
- Tool arguments are marshaled into the `In` value.
- Tool output (the `Out` value) is marshaled into the result's
`StructuredOutput`, as well as the unstructured `Content`.
`StructuredContent`, as well as the unstructured `Content`.
- Output is validated against the tool's output schema.
- If an ordinary error is returned, it is stored int the `CallToolResult` and
`IsError` is set to `true`.
@ -412,6 +646,103 @@ _See [mcp/tool_example_test.go](https://github.com/modelcontextprotocol/go-sdk/b
example, or [examples/server/toolschemas](https://github.com/modelcontextprotocol/go-sdk/blob/main/examples/server/toolschemas/main.go)
for more examples of customizing tool schemas._
### Tool result content
Alongside its structured output, a tool result carries a list of
[`Content`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Content)
blocks in `CallToolResult.Content`, and may mix as many kinds as it needs:
| Type | Carries |
| --- | --- |
| [`TextContent`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#TextContent) | plain text |
| [`ImageContent`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ImageContent) | image data, with a MIME type |
| [`AudioContent`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#AudioContent) | audio data, with a MIME type |
| [`EmbeddedResource`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#EmbeddedResource) | the contents of a resource, inline |
| [`ResourceLink`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ResourceLink) | a reference to a resource the client can read separately |
`ImageContent.Data` and `AudioContent.Data` are plain `[]byte`; the SDK
base64-encodes them on the wire, so handlers never encode by hand, and base64
carried over from another MCP SDK must be decoded before it is assigned. An
`EmbeddedResource` wraps a
[`ResourceContents`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ResourceContents),
which holds either `Text` or, for binary data, `Blob`. Prefer a `ResourceLink`
when the client may not need the contents, since an embedded resource is
transferred whether it is used or not.
When a handler bound with `AddTool` leaves `Content` unset, the SDK fills it
with the JSON encoding of the output value; set `Content` explicitly, as below,
to return anything else. `StructuredContent` is still populated from the output
value either way.
```go
func ExampleAddTool_contentTypes() {
chartPNG := []byte("\x89PNG\r\n\x1a\n")
summaryWAV := []byte("RIFF....WAVE")
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
mcp.AddTool(server, &mcp.Tool{Name: "report"}, func(context.Context, *mcp.CallToolRequest, struct{}) (*mcp.CallToolResult, any, error) {
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "Q3 revenue by region."},
&mcp.ImageContent{Data: chartPNG, MIMEType: "image/png"},
&mcp.AudioContent{Data: summaryWAV, MIMEType: "audio/wav"},
// An embedded resource inlines the contents, so the client
// needs no follow-up resources/read. Binary contents go in
// ResourceContents.Blob instead of Text.
&mcp.EmbeddedResource{
Resource: &mcp.ResourceContents{
URI: "file:///reports/q3.csv",
MIMEType: "text/csv",
Text: "region,revenue\nEMEA,42\n",
},
},
// A resource link only points at a resource. Prefer it when the
// client may not need the contents, since an embedded resource
// is transferred either way.
&mcp.ResourceLink{URI: "file:///reports/q3.pdf", MIMEType: "application/pdf"},
},
}, nil, nil
})
ctx := context.Background()
session, err := connect(ctx, server)
if err != nil {
log.Fatal(err)
}
defer session.Close()
res, err := session.CallTool(ctx, &mcp.CallToolParams{Name: "report"})
if err != nil {
log.Fatal(err)
}
// Clients type-switch over the content they receive.
for _, content := range res.Content {
switch c := content.(type) {
case *mcp.TextContent:
fmt.Println("text:", c.Text)
case *mcp.ImageContent:
fmt.Printf("image: %s, %d bytes\n", c.MIMEType, len(c.Data))
case *mcp.AudioContent:
fmt.Printf("audio: %s, %d bytes\n", c.MIMEType, len(c.Data))
case *mcp.EmbeddedResource:
fmt.Printf("embedded resource: %s (%s)\n", c.Resource.URI, c.Resource.MIMEType)
case *mcp.ResourceLink:
fmt.Printf("resource link: %s (%s)\n", c.URI, c.MIMEType)
}
}
// Output:
// text: Q3 revenue by region.
// image: image/png, 8 bytes
// audio: audio/wav, 12 bytes
// embedded resource: file:///reports/q3.csv (text/csv)
// resource link: file:///reports/q3.pdf (application/pdf)
}
```
`ToolUseContent` and `ToolResultContent` also implement `Content`, but are only
valid in sampling messages, not in tool results.
**Stateless server deployments:** Some deployments create a new
[`Server`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server)
for each incoming request, re-registering tools every time. To avoid repeated
@ -429,6 +760,86 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
}
```
## List changed notifications
Adding or removing a feature on a connected server sends the matching
`notifications/*/list_changed` to every client, which dispatches it to
[`ClientOptions.ToolListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ToolListChangedHandler),
[`PromptListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.PromptListChangedHandler),
or
[`ResourceListChangedHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.ResourceListChangedHandler).
A notification reports only that the list changed, so a client that needs the
new contents lists them again.
These notifications ride on capabilities, and capabilities are inferred from
what is registered before [`Server.Connect`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#Server.Connect)
(see [Capabilities](#capabilities)). A server that registers its features
afterwards, as the example below does, has to declare
`ServerOptions.HasTools`, `HasPrompts`, or `HasResources` itself, or the
client is never told.
```go
func Example_listChanged() {
ctx := context.Background()
changed := make(chan string, 3)
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
ToolListChangedHandler: func(context.Context, *mcp.ToolListChangedRequest) {
changed <- "tools"
},
PromptListChangedHandler: func(context.Context, *mcp.PromptListChangedRequest) {
changed <- "prompts"
},
ResourceListChangedHandler: func(context.Context, *mcp.ResourceListChangedRequest) {
changed <- "resources"
},
})
// Nothing is registered before Connect, so the capabilities that carry these
// notifications have to be declared explicitly.
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{
HasTools: true,
HasPrompts: true,
HasResources: true,
})
t1, t2 := mcp.NewInMemoryTransports()
if _, err := s.Connect(ctx, t1, nil); err != nil {
log.Fatal(err)
}
cs, err := c.Connect(ctx, t2, nil)
if err != nil {
log.Fatal(err)
}
defer cs.Close()
mcp.AddTool(s, &mcp.Tool{Name: "greet"}, func(context.Context, *mcp.CallToolRequest, struct{}) (*mcp.CallToolResult, any, error) {
return &mcp.CallToolResult{}, nil, nil
})
fmt.Println("changed:", <-changed)
s.AddPrompt(&mcp.Prompt{Name: "review"}, func(context.Context, *mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
return &mcp.GetPromptResult{}, nil
})
fmt.Println("changed:", <-changed)
s.AddResource(&mcp.Resource{URI: "file:///a"}, func(context.Context, *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
return &mcp.ReadResourceResult{}, nil
})
fmt.Println("changed:", <-changed)
// Removing a feature notifies the client just as adding one does.
s.RemoveTools("greet")
fmt.Println("changed:", <-changed)
// Output:
// changed: tools
// changed: prompts
// changed: resources
// changed: tools
}
```
## Multi Round-Trip Requests
[SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)
@ -564,27 +975,33 @@ results: clients use `ttlMs` as a freshness hint to reduce polling,
and `cacheScope` (`"public"` or `"private"`) controls whether shared
intermediaries may cache the response.
**Server-side defaults.** After paginating, the SDK sets `CacheScope = "public"`
and leaves `TTLMs = 0` (which the client cache treats as "immediately
stale"). A handler that wants its responses cached must set `TTLMs`
explicitly on the returned result.
**Server-side defaults.** The SDK fills in `CacheScope = "public"` whenever a
result leaves it empty, since the field is required on the wire. Everything
else is left as produced: a `ResourceHandler` that sets `TTLMs` or `CacheScope`
on its `ReadResourceResult` keeps those values. The list results and
`server/discover` are built by the SDK itself, so they start out with
`TTLMs = 0`, which a client cache treats as "immediately stale".
**Setting a policy.** `ServerOptions.SetCacheable` decides the fields for every
result that carries them. It runs after the corresponding handler, receives
what that handler produced, and may keep, adjust, or replace it. The request is
passed in so that policy can vary by method, session, or authorization context.
```go
mcp.AddTool(server, &mcp.Tool{Name: "expensive"}, func(...) (*mcp.CallToolResult, any, error) {
// ...
})
// Override the default for tools/list:
server.AddSendingMiddleware(func(next mcp.MethodHandler) mcp.MethodHandler {
return func(ctx context.Context, method string, req mcp.Request) (mcp.Result, error) {
res, err := next(ctx, method, req)
if lr, ok := res.(*mcp.ListToolsResult); ok {
lr.TTLMs = 30_000 // 30s cache freshness hint
server := mcp.NewServer(impl, &mcp.ServerOptions{
SetCacheable: func(_ context.Context, req mcp.Request, c *mcp.Cacheable) {
// A 30s freshness hint, except where the handler chose its own.
if c.TTLMs == 0 {
c.TTLMs = 30_000
}
return res, err
}
},
})
```
`SetCacheable` may run with the server's lock held, so it must not call back
into the `Server`: adding or removing a feature, or ranging over
`Server.Sessions`, deadlocks.
## Utilities
### Completion
@ -649,8 +1066,12 @@ server produces logs that remain server-side, for use by server maintainers.)
**Server-side**:
The minimum log level is part of the server state.
For stateful sessions, there is no default log level: no log messages will be sent
until the client calls `SetLevel` (see below).
For stateful sessions, the level defaults to "info".
until the client calls `SetLoggingLevel` (see below).
For legacy stateless sessions the level defaults to "info".
For sessionless 2026-07-28 requests there is no server-side default: each request
carries its level in `_meta` (`io.modelcontextprotocol/logLevel`), and nothing is
logged until the client sends one.
(Note: logging is deprecated as of 2026-07-28 / SEP-2577.)
[`ServerSession.Log`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession.Log) is the low-level way for servers to log to clients.
It sends a logging notification to the client if the level of the message
@ -666,7 +1087,7 @@ Servers always report the logging capability.
**Client-side**:
Set [`ClientOptions.LoggingMessageHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.LoggingMessageHandler) to receive log messages.
Call [`ClientSession.SetLevel`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.SetLevel) to change the log level for a session.
Call [`ClientSession.SetLoggingLevel`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.SetLoggingLevel) to change the log level for a session.
```go
func Example_logging() {

Some files were not shown because too many files have changed in this diff Show more