docs: document agent access over MCP and token tag restrictions
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
---
|
||||
id: mcp
|
||||
title: Agent access (MCP)
|
||||
sidebar_label: Agent access (MCP)
|
||||
---
|
||||
|
||||
The Model Context Protocol is how an LLM agent — Claude, or any other MCP
|
||||
client — talks to a tool server over a defined wire format instead of a
|
||||
one-off integration. Connecting Vantage to an agent this way lets it answer
|
||||
questions about your fleet and, if you choose to let it, act on it: run a
|
||||
workflow, apply updates, or draft a step for you to review.
|
||||
|
||||
:::info Requires the MCP feature on your licence
|
||||
Agent access is a per-instance feature you enable on a paid plan. Without it,
|
||||
`/api/mcp` answers `feature_unavailable` and the token form refuses to mint a
|
||||
token carrying an `mcp:*` scope. See
|
||||
[Licensing and entitlements](../hq/licensing-and-entitlements.md).
|
||||
:::
|
||||
|
||||
## Minting a token
|
||||
|
||||
An agent authenticates the same way any script does: with an
|
||||
[API token](../reference/api-tokens.md). Two scopes govern what it can reach
|
||||
on the MCP endpoint specifically, on top of whatever resource scopes
|
||||
(`servers:read`, `workflows:write`, and so on) it also needs:
|
||||
|
||||
- **`mcp:read`** — permission to reach the MCP endpoint at all and use its
|
||||
read-only tools.
|
||||
- **`mcp:write`** — permission to use its write tools: running a workflow,
|
||||
applying updates, assigning a key, or creating a step, workflow or monitor.
|
||||
|
||||
Without `mcp:write`, write tools are not merely refused when called — they
|
||||
are left out of the tool listing entirely, so a read-only agent never learns
|
||||
they exist. A tool it cannot see is not one it can talk a user into asking
|
||||
for.
|
||||
|
||||
Each tool additionally needs the ordinary resource scope for what it touches
|
||||
— `run_workflow` needs `workflows:write`, `list_servers` needs
|
||||
`servers:read`, and so on, exactly as the [scopes table](../reference/api-tokens.md#scopes)
|
||||
describes for any other token. `mcp:read` or `mcp:write` decides whether the
|
||||
MCP surface is open to this token at all; the resource scope decides what it
|
||||
can do once there.
|
||||
|
||||
## Restricting a token by tag
|
||||
|
||||
An agent token benefits from the same [tag
|
||||
restriction](../reference/api-tokens.md#tag-restrictions) every API token
|
||||
can carry, and it is the main lever for keeping an agent's blast radius
|
||||
small. A token restricted to `env=staging` sees only staging hosts from
|
||||
`list_servers`, gets a not-found error asking about a production host by ID,
|
||||
and cannot run a workflow that would touch one.
|
||||
|
||||
**Worked example: a staging-only agent token.** Create a token with
|
||||
`mcp:read`, `mcp:write`, `servers:read`, `workflows:read` and
|
||||
`workflows:write`, restrict it to `env=staging`, and give it to your agent.
|
||||
It can list and inspect staging servers and run a workflow already
|
||||
configured to target them. Point it at a workflow whose saved targets
|
||||
include even one production host, though, and `run_workflow` refuses the
|
||||
call outright — see [what an agent cannot do](#what-an-agent-cannot-do)
|
||||
below for why that refusal is all-or-nothing rather than "run on the
|
||||
servers it can see."
|
||||
|
||||
## Connecting a client
|
||||
|
||||
Agent access lives at `/api/mcp` on your instance, over the streamable HTTP
|
||||
transport, and takes a bearer token the same way the REST API does. It has
|
||||
no separate authentication of its own — a browser tab with a signed-in
|
||||
session does not count, only a real API token does.
|
||||
|
||||
Point an MCP client at it with a configuration block like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"vantage": {
|
||||
"type": "http",
|
||||
"url": "https://YOUR-INSTANCE/api/mcp",
|
||||
"headers": { "Authorization": "Bearer vt_your_token_here" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The endpoint is stateless and answers POST requests only: it holds no
|
||||
per-connection session, so each call is self-contained and there is nothing
|
||||
to resume. A GET request — a client probing for the endpoint, or a browser
|
||||
navigating to it directly — gets a `405`, by design, rather than a page. That
|
||||
is the protocol-correct response from a server that does not offer the
|
||||
GET/SSE half of MCP, not a misconfiguration.
|
||||
|
||||
## The tool list
|
||||
|
||||
Every tool below calls the same service code the REST API and the Vantage
|
||||
UI call. Nothing in this list has its own path to the database — the MCP
|
||||
surface is a presentation layer over existing authority, not a new one.
|
||||
|
||||
| Tool | What it does | Scope required |
|
||||
| --- | --- | --- |
|
||||
| `list_servers` | List servers in the fleet, optionally filtered by tags. | `mcp:read`, `servers:read` |
|
||||
| `get_server` | Get one server's OS, online state and tags. | `mcp:read`, `servers:read` |
|
||||
| `list_monitors` | List monitors and their current state. | `mcp:read`, `monitors:read` |
|
||||
| `get_monitor_status` | Get one monitor's up/down/pending state, last check and last error. | `mcp:read`, `monitors:read` |
|
||||
| `list_incidents` | List monitor incidents (outages), most recent first. | `mcp:read`, `monitors:read` |
|
||||
| `get_monitor_samples` | Get one monitor's recent raw check results. | `mcp:read`, `monitors:read` |
|
||||
| `list_workflows` | List workflows with step count, target count and whether each is scheduled. | `mcp:read`, `workflows:read` |
|
||||
| `get_workflow` | Get one workflow's ordered steps, targets and schedule. | `mcp:read`, `workflows:read` |
|
||||
| `get_run` | Get one workflow run's status and per-server counts. | `mcp:read`, `workflows:read` |
|
||||
| `get_run_logs` | Get the ordered log lines for one server within one run. | `mcp:read`, `workflows:read` |
|
||||
| `list_pending_updates` | List outstanding package updates, fleet-wide or for one server. | `mcp:read`, `servers:read` |
|
||||
| `list_vulnerabilities` | List known CVEs affecting the fleet, one row per CVE/package pair. | `mcp:read`, `vulns:read` |
|
||||
| `get_server_packages` | List installed packages on one server, optionally filtered by name. | `mcp:read`, `vulns:read` |
|
||||
| `search_fleet` | Search every server's installed packages by name across the fleet. | `mcp:read`, `vulns:read` |
|
||||
| `list_audit_events` | List recent audit log events. | `mcp:read`, `settings:read` |
|
||||
| `list_secret_names` | List secret group and key names — metadata only, never a value. | `mcp:read`, `secrets:read` |
|
||||
| `run_workflow` | Run a workflow against the servers it is already saved to target. | `mcp:write`, `workflows:write` |
|
||||
| `cancel_run` | Cancel an in-progress workflow run. | `mcp:write`, `workflows:write` |
|
||||
| `apply_updates` | Apply pending OS package updates on real servers. | `mcp:write`, `servers:write` |
|
||||
| `update_agent` | Trigger the Vantage agent on real servers to update itself. | `mcp:write`, `servers:write` |
|
||||
| `assign_key` | Assign an SSH key to real servers. | `mcp:write`, `keys:write` |
|
||||
| `create_step` | Create a reusable workflow step. | `mcp:write`, `workflows:write` |
|
||||
| `create_workflow` | Create a workflow from existing step IDs. | `mcp:write`, `workflows:write` |
|
||||
| `create_monitor` | Create a monitor, saved disabled. | `mcp:write`, `monitors:write` |
|
||||
|
||||
A known limitation worth calling out on `search_fleet`: its `version_below`
|
||||
argument is not implemented. Passing it gets you an error explaining that
|
||||
correct version ordering depends on each host's own package manager
|
||||
(dpkg, rpm, apk each order versions differently), which the tool has no way
|
||||
to resolve, so it refuses rather than guess with a naive string comparison.
|
||||
Omit `version_below` and it returns every matching install instead, for you
|
||||
or the agent to filter.
|
||||
|
||||
## Running a workflow: targets are fixed, not chosen
|
||||
|
||||
`run_workflow` does not take a target list. A workflow runs the servers and
|
||||
tags it was already saved with — an agent cannot redirect it at a different
|
||||
part of the fleet on the fly. Before starting the run, Vantage checks that
|
||||
those saved targets sit entirely inside the calling token's tag
|
||||
restriction. If even one of them falls outside it, the whole call is
|
||||
refused; there is no partial run against just the servers the token happens
|
||||
to be able to see. A token scoped to `env=staging` cannot use a
|
||||
production-and-staging workflow to touch only the staging half — it has to
|
||||
be pointed at a workflow whose saved targets are staging-only from the
|
||||
start.
|
||||
|
||||
## What an agent can create
|
||||
|
||||
Three tools let an agent propose new work rather than only look at existing
|
||||
work: `create_step`, `create_workflow` and `create_monitor`. Nothing they
|
||||
create is armed:
|
||||
|
||||
- A created **workflow** cannot be given a schedule. `create_workflow`
|
||||
refuses outright if a `schedule` argument is present at all, so scheduling
|
||||
stays a separate, later decision a human makes in the UI.
|
||||
- A created **monitor** is always saved disabled, regardless of any
|
||||
`enabled` argument. It checks nothing and alerts nobody until a human
|
||||
turns it on.
|
||||
- A created **step** may not reference a secret. `create_step` refuses a
|
||||
`secret_refs` argument, because a script an agent wrote is not something
|
||||
you want run with credentials attached before a person has read it.
|
||||
|
||||
Steps an agent creates are marked with a `source` of `mcp`, which the
|
||||
Vantage UI uses to badge them, so a human reviewing the step library can
|
||||
tell at a glance which scripts came from an agent rather than a person.
|
||||
|
||||
There is no tool to update or delete an existing step, workflow or monitor.
|
||||
An agent can add to your instance; it cannot change or remove anything that
|
||||
was already there.
|
||||
|
||||
## What an agent cannot do
|
||||
|
||||
This is the section to read before turning the feature on.
|
||||
|
||||
- **It cannot see a secret's plaintext.** `list_secret_names` returns group
|
||||
and key names only; no tool anywhere in the MCP surface returns a
|
||||
secret's value to a model.
|
||||
- **It cannot open a console or a shell.** There is no MCP tool for the
|
||||
browser console, SSH, RDP or VNC. An agent can trigger a workflow step
|
||||
that runs a script, but it cannot open an interactive session on a
|
||||
machine.
|
||||
- **It cannot exceed its tag restriction.** Every tool that resolves
|
||||
servers — by ID, by tag, or through a saved workflow — resolves them
|
||||
through the token's own scope. A server outside that scope is invisible
|
||||
to `list_servers` and `get_server`, not-found to anything asking about it
|
||||
by ID, and excluded from what a write tool will act on.
|
||||
- **It cannot act at all without `mcp:write`.** Every write tool needs it,
|
||||
on top of its own resource scope, and a token that lacks it never even
|
||||
sees those tools in the listing.
|
||||
- **It cannot touch more than 25 servers in one call without saying so
|
||||
explicitly.** Any write tool that would affect more than 25 servers is
|
||||
refused with a message asking for `confirm:true` before it will proceed —
|
||||
cheap insurance against a mis-parsed tag selector reaching the whole
|
||||
fleet on one badly phrased instruction.
|
||||
- **It cannot edit or delete anything that already exists.** The creation
|
||||
tools only add; there is no MCP tool that updates or removes a step,
|
||||
workflow or monitor a person defined.
|
||||
|
||||
A monitor whose runner server sits outside a token's scope is not hidden
|
||||
from that token — it still appears in `list_monitors` — but the runner
|
||||
field is left out, the same redaction the REST API already applies. A
|
||||
workflow's target list is filtered the same way: servers outside the
|
||||
token's scope are dropped from what `get_workflow` reports, and a
|
||||
`targets_restricted` flag says that some were held back, without saying how
|
||||
many.
|
||||
|
||||
## Every call is audited
|
||||
|
||||
Every tool call reaches the audit log, whether it read something or changed
|
||||
something:
|
||||
|
||||
- A successful call — read or write — is recorded as `mcp.tool_call`.
|
||||
- A call refused by policy — missing scope, an out-of-scope target, or a
|
||||
fan-out over 25 servers without confirmation — is recorded as
|
||||
`mcp.tool_denied`, naming the specific gate that refused it, so a human
|
||||
reading the log can tell "the agent was stopped" from "the agent tried
|
||||
and the underlying action failed."
|
||||
- A write tool that reached the underlying service and that service
|
||||
rejected is recorded as `mcp.tool_failed`.
|
||||
- Creating a step, workflow or monitor is recorded separately as
|
||||
`mcp.created`, naming what was made and its ID — a question like "what
|
||||
has this agent added to my instance" should not require digging through
|
||||
hundreds of read rows to answer.
|
||||
|
||||
See the [audit log](./audit-log.md) for how to read these alongside
|
||||
everything else on the instance.
|
||||
Reference in New Issue
Block a user