Connecting AI Agents to GitLab: How I Implemented MCP Resources in GitLab Core
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 indus

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:
In this article, I will walk you step-by-step through:
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" } } } Enter fullscreen mode Exit fullscreen mode The server responds with its capabilities: tools, prompts, or resources.
When modern MCP clients like Cline connected to GitLab's MCP server, they immediately probed for available context by sending requests to:
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" } } Enter fullscreen mode Exit fullscreen mode This error caused AI clients to either fail to load or disable context-awareness entirely when browsing GitLab repositories.
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:
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 Enter fullscreen mode Exit fullscreen mode By advertising resources: {... }, the connecting agent knows that GitLab can supply contextual resources (such as project files, issues, or pipeline logs).
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 Enter fullscreen mode Exit fullscreen mode Once dispatched, any standard MCP-compliant AI agent can list and subscribe to these resources without breaking.
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.
LLMs would frequently hallucinate invalid scopes like:
These invalid inputs resulted in server-side 500 or 400 errors.
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" ] } } Enter fullscreen mode Exit fullscreen mode 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.
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:
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 Enter fullscreen mode Exit fullscreen mode Both Merge Requests passed:
Both MRs were approved by GitLab maintainers and merged into the main release!
With these changes merged, configuring an AI agent (such as Cline in VS Code or Claude Desktop) is effortless.
{ "mcpServers": { "gitlab": { "command": "gitlab-mcp-server", "args": [ "--url", "https://gitlab.com", "--token", "YOUR_GITLAB_PAT" ] } } } Enter fullscreen mode Exit fullscreen mode Now, when you prompt:
"List the open security issues in project #123456 and suggest a patch based on our current branch."
If you are an engineer looking to break into open-source or contribute to enterprise developer tooling:
Have questions about MCP or contributing to GitLab Core? Drop a comment below!