doc/development/duo_agent_platform/mcp/_index.md
This page includes information about developing and working with the GitLab MCP server.
To set up your development environment:
node and install mcp-remote globally. GDK comes with Node.js but installed AI assistants cannot use the GDK version.Add --debug to the mcp-remote command for more detailed logging. View MCP server logs by opening the Output and selecting
MCP:SERVERNAME. For the example below, it would be MCP:user-GitLab-GDK
{
"mcpServers": {
"GitLab-GDK": {
"command": "npx",
"args": [
"mcp-remote",
"https://gdk.test:3443/api/v4/mcp",
"--debug"
],
"env": {
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
}
}
}
Claude Desktop uses an unsupported version of Node.js. Create a custom wrapper script that uses a specific version:
#!/bin/bash
# Force use of your Node.js version
NODE_BIN="/PATH_TO_NODE_INSTALL/node/22.17.0/bin/node"
MCP_REMOTE_BIN="/PATH_TO_NODE_INSTALL/node/22.17.0/bin/mcp-remote"
# Run mcp-remote with your Node.js
exec "$NODE_BIN" "$MCP_REMOTE_BIN" "$@"
Use the wrapper script in the Claude Desktop configuration.
{
"mcpServers": {
"GitLab-GDK": {
"command": "/PATH_TO_REMOTE_WRAPPER_SCRIPT/mcp-remote-wrapper",
"args": [
"https://gdk.test:3443/api/v4/mcp",
"--debug"
],
"env": {
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
}
}
}
}
mcp-remoteTest the authentication from mcp-remote to GDK outside an AI assistant:
NODE_TLS_REJECT_UNAUTHORIZED=0 npx mcp-remote https://gdk.test:3443/api/v4/mcp --debug
If you switch branches, you may experience authentication issues which can include UNABLE_TO_VERIFY_LEAF_SIGNATURE
errors in the logs. The untrusted certificate error in chain is specific to GDK instances that use TLS. The error is
caused by the https client used in Node.js and the mcp-remote library via npx. The https client doesn't trust
certificates signed outside a bundled certificate authorities list.
If encountering authentication issues clearing your ~/.mcp-auth directory, as a last resort, resets stored
credentials for mcp-remote. When the AI Assistant reconnects to the MCP server, a browser window opens to prompt
for authorization.
rm -rf ~/.mcp-auth
MCP Inspector is an interactive developer tool for testing and debugging MCP servers.
The following command opens an intuitive Web UI for connecting to a server and listing and executing MCP tools:
npx -y @modelcontextprotocol/inspector npx
Our current development guidelines remain in early development. As we continue establishing tool development standards - especially for custom and
aggregated tools - we've created an interim mcp-tool-review-board committee to evaluate proposed tools before implementation and guide teams planning new MCP tools.
To add a new tool, please create a MCP Tool Proposal issue and follow the template instructions.
[!note] Tool implementation location depends on GitLab resource interaction:
- Tools that interact with GitLab resources should eventually live in MCP Server, but can be implemented in the Agent Platform for short-term or urgent needs.
- Tools that don't interact with GitLab resources should be implemented in the Agent Platform.
We are working to integrate MCP server functionality into the Agent Platform. You can track progress via this issue.
We strongly encourage all engineers to follow the tool proposal process and provide clear explanations of their use cases.
This merge request defines a process for creating an MCP tool from an API route.
Adding the following route_setting to an API route definition:
route_setting :mcp, tool_name: :get_merge_request, params: [:id, :merge_request_iid]
get_merge_request tool to the list of tools and enables its executionparams argument. For example, only id and merge_request_iid are advertised and acceptedThis merge request provides more examples.
Aggregated API tools combine multiple related API tools into a single unified interface, reducing tool count and improving the user experience. The search tool demonstrates this pattern by consolidating global, group, and project search into one tool.
When to use aggregated tools:
Use aggregated tools when you have multiple API endpoints that serve similar purposes but operate at different scopes (global, group, project). This reduces cognitive load on the LLM by presenting one tool instead of three.
Implementation steps:
Mcp::Tools::AggregatedService:module Mcp
module Tools
class ExampleAggregatedService < AggregatedService
include Gitlab::Utils::StrongMemoize
extend ::Gitlab::Utils::Override
register_version '0.1.0', {
description: 'My example aggregated tool',
input_schema: {
type: 'object',
properties: {},
required: []
}
}
override :tool_name
def self.tool_name
'new_tool'
end
override :select_tool
def select_tool(args)
tool_name = if args[:group_id]
:example_tool_for_group
elsif args[:project_id]
:example_tool_for_project
end
tools.find { |tool| tool.name.to_sym == tool_name }
end
override :transform_arguments
def transform_arguments(args)
if args[:group_id]
args.merge(id: args[:group_id])
elsif args[:project_id]
args.merge(id: args[:project_id])
else
args
end
end
end
end
end
route_setting :mcp, tool_name: :example_tool_for_group, params: [:id], aggregators: [::Mcp::Tools::ExampleAggregatedService]
route_setting :mcp, tool_name: :example_tool_for_project, params: [:id], aggregators: [::Mcp::Tools::ExampleAggregatedService]
Mcp::Tools::Manager automatically discovers aggregated tools by scanning routes with
aggregators specified and instantiates the aggregator class with the collected tools.For MCP tools that use the GitLab GraphQL API, see the GraphQL integration guidelines.
For tools with distinct functionality that should remain separate from API exposure, you can define a standalone class (see this example for reference).
[!warning] More tools aren't always better. The research shows that both context size and tool count have diminishing returns and eventually lead to performance degradation. Consider tool consolidation, specialized sub-agents, or dynamic tool routing instead of continuously expanding your toolset.
MCP tools use semantic versioning to avoid breaking changes for consumers. When modifying a tool, use the versioning system introduced in this merge request.
Why versioning matters:
LLMs and AI agents cache tool schemas and build workflows around specific tool behaviors. Changes to tool parameters, descriptions, or output formats can break existing integrations. Versioning allows safe evolution while maintaining backward compatibility.
Version registration pattern:
For aggregated API, custom, and graphQL tools, register versions using register_version:
module Mcp
module Tools
class GetServerVersionService < CustomService
register_version '0.1.0', {
description: 'Get the current version of MCP server.',
input_schema: {
type: 'object',
properties: {},
required: []
}
}
def perform_0_1_0(_arguments = {})
data = { version: Gitlab::VERSION, revision: Gitlab.revision }
formatted_content = [{ type: 'text', text: data[:version] }]
::Mcp::Tools::Response.success(formatted_content, data)
end
override :perform_default
def perform_default(arguments = {})
perform_0_1_0(arguments)
end
end
end
end
Adding a new version:
When you need to modify a tool's behavior:
register_version '0.2.0', {
description: 'Get version with additional metadata.',
input_schema: {
type: 'object',
properties: {
include_metadata: {
type: 'boolean',
description: 'Include additional metadata'
}
},
required: []
}
}
def perform_0_2_0(arguments = {})
data = {
version: Gitlab::VERSION,
revision: Gitlab.revision
}
if arguments[:include_metadata]
data[:metadata] = { build_date: Time.current }
end
formatted_content = [{ type: 'text', text: data[:version] }]
::Mcp::Tools::Response.success(formatted_content, data)
end
perform_default to use the latest version:override :perform_default
def perform_default(arguments = {})
perform_0_2_0(arguments)
end
For API tools:
API tools automatically default to version 0.1.0. The version can be specified in the route
setting if needed:
route_setting :mcp, tool_name: :get_merge_request,
params: [:id, :merge_request_iid],
version: '1.0.0'
[!note] API tools from routes use a single version per tool. For tools requiring multiple versions, consider implementing as a custom tool instead.
Version support policy:
The framework automatically uses the latest version when no version is specified. Consumers can request specific versions during tool calls. Follow multi-version compatibility guidelines when deprecating versions.
Renaming a tool requires using tool aliases to maintain backward compatibility. Connected clients cache tool names and do not automatically refresh when tools are renamed. The alias system introduced in this merge request allows graceful renames without breaking existing integrations.
Why aliases are necessary:
MCP clients cache the tool list from tools/list and don't automatically re-fetch when tools
change. Renaming a tool causes clients to call a non-existent tool name, resulting in errors
or indefinite hangs. The MCP specification supports notifications/tools/list_changed to notify
clients of changes, but GitLab MCP server doesn't implement this (tracked
in this issue).
Implementation steps:
tool_aliases in your tool class to include the old name:module Mcp
module Tools
class RenamedService < AggregatedService
override :tool_name
def self.tool_name
'new_name'
end
override :tool_aliases
def self.tool_aliases
['old_name']
end
end
end
end
Update all references to use the new tool name:
tool_name:The Mcp::Tools::Manager automatically resolves aliases during get_tool calls, so clients
using the old name continue to work.
Important notes:
list_tools only returns the canonical tool name, not aliasesManager#resolve_alias which checks all tool registriesDeprecation timeline:
Release M: Add alias and rename tool Release M+1: Remove alias (after clients have had time to refresh their tool lists)
This approach ensures zero downtime for connected clients during tool renames.