Skip to content

Commit 16f2857

Browse files
authored
mcp/tool: duplicate tools should not error (#295)
This CL causes the dup to fail.
1 parent bb6dade commit 16f2857

File tree

2 files changed

+44
-0
lines changed

2 files changed

+44
-0
lines changed

mcp/mcp_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,4 +937,39 @@ func TestKeepAliveFailure(t *testing.T) {
937937
t.Errorf("expected connection to be closed by keepalive, but it wasn't. Last error: %v", err)
938938
}
939939

940+
func TestAddTool_DuplicateNoPanicAndNoDuplicate(t *testing.T) {
941+
// Adding the same tool pointer twice should not panic and should not
942+
// produce duplicates in the server's tool list.
943+
_, cs := basicConnection(t, func(s *Server) {
944+
// Use two distinct Tool instances with the same name but different
945+
// descriptions to ensure the second replaces the first
946+
// This case was written specifically to reproduce a bug where duplicate tools where causing jsonschema errors
947+
t1 := &Tool{Name: "dup", Description: "first", InputSchema: &jsonschema.Schema{}}
948+
t2 := &Tool{Name: "dup", Description: "second", InputSchema: &jsonschema.Schema{}}
949+
s.AddTool(t1, nopHandler)
950+
s.AddTool(t2, nopHandler)
951+
})
952+
defer cs.Close()
953+
954+
ctx := context.Background()
955+
res, err := cs.ListTools(ctx, nil)
956+
if err != nil {
957+
t.Fatal(err)
958+
}
959+
var count int
960+
var gotDesc string
961+
for _, tt := range res.Tools {
962+
if tt.Name == "dup" {
963+
count++
964+
gotDesc = tt.Description
965+
}
966+
}
967+
if count != 1 {
968+
t.Fatalf("expected exactly one 'dup' tool, got %d", count)
969+
}
970+
if gotDesc != "second" {
971+
t.Fatalf("expected replaced tool to have description %q, got %q", "second", gotDesc)
972+
}
973+
}
974+
940975
var testImpl = &Implementation{Name: "test", Version: "v1.0.0"}

mcp/server.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,15 @@ func AddTool[In, Out any](s *Server, t *Tool, h ToolHandlerFor[In, Out]) {
171171

172172
func addToolErr[In, Out any](s *Server, t *Tool, h ToolHandlerFor[In, Out]) (err error) {
173173
defer util.Wrapf(&err, "adding tool %q", t.Name)
174+
// If the exact same Tool pointer has already been registered under this name,
175+
// avoid rebuilding schemas and re-registering. This prevents duplicate
176+
// registration from causing errors (and unnecessary work).
177+
s.mu.Lock()
178+
if existing, ok := s.tools.get(t.Name); ok && existing.tool == t {
179+
s.mu.Unlock()
180+
return nil
181+
}
182+
s.mu.Unlock()
174183
st, err := newServerTool(t, h)
175184
if err != nil {
176185
return err

0 commit comments

Comments
 (0)