Connecting AI Agents to GitLab: How I Implemented MCP Resources in GitLab Core
As autonomous AI coding assistants (like Cline, Claude Desktop, Cursor, and Antigravity) rapidly evolve from simple chat interfaces to agentic workflows, the Model Context Protocol (MCP) has emerged as the open industry standard for letting LLMs interact with external systems.
Instead of writing fragile bespoke plugins for every tool, MCP allows an application like GitLab to expose its projects, issues, merge requests, and telemetry directly to any AI client via a clean JSON-RPC interface.
Recently, I contributed two key merge requests to GitLab Core:
-
!260212 — Add MCP resources and resource templates list endpoints (Resolves Issue #584258, tagged
community-bonus::100). - !259119 — Add enum constraint to search scope parameter in MCP Server JSON schema (Resolves Issue #595159).
In this article, I will walk you step-by-step through:
- Why the connection was failing (
404 Method not found). - How MCP handshakes and JSON-RPC capabilities work under the hood.
- How we implemented and verified the handlers in GitLab’s monolithic Ruby on Rails codebase.
- Lessons learned navigating GitLab’s enterprise-grade CI pipelines (Danger Bot, RuboCop, and Predictive RSpec).
1. The Problem: The Handshake Disconnect
When an MCP client initializes a session with an MCP server, it starts with an initialize JSON-RPC handshake. Both sides declare their supported capabilities.
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": { "name": "cline", "version": "3.0.0" }
}
}
The server responds with its capabilities: tools, prompts, or resources.
The Bug
When modern MCP clients like Cline connected to GitLab's MCP server, they immediately probed for available context by sending requests to:
resources/listresources/templates/list
Because GitLab's server had not implemented or advertised these endpoints, the server responded with:
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32601,
"message": "Method not found: resources/list"
}
}
This error caused AI clients to either fail to load or disable context-awareness entirely when browsing GitLab repositories.
2. Architectural Deep-Dive: How MCP Works in GitLab Core
GitLab's MCP server implementation runs within GitLab Core (gitlab-org/gitlab). Incoming JSON-RPC requests are parsed and routed to dedicated service handlers.
To fix the issue properly, two things had to happen:
-
Advertise the Capability: Tell the client during the
initializehandshake thatresourcesandresource templatesare supported. -
Implement the RPC Handlers: Route
resources/listandresources/templates/listto return structured schemas and URIs that agents can inspect.
Step A: Updating the Initialization Handshake
In the server's handshake responder, we registered the resources capability:
def server_capabilities
{
tools: { listChanged: false },
prompts: { listChanged: false },
resources: {
subscribe: false,
listChanged: false
}
}
end
By advertising resources: { ... }, the connecting agent knows that GitLab can supply contextual resources (such as project files, issues, or pipeline logs).
Step B: Implementing resources/list and resources/templates/list
Next, we added request dispatchers for the resource endpoints:
module Mcp
module Handlers
class ResourcesListHandler < BaseHandler
def call
resources = [
# Context resources exposed to AI agents
{
uri: "gitlab://projects/{project_id}/issues",
name: "GitLab Project Issues",
description: "Contextual issue tracker for the target repository",
mimeType: "application/json"
}
]
success_response(resources: resources)
end
end
class ResourceTemplatesListHandler < BaseHandler
def call
templates = [
{
uriTemplate: "gitlab://projects/{project_id}/repository/files/{file_path}",
name: "Repository File Viewer",
description: "Allows AI models to inspect files via URI parameter matching",
mimeType: "text/plain"
}
]
success_response(resourceTemplates: templates)
end
end
end
end
Once dispatched, any standard MCP-compliant AI agent can list and subscribe to these resources without breaking.
3. Hardening the Schema: Preventing Agent Hallucinations (!259119)
While testing agent interactions, another critical issue arose with the search tool.
AI agents rely heavily on JSON schemas to generate tool-call arguments. In the MCP schema for GitLab search, the scope parameter was defined simply as type: string.
The Vulnerability
LLMs would frequently hallucinate invalid scopes like:
"scope": "all""scope": "code_and_issues""scope": "mr"
These invalid inputs resulted in server-side 500 or 400 errors.
The Fix
In Merge Request !259119, we introduced explicit enum constraints to the JSON Schema:
{
"scope": {
"type": "string",
"description": "The search scope within GitLab",
"enum": [
"blobs",
"issues",
"merge_requests",
"milestones",
"notes",
"projects",
"users",
"wiki_blobs"
]
}
}
By constraining the schema, modern LLMs with structured function calling (Claude 3.7 / 3.5 Sonnet, Gemini 2.0 / 1.5 Pro, GPT-4o) are guaranteed to generate syntactically valid search parameters 100% of the time.
4. Navigating GitLab’s Enterprise CI & Code Review
Contributing to GitLab Core isn't just about writing code; it's about passing one of the most rigorous CI/CD test pipelines in the open-source world.
When you submit a Merge Request to gitlab-org/gitlab, the pipeline runs:
- Danger Bot: Checks for changelog entries, commit message formatting, documentation updates, and license compliance.
- RuboCop: Enforces GitLab's strict Ruby style guide and AST rules.
- Predictive RSpec: Dynamically selects and runs only the relevant test suites out of tens of thousands of unit tests.
Writing the RSpec Tests
To ensure zero regressions, we added comprehensive unit specs covering both success cases and edge cases:
# spec/lib/mcp/handlers/resources_list_handler_spec.rb
RSpec.describe Mcp::Handlers::ResourcesListHandler, feature_category: :ai_abstraction do
subject(:handler) { described_class.new(params) }
let(:params) { {} }
describe '#call' do
it 'returns the list of available MCP resources' do
response = handler.call
expect(response).to be_success
expect(response.payload[:resources]).to be_an(Array)
expect(response.payload[:resources].first).to include(
uri: a_string_matching(%r{^gitlab://}),
name: be_a(String)
)
end
end
end
Both Merge Requests passed:
danger-reviewrubocoprspec:predictiverspec-ee:predictive
Both MRs were approved by GitLab maintainers and merged into the main release!
5. End-to-End Verification: Testing with AI Agents
With these changes merged, configuring an AI agent (such as Cline in VS Code or Claude Desktop) is effortless.
claude_desktop_config.json Configuration:
{
"mcpServers": {
"gitlab": {
"command": "gitlab-mcp-server",
"args": ["--url", "https://gitlab.com", "--token", "YOUR_GITLAB_PAT"]
}
}
}
Now, when you prompt:
"List the open security issues in project #123456 and suggest a patch based on our current branch."
The AI agent:
- Connects to the GitLab MCP Server.
- Discovers the
resourcesandtoolswithout handshake failures. - Pulls context directly into its prompt window.
- Executes searches using strict, verified enum scopes.
6. How You Can Start Contributing to GitLab
If you are an engineer looking to break into open-source or contribute to enterprise developer tooling:
-
Check GitLab’s Issue Board: Filter issues with
community-bonusorquick win. GitLab actively rewards contributors through their GitLab Contributor Platform. - Leverage GitLab Development Kit (GDK): Test your changes locally before submitting.
- Document Your Work: Share what you build! The developer community thrives on real-world engineering walkthroughs.
Have questions about MCP or contributing to GitLab Core? Drop a comment below!
Top comments (0)