/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:
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).
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.
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’sAccessGrant
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
key value immediately — it is never shown again.
List keys (metadata only)
Revoke a key (others unaffected)
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>
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-aiin theops-ainamespace, 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.
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 intools/list.
Scoped tools (every authorized caller; results limited to granted namespaces)
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)
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).