Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit94ee074

Browse files
Add support for safe tool renaming (#1563)
* add suppport for tool aliases for backwards compatibility when tool names change* cleanup* log alias usage as warning* remove comments* remove mock data* remove unused code, move deprecated tool aliases to its own file* remove unused code and add tests* resolve tool aliases in its own explicit step* improve logic by returning aliases used in resolvetoolaliases* remove unused function* remove comments* remove comment* Update pkg/github/deprecated_tool_aliases.goCo-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>* restore comment---------Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent90a1255 commit94ee074

File tree

5 files changed

+182
-7
lines changed

5 files changed

+182
-7
lines changed

‎internal/ghmcp/server.go‎

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

177177
// Register specific tools if configured
178178
iflen(cfg.EnabledTools)>0 {
179-
// Clean and validate tool names
180179
enabledTools:=github.CleanTools(cfg.EnabledTools)
180+
enabledTools,_=tsg.ResolveToolAliases(enabledTools)
181181

182182
// Register the specified tools (additive to any toolsets already enabled)
183183
err=tsg.RegisterSpecificTools(ghServer,enabledTools,cfg.ReadOnly)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// deprecated_tool_aliases.go
2+
package github
3+
4+
// DeprecatedToolAliases maps old tool names to their new canonical names.
5+
// When tools are renamed, add an entry here to maintain backward compatibility.
6+
// Users referencing the old name will receive the new tool with a deprecation warning.
7+
//
8+
// Example:
9+
//
10+
//"get_issue": "issue_read",
11+
//"create_pr": "pull_request_create",
12+
varDeprecatedToolAliases=map[string]string{
13+
// Add entries as tools are renamed
14+
}

‎pkg/github/tools.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,8 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
378378
tsg.AddToolset(stargazers)
379379
tsg.AddToolset(labels)
380380

381+
tsg.AddDeprecatedToolAliases(DeprecatedToolAliases)
382+
381383
returntsg
382384
}
383385

‎pkg/toolsets/toolsets.go‎

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,16 +192,24 @@ func (t *Toolset) AddReadTools(tools ...ServerTool) *Toolset {
192192
}
193193

194194
typeToolsetGroupstruct {
195-
Toolsetsmap[string]*Toolset
196-
everythingOnbool
197-
readOnlybool
195+
Toolsetsmap[string]*Toolset
196+
deprecatedAliasesmap[string]string
197+
everythingOnbool
198+
readOnlybool
198199
}
199200

200201
funcNewToolsetGroup(readOnlybool)*ToolsetGroup {
201202
return&ToolsetGroup{
202-
Toolsets:make(map[string]*Toolset),
203-
everythingOn:false,
204-
readOnly:readOnly,
203+
Toolsets:make(map[string]*Toolset),
204+
deprecatedAliases:make(map[string]string),
205+
everythingOn:false,
206+
readOnly:readOnly,
207+
}
208+
}
209+
210+
func (tg*ToolsetGroup)AddDeprecatedToolAliases(aliasesmap[string]string) {
211+
foroldName,newName:=rangealiases {
212+
tg.deprecatedAliases[oldName]=newName
205213
}
206214
}
207215

@@ -307,6 +315,26 @@ func NewToolDoesNotExistError(name string) *ToolDoesNotExistError {
307315
return&ToolDoesNotExistError{Name:name}
308316
}
309317

318+
// ResolveToolAliases resolves deprecated tool aliases to their canonical names.
319+
// It logs a warning to stderr for each deprecated alias that is resolved.
320+
// Returns:
321+
// - resolved: tool names with aliases replaced by canonical names
322+
// - aliasesUsed: map of oldName → newName for each alias that was resolved
323+
func (tg*ToolsetGroup)ResolveToolAliases(toolNames []string) (resolved []string,aliasesUsedmap[string]string) {
324+
resolved=make([]string,0,len(toolNames))
325+
aliasesUsed=make(map[string]string)
326+
for_,toolName:=rangetoolNames {
327+
ifcanonicalName,isAlias:=tg.deprecatedAliases[toolName];isAlias {
328+
fmt.Fprintf(os.Stderr,"Warning: tool %q is deprecated, use %q instead\n",toolName,canonicalName)
329+
aliasesUsed[toolName]=canonicalName
330+
resolved=append(resolved,canonicalName)
331+
}else {
332+
resolved=append(resolved,toolName)
333+
}
334+
}
335+
returnresolved,aliasesUsed
336+
}
337+
310338
// FindToolByName searches all toolsets (enabled or disabled) for a tool by name.
311339
// Returns the tool, its parent toolset name, and an error if not found.
312340
func (tg*ToolsetGroup)FindToolByName(toolNamestring) (*ServerTool,string,error) {

‎pkg/toolsets/toolsets_test.go‎

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,23 @@ package toolsets
33
import (
44
"errors"
55
"testing"
6+
7+
"github.com/modelcontextprotocol/go-sdk/mcp"
68
)
79

10+
// mockTool creates a minimal ServerTool for testing
11+
funcmockTool(namestring,readOnlybool)ServerTool {
12+
returnServerTool{
13+
Tool: mcp.Tool{
14+
Name:name,
15+
Annotations:&mcp.ToolAnnotations{
16+
ReadOnlyHint:readOnly,
17+
},
18+
},
19+
RegisterFunc:func(_*mcp.Server) {},
20+
}
21+
}
22+
823
funcTestNewToolsetGroupIsEmptyWithoutEverythingOn(t*testing.T) {
924
tsg:=NewToolsetGroup(false)
1025
iflen(tsg.Toolsets)!=0 {
@@ -262,3 +277,119 @@ func TestToolsetGroup_GetToolset(t *testing.T) {
262277
t.Errorf("expected error to be ToolsetDoesNotExistError, got %v",err)
263278
}
264279
}
280+
281+
funcTestAddDeprecatedToolAliases(t*testing.T) {
282+
tsg:=NewToolsetGroup(false)
283+
284+
// Test adding aliases
285+
tsg.AddDeprecatedToolAliases(map[string]string{
286+
"old_name":"new_name",
287+
"get_issue":"issue_read",
288+
"create_pr":"pull_request_create",
289+
})
290+
291+
iflen(tsg.deprecatedAliases)!=3 {
292+
t.Errorf("expected 3 aliases, got %d",len(tsg.deprecatedAliases))
293+
}
294+
iftsg.deprecatedAliases["old_name"]!="new_name" {
295+
t.Errorf("expected alias 'old_name' -> 'new_name', got '%s'",tsg.deprecatedAliases["old_name"])
296+
}
297+
iftsg.deprecatedAliases["get_issue"]!="issue_read" {
298+
t.Errorf("expected alias 'get_issue' -> 'issue_read'")
299+
}
300+
iftsg.deprecatedAliases["create_pr"]!="pull_request_create" {
301+
t.Errorf("expected alias 'create_pr' -> 'pull_request_create'")
302+
}
303+
}
304+
305+
funcTestResolveToolAliases(t*testing.T) {
306+
tsg:=NewToolsetGroup(false)
307+
tsg.AddDeprecatedToolAliases(map[string]string{
308+
"get_issue":"issue_read",
309+
"create_pr":"pull_request_create",
310+
})
311+
312+
// Test resolving a mix of aliases and canonical names
313+
input:= []string{"get_issue","some_tool","create_pr"}
314+
resolved,aliasesUsed:=tsg.ResolveToolAliases(input)
315+
316+
// Verify resolved names
317+
iflen(resolved)!=3 {
318+
t.Fatalf("expected 3 resolved names, got %d",len(resolved))
319+
}
320+
ifresolved[0]!="issue_read" {
321+
t.Errorf("expected 'issue_read', got '%s'",resolved[0])
322+
}
323+
ifresolved[1]!="some_tool" {
324+
t.Errorf("expected 'some_tool' (unchanged), got '%s'",resolved[1])
325+
}
326+
ifresolved[2]!="pull_request_create" {
327+
t.Errorf("expected 'pull_request_create', got '%s'",resolved[2])
328+
}
329+
330+
// Verify aliasesUsed map
331+
iflen(aliasesUsed)!=2 {
332+
t.Fatalf("expected 2 aliases used, got %d",len(aliasesUsed))
333+
}
334+
ifaliasesUsed["get_issue"]!="issue_read" {
335+
t.Errorf("expected aliasesUsed['get_issue'] = 'issue_read', got '%s'",aliasesUsed["get_issue"])
336+
}
337+
ifaliasesUsed["create_pr"]!="pull_request_create" {
338+
t.Errorf("expected aliasesUsed['create_pr'] = 'pull_request_create', got '%s'",aliasesUsed["create_pr"])
339+
}
340+
}
341+
342+
funcTestFindToolByName(t*testing.T) {
343+
tsg:=NewToolsetGroup(false)
344+
345+
// Create a toolset with a tool
346+
toolset:=NewToolset("test-toolset","Test toolset")
347+
toolset.readTools=append(toolset.readTools,mockTool("issue_read",true))
348+
tsg.AddToolset(toolset)
349+
350+
// Find by canonical name
351+
tool,toolsetName,err:=tsg.FindToolByName("issue_read")
352+
iferr!=nil {
353+
t.Fatalf("expected no error, got %v",err)
354+
}
355+
iftool.Tool.Name!="issue_read" {
356+
t.Errorf("expected tool name 'issue_read', got '%s'",tool.Tool.Name)
357+
}
358+
iftoolsetName!="test-toolset" {
359+
t.Errorf("expected toolset name 'test-toolset', got '%s'",toolsetName)
360+
}
361+
362+
// FindToolByName does NOT resolve aliases - it expects canonical names
363+
_,_,err=tsg.FindToolByName("get_issue")
364+
iferr==nil {
365+
t.Error("expected error when using alias directly with FindToolByName")
366+
}
367+
}
368+
369+
funcTestRegisterSpecificTools(t*testing.T) {
370+
tsg:=NewToolsetGroup(false)
371+
372+
// Create a toolset with both read and write tools
373+
toolset:=NewToolset("test-toolset","Test toolset")
374+
toolset.readTools=append(toolset.readTools,mockTool("issue_read",true))
375+
toolset.writeTools=append(toolset.writeTools,mockTool("issue_write",false))
376+
tsg.AddToolset(toolset)
377+
378+
// Test registering with canonical names
379+
err:=tsg.RegisterSpecificTools(nil, []string{"issue_read"},false)
380+
iferr!=nil {
381+
t.Errorf("expected no error registering tool, got %v",err)
382+
}
383+
384+
// Test registering write tool in read-only mode (should skip but not error)
385+
err=tsg.RegisterSpecificTools(nil, []string{"issue_write"},true)
386+
iferr!=nil {
387+
t.Errorf("expected no error when skipping write tool in read-only mode, got %v",err)
388+
}
389+
390+
// Test registering non-existent tool (should error)
391+
err=tsg.RegisterSpecificTools(nil, []string{"nonexistent"},false)
392+
iferr==nil {
393+
t.Error("expected error for non-existent tool")
394+
}
395+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp