feat(mcp): remote MCP server with OAuth 2.1 authentication#224
Open
vigneshrajsb wants to merge 10 commits into
Open
feat(mcp): remote MCP server with OAuth 2.1 authentication#224vigneshrajsb wants to merge 10 commits into
vigneshrajsb wants to merge 10 commits into
Conversation
…ce-server auth
- Mount /mcp + RFC 9728 protected-resource metadata in ws-server HTTP chain,
gated by MCP_SERVER_ENABLED and LIFECYCLE_MODE (web/all only)
- Bearer auth via jose/JWKS with dedicated MCP audience (MCP_RESOURCE_URL),
isolated from the REST API's lifecycle-core audience
- Per-session McpServer bound to the authenticated user identity with
read-only tools: list_builds, get_build, list_services, get_job_logs,
list_sites, get_site + lifecycle://builds/{uuid} resource
- Session manager with idle eviction, per-user session binding, origin
validation (DNS-rebinding protection), body size caps
- Bump @modelcontextprotocol/sdk to 1.29.0; allow jose ESM transform in jest
…ction, detailed job-log errors - get_job_logs: use loadFromDefault (works on host and in-cluster) instead of loadFromCluster-first helper that defers ca.crt read to request time - skip '[init] ' pseudo-container names when picking the log container - surface kubernetes error status/message in tool errors - add scripts/mcp-keycloak-setup.py: idempotent admin-API configurator for the mcp client scope + audience mapper and anonymous-DCR policies
…cy, correct scopes policy name Keycloak rejects a Trusted Hosts policy with both host and client-URI checks disabled; removing the anonymous policy component is the supported way to lift the restriction for VPN-only DCR. The client-scopes policy providerId is the legacy 'allowed-client-templates'; rely on allow-default-scopes + the realm optional 'mcp' scope instead of an explicit allowlist.
- support repeatable --mcp-resource-url in the keycloak setup script (one audience mapper per URL) for realms serving several MCP deployments - enable MCP on the Tilt in-cluster web (helm/environments/local) and add the mcp block to the local lifecycle-keycloak values (in-cluster :5001 primary, host dev :3000 extra audience)
Found in prod e2e with Claude Code: clients derive their DCR registration scope from the 401 challenge and their authorization scope from PRM scopes_supported. Keycloak's anonymous registration policy rejects unknown scope names (including 'openid'), and authorization fails for scopes not assigned to the client — so advertising openid/profile/email caused invalid_scope. Advertise exactly ['mcp', 'offline_access'] in both places, and move the identity claims (preferred_username, email, github_username) onto the mcp client scope via protocol mappers in the setup script.
Found in prod e2e: lifecycle-web runs multiple replicas behind a load balancer without session affinity, so per-pod in-memory sessions 404 when consecutive requests land on different pods. Serve each POST with a fresh stateless server/transport pair (sessionIdGenerator: undefined); GET/DELETE return 405. This also pre-aligns with the 2026-07-28 MCP revision, which drops sessions from the core protocol.
This was referenced Jul 5, 2026
Prune stale positional audience mappers, fail loudly on missing scope during optional-scope registration, guard non-numeric max-clients config, explicit urllib.error import, document one-way Trusted Hosts removal.
- load the MCP handler only when MCP_SERVER_ENABLED (jose is ESM-only; require(esm) needs Node >= 20.19 — disabled deployments on older Node 20 minors keep booting), and warn at startup when auth is on but MCP_RESOURCE_URL is unset - list_builds myEnvironmentsOnly and list_sites mineOnly now error when the identity claim is missing instead of silently dropping the filter - anonymous DCR in the keycloak setup script is now opt-in (--enable-anonymous-dcr) with a loud warning; urlopen timeouts added - oversized bodies get a 413 that actually reaches the client; case-insensitive Bearer scheme; CORS headers reflect only allowlisted origins and are present on real responses, not just preflight - tests: env hygiene, drop removed jose KeyLike type, add secret-redaction regression test and an auth-enabled 401-challenge HTTP test
- list_builds myEnvironmentsOnly filters on githubUsername only (a preferredUsername fallback could match a different user's GitHub login) - pin jwtVerify algorithms to RS256 and allow 30s clock tolerance - friendly error when the keycloak setup script's admin auth fails - docs: document that all authenticated users can read all builds/logs/sites (filters are convenience, not access control), and that anonymous DCR is off by default and one-way to enable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a remote MCP (Model Context Protocol) server to Lifecycle, so AI coding tools (Claude Code, Cursor, VS Code, Codex, and any other MCP client) can securely inspect preview environments.
/mcpon the web service, feature-flagged viaMCP_SERVER_ENABLEDand off by defaultlist_builds,get_build,list_services,get_job_logs,list_sites,get_site, plus alifecycle://builds/{uuid}resourcescripts/mcp-keycloak-setup.py: idempotent admin-API script to configure an existing realm (client scope, audience/identity mappers, optional DCR policies)docs/mcp-server.mdValidation