Skip to content

mcp: fix ListTools panic and null normalization on malformed tools - #1120

Merged
guglielmo-san merged 2 commits into
modelcontextprotocol:mainfrom
n0liu:fix/filter-valid-tools-nil-handling
Jul 30, 2026
Merged

mcp: fix ListTools panic and null normalization on malformed tools#1120
guglielmo-san merged 2 commits into
modelcontextprotocol:mainfrom
n0liu:fix/filter-valid-tools-nil-handling

Conversation

@n0liu

@n0liu n0liu commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

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:

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.

filterValidTools panicked with a nil-pointer dereference when a server
returned a malformed "tools":[null] response, and normalized a
"tools":null response into a non-nil empty slice.

Guard nil tool elements (log and exclude them) so untrusted server
input can no longer crash the client, and preserve a nil tools slice as
nil instead of laundering it into a non-nil empty slice.

Fixes modelcontextprotocol#1119
Comment thread mcp/streamable_headers.go Outdated
Comment on lines +262 to +266
// Preserve a nil slice as nil rather than normalizing a malformed
// "tools":null response into a non-nil empty slice.
if tools == nil {
return nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this code is necessary. Returning an empty list or nil does not change anything from the user point of view

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point — nil vs an empty slice isn't observable to callers, so I've dropped that guard. The nil-tool skip that actually fixes the tools:[null] panic stays. Done in b73df06.

Comment thread mcp/streamable_headers_test.go Outdated
Comment on lines +974 to +976
func TestFilterValidToolsNilHandling(t *testing.T) {
// A malformed "tools":[null] response must not panic the client, and a
// "tools":null response must not be normalized into a non-nil empty slice.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you merge this test with the existing FilterValidTools ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — merged the nil-handling checks into TestFilterValidTools and removed the separate test (b73df06).

- Drop the nil-slice guard: returning nil vs an empty slice is not
  observable to callers, so the special case is unnecessary. The
  nil-*tool* guard that fixes the panic on "tools":[null] stays.
- Merge the nil-handling checks into the existing TestFilterValidTools
  instead of a separate test function.
@guglielmo-san
guglielmo-san merged commit 7743b71 into modelcontextprotocol:main Jul 30, 2026
9 checks passed
@guglielmo-san

Copy link
Copy Markdown
Contributor

Thank you for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mcp: ListTools normalizes tools:null and panics on tools:[null]

2 participants