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>
## 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>
This PR removes the TTL enforcing on the cached tools, when used to
generate the http-parameters for CallTool, according to updated
[spec](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2972)
> Clients **MUST** construct `Mcp-Param-*` headers using the most
recently obtained `inputSchema` for the tool.
## Description
This PR introduces support for custom (non-standard) JSON-RPC methods in
the MCP Go SDK.
This feature enables servers to handle arbitrary JSON-RPC methods and
clients to invoke them, all while maintaining full type safety and
integrating seamlessly with the SDK's existing middleware and session
management.
- Server-Side Registration: Added AddReceivingCustomMethod to allow
servers to register handlers for custom methods. Incoming requests are
automatically unmarshaled into user-defined parameter structs.
- Client-Side Calling: Added AddSendingCustomMethod which returns a
strongly-typed caller function that clients can use to invoke custom
methods and receive strongly-typed results.
- Helper Structs: Provided ParamsBase and ResultBase for users to embed
in their custom structs. This allows them to easily satisfy the SDK's
internal Params and Result interfaces.
Fixes#954
This PR adds the protocol version `2026-07-28` to the list of supported
protocol versions and fixes legacy tests hardcoding the legacy protocol
version for those use cases.
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.
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.
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
mcp: add configurable keepalive failure threshold
Introduce `KeepAliveFailureThreshold` option in both `ClientOptions` and
`ServerOptions` to control how many consecutive keepalive ping failures
are tolerated before closing the session.
This aligns with the MCP spec's guidance that "multiple failed pings MAY
trigger a connection reset," allowing operators to tune resilience
against transient network hiccups without immediately tearing down
otherwise healthy sessions.
A threshold of 0 or 1 (the default) closes on the first failure,
preserving existing behavior. Higher values let isolated misses pass
while still closing the session once consecutive failures reach the
threshold. A successful ping resets the counter.
Tolerated failures are logged at WARN level; the final failure that
closes the session is logged at ERROR level.
This is rework of #979.
### Context
Design (`design/mrtr.md`) and implementation proposal for Multi
Round-Trip Requests (MRTR) per
[SEP-2322](de6d76fba3/seps/2322-MRTR.md).
### Changes
- Introduce `InputRequest` and `InputResponse` sealed interfaces with
corresponding `InputRequestMap` and `InputResponseMap` types for custom
map JSON codec implementation.
- Extend `CallToolParams`, `GetPromptParams`, and `ReadResourceParams`
with `InputResponses`, `RequestState` and an unexported `resultType`
("complete" or "input_required") for retry round-trips. The type is set
by the SDK based on input requests presence.
- Add client-side MRTR middleware enabled by default that automatically
fulfills input requests and retries.
- Added MRTROptions on ClientOptions to allow disabling the middleware
and configuring the number of retries.
- Add server-side MRTR middleware for backward compatibility:
transparently bridges input requests to direct
Elicit/CreateMessage/ListRoots calls for older clients
## Summary
Two error sites in the MCP runtime were previously unobservable:
- **`mcp/shared.go` `startKeepalive`** — when a keepalive ping failed,
the session was closed silently with no indication of why.
- **`mcp/transport.go` `connect`** — `jsonrpc2.OnInternalError` printed
via the package-level `log.Printf`, bypassing the `*slog.Logger`
configured on `ServerOptions`/`ClientOptions`.
Both sites now report through the existing `*slog.Logger` that `Server`
and `Client` already guarantee non-nil via `ensureLogger`. The logger is
threaded into the unexported `startKeepalive` and `connect` helpers as a
parameter — **no new public API surface**.
## Context
Per @jba's [suggestion on
#865](https://github.com/modelcontextprotocol/go-sdk/pull/865#issuecomment-4156166474):
> Maybe we can just use the logger in all the places we currently drop
the error. Then it's still reported, but we don't have to add any API.
This PR is that smaller alternative. It addresses the concrete
observability gap in #218 without expanding `ServerOptions`. PR #865
(the `ErrorHandler` callback proposal) remains open pending community
input on whether a structured callback API is also wanted on top of
logging.
`notifySessions` already logs at `Warn` level on delivery errors and is
intentionally left untouched.
## Test plan
- [x] New regression test `TestKeepAliveFailure_Logged` asserts the
keepalive failure produces a log line on the configured logger.
- [x] Verified the test fails when the new `logger.Error(...)` call is
removed (sanity check that it pins the behavior).
- [x] `go test ./mcp/... -count=1` passes.
- [x] `go vet ./...` clean.
- [x] `go build ./...` clean.
Refs #218
---------
Co-authored-by: Maciej Kisiel <mkisiel@google.com>
This PR fixes a race condition where `ServerSession.keepaliveCancel` was
accessed in `Close` concurrently with its initialization in
`ServerSession.startKeepalive`. It also removes a now redundant test.
The fix moves keepalive initialization into `ServerSession.Connect`
similar to the implementation in `ClientSession.Connect`. This is safe
because the MCP spec allows keepalive pings before the initialization
messages. From the MCP
[spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle)
(emphasis added):
> The client SHOULD NOT send requests **other than pings** before the
server has responded to the initialize request.
The PR includes a reproducer test case, which fails with the following
error without this fix:
```
> go test -count=10 -race -run ^TestStreamableStatelessKeepaliveRace$ github.com/modelcontextprotocol/go-sdk/mcp 8s
==================
WARNING: DATA RACE
Write at 0x00c00007fbf0 by goroutine 1095:
github.com/modelcontextprotocol/go-sdk/mcp.startKeepalive()
/Users/benjamin/Documents/go-sdk/mcp/shared.go:590 +0x58
github.com/modelcontextprotocol/go-sdk/mcp.(*ServerSession).startKeepalive()
/Users/benjamin/Documents/go-sdk/mcp/server.go:1526 +0x130
github.com/modelcontextprotocol/go-sdk/mcp.(*ServerSession).initialized()
/Users/benjamin/Documents/go-sdk/mcp/server.go:1059 +0xf8
github.com/modelcontextprotocol/go-sdk/mcp.init.serverSessionMethod[go.shape.*uint8,go.shape.interface { GetMeta() map[string]interface {}; SetMeta(map[string]interface {}); github.com/modelcontextprotocol/go-sdk/mcp.isResult() }].func27()
/Users/benjamin/Documents/go-sdk/mcp/shared.go:332 +0x98
github.com/modelcontextprotocol/go-sdk/mcp.newServerMethodInfo[go.shape.*github.com/modelcontextprotocol/go-sdk/mcp.InitializedParams,go.shape.interface { GetMeta() map[string]interface {}; SetMeta(map[string]interface {}); github.com/modelcontextprotocol/go-sdk/mcp.isResult() },go.shape.struct { github.com/modelcontextprotocol/go-sdk/mcp.Meta "json:\"_meta,omitempty\"" }].func2()
/Users/benjamin/Documents/go-sdk/mcp/shared.go:272 +0x84
github.com/modelcontextprotocol/go-sdk/mcp.defaultReceivingMethodHandler[go.shape.*uint8]()
/Users/benjamin/Documents/go-sdk/mcp/shared.go:152 +0xe8
github.com/modelcontextprotocol/go-sdk/mcp.defaultReceivingMethodHandler[*github.com/modelcontextprotocol/go-sdk/mcp.ServerSession]()
/Users/benjamin/Documents/go-sdk/mcp/shared.go:146 +0x60
github.com/modelcontextprotocol/go-sdk/mcp.handleReceive[go.shape.*uint8]()
/Users/benjamin/Documents/go-sdk/mcp/shared.go:169 +0x228
github.com/modelcontextprotocol/go-sdk/mcp.(*ServerSession).handle()
/Users/benjamin/Documents/go-sdk/mcp/server.go:1445 +0x370
github.com/modelcontextprotocol/go-sdk/mcp.connect[go.shape.*uint8,go.shape.*uint8].func1.1()
/Users/benjamin/Documents/go-sdk/mcp/transport.go:170 +0x5c
github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2.HandlerFunc.Handle()
/Users/benjamin/Documents/go-sdk/internal/jsonrpc2/jsonrpc2.go:84 +0x48
github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2.(*Connection).handleAsync.func3()
/Users/benjamin/Documents/go-sdk/internal/jsonrpc2/conn.go:717 +0xd8
Previous read at 0x00c00007fbf0 by goroutine 1089:
github.com/modelcontextprotocol/go-sdk/mcp.(*ServerSession).Close()
/Users/benjamin/Documents/go-sdk/mcp/server.go:1502 +0x34
github.com/modelcontextprotocol/go-sdk/mcp.(*StreamableHTTPHandler).ServeHTTP.deferwrap1()
/Users/benjamin/Documents/go-sdk/mcp/streamable.go:519 +0x34
runtime.deferreturn()
/opt/homebrew/Cellar/go/1.25.4/libexec/src/runtime/panic.go:589 +0x5c
github.com/modelcontextprotocol/go-sdk/mcp.TestStreamableStatelessKeepaliveRace.mustNotPanic.func2()
/Users/benjamin/Documents/go-sdk/mcp/streamable_test.go:2156 +0x94
net/http.HandlerFunc.ServeHTTP()
/opt/homebrew/Cellar/go/1.25.4/libexec/src/net/http/server.go:2322 +0x48
net/http.serverHandler.ServeHTTP()
/opt/homebrew/Cellar/go/1.25.4/libexec/src/net/http/server.go:3340 +0x270
net/http.(*conn).serve()
/opt/homebrew/Cellar/go/1.25.4/libexec/src/net/http/server.go:2109 +0x9b0
net/http.(*Server).Serve.gowrap3()
/opt/homebrew/Cellar/go/1.25.4/libexec/src/net/http/server.go:3493 +0x48
Goroutine 1095 (running) created at:
github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2.(*Connection).handleAsync()
/Users/benjamin/Documents/go-sdk/internal/jsonrpc2/conn.go:715 +0x334
github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2.(*Connection).acceptRequest.func2.gowrap1()
/Users/benjamin/Documents/go-sdk/internal/jsonrpc2/conn.go:676 +0x34
Goroutine 1089 (running) created at:
net/http.(*Server).Serve()
/opt/homebrew/Cellar/go/1.25.4/libexec/src/net/http/server.go:3493 +0x5dc
net/http/httptest.(*Server).goServe.func1()
/opt/homebrew/Cellar/go/1.25.4/libexec/src/net/http/httptest/server.go:311 +0x98
==================
--- FAIL: TestStreamableStatelessKeepaliveRace (0.14s)
testing.go:1617: race detected during execution of test
FAIL
FAIL github.com/modelcontextprotocol/go-sdk/mcp 1.727s
FAIL
```
---------
Co-authored-by: Maciej Kisiel <mkisiel@google.com>
As reported in
https://github.com/modelcontextprotocol/go-sdk/issues/805, case
insensitive unmarshaling can pose security issues. This change adjusts
all non-test and non-example unmarshaling to use a case sensitive mode.
This introduces a new external dependency
`github.com/segmentio/encoding`.
Summary
Add support for tool use within sampling requests, as described in the
MCP 2025-11-25 spec's sampling.tools capability.
New types
Content types:
- ToolUseContent — represents a tool invocation request from the
assistant
- ToolResultContent — represents the result of a tool invocation, with
nested []Content for the result blocks
Capability types:
- SamplingCapabilities gains Tools and Context sub-fields
- SamplingToolsCapabilities, SamplingContextCapabilities
Tool-enabled sampling (parallel tool calls):
- CreateMessageWithToolsParams with SamplingMessageV2 (supports array
content)
- CreateMessageWithToolsResult (supports array content)
- ToolChoice — controls tool invocation mode (auto, required, none)
- ServerSession.CreateMessageWithTools
- ClientOptions.CreateMessageWithToolsHandler
Design
Following the TypeScript SDK's pattern, tool-enabled sampling uses
separate types from basic sampling to avoid breaking the existing API.
The basic CreateMessage/CreateMessageResult path
is unchanged.
Both paths share the same wire method (sampling/createMessage) and go
through the method info table. The table uses the broader
CreateMessageWithToolsResult type internally;
CreateMessage downconverts (erroring if multiple content blocks are
returned). This is documented as a rough edge to unify in v2.
Setting CreateMessageWithToolsHandler automatically infers the
sampling.tools capability. It is a panic to set both
CreateMessageHandler and CreateMessageWithToolsHandler.
References
-
https://modelcontextprotocol.io/specification/2025-11-25/client/sampling
- TypeScript SDK: CreateMessageResultWithToolsSchema /
CreateMessageRequestParamsWithTools
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
I encountered these logs, and they were distracting and noisy. We
shouldn't log to stderr unless configured by the user. Thread through
the logger.
Also, use ensureLogger in the streamable client to simplify call sites.
Updates #708
Updates #684
The prime and close SSE events (SEP-1699) were added in protocol version
2025-11-25. Only send these events when the client negotiates that
version or later.
- In servePOST, extract protocol version from InitializeParams for
initialize requests, otherwise use the Mcp-Protocol-Version header
- In serveGET/acquireStream, read protocol version from header
- Only set closeLocked callback when prime/close is supported
- Add unexported protocolVersion field to ClientSessionOptions for
testing
Fixes#686
Expose the jsonrpc.Error type to allow access to underlying JSON-RPC
error codes. Also, expose common JSON-RPC error codes in the jsonrpc
package, and MCP error codes in the mcp package.
Also, implement support for the MCP URL elicitation required error
(-32042), enabling servers to request out-of-band user authorization and
clients to automatically handle these requests with retry logic.
Server-side:
- Add CodeURLElicitationRequired constant
- Add URLElicitationRequiredError() constructor.
Client-side:
- Add urlElicitationMiddleware to intercept URLElicitationRequired
errors and retry calls.
- Update callElicitationCompleteHandler to signal waiting operations.
- Fix capability advertisement to support both form and URL modes.
Fixes#452Fixes#623
Add initial support for URL mode elicitation, by introducing the new
types, as well as both server and client-side validation.
A follow-up CL will add support for ErrURLElicitationRequired.
For #623
Add a timeout option for the streamable HTTP handler that automatically
cleans up idle sessions.
Also, fix a bug in the streamable client, where we hang on a request
even though the client can never get a response (because the HTTP
request terminated without a response or Last-Event-Id).
Fixes#499
As discussed at length in
https://github.com/modelcontextprotocol/go-sdk/issues/518, coupling our
schema fields to
jsonschema.Schema limits our user's ability to use a different schema
package, and makes it impossible for our client to talk to some
backends.
This CL relaxes the type of `*jsonschema.Schema` fields to be of type
`any`, which is a breaking change but is unlikely to affect most server
code. (It *is* a nontrivial change for clients).
Fixes https://github.com/modelcontextprotocol/go-sdk/issues/518
Add a framework for feature documentation, and start populating it with
our SDK documentation.
This framework is as follows:
- internal/docs/**.src.md is the markdown source for the docs/
directory.
- The x/example/internal/cmd/weave tool is used to compile these docs to
the top-level docs, supporting both linked code samples and generated
tables of contents.
- The readme-check workflow is updated to check these docs as well.
- The structure of these docs follows the MCP spec.
- Wherever possible, example code is linked from actual Go documentation
examples, so that it is testable.
Some minor modifications to the weave tool were made to support this
framework.
Additionally, partially fill out this documentation with content on
base protocol and client features, as well as troubleshooting help.
Along the way, a bug was encountered that our LoggingTransport was not
concurrency safe. This is fixed with a mutex.
Fixes https://github.com/modelcontextprotocol/go-sdk/issues/466
Fixes https://github.com/modelcontextprotocol/go-sdk/issues/409
Updates https://github.com/modelcontextprotocol/go-sdk/issues/442
An onClose function is passed to the ServerSession and ClientSession to
help cleanup resources from the caller.
The onClose function will be executed as part of the ServerSession and
ClientSession closure.
Fixes#258
This CL enables the server session to see what capabilities the client
has by introducing the InitializeParams() function.
This CL also adds a test to ensure ClientCapabilities is accurate.
Fixes#141