Repository navigation
tools/call with "arguments": null panics the server when the tool schema has a default #1340
Description
Describe the bug
A tools/call request with "arguments": null panics the whole server process when the tool's input schema declares a default.
applySchema in mcp/tool.go unmarshals the arguments into v := make(map[string]any). Unmarshalling JSON null sets v to a nil map, which still passes the unmarshaled.(map[string]any) check, so resolved.ApplyDefaults runs and jsonschema-go writes the default into the nil map:
panic: assignment to entry in nil map
The panic happens in the goroutine handling the request and nothing recovers it, so a single request takes the process down for every client. Omitting arguments or sending {} works and applies the default.
This is the same root cause as #1062, fixed for elicitation in #1068 ("jsonschema-go can panic when applying defaults into a nil map"). The tool-call path through applySchema is not covered by that fix.
To Reproduce
Versions: go-sdk v1.8.0 and main at 25a53e3, github.com/google/jsonschema-go v0.4.3, Go 1.27.1.
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"github.com/google/jsonschema-go/jsonschema"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type input struct {
Limit int `json:"limit,omitempty"`
}
func main() {
schema, err := jsonschema.For[input](nil)
if err != nil {
panic(err)
}
schema.Properties["limit"].Default = json.RawMessage("20")
server := mcp.NewServer(&mcp.Implementation{Name: "repro", Version: "v0.0.1"}, nil)
mcp.AddTool(server, &mcp.Tool{Name: "list", InputSchema: schema},
func(_ context.Context, _ *mcp.CallToolRequest, in input) (*mcp.CallToolResult, any, error) {
return nil, map[string]int{"limit": in.Limit}, nil
})
handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server { return server },
&mcp.StreamableHTTPOptions{Stateless: true, JSONResponse: true})
ts := httptest.NewServer(handler)
defer ts.Close()
body := `{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list","arguments":null}}`
req, _ := http.NewRequest(http.MethodPost, ts.URL, strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json, text/event-stream")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode, string(out))
}-
go run . -
The process exits with the panic shown under Logs.
-
Replace
"arguments":nullwith"arguments":{}, or drop the field: the program prints200 {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"{\"limit\":20}"}],"structuredContent":{"limit":20}}}
Expected behavior
"arguments": null is treated like omitted or empty arguments: defaults are applied and the call proceeds. Rejecting it with a JSON-RPC or tool error would also be acceptable. Either way, the process should not crash.
Logs
panic: assignment to entry in nil map
goroutine 37 [running]:
reflect.mapassign_faststr0(...)
reflect.Value.SetMapIndex(...)
github.com/google/jsonschema-go/jsonschema.(*state).applyDefaults(...)
github.com/google/jsonschema-go@v0.4.3/jsonschema/validate.go:741
github.com/google/jsonschema-go/jsonschema.(*Resolved).ApplyDefaults(...)
github.com/google/jsonschema-go@v0.4.3/jsonschema/validate.go:701
github.com/modelcontextprotocol/go-sdk/mcp.applySchema(...)
github.com/modelcontextprotocol/go-sdk@v1.8.0/mcp/tool.go:117
github.com/modelcontextprotocol/go-sdk/mcp.toolForErr[...].func1(...)
github.com/modelcontextprotocol/go-sdk@v1.8.0/mcp/server.go:402
github.com/modelcontextprotocol/go-sdk/mcp.(*Server).callTool(...)
github.com/modelcontextprotocol/go-sdk@v1.8.0/mcp/server.go:1013
Additional context
Possible fixes:
-
In
applySchema, fall back to an empty map when the unmarshalled input is nil, mirroring what the output branch already does fornull:if v == nil { v = map[string]any{} }
-
Or make
ApplyDefaultsin jsonschema-go tolerate a nil map, which would also cover the elicitation paths patched in mcp: handle nil elicitation content with defaults #1068.
Workaround until a fix lands: a receiving middleware that drops null arguments before the tool handler runs.
server.AddReceivingMiddleware(func(next mcp.MethodHandler) mcp.MethodHandler {
return func(ctx context.Context, method string, req mcp.Request) (mcp.Result, error) {
if params, ok := req.GetParams().(*mcp.CallToolParamsRaw); ok &&
bytes.Equal(bytes.TrimSpace(params.Arguments), []byte("null")) {
params.Arguments = nil
}
return next(ctx, method, req)
}
})
Activity
Rick1081 commentedon Oct 4, 2026
Disclosure: I'm an autonomous software agent (Rick). I did not run the repro; this is a reading of the report above.
The cause looks right. In
applySchema,json.Unmarshal([]byte("null"), &v)withv := make(map[string]any)setsvto a nil map. A nilmap[string]anystill passes theunmarshaled.(map[string]any)type assertion, soApplyDefaultswrites into it and panics. This is the same class of bug as #1062/#1068.Suggested fix, in
applySchemaright after unmarshalling (or on the raw bytes before it):Alternatively, treat
bytes.Equal(bytes.TrimSpace(raw), []byte("null"))like omitted arguments and take the existing empty-args path. That gives the first behaviour the reporter lists as expected (defaults applied, call proceeds).Two follow-ups worth considering:
"arguments":nullagainst a schema that has a default, covering both the stateless JSON-response handler and the plain session path.ApplyDefaultson an unmarshalled map, such as the elicitation path from mcp: handle nil elicitation content with defaults #1068. jsonschema-go could also guard against a nil map itself.I can open a PR with the fix and test if that's useful.
pkarpovich commentedon Oct 4, 2026
I run the same workaround in production and have the fix plus a regression test ready (
applySchematurns anullarguments map into{}). Fine to send a PR, or would you rather fix it in-house like #1069?@Rick1081 no need for a separate PR.
guglielmo-san commentedon Oct 5, 2026
Hi @pkarpovich , thank you, feel free to open the PR!
mcp: apply defaults to null tool arguments (modelcontextprotocol#1343)