From 9820a28ab0af4ef5f6f53663f89d0d47578fdb18 Mon Sep 17 00:00:00 2001 From: wucm667 Date: Thu, 30 Apr 2026 22:16:25 +0800 Subject: [PATCH 1/7] fix(mcp): add nil check for schema to prevent SIGSEGV in AddTool --- mcp/server.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mcp/server.go b/mcp/server.go index 28504376..ef942711 100644 --- a/mcp/server.go +++ b/mcp/server.go @@ -437,6 +437,9 @@ func setSchema[T any](sfield *any, rfield **jsonschema.Resolved, cache *SchemaCa if err != nil { return zero, err } + if internalSchema == nil { + return zero, fmt.Errorf("schema is nil for type %v", rt) + } *sfield = internalSchema resolved, err := internalSchema.Resolve(&jsonschema.ResolveOptions{ValidateDefaults: true}) @@ -466,6 +469,10 @@ func setSchema[T any](sfield *any, rfield **jsonschema.Resolved, cache *SchemaCa } } + if internalSchema == nil { + return zero, fmt.Errorf("schema is nil for type %v", rt) + } + resolved, err := internalSchema.Resolve(&jsonschema.ResolveOptions{ValidateDefaults: true}) if err != nil { return zero, err From 889a458ca0ab1dbdfc08b4665f0ed54fc2e263d4 Mon Sep 17 00:00:00 2001 From: wucm667 Date: Tue, 5 May 2026 13:50:59 +0800 Subject: [PATCH 2/7] fix(mcp): handle typed nil schema for InputSchema and OutputSchema Add nil checks for *jsonschema.Schema after type assertion to catch the case where a typed nil pointer is assigned to the interface field, which would otherwise cause a SIGSEGV when accessing schema properties. Suggested-by: guglielmo-san Signed-off-by: wucm667 --- mcp/server.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mcp/server.go b/mcp/server.go index ef942711..16d06ca8 100644 --- a/mcp/server.go +++ b/mcp/server.go @@ -247,6 +247,9 @@ func (s *Server) AddTool(t *Tool, h ToolHandler) { panic(fmt.Errorf("AddTool %q: missing input schema", t.Name)) } if s, ok := t.InputSchema.(*jsonschema.Schema); ok { + if s == nil { + panic(fmt.Errorf("AddTool %q: input schema is nil", t.Name)) + } if s.Type != "object" { panic(fmt.Errorf(`AddTool %q: input schema must have type "object"`, t.Name)) } @@ -261,6 +264,9 @@ func (s *Server) AddTool(t *Tool, h ToolHandler) { } if t.OutputSchema != nil { if s, ok := t.OutputSchema.(*jsonschema.Schema); ok { + if s == nil { + panic(fmt.Errorf("AddTool %q: output schema is nil", t.Name)) + } if s.Type != "object" { panic(fmt.Errorf(`AddTool %q: output schema must have type "object"`, t.Name)) } From ee11877cba7cf4037dacf0d2a445862922aded19 Mon Sep 17 00:00:00 2001 From: wucm667 Date: Tue, 5 May 2026 17:48:33 +0800 Subject: [PATCH 3/7] test(mcp): add tests for typed nil schema checks in AddTool Cover the typed nil InputSchema and OutputSchema cases that were added to prevent SIGSEGV when a typed nil *jsonschema.Schema is passed as a schema argument. --- mcp/server_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/mcp/server_test.go b/mcp/server_test.go index 1312e1d9..8ec20016 100644 --- a/mcp/server_test.go +++ b/mcp/server_test.go @@ -8,6 +8,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "log" "log/slog" "slices" @@ -604,6 +605,44 @@ func TestAddToolNameValidation(t *testing.T) { } } +func TestAddToolNilSchema(t *testing.T) { + var nilSchema *jsonschema.Schema + + panicMsg := func(f func()) string { + var msg string + func() { + defer func() { + if r := recover(); r != nil { + msg = fmt.Sprintf("%v", r) + } + }() + f() + }() + return msg + } + + // Call s.AddTool directly to exercise the typed-nil checks added to that method. + msg := panicMsg(func() { + s := NewServer(testImpl, nil) + s.AddTool(&Tool{Name: "T", InputSchema: nilSchema}, nil) + }) + if msg == "" { + t.Error("typed nil InputSchema: expected panic") + } else if !strings.Contains(msg, "input schema is nil") { + t.Errorf("typed nil InputSchema: panic message %q does not contain %q", msg, "input schema is nil") + } + + msg = panicMsg(func() { + s := NewServer(testImpl, nil) + s.AddTool(&Tool{Name: "T", InputSchema: &jsonschema.Schema{Type: "object"}, OutputSchema: nilSchema}, nil) + }) + if msg == "" { + t.Error("typed nil OutputSchema: expected panic") + } else if !strings.Contains(msg, "output schema is nil") { + t.Errorf("typed nil OutputSchema: panic message %q does not contain %q", msg, "output schema is nil") + } +} + type schema = jsonschema.Schema func testToolForSchema[In, Out any](t *testing.T, tool *Tool, in string, out Out, wantIn, wantOut any, wantErrContaining string) { From 3da642e7a20fceb09edd9ec0fd67d5e665a59063 Mon Sep 17 00:00:00 2001 From: wucm667 <109257021+wucm667@users.noreply.github.com> Date: Tue, 5 May 2026 19:16:03 +0800 Subject: [PATCH 4/7] Update mcp/server_test.go Co-authored-by: Guglielmo Colombo --- mcp/server_test.go | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/mcp/server_test.go b/mcp/server_test.go index 8ec20016..556f94b9 100644 --- a/mcp/server_test.go +++ b/mcp/server_test.go @@ -608,18 +608,15 @@ func TestAddToolNameValidation(t *testing.T) { func TestAddToolNilSchema(t *testing.T) { var nilSchema *jsonschema.Schema - panicMsg := func(f func()) string { - var msg string - func() { - defer func() { - if r := recover(); r != nil { - msg = fmt.Sprintf("%v", r) - } - }() - f() - }() - return msg - } + panicMsg := func(f func()) (msg string) { + defer func() { + if r := recover(); r != nil { + msg = fmt.Sprintf("%v", r) + } + }() + f() + return msg + } // Call s.AddTool directly to exercise the typed-nil checks added to that method. msg := panicMsg(func() { From 6250702dacd24cfa0227344831bfce93dad4275a Mon Sep 17 00:00:00 2001 From: wucm667 <109257021+wucm667@users.noreply.github.com> Date: Tue, 5 May 2026 19:16:15 +0800 Subject: [PATCH 5/7] Update mcp/server_test.go Co-authored-by: Guglielmo Colombo --- mcp/server_test.go | 46 ++++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/mcp/server_test.go b/mcp/server_test.go index 556f94b9..7143b286 100644 --- a/mcp/server_test.go +++ b/mcp/server_test.go @@ -619,26 +619,36 @@ func TestAddToolNilSchema(t *testing.T) { } // Call s.AddTool directly to exercise the typed-nil checks added to that method. - msg := panicMsg(func() { - s := NewServer(testImpl, nil) - s.AddTool(&Tool{Name: "T", InputSchema: nilSchema}, nil) - }) - if msg == "" { - t.Error("typed nil InputSchema: expected panic") - } else if !strings.Contains(msg, "input schema is nil") { - t.Errorf("typed nil InputSchema: panic message %q does not contain %q", msg, "input schema is nil") +tests := []struct { + name string + tool *Tool + wantContain string + }{ + { + name: "typed nil InputSchema", + tool: &Tool{Name: "T", InputSchema: nilSchema}, + wantContain: "input schema is nil", + }, + { + name: "typed nil OutputSchema", + tool: &Tool{Name: "T", InputSchema: &jsonschema.Schema{Type: "object"}, OutputSchema: nilSchema}, + wantContain: "output schema is nil", + }, } - - msg = panicMsg(func() { - s := NewServer(testImpl, nil) - s.AddTool(&Tool{Name: "T", InputSchema: &jsonschema.Schema{Type: "object"}, OutputSchema: nilSchema}, nil) - }) - if msg == "" { - t.Error("typed nil OutputSchema: expected panic") - } else if !strings.Contains(msg, "output schema is nil") { - t.Errorf("typed nil OutputSchema: panic message %q does not contain %q", msg, "output schema is nil") + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + s := NewServer(testImpl, nil) + + msg := panicMsg(func() { + s.AddTool(tc.tool, nil) + }) + if msg == "" { + t.Error("expected panic") + } else if !strings.Contains(msg, tc.wantContain) { + t.Errorf("panic message %q does not contain %q", msg, tc.wantContain) + } + }) } -} type schema = jsonschema.Schema From d5ece53e7af5b26726ca0d0420d024a58c36dda8 Mon Sep 17 00:00:00 2001 From: wucm667 Date: Tue, 5 May 2026 20:05:32 +0800 Subject: [PATCH 6/7] fix(mcp): add missing closing brace in TestAddToolNilSchema The previous GitHub suggested change introduced a syntax error by omitting the closing brace for TestAddToolNilSchema, causing lint and test failures in CI. Signed-off-by: wucm667 --- mcp/server_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/mcp/server_test.go b/mcp/server_test.go index 7143b286..e96dd04b 100644 --- a/mcp/server_test.go +++ b/mcp/server_test.go @@ -649,6 +649,7 @@ tests := []struct { } }) } +} type schema = jsonschema.Schema From 73c51a0b8a17c4b4b59cc6945ab289e012943cbf Mon Sep 17 00:00:00 2001 From: wucm667 Date: Wed, 6 May 2026 09:55:44 +0800 Subject: [PATCH 7/7] fix(mcp): run gofmt on server_test.go --- mcp/server_test.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/mcp/server_test.go b/mcp/server_test.go index e96dd04b..2937ea2b 100644 --- a/mcp/server_test.go +++ b/mcp/server_test.go @@ -608,18 +608,18 @@ func TestAddToolNameValidation(t *testing.T) { func TestAddToolNilSchema(t *testing.T) { var nilSchema *jsonschema.Schema - panicMsg := func(f func()) (msg string) { - defer func() { - if r := recover(); r != nil { - msg = fmt.Sprintf("%v", r) - } - }() - f() - return msg - } + panicMsg := func(f func()) (msg string) { + defer func() { + if r := recover(); r != nil { + msg = fmt.Sprintf("%v", r) + } + }() + f() + return msg + } // Call s.AddTool directly to exercise the typed-nil checks added to that method. -tests := []struct { + tests := []struct { name string tool *Tool wantContain string @@ -638,7 +638,7 @@ tests := []struct { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { s := NewServer(testImpl, nil) - + msg := panicMsg(func() { s.AddTool(tc.tool, nil) })