Add MCP Orbit API endpoint for Knowledge Graph queries
What does this MR do and why?
Adds a JSON-RPC 2.0 endpoint at POST /api/v4/mcp-orbit for querying the Knowledge Graph via MCP protocol. Proxies tools/list and tools/call to the GKG Rust service over gRPC; handles initialize and notifications/initialized locally in Rails.
This is a separate endpoint from the existing /api/v4/mcp (AI Framework). We need a dedicated server because the Knowledge Graph is a distinct data product with its own GA timeline, monetization for the existing MCP endpoint has not been decided, and bundling KG tools into the existing server would bloat the system prompt by ~3.3k tokens for all consumers. Separating the endpoints lets us manage rate limiting, feature flagging, and beta status independently without touching the AI Framework surface area.
Context: https://gitlab.slack.com/archives/C089YV8KZL1/p1771895586482919
Stacked on !224421 (merged) (gRPC client)
Closes #591397 (closed)
Design decisions
- Same architecture as the existing MCP server (
lib/api/mcp/base.rb) with a separatemcp_orbitnamespace - CE
feature_available?returnsfalse(KG is EE-only); EE prepend checksFeature.enabled?(:knowledge_graph, current_user) -
tools/listandtools/callforward to the GKG Rust service viaAnalytics::KnowledgeGraph::GrpcClient -
initializeandnotifications/initializedhandled in Rails, no gRPC needed -
ConnectionErrorontools/listreturns empty tools array; ontools/callreturns JSON-RPC internal error (-32603) -
ExecutionErrorontools/callreturns MCP-format error withisError: true - Beta endpoint per team discussion
MCP method dispatch
| Method | Handler | Backend |
|---|---|---|
initialize |
InitializeRequest |
Local (Rails) |
notifications/initialized |
InitializedNotification |
Local (no-op) |
tools/list |
ListTools |
gRPC list_tools
|
tools/call |
CallTool |
gRPC execute_tool
|
* |
- | JSON-RPC -32601 |
Authentication
-
authenticate!-- valid OAuth token required -
not_found! unless feature_available?-- EE checks:knowledge_graphfeature flag -
forbidden!unless token hasmcpscope
How to set up and validate locally
Checkout the branch and start GDK:
git checkout orbit-mcp-api
gdk restart rails-web
Wait for https://gdk.test:3443/-/health to return 200.
Set up test credentials
In bin/rails c:
user = User.find_by(admin: true)
Feature.enable(:knowledge_graph, user)
app = Authn::OauthApplication.create(
name: 'MCP Orbit Test',
redirect_uri: 'http://127.0.0.1:3334/callback',
scopes: 'mcp_orbit',
confidential: false,
owner: user
)
token = Doorkeeper::AccessToken.new(
application_id: app.id,
resource_owner_id: user.id,
scopes: 'mcp_orbit',
expires_in: 7200,
organization: Organizations::Organization.first
)
token.save!
puts "TOKEN=#{token.plaintext_token}"
Then export it:
export TOKEN="<paste token here>"
1. MCP initialize handshake
curl -sk -X POST https://gdk.test:3443/api/v4/mcp_orbit \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"initialize","id":"1","params":{"protocolVersion":"2025-06-18"}}'
You should get back protocolVersion, capabilities.tools.listChanged: false, and serverInfo.name: "GitLab Orbit MCP Server".
2. List tools
curl -sk -X POST https://gdk.test:3443/api/v4/mcp_orbit \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":"2"}'
Should return two tools: query_graph (full DSL schema in the description) and get_graph_schema (with expand_nodes parameter). Both come from the Rust gRPC service via ListTools.
3. Call a tool (needs a running GKG stack)
curl -sk -X POST https://gdk.test:3443/api/v4/mcp_orbit \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","id":"3","params":{"name":"query_graph","arguments":{"query":{"query_type":"search","node":{"id":"u","entity":"User"},"limit":5}}}}'
With GKG running: isError: false with query results in content[0].text.
Without GKG: JSON-RPC internal error (code: -32603).
4. Unknown tool rejection
curl -sk -X POST https://gdk.test:3443/api/v4/mcp_orbit \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","id":"4","params":{"name":"bad_tool","arguments":{}}}'
Should return -32602 (invalid params) with "Unknown tool: bad_tool". Only query_graph and get_graph_schema are accepted.
5. Scope isolation
Create a token with mcp scope only and verify it gets rejected:
# In rails console
mcp_token = Doorkeeper::AccessToken.new(
application_id: app.id,
resource_owner_id: user.id,
scopes: 'mcp',
expires_in: 7200,
organization: Organizations::Organization.first
)
mcp_token.save!
puts "MCP_TOKEN=#{mcp_token.plaintext_token}"
curl -sk -X POST https://gdk.test:3443/api/v4/mcp_orbit \
-H "Authorization: Bearer $MCP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"initialize","id":"5","params":{"protocolVersion":"2025-06-18"}}'
Should return HTTP 403 with {"error":"insufficient_scope",...}. A mcp token does not work on the mcp_orbit endpoint.
6. OAuth discovery
curl -sk https://gdk.test:3443/.well-known/oauth-authorization-server/api/v4/mcp_orbit
Should return OAuth metadata with scopes_supported: ["mcp_orbit"] and a registration_endpoint.
7. mcp-remote end-to-end (optional)
echo '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","method":"tools/list","id":2}' | \
NODE_TLS_REJECT_UNAUTHORIZED=0 npx -y mcp-remote \
"https://gdk.test:3443/api/v4/mcp_orbit" \
--header "Authorization: Bearer $TOKEN"
Two JSON-RPC responses on stdout (initialize and tools/list). The proxy connects over StreamableHTTPClientTransport.
References
- Depends on: !224421 (merged) (gRPC client)
- Stacked on: !224386 (merged) (JWT auth) → !224372 (merged) (traversal ID authorization)
- Extracted from: !221417 (closed) (GKG feature branch working copy)
- Issue: #591397 (closed)
- Part of: &20566 (Rails Integration)
- Related: &21076 (GKG Q1 & Q2 Roadmap)
- Pattern reference:
lib/api/mcp/base.rb
MR acceptance checklist
This MR was evaluated against the MR acceptance checklist.