MCP Server
Lifecycle ships a built-in MCP server (Model Context Protocol — a standard way for AI assistants to call tools), so assistants like Claude Code, Cursor, VS Code, and Codex can look at your preview environments for you. Once connected, you can ask your AI tool things like:
- “Is the environment for my PR up? What’s the frontend URL?”
- “Why did the backend build fail in
snowy-field-978044? Show me the logs.” - “List my environments and tell me which ones errored.”
The assistant answers by calling Lifecycle directly — no copy-pasting UUIDs, dashboards, or log snippets into the chat.
Looking for the opposite direction — connecting external MCP servers to Lifecycle’s built-in AI Agent? See MCP Integration.
Connect your AI tool
Your Lifecycle MCP URL is your Lifecycle app host plus /mcp — for example
https://app.lifecycle.example.com/mcp. If your admin has enabled dynamic client registration,
that URL is all you need — registration and login happen automatically. Otherwise (the default),
you also need an OAuth client ID from your admin — see
Without dynamic client registration.
Two prerequisites: your Lifecycle admin must have the MCP server enabled, and you need network access to your Lifecycle app host (e.g. VPN) — the same reachability as the Lifecycle UI.
The first time you connect, your browser opens your organization’s normal single sign-on page —
the same login as the Lifecycle UI and lfc CLI — followed by a one-time consent screen. That’s
it. When your session eventually expires, just run the client’s login step again.
Claude Code
Or run /mcp inside a session and pick lifecycle → Authenticate.
Cursor
Add the server to ~/.cursor/mcp.json (or per-project .cursor/mcp.json):
Cursor shows a Needs login prompt next to the server in Settings → MCP; clicking it opens the browser sign-in.
VS Code
Add the server to .vscode/mcp.json in your workspace (or your user-level mcp.json):
VS Code prompts you to trust and authenticate the server the first time Copilot Chat uses it.
Codex CLI
Add the server to ~/.codex/config.toml, then log in:
Try it with a prompt like “using the lifecycle tools, list my environments”. Every tool call runs under your identity — the same login as the Lifecycle UI — but note that this scopes filters, not visibility; see the note under What you can do.
What you can do
All v1 tools are read-only: your assistant can look things up, but it cannot deploy, redeploy,
delete, or change environment variables from here — use the Lifecycle UI or the lfc CLI for
those.
Like the REST API and UI, any authenticated user can read any build, service,
log, or site — myEnvironmentsOnly/mineOnly are convenience filters, not
access controls. If a preview environment’s logs may contain sensitive output,
treat them as visible to all authenticated Lifecycle users.
| Tool | What it answers |
|---|---|
list_builds | ”What environments exist?” — searchable, pageable, with a my environments only filter |
get_build | ”What’s the status of environment X?” — services, URLs, branch, and PR details for one build |
list_services | ”Which services are in this environment, and where are they running?” — status, branch, image, URL |
get_job_logs | ”Why did the build/deploy fail?” — build-job or deploy-job logs, live from the cluster or archived |
list_sites | ”What static sites are published?” |
get_site | ”Where is site X and when does it expire?” |
It also exposes one MCP resource, lifecycle://builds/{uuid}, which returns a build’s detail
document as JSON for clients that support attaching resources.
Tool reference
| Tool | Parameters |
|---|---|
list_builds | search (string, optional) · myEnvironmentsOnly (boolean, optional) · page (int, optional) · limit (int, optional, default 25, max 100) |
get_build | uuid (string, required) |
list_services | uuid (string, required) |
get_job_logs | uuid (string, required) · service (string, required) · jobType (build | deploy, required) · jobName (string, optional — defaults to the most recent job) · tailLines (int, optional, default 200, max 2000) |
list_sites | mineOnly (boolean, optional) · page (int, optional) · limit (int, optional, default 25, max 100) |
get_site | siteId (string, required) |
How it works
A one-page tour for the curious:
-
One endpoint on the web service. The MCP server is part of the Lifecycle web deployment, served over MCP’s Streamable HTTP transport at
/mcp. There is nothing extra to run — it reads the same data the UI and API do, and it’s stateless, so it scales with your web replicas. -
Login is your normal SSO. Lifecycle’s Keycloak realm acts as the OAuth 2.1 authorization server. When a tool connects for the first time it discovers the auth settings automatically (via standard OAuth metadata), identifies itself to Keycloak — by registering itself when your admin has enabled dynamic client registration, or with the client ID your admin pre-registered — and sends you through the same browser single sign-on used by the UI and the
lfcCLI. A one-time consent screen shows what the tool is asking for. -
Tokens only work here. Access tokens issued for the MCP server are bound to its exact URL (the OAuth audience). A CLI or UI token is rejected at
/mcp, and an MCP token is rejected by the REST API — one leaked token never unlocks the other surface. -
Everything runs under your identity. Each tool call executes under the identity in your token, so filters like my environments only use your account — though, as in the UI, any authenticated user can read any environment (see What you can do). Tool output never includes service environment variables, which can contain secrets.
Setup (admins)
Enabling the MCP server for your Lifecycle deployment is two switches — one on the app, one on Keycloak.
Enable the endpoint on the web service
Set two environment variables on the Lifecycle web deployment:
MCP_RESOURCE_URL is the canonical URL clients will use — access tokens are audience-bound to
this exact value, so it must match the resourceUrl you give Keycloak below, byte for byte
(scheme, host, port, path). The endpoint is served only by the web process (LIFECYCLE_MODE
web/all) and is absent unless MCP_SERVER_ENABLED is true.
Configure the Keycloak realm
The realm needs an mcp client scope (with audience and identity mappers) and, optionally,
anonymous dynamic client registration so tools can self-register. The lifecycle-keycloak chart
does this for you with an idempotent post-install/post-upgrade job:
The Job is idempotent and runs on both fresh installs and existing realms, so a Helm upgrade with these values is all that’s needed — no separate script to run by hand.
All URLs listed (resourceUrl + extraAudiences) share a single token trust
boundary — an access token issued for one is accepted by all of them. Use
extraAudiences only for alternate URLs of the same deployment; do not
use it to share one realm across production and staging.
Verify
should return the server’s OAuth metadata, and connecting any MCP client to
https://app.lifecycle.example.com/mcp should walk you through browser login.
Anonymous dynamic client registration is off by default and one-way to
enable. It lets anyone who can reach your Keycloak host register an OAuth
client (registering grants no access by itself — a real SSO login is still
required for tokens), and turning it on deletes the realm’s anonymous
Trusted Hosts policy, which cannot be undone by disabling the setting later.
Only enable it (mcp.dcr.enabled: true in the chart, or
--enable-anonymous-dcr with the script) when Keycloak is not reachable
from the public internet. Otherwise leave it off and pre-register a client for
your users, as described below. Back up the realm first if you may want to
revert.
Without dynamic client registration
When dynamic client registration stays disabled (the default), pre-register one OAuth client in the Lifecycle Keycloak realm and distribute its client ID to your users:
- In the realm, create a public client (no client secret) with the Standard flow enabled
and PKCE enforced (set Proof Key for Code Exchange Code Challenge Method to
S256in the client’s advanced settings). - Turn on Consent required in the client’s settings, so users get the same one-time consent screen that dynamically registered clients show.
- Add the redirect URIs your users’ MCP clients need — CLI tools typically use a localhost
callback such as
http://localhost:8080/callback, while some editors use fixed HTTPS or app-scheme callbacks. Check each client’s documentation for its exact callback URL. - Assign the
mcpclient scope to the client (as an optional client scope). - Hand the client ID to your users.
Support for pre-registered client IDs varies by MCP client, so check your client’s documentation for its current state. At the time of writing:
-
Claude Code supports it — pass the ID and pin the callback port when adding the server, and register the matching
http://localhost:8080/callbackredirect URI in Keycloak: -
Cursor supports it — add an
authobject next to the server’surlinmcp.jsonwith"CLIENT_ID": "<client-id>", and register Cursor’s fixed callbacks as redirect URIs:https://www.cursor.com/agents/mcp/oauth/callback(web/cloud agents) andcursor://anysphere.cursor-mcp/oauth/callback(desktop app). -
VS Code and Codex CLI rely on dynamic client registration and do not currently document a way to supply a pre-registered client ID — consult their documentation for updates.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Client says the server needs authentication and nothing happens | Run the client’s login step (claude mcp login …, codex mcp login …, or the login prompt in Cursor/VS Code settings) |
Browser login loops or shows invalid_scope | The Keycloak realm is missing the mcp client scope configuration — re-run the setup job or script |
401 with a valid login | The token audience doesn’t match MCP_RESOURCE_URL — make sure the URL configured in your client matches it exactly |
| Tools list is empty or calls fail after login | The endpoint may be disabled (MCP_SERVER_ENABLED) on the web deployment |
| Registration rejected | Dynamic client registration is disabled — the expected default. Use a pre-registered client ID (see Without dynamic client registration) or ask your Lifecycle admin |