DocsExtending KHUB

MCP integration

The entire KHUB REST API, exposed as MCP tools generated live from its own OpenAPI spec — zero bespoke integration code.

services/mcp exposes the entirety of the KHUB REST API to any Model Context Protocol client — Claude, Claude Code, or any other MCP-compatible agent — as a set of callable tools, with no KHUB-specific integration code written per feature. This is the architectural core of it: the tool list is generated live from KHUB's own OpenAPI specification, so a new REST endpoint becomes a usable MCP tool automatically, the moment it ships.

How tool generation works#

On startup, and again every MCP_SPEC_REFRESH_MS (default 5 minutes), the bridge fetches GET {KHUB_API_URL}/api/docs-json — the same machine-readable OpenAPI document that backs KHUB's own Swagger UI at /api/docs — and, for every operation in it:

  • Skips anything under /internal/* (the Git-hook callback endpoints and other process-internal routes are never eligible, regardless of tag filtering).
  • Skips operations tagged uploads, and a short explicit blocklist of binary upload/download operations (release assets, pipeline artifacts) that don't map cleanly onto a text-based tool result.
  • Resolves the operation's parameters and request body schema — including $ref, allOf, oneOf, and anyOf — into a flat JSON Schema inputSchema for the tool, so an MCP client gets real, specific argument shapes (not "a JSON blob") for every tool.
  • Registers it as one MCP tool named after the NestJS operationId (e.g. IssuesController_create), with a human-readable description like Issues: create — POST /organizations/{orgSlug}/projects/{projectSlug}/issues (issues).

An optional MCP_TAG_FILTER environment variable (comma-separated OpenAPI tags) restricts which controllers are exposed — e.g. setting it to issues,pull-requests,repositories exposes only those areas of the API as tools, useful for scoping what a particular MCP client deployment is allowed to even attempt, independent of what its credential could otherwise reach.

Transport#

The bridge is a stateless Express server speaking the MCP Streamable HTTP transport at POST /mcp (one request/response pair per tool call, sessionIdGenerator: undefined — there is no server-side session to resume, which is what makes horizontal scaling and rolling restarts of the bridge itself safe with zero coordination). GET /mcp and DELETE /mcp are stubbed to say so explicitly rather than silently no-opping. A plain GET /healthz reports { ok, tools, khubApiUrl } for container health checks.

Authentication and authorization#

The MCP bridge holds no credentials of its own and performs no authorization logic:

  • Every POST /mcp request must carry Authorization: Bearer <token>, where <token> is a KHUB personal access token with the api scope (Settings → Access tokens — see Authentication & sessions), unless the deployment sets a fixed KHUB_DEFAULT_TOKEN on the container for single-tenant/service-account use.
  • That exact token is forwarded, unmodified, as the Authorization header on every underlying REST call the tool invocation makes.
  • Consequently, an MCP tool call can do precisely what that token's scope and the calling user's role/permission graph allow — nothing more. A token scoped to a read-only role cannot be used, via MCP, to merge a pull request or push code, for exactly the same reason it couldn't do so via a direct curl call: the permission check happens in apps/api, identically, regardless of which door the request came through.

This means access control for AI-agent usage of KHUB is the same access control as everything else — there is no separate "AI permissions" concept to configure, audit, or get out of sync with the human-facing permission model.

Configuring an MCP client#

Point any MCP client that supports the Streamable HTTP transport at the bridge's public path (proxied by Caddy at /mcp on the same host as the rest of KHUB):

mcp.json
{
  "mcpServers": {
    "khub": {
      "url": "https://<your-khub-host>/mcp",
      "headers": {
        "Authorization": "Bearer khub_pat_..."
      }
    }
  }
}

No further setup is required on the KHUB side — the moment the token is valid and carries the api scope, every API operation that token's owner can perform is available as a tool call.

Operational configuration#

Environment variableDefaultPurpose
MCP_PORT3100Port the bridge listens on.
KHUB_API_URLhttp://api:4000Internal URL of apps/api, used both to fetch the OpenAPI spec and to make tool-call requests.
KHUB_DEFAULT_TOKENunsetA fallback Bearer token used when a client doesn't supply its own Authorization header — for a deployment that wants one fixed service identity for MCP rather than per-user tokens.
MCP_TAG_FILTERunset (all tags)Comma-separated OpenAPI tags to expose as tools; omit to expose everything eligible.
MCP_SPEC_REFRESH_MS300000 (5 min)How often to re-fetch the OpenAPI spec and regenerate the tool list; set to 0 to disable background refresh (the spec is still fetched once at startup).

Why this design instead of a bespoke tool set#

A hand-written MCP tool per feature would need a matching update every time an API endpoint changed shape, and would inevitably lag behind the REST API in coverage. Generating tools from the live OpenAPI document means the MCP surface is, by construction, exactly as complete and exactly as up to date as the REST API itself — the same guarantee KHUB's own Swagger UI has, extended to agents for free.