package github import ( "context" "encoding/json" "testing" "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/translations" "github.com/google/jsonschema-go/jsonschema" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // createDynamicRequest creates an MCP request with the given arguments for dynamic tools. func createDynamicRequest(args map[string]any) *mcp.CallToolRequest { argsJSON, _ := json.Marshal(args) return &mcp.CallToolRequest{ Params: &mcp.CallToolParamsRaw{ Arguments: json.RawMessage(argsJSON), }, } } func TestDynamicTools_ListAvailableToolsets(t *testing.T) { // Build a registry with no toolsets enabled (dynamic mode) reg, err := NewInventory(translations.NullTranslationHelper). WithToolsets([]string{}). Build() require.NoError(t, err) // Create a mock server server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil) // Create dynamic tool dependencies deps := DynamicToolDependencies{ Server: server, Inventory: reg, ToolDeps: nil, T: translations.NullTranslationHelper, } // Get the list_available_toolsets tool tool := ListAvailableToolsets() handler := tool.Handler(deps) // Call the handler result, err := handler(context.Background(), createDynamicRequest(map[string]any{})) require.NoError(t, err) require.NotNil(t, result) require.Len(t, result.Content, 1) // Parse the result var toolsets []map[string]string textContent := result.Content[0].(*mcp.TextContent) err = json.Unmarshal([]byte(textContent.Text), &toolsets) require.NoError(t, err) // Verify we got toolsets assert.NotEmpty(t, toolsets, "should have available toolsets") // Find the repos toolset and verify it's not enabled var reposToolset map[string]string for _, ts := range toolsets { if ts["name"] == "repos" { reposToolset = ts break } } require.NotNil(t, reposToolset, "repos toolset should exist") assert.Equal(t, "false", reposToolset["currently_enabled"], "repos should not be enabled initially") } func TestDynamicTools_GetToolsetTools(t *testing.T) { // Build a registry with no toolsets enabled (dynamic mode) reg, err := NewInventory(translations.NullTranslationHelper). WithToolsets([]string{}). Build() require.NoError(t, err) // Create a mock server server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil) // Create dynamic tool dependencies deps := DynamicToolDependencies{ Server: server, Inventory: reg, ToolDeps: nil, T: translations.NullTranslationHelper, } // Get the get_toolset_tools tool tool := GetToolsetsTools(reg) handler := tool.Handler(deps) // Call the handler for repos toolset result, err := handler(context.Background(), createDynamicRequest(map[string]any{ "toolset": "repos", })) require.NoError(t, err) require.NotNil(t, result) require.Len(t, result.Content, 1) // Parse the result var tools []map[string]string textContent := result.Content[0].(*mcp.TextContent) err = json.Unmarshal([]byte(textContent.Text), &tools) require.NoError(t, err) // Verify we got tools for the repos toolset assert.NotEmpty(t, tools, "repos toolset should have tools") // Verify at least get_commit is there (a repos toolset tool) var foundGetCommit bool for _, tool := range tools { if tool["name"] == "get_commit" { foundGetCommit = true break } } assert.True(t, foundGetCommit, "get_commit should be in repos toolset") } func TestDynamicTools_EnableToolset(t *testing.T) { // Build a registry with no toolsets enabled (dynamic mode) reg, err := NewInventory(translations.NullTranslationHelper). WithToolsets([]string{}). Build() require.NoError(t, err) // Create a mock server server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil) // Create dynamic tool dependencies deps := DynamicToolDependencies{ Server: server, Inventory: reg, ToolDeps: NewBaseDeps(nil, nil, nil, nil, translations.NullTranslationHelper, FeatureFlags{}, 0, nil, stubExporters()), T: translations.NullTranslationHelper, } // Verify repos is not enabled initially assert.False(t, reg.IsToolsetEnabled(inventory.ToolsetID("repos"))) // Get the enable_toolset tool tool := EnableToolset(reg) handler := tool.Handler(deps) // Enable the repos toolset result, err := handler(context.Background(), createDynamicRequest(map[string]any{ "toolset": "repos", })) require.NoError(t, err) require.NotNil(t, result) require.Len(t, result.Content, 1) // Verify the toolset is now enabled assert.True(t, reg.IsToolsetEnabled(inventory.ToolsetID("repos")), "repos should be enabled after enable_toolset") // Verify the success message textContent := result.Content[0].(*mcp.TextContent) assert.Contains(t, textContent.Text, "enabled") // Try enabling again - should say already enabled result2, err := handler(context.Background(), createDynamicRequest(map[string]any{ "toolset": "repos", })) require.NoError(t, err) textContent2 := result2.Content[0].(*mcp.TextContent) assert.Contains(t, textContent2.Text, "already enabled") } func TestDynamicTools_EnableToolset_InvalidToolset(t *testing.T) { // Build a registry with no toolsets enabled (dynamic mode) reg, err := NewInventory(translations.NullTranslationHelper). WithToolsets([]string{}). Build() require.NoError(t, err) // Create a mock server server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil) // Create dynamic tool dependencies deps := DynamicToolDependencies{ Server: server, Inventory: reg, ToolDeps: nil, T: translations.NullTranslationHelper, } // Get the enable_toolset tool tool := EnableToolset(reg) handler := tool.Handler(deps) // Try to enable a non-existent toolset result, err := handler(context.Background(), createDynamicRequest(map[string]any{ "toolset": "nonexistent", })) require.NoError(t, err) require.NotNil(t, result) // Should be an error result textContent := result.Content[0].(*mcp.TextContent) assert.Contains(t, textContent.Text, "not found") } func TestDynamicTools_ToolsetsEnum(t *testing.T) { // Build a registry reg, err := NewInventory(translations.NullTranslationHelper).Build() require.NoError(t, err) // Get tools to verify they have proper enum values tools := DynamicTools(reg) // Find enable_toolset and get_toolset_tools for _, tool := range tools { if tool.Tool.Name == "enable_toolset" || tool.Tool.Name == "get_toolset_tools" { // Verify the toolset property has an enum schema := tool.Tool.InputSchema.(*jsonschema.Schema) toolsetProp := schema.Properties["toolset"] require.NotNil(t, toolsetProp, "toolset property should exist") assert.NotEmpty(t, toolsetProp.Enum, "toolset property should have enum values") // Verify repos is in the enum var foundRepos bool for _, v := range toolsetProp.Enum { if v == inventory.ToolsetID("repos") { foundRepos = true break } } assert.True(t, foundRepos, "repos should be in toolset enum for %s", tool.Tool.Name) } } }