Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pkg/github/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,14 @@ func GetDefaultToolsetIDs() []string {
}
}

// DeprecatedToolAliases maps old tool names to their new canonical names.
// This allows tool renames without breaking existing user configurations.
// When a user requests an old tool name, it will silently resolve to the new name.
var DeprecatedToolAliases = map[string]string{
"test_1": "get_me",
"test_2": "get_me",
}

func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetGQLClientFn, getRawClient raw.GetRawClientFn, t translations.TranslationHelperFunc, contentWindowSize int, flags FeatureFlags, cache *lockdown.RepoAccessCache) *toolsets.ToolsetGroup {
tsg := toolsets.NewToolsetGroup(readOnly)

Expand Down Expand Up @@ -378,6 +386,8 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
tsg.AddToolset(stargazers)
tsg.AddToolset(labels)

tsg.AddDeprecatedToolAliases(DeprecatedToolAliases)

return tsg
}

Expand Down
73 changes: 67 additions & 6 deletions pkg/toolsets/toolsets.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,19 +192,38 @@ func (t *Toolset) AddReadTools(tools ...ServerTool) *Toolset {
}

type ToolsetGroup struct {
Toolsets map[string]*Toolset
everythingOn bool
readOnly bool
Toolsets map[string]*Toolset
deprecatedAliases map[string]string // oldName → newName for renamed tools
everythingOn bool
readOnly bool
}

func NewToolsetGroup(readOnly bool) *ToolsetGroup {
return &ToolsetGroup{
Toolsets: make(map[string]*Toolset),
everythingOn: false,
readOnly: readOnly,
Toolsets: make(map[string]*Toolset),
deprecatedAliases: make(map[string]string),
everythingOn: false,
readOnly: readOnly,
}
}

func (tg *ToolsetGroup) AddDeprecatedToolAlias(oldName, newName string) {
tg.deprecatedAliases[oldName] = newName
}

func (tg *ToolsetGroup) AddDeprecatedToolAliases(aliases map[string]string) {
for oldName, newName := range aliases {
tg.deprecatedAliases[oldName] = newName
}
}

// IsDeprecatedToolAlias checks if a tool name is a deprecated alias.
// Returns the canonical name and true if it's an alias, or empty string and false otherwise.
func (tg *ToolsetGroup) IsDeprecatedToolAlias(toolName string) (canonicalName string, isAlias bool) {
canonicalName, isAlias = tg.deprecatedAliases[toolName]
return canonicalName, isAlias
}

func (tg *ToolsetGroup) AddToolset(ts *Toolset) {
if tg.readOnly {
ts.SetReadOnly()
Expand Down Expand Up @@ -307,9 +326,24 @@ func NewToolDoesNotExistError(name string) *ToolDoesNotExistError {
return &ToolDoesNotExistError{Name: name}
}

// ToolLookupResult contains the result of a tool lookup operation.
type ToolLookupResult struct {
Tool *ServerTool
ToolsetName string
RequestedName string // The name that was requested (may differ from Tool.Name if alias was used)
AliasUsed bool // True if the requested name was a deprecated alias
}

// FindToolByName searches all toolsets (enabled or disabled) for a tool by name.
// It resolves deprecated aliases automatically and logs a warning when an alias is used.
// Returns the tool, its parent toolset name, and an error if not found.
func (tg *ToolsetGroup) FindToolByName(toolName string) (*ServerTool, string, error) {
// Resolve deprecated alias if applicable
if canonicalName, isAlias := tg.deprecatedAliases[toolName]; isAlias {
fmt.Fprintf(os.Stderr, "Warning: tool %q is deprecated, use %q instead\n", toolName, canonicalName)
toolName = canonicalName
}

for toolsetName, toolset := range tg.Toolsets {
// Check read tools
for _, tool := range toolset.readTools {
Expand All @@ -327,9 +361,36 @@ func (tg *ToolsetGroup) FindToolByName(toolName string) (*ServerTool, string, er
return nil, "", NewToolDoesNotExistError(toolName)
}

// FindToolWithAliasInfo searches all toolsets for a tool by name and returns
// additional metadata about whether a deprecated alias was used.
// Use this when you need to track/log deprecated alias usage (e.g., for telemetry).
func (tg *ToolsetGroup) FindToolWithAliasInfo(toolName string) (*ToolLookupResult, error) {
requestedName := toolName
aliasUsed := false

// Check if this is a deprecated alias and resolve to canonical name
if canonicalName, isAlias := tg.deprecatedAliases[toolName]; isAlias {
toolName = canonicalName
aliasUsed = true
}

tool, toolsetName, err := tg.FindToolByName(toolName)
if err != nil {
return nil, NewToolDoesNotExistError(requestedName)
}

return &ToolLookupResult{
Tool: tool,
ToolsetName: toolsetName,
RequestedName: requestedName,
AliasUsed: aliasUsed,
}, nil
}

// RegisterSpecificTools registers only the specified tools.
// Respects read-only mode (skips write tools if readOnly=true).
// Returns error if any tool is not found.
// Deprecated tool aliases are resolved automatically.
func (tg *ToolsetGroup) RegisterSpecificTools(s *mcp.Server, toolNames []string, readOnly bool) error {
var skippedTools []string
for _, toolName := range toolNames {
Expand Down