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
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>
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.