Skip to content

Commit 9c96523

Browse files
committed
resolve tool aliases in its own explicit step
1 parent 0d82b4b commit 9c96523

File tree

3 files changed

+56
-55
lines changed

3 files changed

+56
-55
lines changed

internal/ghmcp/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,9 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) {
176176

177177
// Register specific tools if configured
178178
if len(cfg.EnabledTools) > 0 {
179-
// Clean and validate tool names
179+
// Clean tool names and resolve deprecated aliases
180180
enabledTools := github.CleanTools(cfg.EnabledTools)
181+
enabledTools = tsg.ResolveToolAliases(enabledTools)
181182

182183
// Register the specified tools (additive to any toolsets already enabled)
183184
err = tsg.RegisterSpecificTools(ghServer, enabledTools, cfg.ReadOnly)

pkg/toolsets/toolsets.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -322,16 +322,26 @@ func NewToolDoesNotExistError(name string) *ToolDoesNotExistError {
322322
return &ToolDoesNotExistError{Name: name}
323323
}
324324

325-
// FindToolByName searches all toolsets (enabled or disabled) for a tool by name.
326-
// It resolves deprecated aliases automatically and logs a warning when an alias is used.
327-
// Returns the tool, its parent toolset name, and an error if not found.
328-
func (tg *ToolsetGroup) FindToolByName(toolName string) (*ServerTool, string, error) {
329-
// Resolve deprecated alias if applicable
330-
if canonicalName, isAlias := tg.deprecatedAliases[toolName]; isAlias {
331-
fmt.Fprintf(os.Stderr, "Warning: tool %q is deprecated, use %q instead\n", toolName, canonicalName)
332-
toolName = canonicalName
325+
// ResolveToolAliases resolves deprecated tool aliases to their canonical names.
326+
// It logs a warning to stderr for each deprecated alias that is resolved.
327+
// Returns the list of tool names with aliases replaced by canonical names.
328+
func (tg *ToolsetGroup) ResolveToolAliases(toolNames []string) []string {
329+
resolved := make([]string, 0, len(toolNames))
330+
for _, toolName := range toolNames {
331+
if canonicalName, isAlias := tg.deprecatedAliases[toolName]; isAlias {
332+
fmt.Fprintf(os.Stderr, "Warning: tool %q is deprecated, use %q instead\n", toolName, canonicalName)
333+
resolved = append(resolved, canonicalName)
334+
} else {
335+
resolved = append(resolved, toolName)
336+
}
333337
}
338+
return resolved
339+
}
334340

341+
// FindToolByName searches all toolsets (enabled or disabled) for a tool by its canonical name.
342+
// Returns the tool, its parent toolset name, and an error if not found.
343+
// Note: This function does NOT resolve deprecated aliases. Use ResolveToolAliases first if needed.
344+
func (tg *ToolsetGroup) FindToolByName(toolName string) (*ServerTool, string, error) {
335345
for toolsetName, toolset := range tg.Toolsets {
336346
// Check read tools
337347
for _, tool := range toolset.readTools {
@@ -352,7 +362,7 @@ func (tg *ToolsetGroup) FindToolByName(toolName string) (*ServerTool, string, er
352362
// RegisterSpecificTools registers only the specified tools.
353363
// Respects read-only mode (skips write tools if readOnly=true).
354364
// Returns error if any tool is not found.
355-
// Deprecated tool aliases are resolved automatically.
365+
// Note: This function expects canonical tool names. Use ResolveToolAliases first if needed.
356366
func (tg *ToolsetGroup) RegisterSpecificTools(s *mcp.Server, toolNames []string, readOnly bool) error {
357367
var skippedTools []string
358368
for _, toolName := range toolNames {

pkg/toolsets/toolsets_test.go

Lines changed: 35 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -325,17 +325,39 @@ func TestIsDeprecatedToolAlias(t *testing.T) {
325325
}
326326
}
327327

328-
func TestFindToolByName_WithAlias(t *testing.T) {
328+
func TestResolveToolAliases(t *testing.T) {
329+
tsg := NewToolsetGroup(false)
330+
tsg.AddDeprecatedToolAliases(map[string]string{
331+
"get_issue": "issue_read",
332+
"create_pr": "pull_request_create",
333+
})
334+
335+
// Test resolving a mix of aliases and canonical names
336+
input := []string{"get_issue", "some_tool", "create_pr"}
337+
resolved := tsg.ResolveToolAliases(input)
338+
339+
if len(resolved) != 3 {
340+
t.Fatalf("expected 3 resolved names, got %d", len(resolved))
341+
}
342+
if resolved[0] != "issue_read" {
343+
t.Errorf("expected 'issue_read', got '%s'", resolved[0])
344+
}
345+
if resolved[1] != "some_tool" {
346+
t.Errorf("expected 'some_tool' (unchanged), got '%s'", resolved[1])
347+
}
348+
if resolved[2] != "pull_request_create" {
349+
t.Errorf("expected 'pull_request_create', got '%s'", resolved[2])
350+
}
351+
}
352+
353+
func TestFindToolByName(t *testing.T) {
329354
tsg := NewToolsetGroup(false)
330355

331356
// Create a toolset with a tool
332357
toolset := NewToolset("test-toolset", "Test toolset")
333358
toolset.readTools = append(toolset.readTools, mockTool("issue_read", true))
334359
tsg.AddToolset(toolset)
335360

336-
// Add an alias
337-
tsg.AddDeprecatedToolAliases(map[string]string{"get_issue": "issue_read"})
338-
339361
// Find by canonical name
340362
tool, toolsetName, err := tsg.FindToolByName("issue_read")
341363
if err != nil {
@@ -348,40 +370,14 @@ func TestFindToolByName_WithAlias(t *testing.T) {
348370
t.Errorf("expected toolset name 'test-toolset', got '%s'", toolsetName)
349371
}
350372

351-
// Find by deprecated alias (should resolve to canonical)
352-
tool, toolsetName, err = tsg.FindToolByName("get_issue")
353-
if err != nil {
354-
t.Fatalf("expected no error when using alias, got %v", err)
355-
}
356-
if tool.Tool.Name != "issue_read" {
357-
t.Errorf("expected tool name 'issue_read' when using alias, got '%s'", tool.Tool.Name)
358-
}
359-
if toolsetName != "test-toolset" {
360-
t.Errorf("expected toolset name 'test-toolset', got '%s'", toolsetName)
361-
}
362-
}
363-
364-
func TestFindToolByName_NotFound(t *testing.T) {
365-
tsg := NewToolsetGroup(false)
366-
367-
// Create a toolset with a tool
368-
toolset := NewToolset("test-toolset", "Test toolset")
369-
toolset.readTools = append(toolset.readTools, mockTool("some_tool", true))
370-
tsg.AddToolset(toolset)
371-
372-
// Try to find a non-existent tool
373-
_, _, err := tsg.FindToolByName("nonexistent_tool")
373+
// FindToolByName does NOT resolve aliases - it expects canonical names
374+
_, _, err = tsg.FindToolByName("get_issue")
374375
if err == nil {
375-
t.Error("expected error for non-existent tool")
376-
}
377-
378-
var toolErr *ToolDoesNotExistError
379-
if !errors.As(err, &toolErr) {
380-
t.Errorf("expected ToolDoesNotExistError, got %T", err)
376+
t.Error("expected error when using alias directly with FindToolByName")
381377
}
382378
}
383379

384-
func TestRegisterSpecificTools_WithAliases(t *testing.T) {
380+
func TestRegisterSpecificTools(t *testing.T) {
385381
tsg := NewToolsetGroup(false)
386382

387383
// Create a toolset with both read and write tools
@@ -390,20 +386,14 @@ func TestRegisterSpecificTools_WithAliases(t *testing.T) {
390386
toolset.writeTools = append(toolset.writeTools, mockTool("issue_write", false))
391387
tsg.AddToolset(toolset)
392388

393-
// Add aliases
394-
tsg.AddDeprecatedToolAliases(map[string]string{
395-
"get_issue": "issue_read",
396-
"create_issue": "issue_write",
397-
})
398-
399-
// Test registering with aliases (should work)
400-
err := tsg.RegisterSpecificTools(nil, []string{"get_issue"}, false)
389+
// Test registering with canonical names
390+
err := tsg.RegisterSpecificTools(nil, []string{"issue_read"}, false)
401391
if err != nil {
402-
t.Errorf("expected no error registering aliased tool, got %v", err)
392+
t.Errorf("expected no error registering tool, got %v", err)
403393
}
404394

405-
// Test registering write tool alias in read-only mode (should skip but not error)
406-
err = tsg.RegisterSpecificTools(nil, []string{"create_issue"}, true)
395+
// Test registering write tool in read-only mode (should skip but not error)
396+
err = tsg.RegisterSpecificTools(nil, []string{"issue_write"}, true)
407397
if err != nil {
408398
t.Errorf("expected no error when skipping write tool in read-only mode, got %v", err)
409399
}

0 commit comments

Comments
 (0)