Log the canonical tool name when an MCP tool is called by alias
What does this MR do and why?
API::Mcp::Handlers::CallTool logs tool_name as the caller sent it, so calls made under a legacy alias such as get_job_log already appear in mcp.log. What the log does not say is that the name was an alias, or which tool it resolved to. Working out that semantic_code_search and semantic_search rows describe the same defect, as https://gitlab.com/gitlab-org/gitlab/-/work_items/629245 had to, means knowing the alias map from memory.
This MR adds a canonical_tool_name field to the tool_call event whenever the name resolves to a different tool. The field is absent for canonical calls, so its presence alone identifies alias traffic and _exists_: json.canonical_tool_name becomes the single "all legacy alias calls" filter. Log volume is unchanged for almost every call.
tool_name keeps its current meaning on purpose: it is an indexed field with existing queries against it, and repurposing it to hold the canonical name would silently empty them. A new field breaks nothing.
Manager#resolve_alias is a memoised hash lookup, and the tool registry is already built by the time the first log line is written on both the success path and the ToolNotFoundError path, so this adds no work beyond that lookup.
References
- Resolves #629876 (closed)
- Follows on from !255111 (merged), which makes
tools/listresolve aliases the waytools/callalready did; this issue was opened during that review. - Out of scope, as noted on the issue: the EE internal events (
track_start_event/track_finish_event) and thetools/listheader side, which is not logged at all.
Differences
Before: A call to get_job_log produced a tool_call log line with tool_name: "get_job_log" and nothing to distinguish it from a canonical tool name. Finding alias traffic meant querying each alias by name from a list held outside the logs.
After: The same call produces tool_name: "get_job_log" and canonical_tool_name: "get_job". A call to get_job directly, or to a name that does not exist, has no canonical_tool_name field. Nothing else in the log line changes.
How to set up and validate locally
- With an MCP-scoped token, call a tool by one of its aliases, for example
get_job_log(an alias ofget_job), then callget_jobdirectly. - In
log/mcp.log, the firsttool_callevent carries"tool_name":"get_job_log","canonical_tool_name":"get_job"; the second carries"tool_name":"get_job"with nocanonical_tool_namekey.
Specs:
bin/rspec spec/lib/api/mcp/handlers/call_tool_spec.rb spec/requests/api/mcp/handlers/call_tool_spec.rbThe unit spec asserts an alias call logs both names, and that a canonical call and a not-found call log no canonical_tool_name. The request spec asserts, end to end through POST /mcp, that calling get_job_log logs tool_name: "get_job_log" with canonical_tool_name: "get_job".
MR acceptance checklist
- Follows the implementation plan agreed on the issue;
tool_nameis not repurposed. - The new field is only emitted for alias calls, so existing queries and log volume are unaffected.
- Resolution is a memoised hash lookup on a registry that is already built, on every logging path.
- Unit specs cover the alias, canonical and not-found cases; the request spec covers the alias case through the real endpoint.
- No database, API surface, or frontend changes; no documentation change required for an internal log field.
- No feature flag needed; the change is additive and cannot alter a response.