Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 97 additions & 7 deletions docs/getting-started/install-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ capabilities through CLI commands, with nothing to deploy.
will not work against earlier releases.
- `kubectl` and the [`epinio` CLI](./install-cli.md) pointed at your cluster.
- `make` and a Go toolchain, plus a clone of [epinio/mcp](https://github.com/epinio/mcp).
- A route for the MCP server. OAuth clients outside the cluster require a
publicly reachable HTTPS URL.

## Choose an install path

Expand All @@ -57,24 +59,99 @@ This targets the `mcp` namespace (creating it if needed), pushes the server, and
smoke-tests `/healthz` and `/readyz`. Override the namespace with
`make setup NAMESPACE=<name>`, and run `make help` to see every target.

`epinio.yml` carries the connection details. Fill in the `environment` section
with your cluster's API URL and credentials (default `admin` / `password`):
`epinio.yml` carries the connection and OAuth discovery details. Fill in the
`environment` section:

```yaml
environment:
EPINIO_API_URL: "https://epinio.your-cluster.example.com"
EPINIO_USERNAME: "admin"
EPINIO_PASSWORD: "your-password"
EPINIO_MCP_RESOURCE_URL: "https://epinio-mcp.your-cluster.example.com"
EPINIO_MCP_OIDC_ISSUER: "https://auth.your-cluster.example.com"
```

For OIDC clusters, leave the username and password empty and set `EPINIO_TOKEN`,
`EPINIO_REFRESH_TOKEN`, and `EPINIO_TOKEN_ENDPOINT` instead.
`EPINIO_MCP_RESOURCE_URL` must exactly match the URL entered in the MCP client,
including any path. `EPINIO_MCP_OIDC_ISSUER` is the issuer reported by Dex's
OpenID Connect discovery document. Both values are required.

The server does not store an Epinio username, password, access token, or refresh
token. Every MCP request must carry credentials that Epinio accepts. Tools
therefore run with the calling user's permissions instead of a shared server
identity.

The push runs the full build cycle (upload source, stage, deploy, wait for ready)
and assigns a route, for example `https://epinio-mcp.192.168.X.X.sslip.io`. The
MCP endpoint is that route's root — point your agent at the URL as-is (no `/mcp`
suffix).

## Configure Dex for OAuth clients

OAuth-capable MCP clients discover Dex through the MCP server, open the Dex
login page, and return an access token to the server. Dex requires each client
and its exact callback URI to be registered in the `dex-config` Secret.

For example, a public client used by Claude can be added to `staticClients`:

```yaml
- id: claude-mcp
name: Claude MCP
public: true
redirectURIs:
# Claude on the web
- https://claude.ai/api/mcp/auth_callback
# Claude Code with --callback-port 3118
- http://localhost:3118/callback
- http://127.0.0.1:3118/callback
```

Also add the client ID to the `trustedPeers` of `epinio-api`:

```yaml
- id: epinio-api
# ...
trustedPeers:
- epinio-cli
- epinio-ui
- claude-mcp
```

Back up the Secret before changing it:

```bash
kubectl get secret dex-config -n epinio -o yaml > dex-config-backup.yaml
kubectl get secret dex-config -n epinio \
-o jsonpath='{.data.config\.yaml}' | base64 -d > dex-config.yaml
```

After editing `dex-config.yaml`, update only its key in the existing Secret so
the other Dex settings are preserved, then restart Dex:

```bash
CONFIG=$(base64 < dex-config.yaml | tr -d '\n')
kubectl patch secret dex-config -n epinio --type merge \
-p "{\"data\":{\"config.yaml\":\"${CONFIG}\"}}"
kubectl rollout restart deployment/dex -n epinio
kubectl rollout status deployment/dex -n epinio
```

:::note
The Epinio Helm chart manages `dex-config`. Reapply this customization after an
upgrade that replaces the Secret.
:::

Dex does not support dynamic client registration or Client ID Metadata
Documents (CIMD). Configure the client ID explicitly in clients that support
it. For Claude Code:

```bash
claude mcp add --transport http \
--client-id claude-mcp --callback-port 3118 \
epinio https://epinio-mcp.your-cluster.example.com
```

Other MCP clients can use the same OAuth flow, but need their own registered
client ID and exact callback URI. A local Epinio user can instead supply HTTP
Basic credentials when the MCP client supports manual authorization headers.

### Elevated tier (optional)

The core install wires only to the Epinio API. To turn on the opt-in
Expand All @@ -97,7 +174,8 @@ RBAC. The install manifest is self-contained: it creates the namespace, the
server's ServiceAccount and RBAC, the Deployment and Service, and an Epinio `App`
record so `epinio app list/show/logs` keep working.

Deploy the server (edit the image tag, credentials, and Ingress host first):
Deploy the server (edit the image tag, authentication URLs, and Ingress host
first):

```bash
kubectl apply -f install/epinio-mcp.yaml
Expand Down Expand Up @@ -131,6 +209,18 @@ A healthy `/readyz` response reports the Epinio version it reached:
{"epinio":{"kube_version":"...","platform":"...","version":"..."},"status":"ok","version":"..."}
```

Verify OAuth discovery:

```bash
curl https://epinio-mcp.<your-route>/.well-known/oauth-protected-resource
curl -i https://epinio-mcp.<your-route>/
```

The metadata request returns the configured resource and Dex issuer. The
unauthenticated MCP request returns `401 Unauthorized` with a
`WWW-Authenticate` header pointing to that metadata. A client can then begin
the OAuth flow.

## See also

- [MCP server reference](../reference/mcp.md)
Expand Down
54 changes: 48 additions & 6 deletions docs/reference/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,65 @@ agent and the Epinio REST API to the cluster:

```text
AI Agent (Claude, etc.)
| MCP protocol (Streamable HTTP, served at the server root)
| MCP protocol (Streamable HTTP) + caller credentials
Epinio MCP Server
| REST API (Basic Auth or OIDC, TLS)
| REST API using the same caller credentials
Epinio API Server
| Kubernetes API
Kubernetes Cluster
```

Authentication is per request: the agent passes an `Authorization` header
(`Bearer <token>` or `Basic <base64(user:pass)>`) that the server forwards to
Epinio. When no header is present, the server falls back to the credentials it was
configured with (default `admin` / `password`).
The MCP endpoint is served at the server root. Authentication is mandatory and
per caller: the agent passes either an OIDC bearer token or HTTP Basic
credentials, and the MCP server asks Epinio's authenticated `/me` endpoint to
validate it. Epinio remains the authority for users and permissions.

There is no anonymous mode or shared server-credential fallback. A request
without credentials receives `401 Unauthorized` and cannot access tools. Every
tool call uses an Epinio API client created from the caller's credential, so
the result is limited by that user's Epinio permissions.

By default every tool wires **only** to the Epinio REST API, as the calling user.
An optional [elevated tier](#elevated-tier) that reaches directly into Kubernetes
is off unless explicitly enabled.

### OAuth discovery

The server is an OAuth 2.0 protected resource. It exposes
`/.well-known/oauth-protected-resource`, which contains:

- The MCP resource URL.
- The Dex authorization-server issuer.
- The supported bearer-token method and scopes.

An unauthenticated MCP request returns a `WWW-Authenticate` challenge pointing
to this document. An OAuth-capable MCP client can then discover Dex, perform
Authorization Code with PKCE, and send the resulting access token as
`Authorization: Bearer <token>`.

Epinio accepts tokens intended for the `epinio-api` client. The MCP OAuth client
must therefore be registered as a trusted peer of `epinio-api`, and request the
`audience:server:client_id:epinio-api` scope. Dex requires static client
registration and an exact callback URI; it does not support dynamic client
registration or Client ID Metadata Documents (CIMD).

See [Install the MCP server](../getting-started/install-mcp#configure-dex-for-oauth-clients)
for Dex configuration and a Claude Code example. Compatibility with another
MCP client depends on its support for Streamable HTTP, protected-resource
discovery, and an explicitly configured OAuth client ID.

### Authentication configuration

| Variable | Required | Purpose |
| --- | --- | --- |
| `EPINIO_API_URL` | Yes | Epinio API base URL. |
| `EPINIO_MCP_RESOURCE_URL` | Yes | Exact MCP URL entered by users and advertised in protected-resource metadata. |
| `EPINIO_MCP_OIDC_ISSUER` | Yes | Dex issuer URL advertised to OAuth clients. |

The resource URL is compared exactly by OAuth clients, so scheme, hostname,
port, and path must match. The issuer must match the `issuer` field in Dex's
OpenID Connect discovery document.

## Core tools

These are always available and act purely through the Epinio API.
Expand Down