Fix missing namespace on MCP tool-call events
What does this MR do and why?
About 33% of start_mcp_tool_call events have no namespace. The issue lists three causes:
- The lookup only read
project_idandgroup_id. Tools that takeid(route-backed API tools),url,full_path, orproject_full_pathgot nothing. track_start_eventran beforefetch_tool, so the tool's argument shape was not known yet.- The lookup was memoized with the mutable
paramshash as its key. Aggregated tools rewriteparams[:arguments]in place, so the finish event looked up the rewritten arguments and missed (for example,semantic_searchdropsproject_id).
This MR:
- Moves
track_start_eventafterfetch_toolinlib/api/mcp/handlers/call_tool.rb. When the tool is not found, the start event still fires next to the finish event, so event counts do not change. - Resolves the scope from the tool's own container declaration by calling
governed_containers, the same declaration that tool governance uses. That coversproject_id,group_id,id(routeboundary_type),url(viaUrlParser),full_path-style names, andrecord:resolvers. If the tool is unknown, the default argument names are used. - Resolves the scope once per call, before the tool runs. The start event, the finish event, and the log line all reuse that one result, so an aggregated tool's in-place rewrite can no longer change it.
- Tools marked
ungovernable!declare no container arguments, so they are now the only tools that resolve no namespace. - Adds
gl_root_namespace_idto themcp.logtool-call line as a regular field, so it is always indexed and can be searched in Kibana. Only the numeric ID is logged, not the path.
Security: no permission check
The lookup uses Gitlab::ResourceLookup and the record resolvers (find_by_id). Neither checks abilities, which matches the old lookup_project / lookup_group behaviour. The result only goes to telemetry and to the expanded-logging gate. It is never returned to the caller. Any error raised during resolution is rescued: ArgumentError (too many identifiers) silently, anything else is reported with Gitlab::ErrorTracking.track_exception. The call then continues with no namespace, so the tool response is never affected.
⚠️ Expanded-logging consequence (needs reviewer sign-off)
The resolved root namespace is also what gates expanded logging in mcp.log. In EE::Gitlab::Mcp::Logger#should_log_expanded?, return false unless namespace means that today, on instances where neither enabled_expanded_logging nor :expanded_ai_logging is on, the roughly 33% of calls with no resolved namespace never log arguments or error_message.
After this MR, those calls resolve a namespace. When that root namespace has ai_usage_data_collection_enabled, their argument values and error messages will appear in mcp.log. No new opt-in is added. This is the customer's existing opt-in, and the gate no longer fails closed by accident. Expect mcp.log volume to grow for opted-in namespaces. This was agreed as an acceptance criterion in the issue discussion. A reviewer needs to sign off on it explicitly.
Deviation from the issue wording: aggregated tools
The acceptance criterion says aggregated tools should resolve "using the rewritten arguments". This MR does not call transform_arguments / select_tool to get them. Those methods can raise, they look up users (SaveMergeRequestService#resolve_usernames), and they could run checks that do not belong in a telemetry path. Instead, it resolves from the aggregated tool's own declaration on the arguments the caller sent. The rule is the same either way: governable_tools_container_spec already requires every governable aggregated tool to declare a container argument from its own input schema. The scope is computed before the rewrite, so the rewrite cannot erase it. The spec covers semantic_search, which removes project_id when it rewrites arguments.
No event definition changes: start_mcp_tool_call and finish_mcp_tool_call already list the namespace identifier. No feature flag: the change only affects telemetry attribution, and the logging consequence is described above.
References
Closes #630105 (closed)
- Epic: &23391
- Related: the alias-flag issue. It also needs
track_start_eventafterfetch_tool, which this MR does.
Screenshots or screen recordings
Not applicable. This is a backend telemetry and logging change with no UI.
| Before | After |
|---|---|
get_pipeline with id: "group/project": event namespace nil |
event namespace is the project's namespace |
How to set up and validate locally
- In GDK, connect an MCP client to
https://gdk.test:3443/api/v4/mcp. - Call
get_pipelinewithidset to a project path, andget_merge_requestwith only aurl. - In
log/mcp.log, check that eachtool_callline hasgl_root_namespace_idset. - In
log/snowplow(or Snowplow Micro), check that thestart_mcp_tool_callandfinish_mcp_tool_callevents carrynamespace_id. - Call
get_server_version(markedungovernable!) and check thatgl_root_namespace_idisnull.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.