> ## Documentation Index
> Fetch the complete documentation index at: https://autopilot.docs.xano.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Server

> The per-cluster, read-only Model Context Protocol server for querying live cluster state from an LLM client.

Autopilot exposes a per-cluster, **read-only**
[Model Context Protocol](https://modelcontextprotocol.io) server as a second protocol head
on the existing dashboard server, mounted at **`/mcp`** (Streamable HTTP). It lets an MCP
client (e.g. an LLM assistant) query live cluster state through a small set of read-only
tools.

The MCP server enforces the **same authorization** as the dashboard: a caller's role and
namespace scope come from their `AccessGrant` in the `AccessPolicy` CRD. There is no
separate authorization model.

## When is it active?

`/mcp` rides entirely on the existing **`auth.enabled`** feature gate:

| `auth.enabled`    | Behavior at `/mcp`                                                                                                                       |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `false` (default) | Open, exactly like the rest of the dashboard on an internal cluster — any reachable client gets all tools, unscoped. API keys are inert. |
| `true`            | Requires a credential (session cookie **or** API key) and enforces role + namespace scope per tool.                                      |

There is **no separate listener, port, or certificate** — `/mcp` is reachable wherever that
cluster's dashboard already is (via the dashboard ingress/TLS, or in-cluster over the Service).

<Note>
  A cluster with no external ingress is not reachable from a public MCP client until an ingress
  is added — a per-cluster decision. In-cluster access (below) works regardless.
</Note>

## Credentials: API keys

For non-interactive clients, mint a long-lived, revocable **API key**. A key resolves only to
an *email*; the role and namespace scope are read **live** from that email's `AccessGrant`
on every request, so revoking or re-scoping the grant instantly re-scopes the key. Only a
SHA-256 hash of the key is stored (in the `ops-ai-api-keys` ConfigMap in the controller
namespace); the plaintext is shown exactly once at mint time.

All key-management endpoints are **admin-only** and must be called with an admin **session
cookie** — an API key can never manage keys (keys are read-only everywhere).

### Mint a key

```bash theme={null}
# The email MUST already have an authorized AccessGrant.
curl -sS -X POST https://ops.<tier>.xano.com/api/v1/api-keys \
  -H 'Content-Type: application/json' \
  -b "$ADMIN_SESSION_COOKIE" \
  -d '{"email":"analyst@xano.com","label":"claude-desktop"}'
# → {"data":{"id":"...","key":"opsai_<id>_<secret>","email":"...","label":"...","createdAt":"..."}}
```

Copy the `key` value immediately — it is **never** shown again.

### List keys (metadata only)

```bash theme={null}
curl -sS https://ops.<tier>.xano.com/api/v1/api-keys -b "$ADMIN_SESSION_COOKIE"
```

### Revoke a key (others unaffected)

```bash theme={null}
curl -sS -X DELETE https://ops.<tier>.xano.com/api/v1/api-keys/<id> -b "$ADMIN_SESSION_COOKIE"
```

Revocation is the **instant kill switch** if a key leaks.

## Connecting an MCP client

Point a Streamable-HTTP MCP client at the cluster's `/mcp` URL and send the API key as a
bearer token:

* **Endpoint:** `https://ops.<tier>.xano.com/mcp`
* **Header:** `Authorization: Bearer opsai_<id>_<secret>`

Any spec-compliant MCP client can discover the available tools via `tools/list`.

## In-cluster access

`/mcp` rides on the controller's existing `ClusterIP` Service, so a workload running **in the
same cluster** reaches it over internal cluster DNS — no ingress, no external DNS, no TLS
termination required.

* **Service:** `ops-ai` in the `ops-ai` namespace, port **9090** (the dashboard port).
* **Scheme is `http://`** — TLS terminates at the ingress for external callers; the in-cluster Service port is plain HTTP. The traffic never leaves the cluster.

| Calling pod is in…         | MCP URL                                                                                |
| -------------------------- | -------------------------------------------------------------------------------------- |
| a **different** namespace  | `http://ops-ai.ops-ai.svc.cluster.local:9090/mcp` (or `http://ops-ai.ops-ai:9090/mcp`) |
| the **`ops-ai`** namespace | `http://ops-ai:9090/mcp`                                                               |

Authentication depends on `auth.enabled`: with auth off, nothing is required (open to
anything that can reach the Service); with auth on, supply a read-only API key as a bearer
token. Scope the key's `AccessGrant` to just the namespaces the service needs — the tools
enforce it per-request.

## Tool catalog

Tools are split by role. A scoped (non-admin) credential is routed to a server that only
registers the **scoped** tools — it never even sees the admin tools in `tools/list`.

### Scoped tools (every authorized caller; results limited to granted namespaces)

| Tool                    | Args                             | Returns                                                                                                                  |
| ----------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `list_workloads`        | `namespace?`, `status?`          | Workloads + resize recommendations (current vs recommended CPU/memory, confidence, savings, idle/awaiting state).        |
| `get_workload`          | `namespace`, `name`              | One workload's per-container recommendation detail.                                                                      |
| `list_exceptions`       | `namespace?`, `type?`            | Active limit exceptions (OOM/limit overage) and storage exceptions (filling PVCs).                                       |
| `get_namespace_health`  | `namespace?`                     | Per-namespace rollup: workload/idle/awaiting counts, active exception counts, `healthy`/`attention`/`critical` status.   |
| `list_scheduled_scales` | `namespace?`                     | Scheduled scaling policies: target HPA, mode, active/next window, current vs original minReplicas, full window schedule. |
| `get_scheduled_scale`   | `namespace`, `name`              | One scheduled scale policy's full detail + activation history.                                                           |
| `get_workload_usage`    | `namespace`, `name`, `duration?` | Per-container CPU/memory usage time-series + HPA replica history.                                                        |
| `list_idle_workloads` † | `namespace?`                     | Workloads auto-parked in idle mode: idle vs natural CPU, idle-since, replicas, projected CPU savings.                    |

A scoped caller requesting an out-of-scope namespace gets an authorization error with **no
data**.

**† Feature-gated.** `list_idle_workloads` follows the `idleWorkloads` dashboard feature
toggle: when that concern is disabled it is **not registered at all** — it never appears in
`tools/list` and calling it is an unknown-tool error.

### Admin tools (unscoped/admin credentials only)

| Tool                     | Returns                                                                                                                                 |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| `list_nodes`             | All nodes: CPU/memory capacity + requested, pod counts, machine type, zone, spot/on-demand, managed-vs-GKE, kubelet version, readiness. |
| `get_scaling_status`     | Autoscaler status: bootstrapping nodes, scale-up suppression, the decision log, node lifecycle.                                         |
| `get_cluster_summary`    | Cluster totals: node/pod counts, CPU/memory capacity/allocatable/requested/used, utilization %.                                         |
| `get_cluster_usage`      | Cluster-wide CPU/memory usage time-series (provisioned/requested/recommended/used).                                                     |
| `get_maintenance_status` | Controller mode, next resize/consolidation windows, deferred-recommendation count, most recent resize + consolidation runs.             |

## Security model

* **Read-only everywhere.** Only read tools are registered; an API-key credential is additionally rejected on *any* mutating request, so a key can never change cluster state.
* **Tenant isolation.** Scoped tools reuse the exact namespace-scope enforcement the dashboard uses. A tenant credential is structurally routed to the scoped tool set; each admin tool re-checks admin in-handler.
* **Per-cluster.** A key minted on one cluster exists only in that cluster's ConfigMap and resolves only against that cluster's `AccessPolicy`.
* **Revocation** is immediate (the store is read fresh per request).

## Rollback

Redeploy the previous image (there is no separate runtime gate). For an immediate
customer-facing mitigation, **revoke the affected keys**.
