Skip to content

Commit 2cd60ac

Browse files
feat(get-server-tools): add GetTools method for retrieve MCPServer.tools
1 parent 8f5b048 commit 2cd60ac

File tree

2 files changed

+366
-0
lines changed

2 files changed

+366
-0
lines changed

server/server.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,16 @@ func (s *MCPServer) SetTools(tools ...ServerTool) {
493493
s.AddTools(tools...)
494494
}
495495

496+
// GetTools retrieves the currently registered tools
497+
func (s *MCPServer) GetTools() (map[string]ServerTool, error) {
498+
s.toolsMu.RLock()
499+
defer s.toolsMu.RUnlock()
500+
if s.tools != nil {
501+
return s.tools, nil
502+
}
503+
return nil, nil
504+
}
505+
496506
// DeleteTools removes tools from the server
497507
func (s *MCPServer) DeleteTools(names ...string) {
498508
s.toolsMu.Lock()

server/server_test.go

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,3 +2022,359 @@ func TestMCPServer_ProtocolNegotiation(t *testing.T) {
20222022
})
20232023
}
20242024
}
2025+
2026+
func TestMCPServer_GetTools(t *testing.T) { t.Run("EmptyServer", func(t *testing.T) {
2027+
server := NewMCPServer("test-server", "1.0.0")
2028+
2029+
tools, err := server.GetTools()
2030+
2031+
assert.NoError(t, err)
2032+
assert.NotNil(t, tools)
2033+
assert.Len(t, tools, 0)
2034+
})
2035+
2036+
t.Run("SingleTool", func(t *testing.T) {
2037+
server := NewMCPServer("test-server", "1.0.0")
2038+
2039+
expectedTool := mcp.Tool{
2040+
Name: "test-tool",
2041+
Description: "A test tool",
2042+
InputSchema: mcp.ToolInputSchema{
2043+
Type: "object",
2044+
Properties: map[string]any{
2045+
"input": map[string]any{
2046+
"type": "string",
2047+
"description": "Test input",
2048+
},
2049+
},
2050+
},
2051+
}
2052+
2053+
expectedHandler := func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
2054+
return &mcp.CallToolResult{
2055+
Content: []mcp.Content{
2056+
mcp.TextContent{
2057+
Type: "text",
2058+
Text: "test result",
2059+
},
2060+
},
2061+
}, nil
2062+
}
2063+
2064+
server.AddTool(expectedTool, expectedHandler)
2065+
2066+
tools, err := server.GetTools()
2067+
2068+
assert.NoError(t, err)
2069+
assert.NotNil(t, tools)
2070+
assert.Len(t, tools, 1)
2071+
2072+
serverTool, exists := tools["test-tool"]
2073+
assert.True(t, exists)
2074+
assert.Equal(t, expectedTool, serverTool.Tool)
2075+
assert.NotNil(t, serverTool.Handler)
2076+
})
2077+
2078+
t.Run("MultipleTools", func(t *testing.T) {
2079+
server := NewMCPServer("test-server", "1.0.0")
2080+
2081+
tools := []struct {
2082+
tool mcp.Tool
2083+
handler ToolHandlerFunc
2084+
}{
2085+
{
2086+
tool: mcp.Tool{
2087+
Name: "tool1",
2088+
Description: "First tool",
2089+
InputSchema: mcp.ToolInputSchema{Type: "object"},
2090+
},
2091+
handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
2092+
return &mcp.CallToolResult{}, nil
2093+
},
2094+
},
2095+
{
2096+
tool: mcp.Tool{
2097+
Name: "tool2",
2098+
Description: "Second tool",
2099+
InputSchema: mcp.ToolInputSchema{Type: "object"},
2100+
},
2101+
handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
2102+
return &mcp.CallToolResult{}, nil
2103+
},
2104+
},
2105+
{
2106+
tool: mcp.Tool{
2107+
Name: "tool3",
2108+
Description: "Third tool",
2109+
InputSchema: mcp.ToolInputSchema{Type: "object"},
2110+
},
2111+
handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
2112+
return &mcp.CallToolResult{}, nil
2113+
},
2114+
},
2115+
}
2116+
2117+
// Add tools one by one
2118+
for _, tool := range tools {
2119+
server.AddTool(tool.tool, tool.handler)
2120+
}
2121+
2122+
retrievedTools, err := server.GetTools()
2123+
2124+
assert.NoError(t, err)
2125+
assert.NotNil(t, retrievedTools)
2126+
assert.Len(t, retrievedTools, 3)
2127+
2128+
// Verify each tool exists with correct data
2129+
for _, expectedTool := range tools {
2130+
serverTool, exists := retrievedTools[expectedTool.tool.Name]
2131+
assert.True(t, exists, "Tool %s should exist", expectedTool.tool.Name)
2132+
assert.Equal(t, expectedTool.tool, serverTool.Tool)
2133+
assert.NotNil(t, serverTool.Handler)
2134+
}
2135+
})
2136+
2137+
t.Run("AfterToolDeletion", func(t *testing.T) {
2138+
server := NewMCPServer("test-server", "1.0.0")
2139+
2140+
// Add multiple tools
2141+
server.AddTool(mcp.Tool{Name: "tool1", Description: "Tool 1"}, nil)
2142+
server.AddTool(mcp.Tool{Name: "tool2", Description: "Tool 2"}, nil)
2143+
server.AddTool(mcp.Tool{Name: "tool3", Description: "Tool 3"}, nil)
2144+
2145+
// Verify all tools exist
2146+
tools, err := server.GetTools()
2147+
assert.NoError(t, err)
2148+
assert.Len(t, tools, 3)
2149+
2150+
// Delete one tool
2151+
server.DeleteTools("tool2")
2152+
2153+
// Verify tool is removed
2154+
tools, err = server.GetTools()
2155+
assert.NoError(t, err)
2156+
assert.Len(t, tools, 2)
2157+
2158+
_, exists := tools["tool1"]
2159+
assert.True(t, exists)
2160+
_, exists = tools["tool2"]
2161+
assert.False(t, exists)
2162+
_, exists = tools["tool3"]
2163+
assert.True(t, exists)
2164+
})
2165+
2166+
t.Run("SetToolsReplacesExisting", func(t *testing.T) {
2167+
server := NewMCPServer("test-server", "1.0.0")
2168+
2169+
// Add initial tools
2170+
server.AddTool(mcp.Tool{Name: "old-tool1", Description: "Old Tool 1"}, nil)
2171+
server.AddTool(mcp.Tool{Name: "old-tool2", Description: "Old Tool 2"}, nil)
2172+
2173+
// Verify initial tools
2174+
tools, err := server.GetTools()
2175+
assert.NoError(t, err)
2176+
assert.Len(t, tools, 2)
2177+
2178+
// Set new tools (should replace existing)
2179+
newTools := []ServerTool{
2180+
{
2181+
Tool: mcp.Tool{Name: "new-tool1", Description: "New Tool 1"},
2182+
Handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
2183+
return &mcp.CallToolResult{}, nil
2184+
},
2185+
},
2186+
{
2187+
Tool: mcp.Tool{Name: "new-tool2", Description: "New Tool 2"},
2188+
Handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
2189+
return &mcp.CallToolResult{}, nil
2190+
},
2191+
},
2192+
}
2193+
server.SetTools(newTools...)
2194+
2195+
// Verify only new tools exist
2196+
tools, err = server.GetTools()
2197+
assert.NoError(t, err)
2198+
assert.Len(t, tools, 2)
2199+
2200+
_, exists := tools["old-tool1"]
2201+
assert.False(t, exists)
2202+
_, exists = tools["old-tool2"]
2203+
assert.False(t, exists)
2204+
_, exists = tools["new-tool1"]
2205+
assert.True(t, exists)
2206+
_, exists = tools["new-tool2"]
2207+
assert.True(t, exists)
2208+
})
2209+
2210+
t.Run("ConcurrentAccess", func(t *testing.T) {
2211+
server := NewMCPServer("test-server", "1.0.0")
2212+
2213+
// Number of goroutines for testing
2214+
numGoroutines := 100
2215+
numToolsPerGoroutine := 10
2216+
2217+
// Channel to collect results
2218+
results := make(chan map[string]ServerTool, numGoroutines)
2219+
2220+
// Start goroutines that concurrently call GetTools
2221+
for i := 0; i < numGoroutines; i++ {
2222+
go func(id int) {
2223+
// Add some tools specific to this goroutine
2224+
for j := 0; j < numToolsPerGoroutine; j++ {
2225+
toolName := fmt.Sprintf("tool-%d-%d", id, j)
2226+
server.AddTool(mcp.Tool{
2227+
Name: toolName,
2228+
Description: fmt.Sprintf("Tool %d from goroutine %d", j, id),
2229+
}, nil)
2230+
}
2231+
2232+
// Get tools
2233+
tools, err := server.GetTools()
2234+
assert.NoError(t, err)
2235+
results <- tools
2236+
}(i)
2237+
}
2238+
2239+
// Collect all results
2240+
var allResults []map[string]ServerTool
2241+
for i := 0; i < numGoroutines; i++ {
2242+
result := <-results
2243+
allResults = append(allResults, result)
2244+
}
2245+
2246+
// Verify that no data races occurred and all results are valid
2247+
for _, result := range allResults {
2248+
assert.NotNil(t, result)
2249+
// Each result should have at least some tools (may not have all due to timing)
2250+
assert.Greater(t, len(result), 0)
2251+
}
2252+
2253+
// Final check - get all tools at the end
2254+
finalTools, err := server.GetTools()
2255+
assert.NoError(t, err)
2256+
assert.NotNil(t, finalTools)
2257+
// Should have exactly numGoroutines * numToolsPerGoroutine tools
2258+
assert.Equal(t, numGoroutines*numToolsPerGoroutine, len(finalTools))
2259+
})
2260+
t.Run("ConsistentResults", func(t *testing.T) {
2261+
server := NewMCPServer("test-server", "1.0.0")
2262+
2263+
// Add a tool
2264+
server.AddTool(mcp.Tool{
2265+
Name: "test-tool",
2266+
Description: "Test tool",
2267+
}, nil)
2268+
2269+
// Get tools multiple times
2270+
tools1, err1 := server.GetTools()
2271+
tools2, err2 := server.GetTools()
2272+
tools3, err3 := server.GetTools()
2273+
2274+
assert.NoError(t, err1)
2275+
assert.NoError(t, err2)
2276+
assert.NoError(t, err3)
2277+
assert.NotNil(t, tools1)
2278+
assert.NotNil(t, tools2)
2279+
assert.NotNil(t, tools3)
2280+
2281+
// Verify all calls return consistent results
2282+
assert.Equal(t, tools1, tools2)
2283+
assert.Equal(t, tools2, tools3)
2284+
assert.Equal(t, tools1, tools3)
2285+
2286+
// All should have the same tool
2287+
assert.Len(t, tools1, 1)
2288+
assert.Len(t, tools2, 1)
2289+
assert.Len(t, tools3, 1)
2290+
2291+
assert.Contains(t, tools1, "test-tool")
2292+
assert.Contains(t, tools2, "test-tool")
2293+
assert.Contains(t, tools3, "test-tool")
2294+
})
2295+
2296+
t.Run("WithComplexToolSchema", func(t *testing.T) {
2297+
server := NewMCPServer("test-server", "1.0.0")
2298+
2299+
complexTool := mcp.Tool{
2300+
Name: "complex-tool",
2301+
Description: "A complex tool with detailed schema",
2302+
InputSchema: mcp.ToolInputSchema{
2303+
Type: "object",
2304+
Properties: map[string]any{
2305+
"stringParam": map[string]any{
2306+
"type": "string",
2307+
"description": "A string parameter",
2308+
"enum": []string{"option1", "option2", "option3"},
2309+
},
2310+
"numberParam": map[string]any{
2311+
"type": "number",
2312+
"description": "A number parameter",
2313+
"minimum": 0,
2314+
"maximum": 100,
2315+
},
2316+
"objectParam": map[string]any{
2317+
"type": "object",
2318+
"properties": map[string]any{
2319+
"nestedString": map[string]any{
2320+
"type": "string",
2321+
},
2322+
"nestedArray": map[string]any{
2323+
"type": "array",
2324+
"items": map[string]any{
2325+
"type": "integer",
2326+
},
2327+
},
2328+
},
2329+
"required": []string{"nestedString"},
2330+
},
2331+
},
2332+
Required: []string{"stringParam", "numberParam"},
2333+
},
2334+
Annotations: mcp.ToolAnnotation{
2335+
Title: "Complex Tool",
2336+
ReadOnlyHint: mcp.ToBoolPtr(false),
2337+
DestructiveHint: mcp.ToBoolPtr(true),
2338+
IdempotentHint: mcp.ToBoolPtr(false),
2339+
OpenWorldHint: mcp.ToBoolPtr(true),
2340+
},
2341+
}
2342+
2343+
handler := func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
2344+
return &mcp.CallToolResult{
2345+
Content: []mcp.Content{
2346+
mcp.TextContent{
2347+
Type: "text",
2348+
Text: "Complex tool executed",
2349+
},
2350+
},
2351+
IsError: false,
2352+
}, nil
2353+
}
2354+
2355+
server.AddTool(complexTool, handler)
2356+
2357+
tools, err := server.GetTools()
2358+
2359+
assert.NoError(t, err)
2360+
assert.NotNil(t, tools)
2361+
assert.Len(t, tools, 1)
2362+
2363+
retrievedTool, exists := tools["complex-tool"]
2364+
assert.True(t, exists)
2365+
assert.Equal(t, complexTool, retrievedTool.Tool)
2366+
assert.NotNil(t, retrievedTool.Handler)
2367+
2368+
// Verify the complex schema is preserved
2369+
assert.Equal(t, "object", retrievedTool.Tool.InputSchema.Type)
2370+
assert.Contains(t, retrievedTool.Tool.InputSchema.Properties, "stringParam")
2371+
assert.Contains(t, retrievedTool.Tool.InputSchema.Properties, "numberParam")
2372+
assert.Contains(t, retrievedTool.Tool.InputSchema.Properties, "objectParam")
2373+
assert.Equal(t, []string{"stringParam", "numberParam"}, retrievedTool.Tool.InputSchema.Required)
2374+
2375+
// Verify annotations
2376+
assert.Equal(t, "Complex Tool", retrievedTool.Tool.Annotations.Title)
2377+
assert.NotNil(t, retrievedTool.Tool.Annotations.DestructiveHint)
2378+
assert.True(t, *retrievedTool.Tool.Annotations.DestructiveHint)
2379+
})
2380+
}

0 commit comments

Comments
 (0)