tools/call with "arguments": null panics the server when the tool schema has a default

已关闭 适合新手
#1,340 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
86/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
go
领域
api, backend

调研方向

从 mcp/tool.go 中的 applySchema 开始,将其对 null 的处理与 issue 中提到的输出分支进行比较。为带有 null 参数和 schema 默认值的 tools/call 请求添加回归测试;完成标准是该请求不再引发 panic,并且要么应用默认值,要么返回 JSON-RPC 错误或工具错误。

由索引模型根据 Issue 内容生成。

描述

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))
}
  1. go run .

  2. The process exits with the panic shown under Logs.

  3. Replace "arguments":null with "arguments":{}, or drop the field: the program prints

    200 {"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/[email protected]/jsonschema/validate.go:741
github.com/google/jsonschema-go/jsonschema.(*Resolved).ApplyDefaults(...)
	github.com/google/[email protected]/jsonschema/validate.go:701
github.com/modelcontextprotocol/go-sdk/mcp.applySchema(...)
	github.com/modelcontextprotocol/[email protected]/mcp/tool.go:117
github.com/modelcontextprotocol/go-sdk/mcp.toolForErr[...].func1(...)
	github.com/modelcontextprotocol/[email protected]/mcp/server.go:402
github.com/modelcontextprotocol/go-sdk/mcp.(*Server).callTool(...)
	github.com/modelcontextprotocol/[email protected]/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 for null:

    if v == nil {
    	v = map[string]any{}
    }
    
  • Or make ApplyDefaults in jsonschema-go tolerate a nil map, which would also cover the elicitation paths patched in #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)
	}
})
主要语言
Go
星标
5.3k
派生
607
平均合并
1 天 7 小时
30 天内合并 PR
81

环境准备

在 Codespaces 中打开

在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

modelcontextprotocol/go-sdk 的其他 Issue

查看 modelcontextprotocol/go-sdk 的全部 Issue

相似的 Issue

更多 Go Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。