go-sdk/mcp
Repository files (latest commit first)
Filename Latest commit message Latest commit date
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
..
testdata mcp: update return meta in DiscoverResult (SEP-2575) (#1097) 2026-07-16 16:10:12 +02:00
cache.go mcp: do not enforce TTL on cached tool (#1053) 2026-07-07 13:43:10 +02:00
capabilities_test.go mcp: add 2026-07-28 to the supported protocol versions (#1020) 2026-06-24 12:02:56 +02:00
client.go mcp: add HasParams so middleware can tell whether params are present (#1269) 2026-09-14 18:45:48 +02:00
client_example_test.go mcp: add docs for tier 1 assessment (#1195) 2026-08-24 12:00:59 +02:00
client_list_test.go mcp: allow any value for schemas (#517) 2025-09-26 11:55:54 -04:00
client_test.go mcp: export ClientSessionOptions.ProtocolVersion (#1127) 2026-08-03 15:29:52 +02:00
cmd.go mcp: speed up TestCommandTransportDuration 2025-09-12 12:00:29 -04:00
cmd_export_test.go mcp: speed up TestCommandTransportDuration 2025-09-12 12:00:29 -04:00
cmd_test.go all: modernize code (#868) 2026-03-31 10:58:02 +02:00
conformance_test.go mcp: add 2026-07-28 to the supported protocol versions (#1020) 2026-06-24 12:02:56 +02:00
content.go mcp: Deprecate roots, sampling and logging (SEP-2577) (#1017) 2026-06-23 11:11:08 +02:00
content_nil_test.go mcp: update return meta in DiscoverResult (SEP-2575) (#1097) 2026-07-16 16:10:12 +02:00
content_test.go mcp: switch icon theme to named string type (#733) 2025-12-19 16:44:10 -05:00
elicitation_test.go mcp: add ServerSession.NotifyElicitationComplete (#1194) 2026-08-24 10:14:28 +02:00
error_test.go mcp: add ServerSession.NotifyElicitationComplete (#1194) 2026-08-24 10:14:28 +02:00
event.go mcp: add an sse event size cap (#1205) 2026-09-02 11:35:23 +02:00
event_test.go mcp: add an sse event size cap (#1205) 2026-09-02 11:35:23 +02:00
features.go mcp: minor cleanup of some TODOs (#332) 2025-08-20 12:55:19 -04:00
features_test.go mcp: change tool handler design (#325) 2025-08-20 19:36:34 -04:00
logging.go mcp: keep request log levels scoped to each request (#1168) 2026-08-17 14:00:41 +02:00
mcp.go mcp: fix inaccurate package doc (#534) 2025-09-26 14:16:10 -04:00
mcp_example_test.go mcp: add 2026-07-28 to the supported protocol versions (#1020) 2026-06-24 12:02:56 +02:00
mcp_test.go mcp: carry a cancelled notification's reason as the request's cancel cause (#1255) 2026-09-14 11:33:30 +00:00
mrtr.go mcp: label resultType on results that bypass the dispatcher (#1226) 2026-09-14 09:52:10 +02:00
mrtr_test.go mcp: don't mutate caller params on multi-round-trip retries (#1145) 2026-08-31 13:53:34 +02:00
prompt.go mcp: align PromptHandler args with other handlers (#348) 2025-08-22 10:44:05 -04:00
protocol.go mcp: label resultType on results that bypass the dispatcher (#1226) 2026-09-14 09:52:10 +02:00
protocol_test.go mcp: include resultType on new-protocol responses (#1060) 2026-07-13 11:51:19 +00:00
requests.go mcp: Implement subscriptions/listen rpc (SEP-2575) (#1007) 2026-06-22 18:57:36 +02:00
resource.go mcp: align resource not found error code with sep-2164 (#931) 2026-05-05 16:20:02 +02:00
resource_test.go mcp/Resource: Refactor URI Template Matching with uritemplate Library (#100) 2025-07-08 15:52:54 -04:00
sampling_test.go mcp: export ClientSessionOptions.ProtocolVersion (#1127) 2026-08-03 15:29:52 +02:00
schema_cache.go perf: add schema caching to avoid repeated reflection (#685) 2026-01-20 20:16:46 -05:00
schema_cache_test.go all: modernize code (#868) 2026-03-31 10:58:02 +02:00
server.go mcp: add HasParams so middleware can tell whether params are present (#1269) 2026-09-14 18:45:48 +02:00
server_example_test.go mcp: add docs for tier 1 assessment (#1195) 2026-08-24 12:00:59 +02:00
server_test.go mcp: classify the per-request protocol version by membership (#1268) 2026-09-14 15:49:53 +00:00
session.go mcp: allow restricting the protocol versions a server supports (#1199) 2026-08-28 16:06:10 +02:00
shared.go mcp: add HasParams so middleware can tell whether params are present (#1269) 2026-09-14 18:45:48 +02:00
shared_test.go mcp: add HasParams so middleware can tell whether params are present (#1269) 2026-09-14 18:45:48 +02:00
sse.go refactor: remove legacy MCPGODEBUG compatibility for new protocol release (#1237) 2026-09-04 10:08:52 +02:00
sse_example_test.go mcp: add docs for tier 1 assessment (#1195) 2026-08-24 12:00:59 +02:00
sse_test.go mcp: add a max request body size of the old transport (#1224) 2026-09-01 17:28:35 +02:00
streamable.go refactor: remove legacy MCPGODEBUG compatibility for new protocol release (#1237) 2026-09-04 10:08:52 +02:00
streamable_bench_test.go all: modernize code (#868) 2026-03-31 10:58:02 +02:00
streamable_client.go mcp: do not call DELETE for sessionless & doc adjustments (#960) 2026-05-15 21:47:34 +02:00
streamable_client_test.go mcp: tolerate 404 on subscriptions/listen in stateless mode (#1193) 2026-08-25 14:27:37 +02:00
streamable_example_test.go mcp: add 2026-07-28 to the supported protocol versions (#1020) 2026-06-24 12:02:56 +02:00
streamable_headers.go mcp: base64-encode a non-header-safe Mcp-Name (#1246) 2026-09-07 10:16:16 +02:00
streamable_headers_test.go mcp: base64-encode a non-header-safe Mcp-Name (#1246) 2026-09-07 10:16:16 +02:00
streamable_server.go mcp: simplify stream state management in streamable transport 2025-12-10 13:28:25 -05:00
streamable_test.go mcp: add an sse event size cap (#1205) 2026-09-02 11:35:23 +02:00
tool.go mcp: add docs for tier 1 assessment (#1195) 2026-08-24 12:00:59 +02:00
tool_example_test.go mcp: add docs for tier 1 assessment (#1195) 2026-08-24 12:00:59 +02:00
tool_test.go mcp: tool.InputSchema and tool.OutputSchema validation (SEP-2106) (#1009) 2026-06-16 13:55:47 +02:00
transport.go mcp: carry a cancelled notification's reason as the request's cancel cause (#1255) 2026-09-14 11:33:30 +00:00
transport_example_test.go mcp: update return meta in DiscoverResult (SEP-2575) (#1097) 2026-07-16 16:10:12 +02:00
transport_test.go mcp: add an sse event size cap (#1205) 2026-09-02 11:35:23 +02:00
util.go all: use case-sensitive JSON unmarshaling (#807) 2026-02-18 16:38:36 +01:00