MCP server: renaming/aliasing a tool silently drops it from stored agent definitions
Summary
When an MCP tool is renamed (or consolidated) and the old name is preserved as an alias for backward compatibility, stored agent definitions that reference the old tool name silently lose that tool. The agent keeps running without the capability, and no error or warning is surfaced.
This is reproducible today. Two aliases already ship:
gitlab_search→search(app/services/mcp/tools/search/search_service.rb:25)gitlab_merge_request_search→list_merge_requests(app/services/mcp/tools/merge_requests/list_merge_requests_service.rb:83)
Any agent definition saved before those renames has been missing that tool ever since.
Root cause
1. Stored alias names are dropped
Ai::DuoWorkflows::McpConfigService#mcp_tools_for_agent builds an agent's tool set by intersecting the agent's stored tool names against the set of currently-available tools:
available = all_mcp_tools # = Mcp::Tools::Manager.new.list_tools.keys
((built_in_names + Array(agent_mcp_tools)) & available).uniqlist_tools returns canonical names only. Aliases live in a separate alias_map (Manager#build_alias_map) that is consulted only on the tools/call path via resolve_alias. So an alias resolves correctly when a tool is called, but a stored mcp_tools: ["old_name"] entry is dropped by & available.
2. API-backed tools cannot declare aliases
Mcp::Tools::Base::ApiTool is a single class instantiated once per route (Manager#discover_api_tools). A class-level self.tool_aliases would therefore be shared by every API tool, so it is structurally unusable for them — aliases must be per-instance, read from route_setting :mcp.
Note this is not simply a matter of ApiTool not inheriting Base::BaseService; making it inherit would not fix the per-route problem.
doc/development/duo_agent_platform/mcp/_index.md:519 currently claims "Aliases work for all tool types: custom, GraphQL, API, and aggregated tools", which is false for API tools.
Current behavior
Renaming or aliasing a tool causes stored agent definitions naming the old tool to silently lose it, with no error. API-backed tools cannot declare an alias at all.
Expected behavior
A tool preserved via an alias remains resolvable for stored agent definitions, and API-backed tools can declare aliases.
Proposed fix
Resolve aliases to canonical names on the way into the intersection. Do not add aliases to list_tools.
list_tools must stay canonical because it is shared by consumers that would break:
- the MCP
tools/listresponse — aliases would appear to every client as duplicate, fully-schema'd tools and inflate the tool budget, defeating the point of consolidation Ai::Catalog::McpTool.fixed_items— aliases would become newly selectable in the agent builder, the opposite of deprecating a name_index.md:518documentslist_toolsas canonical-only, andmanager_spec.rbguards it
Canonical output is required rather than merely preferred: gitlab_enabled_tools becomes the x-gitlab-enabled-mcp-server-tools header, which returns to the MCP server as allowed_tools, and ListTools#invoke filters that against canonical keys while logging unknown names. Emitting an alias would drop the tool one layer later and log a warning.
MR A — stop dropping alias-named tools
McpConfigService#mcp_tools_for_agent: map storedagent_mcp_toolsthroughManager#resolve_aliasbefore& all_mcp_tools- Make
Manager#resolve_aliaspublic - Do not resolve
built_in_names. Built-in tool names are a separate namespace, and an MCP alias colliding with a built-in name would silently rewrite the built-in
Verifiable against the two aliases that already exist, so it carries no dependency on MR B.
MR B — let API tools declare aliases
Base::ApiTool#tool_aliases, sourced fromsettings[:tool_aliases]BaseService#tool_aliasesinstance-level delegator to the existing class methodManager#build_alias_map: use the instance method rather thantool.class- Correct
_index.md:519, and note thattool_aliases:on a route carryingaggregators:is dead config
Declaring an actual production alias belongs in the consolidation MR, not here. Once a readOnlyHint route gains an alias, the pre-approved tool list grows and the hardcoded expectations in workflows_spec.rb and mcp_config_service_spec.rb will fail by design — those lists are deliberate change-detectors.
Out of scope
notifications/tools/list_changed— tracked separately in #582750- Alias-with-default-args (old name → new tool plus a forced argument) — a different mechanism; it changes
tools/callargument handling rather than name resolution, and needs a decision on merge/override semantics against caller-supplied arguments. Should be its own issue, blocked on the consolidation design ListTools#invokedoes not alias-resolveallowed_tools— same bug class one layer up. During a rolling deploy, a session whose header was built by a pre-rename node has the tool filtered out by a post-rename node. - tracked in #618876
Why it matters
This blocks backward-compatible tool consolidation. The get_merge_request consolidation needs stored agent definitions referencing old names (for example get_merge_request_commits) to keep working.
References
- Discussion: gitlab-org#20222 (comment 3557221499)
- Parent epic: &22414