test(gitlab): probe the MCP tool inventory, not just the handshake

The probe asserted an HTTP 200 on initialize and treated that as a working MCP
surface. It only proves the path exists and the token is accepted. This role
deploys the CE images while GitLab serves MCP through Duo, which is Premium and
Ultimate only, so an endpoint that answers but advertises nothing was
indistinguishable from a working one.

The probe now completes the handshake and calls tools/list, reading an SSE frame
when the response is one, and reports the count. A separate assertion fails the
deploy on zero tools and names the licence as the reason, which would put this
role behind 'blocker: licence' like web-app-openproject.

The assertion uses two substring checks rather than regex_search: backslash
escaping inside YAML scalars is a trap this repository has been bitten by.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-08-03 22:11:12 +02:00
1 parent c0f01ab639
commit c76de1a2d4
2 files changed
+47 -11

No files matched your search

+36 -11
View File
@@ -4,11 +4,33 @@ require 'uri'
uri = URI("http://#{ENV.fetch('GITLAB_MCP_HOST')}:#{ENV.fetch('GITLAB_MCP_PORT')}#{ENV.fetch('GITLAB_MCP_PATH')}")
request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{ENV.fetch('GITLAB_MCP_TOKEN')}"
request['Content-Type'] = 'application/json'
request['Accept'] = 'application/json, text/event-stream'
request.body = JSON.generate(
def post(uri, token, payload, session_id)
request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{token}"
request['Content-Type'] = 'application/json'
request['Accept'] = 'application/json, text/event-stream'
request['Mcp-Session-Id'] = session_id if session_id
request.body = JSON.generate(payload)
Net::HTTP.start(uri.hostname, uri.port, read_timeout: 30, open_timeout: 10) do |http|
http.request(request)
end
end
def rpc_body(response)
raw = response.body.to_s
return JSON.parse(raw) if response['Content-Type'].to_s.include?('application/json')
data = raw.each_line.select { |line| line.start_with?('data:') }
.map { |line| line.sub(/\Adata:\s*/, '').strip }
.join
data.empty? ? {} : JSON.parse(data)
rescue JSON::ParserError
{}
end
token = ENV.fetch('GITLAB_MCP_TOKEN')
response = post(uri, token, {
jsonrpc: '2.0',
id: 1,
method: 'initialize',
@@ -17,12 +39,15 @@ request.body = JSON.generate(
capabilities: {},
clientInfo: { name: 'infinito-nexus', version: '1' }
}
)
response = Net::HTTP.start(uri.hostname, uri.port, read_timeout: 30, open_timeout: 10) do |http|
http.request(request)
end
}, nil)
abort("REJECTED #{response.code}") unless response.code.to_i == 200
puts "ACCEPTED #{response.code}"
session_id = response['Mcp-Session-Id']
post(uri, token, { jsonrpc: '2.0', method: 'notifications/initialized' }, session_id)
listing = post(uri, token, { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} }, session_id)
tools = rpc_body(listing).dig('result', 'tools')
tools = [] unless tools.is_a?(Array)
puts "ACCEPTED #{response.code} tools=#{tools.length}"
+11
View File
@@ -31,3 +31,14 @@
The instance rejected the token stored for {{ application_id }}, so every
MCP client would be configured with a bearer {{ GITLAB_MCP_PATH }} answers
401 to.
- name: "🛑 Refuse an instance that authenticates but advertises no MCP tool"
ansible.builtin.assert:
that:
- "'tools=' in (gitlab_mcp_probe.stdout | default(''))"
- "'tools=0' not in (gitlab_mcp_probe.stdout | default(''))"
fail_msg: >-
{{ GITLAB_MCP_PATH }} accepted the token but advertised no tool. GitLab
serves MCP only on Premium and Ultimate, so this Community deployment
cannot carry a served MCP surface and the role belongs behind
'blocker: licence' instead.