Commit graph go-sdk/internal
Author SHA1 Message Date
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
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
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
Guglielmo Colombo
3f3b699b2b
refactor: remove legacy MCPGODEBUG compatibility for new protocol release (#1237) 2026-09-04 10:08:52 +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
Guglielmo Colombo
0d3036f785
mcp: allow per request Cacheable customization (#1203)
Fixes #1094
2026-09-01 18:57:51 +00:00
Guglielmo Colombo
21c18c6229
mcp: allow restricting the protocol versions a server supports (#1199)
Fixes #945
2026-08-28 16:06:10 +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
杨成锴
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
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
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
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
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
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
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
9576afc114
mcp: wrap unmarshalParams with jsonrpc error (#1087)
Fixes #976
2026-07-14 14:26:18 +00:00
Guglielmo Colombo
c79a30ae0a
mcp: add param check for CompleteResult (#1080)
Fixes #1071
2026-07-13 12:14:21 +00:00
Guglielmo Colombo
22d4988a16
mcp: update docs references (#1023)
Update docs
2026-06-24 12:39:39 +00:00
Guglielmo Colombo
b9e6fc5a66
mcp: add documentation for new protocol version (#1019)
Add documentation for new protocol version in the `internal/docs`
2026-06-23 17:07:38 +02:00
Guglielmo Colombo
b6649db4f9
mcp: Deprecate roots, sampling and logging (SEP-2577) (#1017)
Fixes #1013
2026-06-23 11:11:08 +02:00
Guglielmo Colombo
cd7d80c591
mcp: Implement subscriptions/listen rpc (SEP-2575) (#1007)
Fixes #966
2026-06-22 18:57:36 +02:00
Guglielmo Colombo
3486f6bcaf
mco: update JSON-RPC error codes to align with updated MCP specification (#1016)
This PR updates the error codes to align with the updated specifications
in
https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2907

ErrServerClosing and ErrClientClosing are reverted to their 1.6.0
version codes, as they do not overlap anymore with the newly introduced
error codes.
2026-06-22 10:27:31 +02:00
Guglielmo Colombo
b85574f685
mcp: Enable legacy initialize fallback on any error (#1014)
To guarantee a better compatibility with legacy server, enable the
fallback to the legacy initialize rpc in case of any error returned by
the server, in case of `server/discover` request.
2026-06-18 15:52:11 +02:00
Guglielmo Colombo
71ffcb412a
mcp: add MCPGPDEBUG for opt-in Content-Type check (#1012)
Copy of existing
[PR](https://github.com/modelcontextprotocol/go-sdk/pull/972) patching
1.6.0 version
2026-06-17 11:41:35 +02:00
Thomas Bechtold
d9728a88f3
auth: bind refreshing token source to a background context, not the request ctx (#988)
## Problem

`AuthorizationCodeHandler.exchangeAuthorizationCode` builds the
long-lived token source from the **request context** passed to
`Authorize()`:

```go
clientCtx := context.WithValue(ctx, oauth2.HTTPClient, h.config.Client)
...
h.tokenSource = cfg.TokenSource(clientCtx, token)
```

`golang.org/x/oauth2` captures that context and reuses it for **every**
future refresh:
- `oauth2.go`: `Config.TokenSource(ctx, …)` stores `ctx` in
`tokenRefresher{ctx}`
- `tokenRefresher.Token()` (no ctx arg) calls `retrieveToken(tf.ctx, …)`
- `internal/token.go`: `ContextClient(ctx).Do(req.WithContext(ctx))`

`Authorize()` is normally invoked from a request- or connect-scoped
context — e.g. the streamable client transport calls it during
`setMCPHeaders`/`initialize`, and that context is cancelled once the
operation completes. The cached access token works until expiry; the
**first refresh after expiry** then fails instantly with `context
canceled`, before any HTTP request is sent. The connection can no longer
refresh.

`setMCPHeaders` only re-invokes `Authorize()` for an `invalid_grant`
`oauth2.RetrieveError` (#917). A `context canceled` is not a
`RetrieveError`, so it is never recovered — the connection is wedged
until the client reconnects.

### Reproduce

1. Connect a `StreamableClientTransport` with an
`AuthorizationCodeHandler` to a server whose `initialize` returns 401
(so `Authorize()` runs during connect, under the connect context).
2. Let the connect context be cancelled after connect returns (the
common pattern: a `context.WithTimeout(parent, …)` + `defer cancel()`
bounding the interactive flow).
3. Wait for the access token to expire and issue a request.
4. Every request fails with `Post "<token endpoint>": context canceled`
and `dur≈0` — the refresh never hits the network.

## Fix

A token source outlives the request that created it, so bind its
refreshes to `context.Background()` (still carrying the configured HTTP
client via `oauth2.HTTPClient`). The one-shot token exchange keeps using
the request context.

```go
refreshCtx := context.WithValue(context.Background(), oauth2.HTTPClient, h.config.Client)
h.tokenSource = cfg.TokenSource(refreshCtx, token)
```

`auth` package tests pass. Behaviour is otherwise unchanged: the same
HTTP client value is carried; only the cancellation parent differs.

🤖 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-06-16 16:42:53 +02:00
Guglielmo Colombo
79e1a13dcf
mcp: tool.InputSchema and tool.OutputSchema validation (SEP-2106) (#1009)
Fixes #990
2026-06-16 13:55:47 +02:00
Piyush Bag
bcbfe0ce36
fix(jsonrpc2): decode requests when method key is present (#1000)
## Summary

Fixes #976.

`DecodeMessage` used `msg.Method != ""` to classify messages. A wire
payload with `"method":""` was decoded as a **response**, so stdio
servers dropped it without a correlated JSON-RPC error.

## Approach

Decode into a `wireDecode` struct whose `method` field is
`json.RawMessage`. Treat the message as a **request** when the `method`
key is **present on the wire** (including `""`), which matches JSON-RPC
semantics.

This revision:

- Uses a **single JSON parse** per message (dedicated decode struct, no
secondary fields-map unmarshal)
- Documents why `json.RawMessage` is used for `method`
- Adds regression coverage so normal responses and id-only payloads
without a `method` key stay on the response path

## Test plan

- [x] `go test ./internal/jsonrpc2/... -v`
- [x] `go test ./mcp/... -run TestIOConnRead_EmptyMethod -v`
- [x] `go test ./... -count=1`
- [x] `go test -race ./internal/jsonrpc2/... -count=1`
- [x] Server conformance (`npx @modelcontextprotocol/conformance server
--url http://127.0.0.1:3000/mcp`) — 40 passed, 0 failed
- [x] Client conformance (`./scripts/client-conformance.sh`) — 217
passed, 0 failed

## Scope note

This PR addresses the primary repro in #976 (empty `method` with valid
`id`). Other cases listed in the issue (`id: null`, structurally invalid
envelopes) are unchanged.

Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
2026-06-16 09:57:18 +02:00
Guglielmo Colombo
dd978160ab
mcp: Implement server-side support for discover method (SEP-2575) (#987)
## Summary
Part 3 of the SEP-2575 ("Make MCP Stateless") implementation. Builds on
PR #975 which landed the client-side discover/fallback flow and the
per-request `_meta` plumbing. This PR adds the **server-side**
counterparts:

- `server/discover` handler (replaces the
stub),ProtocolVersionSupporter`
- transport hook for per-transport version filtering,
- transport-agnostic version validation and method-removal enforcement
in `ServerSession.handle`,
- HTTP-level wire compliance for SEP-2575 error codes/status codes,
- bug fix in `internal/jsonrpc2`,`processResult` with an `MCPGODEBUG`
opt-out for backward compatibility.
2026-06-08 17:52:10 +02:00
Guglielmo Colombo
c60a318d22
mcp: Implement support for SEP-2575 on client side (#975)
Implements the client half of the SEP-2575 / `2026-06-30` protocol. This
CL is client-only; server-side `server/discover` is still a
`MethodNotFound` stub.
### Changes
- **`server/discover` probe in `Client.Connect()`** (`client.go`). When
the caller opts into `protocolVersion >= 2026-06-30`, the client probes
the server with `server/discover` before falling back to the legacy
`initialize`
  handshake.
- **Per-request `_meta` injection** (`shared.go`, `injectRequestMeta`).
For`>= 2026-06-30` sessions every outgoing request gets the SEP-2575
triple (`protocolVersion`, `clientInfo`, `clientCapabilities`).
- **Server-side fix**: `ServerSession.handle` no longer pre-populates
`state.InitializeParams` from a `server/discover` request's `_meta`, so
a subsequent fallback `initialize` is not rejected as a duplicate.
### Fallback rules
Client falls back to the legacy `initialize` handshake when discover
returns:
- `MethodNotFound` (-32601),
- `UnsupportedProtocolVersionError` (-32004),
- an HTTP `Bad Request` (substring-matched on the error message, to
interop
  with pre-2026-06-30 servers).

Fixes #966
2026-06-02 14:17:01 +02:00
Guglielmo Colombo
dfb45f1e11
mcp: add optional issuer validator for pre-registered client validation (SEP-2352) (#946)
Fixes #978
2026-05-29 12:46:34 +00:00
Max Gerber
2d47cc9664
auth: issuer mix-up mitigation (#859)
This PR functions as a Reference implementation of
[SEP-2468](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2468)
/ [RFC9207](https://datatracker.ietf.org/doc/rfc9207/).

This PR hardens the MCP OAuth Client functionality against Mix-Up
attacks:
>   Mix-up attacks aim to steal an authorization code or access token by
>   tricking the client into sending the authorization code or access
> token to the attacker instead of the honest authorization or resource
>   server

This PR hardens the client by adding support for a new `iss` parameter
in authorization responses:
- Authorization Servers broadcast support for the `iss` parameter via
the `authorization_response_iss_parameter_supported` metadata parameter
- If the parameter is supported, clients expect to receive the `iss`
parameter in the authorization response
- Clients compare the `iss` parameter in the authorization response to
the `Issuer` parameter in the authorization metadata. The two must match
exactly for the response to be processed.

Fixes #941
2026-05-27 11:39:08 +02:00
Pavel Bazin
c044cdb734
fix: do not omitempty ReadOnlyHint in ToolAnnotations (#908)
`ReadOnlyHint` is a `bool` with omitempty, so the zero value (false) is
indistinguishable from unset, and **drops out** of marshaled JSON.

The current behavior causes an issue with the OpenAI MCP app submission
because they require explicit hints.

Consumers that explicitly set `ReadOnlyHint: false` on write tools lose
the field on the wire. Removing `omitempty` ensures `false` is always
serialized, which matches the MCP spec default.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Maciej Kisiel <mkisiel@google.com>
Co-authored-by: guglielmoc <guglielmoc@google.com>
2026-05-25 15:52:15 +02:00
Guglielmo Colombo
bf69179066
mcp: Add client-side scope accumulation (#944)
## Description

This PR implements
https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2350

Fixes #943

---------

Co-authored-by: Maciej Kisiel <mkisiel@google.com>
2026-05-18 08:26:02 +00:00
Maciej Kisiel
8eb54632ad
mcp: remove session header handling in stateless mode (#952) 2026-05-15 09:51:14 +02:00
Guglielmo Colombo
438cd4384c
mcp: OIDC-Flavored Refresh Token Guidance (#939)
## Description

Implemets SEP-2207
(https://modelcontextprotocol.io/seps/2207-oidc-refresh-token-guidance)

Fixes #940
2026-05-06 15:43:26 +02:00
Yaroslav
2b7c9937e6
mcp: align resource not found error code with sep-2164 (#931)
Change the error code returned by ResourceNotFoundError from -32002 to
-32602 (jsonrpc.CodeInvalidParams), aligning with
[SEP-2164](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2164).

* Not gated with a protocol version check, because the previous error
code was never formally specified and the behaviour varied across SDKs.
* In case clients which rely on the old error code exist, the previous
behaviour can be restored with `MCPGODEBUG=customresnotfounderrcode=1`.
* `CodeResourceNotFound` deprecated since its only purpose going forward
is backward compatibility. The new code should use
`jsonrpc.CodeInvalidParams` directly.
* `CodeResourceNotFound` changed to `var` so it can stay in sync with
`ResourceNotFoundError` and existing code doing `code ==
mcp.CodeResourceNotFound` keeps working in both the default and compat
modes.
2026-05-05 16:20:02 +02:00
Maciej Kisiel
f5f2015413
MCPGODEBUG update for 1.6.0 (#893) 2026-04-30 09:54:32 +02:00
Maciej Kisiel
93a41b2cfa
internal/jsonrpc2: remove unused code (#910)
Remove code inherited from x/tools/internal/jsonrpc2_v2 that is not used
by MCP transports. MCP transports implement their own framing and
connection management, using only the Connection engine and message
encode/decode from this package.

Removed files:
- serve.go: Server, Listener, Dialer, idleListener, Dial, NewServer
- net.go: NetListener, NetDialer, NetPipeListener
- serve_test.go, jsonrpc2_test.go: tests for removed infrastructure

Removed from remaining files:
- frame.go: Framer interface, RawFramer, HeaderFramer implementations
- conn.go: Binder, BinderFunc, ConnectionOptions, bindConnection
- jsonrpc2.go: ErrIdleTimeout, PreempterFunc, defaultHandler, async
helper
- wire.go: ErrServerOverloaded
- conn.go: AsyncCall.IsReady (unused method)
2026-04-28 09:56:15 +02:00
Ravish Gupta
2e21834ad3
extauth: add ClientCredentialsHandler for OAuth client credentials grant (#895)
## Summary

- Add `ClientCredentialsHandler` implementing `auth.OAuthHandler` using
the OAuth 2.0 Client Credentials grant (RFC 6749 Section 4.4) for
service-to-service authentication with pre-registered credentials.
- Bypasses both dynamic client registration and the authorization code
flow — the handler takes a client ID + secret directly and exchanges
them at the token endpoint.
- Supports two modes: direct `TokenEndpoint` URL, or metadata discovery
via `AuthServerURL` (RFC 8414).
- Add `client_credentials` grant type support to the fake authorization
server in `internal/oauthtest` for testing.

## Context

Per @jba's [comment on
#627](https://github.com/modelcontextprotocol/go-sdk/issues/627#issuecomment-3640982691):

> Our default implementation for that framework should support client ID
and client secret as options, to handle that variant. [...] If those are
set, our implementation would bypass dynamic client reservation. That is
just a couple of lines of code, I believe.

The client-side OAuth scaffolding (#785) that this was blocked on is now
complete, as @brkane noted.

Implements the client credentials variant of
[SEP-1046](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1046).
The JWT Assertions variant (RFC 7523) is left for a follow-up as its API
surface needs more design discussion.

## Test plan

- [x] `TestNewClientCredentialsHandler_Validation` — validates all
config error cases (nil config, missing fields, mutual exclusivity)
- [x] `TestClientCredentialsHandler_Authorize/direct_token_endpoint` —
end-to-end with fake auth server
- [x] `TestClientCredentialsHandler_Authorize/metadata_discovery` —
discovers token endpoint via RFC 8414 metadata
- [x] `TestClientCredentialsHandler_Authorize/bad_credentials` —
verifies failure with wrong secret
- [x] `go test ./... -count=1` passes
- [x] `go vet ./...` clean

Refs #627

Co-authored-by: Maciej Kisiel <mkisiel@google.com>
2026-04-27 13:28:35 +02:00
Maciej Kisiel
41e1f941bb
mcp: remove default cross origin protection (#906)
Fixes #902
2026-04-24 11:35:22 +02:00
Maciej Kisiel
15e93a231c
mcp: add DNS rebinding and cross origin protections to SSE transport (#891)
Similar protections were already introduced before to the Streamable
transport.
2026-04-14 11:36:20 +02:00
Dinesh
1209861858
Add documentation backed by mkdocs (#883)
Affects /docs - (Refined Documentation for deployment and added Some
Supporting documentation)
Added a github action for deployment

Fixes #835  

Added Support for mkdocs
Example -  https://dinesht04.github.io/go-sdk/

Requires Configuration of Github pages as follows:-

1. Enable Github Pages
2. Select gh-pages branch for deployment

<img width="1237" height="817" alt="gh-pages"
src="https://github.com/user-attachments/assets/01f0b012-d5f3-4011-9af9-7269e657ae6a"
/>

## Sources
# For mkdocs.yaml
1.
https://github.com/modelcontextprotocol/python-sdk/blob/main/mkdocs.yml
2. https://github.com/modelcontextprotocol/java-sdk/blob/main/mkdocs.yml
  3. MKDocs official documentation
# For Github Action
1.
https://github.com/modelcontextprotocol/python-sdk/blob/main/.github/workflows/publish-docs-manually.yml
2.
https://github.com/modelcontextprotocol/java-sdk/blob/main/.github/workflows/docs.yml
3.
https://github.com/james-willett/material-mkdocs-youtube-2024/blob/main/.github/workflows/ci.yml
- From Youtube Tutorial
2026-04-09 14:25:43 +02:00