diff --git a/src/pages/docs/features/_meta.ts b/src/pages/docs/features/_meta.ts
index a6d00f9..ca2366d 100644
--- a/src/pages/docs/features/_meta.ts
+++ b/src/pages/docs/features/_meta.ts
@@ -56,5 +56,8 @@ export default {
},
"mcp-integration": {
"title": "MCP Integration"
+ },
+ "mcp-server": {
+ "title": "MCP Server"
}
-};
+};
\ No newline at end of file
diff --git a/src/pages/docs/features/mcp-server.mdx b/src/pages/docs/features/mcp-server.mdx
new file mode 100644
index 0000000..2ec6b04
--- /dev/null
+++ b/src/pages/docs/features/mcp-server.mdx
@@ -0,0 +1,238 @@
+---
+title: MCP Server
+tags:
+ - ai
+ - mcp
+ - tools
+ - claude
+ - cursor
+ - vscode
+ - codex
+---
+
+import { Callout, Steps } from "nextra/components";
+
+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](/docs/features/ai-agent)? See [MCP
+ Integration](/docs/features/mcp-integration).
+
+
+## Connect your AI tool
+
+You only need one thing: your Lifecycle MCP URL, which is your Lifecycle app host plus `/mcp` —
+for example `https://app.lifecycle.example.com/mcp`. Everything else (registration, login) happens
+automatically.
+
+
+ Two prerequisites: your Lifecycle operator 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
+
+```bash
+claude mcp add --transport http lifecycle https://app.lifecycle.example.com/mcp
+claude mcp login lifecycle
+```
+
+Or run `/mcp` inside a session and pick **lifecycle → Authenticate**.
+
+### Cursor
+
+Add the server to `~/.cursor/mcp.json` (or per-project `.cursor/mcp.json`):
+
+```json filename="~/.cursor/mcp.json"
+{
+ "mcpServers": {
+ "lifecycle": { "url": "https://app.lifecycle.example.com/mcp" }
+ }
+}
+```
+
+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`):
+
+```json filename=".vscode/mcp.json"
+{
+ "servers": {
+ "lifecycle": {
+ "type": "http",
+ "url": "https://app.lifecycle.example.com/mcp"
+ }
+ }
+}
+```
+
+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:
+
+```toml filename="~/.codex/config.toml"
+[mcp_servers.lifecycle]
+url = "https://app.lifecycle.example.com/mcp"
+```
+
+```bash
+codex mcp login lifecycle
+```
+
+
+ Try it with a prompt like _"using the lifecycle tools, list my environments"_.
+ Your assistant only sees environments through your own account — the same
+ access you have in the UI.
+
+
+## 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.
+
+| 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:
+
+1. **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.
+
+2. **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), registers itself with Keycloak, and sends you through the same
+ browser single sign-on used by the UI and the `lfc` CLI. A one-time consent screen shows what
+ the tool is asking for.
+
+3. **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.
+
+4. **Everything runs as you.** Each tool call executes under the identity in your token, so
+ filters like _my environments only_ use your account. Tool output never includes service
+ environment variables, which can contain secrets.
+
+## Setup (operators)
+
+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:
+
+```yaml filename="values.yaml (lifecycle chart, web component)"
+components:
+ web:
+ deployment:
+ extraEnv:
+ - name: MCP_SERVER_ENABLED
+ value: "true"
+ - name: MCP_RESOURCE_URL
+ value: "https://app.lifecycle.example.com/mcp"
+```
+
+`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:
+
+```yaml filename="values.yaml (lifecycle-keycloak chart)"
+mcp:
+ enabled: true
+ resourceUrl: "https://app.lifecycle.example.com/mcp"
+ # Only when Keycloak is NOT reachable from the public internet — see the warning below.
+ dcr:
+ enabled: true
+ # If several MCP deployments share this realm, list the others here:
+ # extraAudiences:
+ # - "https://app.staging.example.com/mcp"
+```
+
+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. Pass `extraAudiences` (as
+shown above) when several MCP deployments share one realm.
+
+### Verify
+
+```bash
+curl https://app.lifecycle.example.com/.well-known/oauth-protected-resource/mcp
+```
+
+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. Back up the realm first if you may want to revert.
+
+
+## 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 or restricted — ask your Lifecycle operator |