MCP server toolset selection support
Problem
Not every usage scenario needs all the tools our MCP server can expose and even with gitlab-org#20222 (closed), there is likely going to be 40+ tools over time. This can create a strain on the context of an LLM especially if multiple mcp servers are used.
In addition to this, we might not want to automatically expose all newly added tools per default via the MCP server. Even when using MCP we might have tools that only make sense to expose per default if a full set is available (e.g. to work with packages) or that only make sense to expose to DAP but not to other MCP clients.
As analyzed in gitlab-org#20222 (comment 3514519006) many MCP servers already provide a way to opt into specific sets of their tools.
Desired Outcome
- The ability to assign individual tools to a specific toolset (e.g.
work_items) (see gitlab-org#20222 (comment 3514825550) - The ability for users to configure which toolsets to use
- A way to designate a "default" toolset that spans across the specific toolsets. New tools should not automatically be added to this.
Implementation Plan
v1 scope: Rails layer only. Additive toolset filtering on top of the existing per-name allow-list in the /api/v4/mcp endpoint. External consumers (Claude Desktop, VS Code, custom integrations) can select toolsets via a new request header. Workhorse/DAP (McpConfigService) changes are deferred to a follow-up — they already compute their own tool lists.
Toolset definitions
Toolsets group tools by domain. Each tool belongs to exactly one toolset.
"Default" is a set of toolsets, not a set of tools. No header → the server returns tools from ALWAYS_ON_TOOLSETS + DEFAULT_TOOLSETS. A header value of work_items,ci → the server returns tools from ALWAYS_ON_TOOLSETS + [work_items, ci]. No toolset is named "default".
Toolset names and tool assignments are derived from the GA must-have tools epic and the consolidated toolset proposal.
GA toolsets
| Toolset | Behavior | Tools | Count |
|---|---|---|---|
meta |
always-on | authenticate, complete_authentication, get_mcp_server_version |
3 |
core |
default | search, get_project, save_project, save_note, get_user, list_projects, list_groups |
7 |
merge_requests |
default | list_merge_requests, get_merge_request, save_merge_request, save_merge_request_review |
4 |
work_items |
default | list_work_items, get_work_item, save_work_item |
3 |
repository |
default | get_repository_file, list_repository_tree, save_commit, list_commits, get_commit |
5 |
ci |
default | get_job, get_pipeline, list_pipelines, save_pipeline |
4 |
agent_platform |
opt-in | list_flows, start_flow, list_duo_sessions, get_duo_session |
4 |
30 GA tools total. 23 in default toolsets, 4 opt-in, 3 always-on.
Post-GA toolsets
| Toolset | Behavior | Tools | Count |
|---|---|---|---|
wikis |
opt-in | list_wiki_pages, get_wiki_page, save_wiki_page |
3 |
code_security |
opt-in | list_vulnerabilities, get_vulnerability, save_vulnerability |
3 |
audit |
opt-in | list_audit_events |
1 |
Post-GA additions to existing toolsets:
core:get_current_user,get_users,list_todos,save_todo,semantic_searchci:list_ci_artifacts,get_ci_artifacts,save_ci_artifacts,validate_ci_file,lint_ci_config
Constants
VALID_TOOLSETS = %i[meta core merge_requests work_items repository ci agent_platform wikis code_security audit].freeze
DEFAULT_TOOLSETS = %i[core merge_requests work_items repository ci].freeze
ALWAYS_ON_TOOLSETS = %i[meta].freezeCurrent tool → toolset mapping (v1)
The v1 implementation tags each existing tool with its toolset. Many of these tools will later be consolidated into the target tool names above (tracked separately in #613080). Until consolidation happens, the current names carry their toolset assignment.
| Toolset | Current tools on master |
|---|---|
meta |
get_mcp_server_version |
core |
search, semantic_code_search (EE), search_labels, get_work_item_types, save_note, list_project_members |
merge_requests |
get_merge_request, save_merge_request, list_merge_requests, get_merge_request_commits, get_merge_request_diffs, get_merge_request_pipelines, get_merge_request_notes, get_merge_request_conflicts, save_merge_request_review, accept_merge_request |
work_items |
get_issue, create_issue, save_work_item, get_workitem_notes, get_saved_view_work_items, link_work_items |
repository |
add_branch, get_commit, get_repository_file, list_repository_tree, add_commit, list_commits, fork_repository |
ci |
get_pipeline, list_pipelines, get_pipeline_jobs, get_job, manage_pipeline, save_pipeline |
agent_platform |
list_duo_sessions (EE) |
wikis |
list_wiki_pages |
code_security |
attach_scan_profile (EE) |
Backwards compatibility
- No header = default toolsets (at time of creation of toolset feature) returned (preserves every existing consumer).
- New toolsets must be evaluated as to whether they will become part of the "no header" toolset groups or not.
1. Declare a toolset per tool (required, no default)
toolset is a required declaration on every MCP tool. No default — a tool without a toolset declaration fails at registration/boot, not at runtime.
BaseService#toolsetraisesNotImplementedError(same pattern asperform).- Versionable tools:
toolsetread fromregister_versionmetadata hash; raises at class load if missing. - ApiTool: read from
route_setting :mcp, toolset: :work_items, ...;discover_api_toolsraises if missing. - Valid toolsets:
VALID_TOOLSETS = %i[meta core merge_requests work_items repository ci agent_platform wikis code_security audit].freezevalidated at build time. - A spec iterates all registered tools and asserts each declares a valid toolset.
Tool assignments per the toolset definitions above.
2. Manager grouping helpers
Manager#toolsets→{ toolset_sym => [canonical_name, ...] }, built fromlist_tools.Manager#tools_in_toolsets(ids)→ flat array of canonical names, always includingmeta.
3. Rails server-side filter in tools/list
- New header
X-Gitlab-Enabled-Mcp-Server-Toolsets(comma-separated toolset ids). Parsed inlib/api/mcp/base.rb, returnsnilwhen absent. allpseudo-value:X-Gitlab-Enabled-Mcp-Server-Toolsets: allreturns every tool across every toolset, including opt-in. Useful for consumers that want the full surface without listing every toolset name.- Passed as
allowed_toolsets:toListTools#invoke. - Filter in
list_tools.rb: skip ifallowed_toolsetsis present, notall, tool is notmeta, and toolset is not in the list. - Invalid toolset names in the header return a 400 Bad Request with a message listing the unrecognized names and the valid set. This surfaces misconfiguration immediately rather than hiding typos.
- Combination rule: when both per-name and per-toolset headers are present, behavior per open question #2.
- Toolset annotation: each tool's
tools/listentry includestoolsetin its annotations (e.g.,{ "toolset": "work_items", ... }). This lets agents see which toolsets are loaded without reading docs. - Gated behind feature flag
mcp_toolsets. When disabled, the header is ignored.
4. Docs
- User-facing: document the new header + toolset list in
doc/user/model_context_protocol/mcp_server.md. - Developer-facing:
toolsetdeclaration guide indoc/development/duo_agent_platform/mcp/_index.md.
Open questions
- Combination rule — when both
X-Gitlab-Enabled-Mcp-Server-Tools(per-name) andX-Gitlab-Enabled-Mcp-Server-Toolsetsare present, should the result be a union (tool passes if it matches either header) or an intersection (tool must match both)? Union is recommended — toolsets for broad selection, explicit names for surgical additions.
Out of scope / follow-ups
- Workhorse/DAP integration (
McpServerConfig.Toolsets,McpConfigServicetoolset-based configs) - Read-only connection mode (
X-Gitlab-Mcp-Server-Read-Only) - Catalog UI grouping by toolset
- #609451 deprecation lifecycle and stored-name migration
- Exclude header (
X-Gitlab-Exclude-Mcp-Server-Tools) — a way to opt out of specific tools while keeping the rest of a toolset (e.g., "give me everything except agent_platform tools") contexttoolset (per #613080) for project/user/group lookup tools (get_project,get_user,list_projects,list_groups). Added toVALID_TOOLSETSwhen those tools are built.
Verification
- Unit specs for
toolsetreader on each tool flavor +Manager#toolsets/#tools_in_toolsets. - Handler spec: toolset filtering,
metaalways present, absent header = all tools. - Request spec:
POST /api/v4/mcptools/listwithX-Gitlab-Enabled-Mcp-Server-Toolsets: work_itemsreturns only work_items + meta tools. - All-tools coverage spec: every registered tool declares a valid toolset.
- Manual GDK validation with
curl.
Rollout
Feature flag mcp_toolsets gates filtering. Toolset declarations always present (metadata). Inherently backward compatible: no header = all tools.