chore: replace em dashes with hyphens, add no-em-dash rule to CLAUDE.md
Deploy / deploy (push) Successful in 2m47s

This commit is contained in:
2026-09-10 09:18:57 +00:00
parent e8455c99fe
commit 7762629634
22 changed files with 126 additions and 122 deletions
+22 -22
View File
@@ -4,8 +4,8 @@ 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
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.
@@ -24,18 +24,18 @@ An agent authenticates the same way any script does: with an
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
- **`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,
- **`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
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
- `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
@@ -56,7 +56,7 @@ and cannot run a workflow that would touch one.
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)
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."
@@ -64,7 +64,7 @@ servers it can see."
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
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:
@@ -83,15 +83,15 @@ Point an MCP client at it with a configuration block like this:
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
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
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 |
@@ -111,7 +111,7 @@ surface is a presentation layer over existing authority, not a new one.
| `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` |
| `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` |
@@ -132,13 +132,13 @@ 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
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
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.
@@ -184,7 +184,7 @@ This is the section to read before turning the feature on.
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
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.
@@ -193,7 +193,7 @@ This is the section to read before turning the feature on.
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
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
@@ -201,10 +201,10 @@ This is the section to read before turning the feature on.
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`. The REST API reaches
from that token - it still appears in `list_monitors`. The REST API reaches
that outcome by redacting: it replaces the runner with a
`RunnerRestricted` placeholder when the runner falls outside the caller's
visible servers. The MCP tools reach the same outcome a different way
visible servers. The MCP tools reach the same outcome a different way -
`list_monitors` and `get_monitor_status` project a monitor onto a summary
that never carries a runner field for any caller, restricted or not, so
there is nothing there to redact in the first place. Two mechanisms, chosen
@@ -219,16 +219,16 @@ many.
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
- 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
`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.
+1 -1
View File
@@ -67,7 +67,7 @@ Port `465` uses implicit TLS; anything else uses STARTTLS.
### Credentials are never read back
The SMTP `password`, the Telegram `token` and the webhook, Slack and Discord
`url`s come back from `GET /api/channels` as `••••••••` a webhook URL is the
`url`s come back from `GET /api/channels` as `••••••••` - a webhook URL is the
authorisation to post to that channel, so it is treated as a credential like
the rest. Writing that value back unchanged keeps the stored one, which is what
lets you rename a channel without retyping its password. Anything else you send
+2 -2
View File
@@ -89,7 +89,7 @@ metrics is normal rather than a fault.
### OS updates
Agents check for pending package updates hourly and report the count the
Agents check for pending package updates hourly and report the count - the
machine's own package manager on Linux, the Windows Update COM API on Windows.
From the server page you can:
@@ -113,7 +113,7 @@ Opens a browser SSH, RDP or VNC session. See [Browser console](./browser-console
Windows agents register, heartbeat, run workflow steps, report inventory,
check and apply OS updates, report workloads (services and containers), and
serve the browser console. They do not manage `authorized_keys`, and they are
not covered by package inventory or CVE scanning the vulnerability feeds
not covered by package inventory or CVE scanning - the vulnerability feeds
this project uses carry no Windows data.
## Removing a server
+15 -15
View File
@@ -6,12 +6,12 @@ sidebar_label: Status pages
A status page is a public page reporting a chosen set of monitors as up-front
components, with a 90-day history and an uptime percentage per component. It
needs no session and no token to read anyone with the link can open it,
needs no session and no token to read - anyone with the link can open it,
which is the point: it is what you hand a customer instead of an incident
email.
Requires the **Status pages** licence feature. If the licence lapses, or the
tier does not include the feature, the page keeps serving it renders an
tier does not include the feature, the page keeps serving - it renders an
explanation rather than data or a broken page, so a customer who follows an
old link never sees an error.
@@ -28,7 +28,7 @@ https://<your-vantage-address>/status/<page-id>
On **Vantage Cloud** that address is your instance's own subdomain, so the page
is at `https://<your-instance>.vantage.hostxtra.co.uk/status/<page-id>`.
On a **self-hosted** install it is whatever address you reach Vantage on
On a **self-hosted** install it is whatever address you reach Vantage on -
`https://vantage.acme.com/status/<page-id>`, or an IP and port on a LAN
install. A self-hosted install serves exactly one Vantage instance, so no
subdomain is needed to say which one you mean. The **Copy** control next to the
@@ -37,26 +37,26 @@ the one to hand out.
**The page id cannot be changed after creation.** Once you have shared the
link, changing the id would break it, so pick something you would still be
happy with in a year `platform`, `api`, a customer's own name for a
happy with in a year - `platform`, `api`, a customer's own name for a
dedicated page.
## Draft versus published
A new page starts unpublished. Unpublished pages answer *not found* to
anyone who requests them, including you, from a browser without a session
anyone who requests them, including you, from a browser without a session -
so you can build out the components and copy before announcing it. Toggle
**Published** when it is ready. Un-publishing later takes it back to *not
found* rather than deleting anything.
**Delete page**, in the editor header, is the only way to correct a page id you
regret the id is fixed once created. It takes the page, its sections and its
regret - the id is fixed once created. It takes the page, its sections and its
authored incidents with it; monitors and their history are untouched. If you
only want the page off the internet, un-publish it instead.
## Sections and components
A page is organised into **sections** arbitrary groupings such as "API" or
"Region: EU" each holding one or more **components**. A component is a
A page is organised into **sections** - arbitrary groupings such as "API" or
"Region: EU" - each holding one or more **components**. A component is a
monitor plus a **display name** you choose for this page.
The display name is never the monitor's own name unless you type it in. An
@@ -64,7 +64,7 @@ internal monitor name ("prod-db-primary-eu1") is rarely what you want a
customer reading; give it whatever name makes sense to them, and change it
for a different page without touching the monitor.
If a monitor listed on a page is later deleted, its component still appears
If a monitor listed on a page is later deleted, its component still appears -
reading `Unknown` rather than up or down, because nothing is checking it any
more and claiming otherwise would be a false claim of health.
@@ -77,7 +77,7 @@ more and claiming otherwise would be a false claim of health.
- A 90-day history bar per component.
- Any active incidents, upcoming maintenance, and a rolling history of both.
- An optional banner across the top of the page, for anything you want said
regardless of component state. It is one notice with one appearance there
regardless of component state. It is one notice with one appearance - there
are no severity levels to choose between.
A visitor never sees a target URL, host or port, the check's expected status
@@ -90,17 +90,17 @@ reachable is on this page.
Two kinds of entries appear on a page's timeline:
- **Automatic** a monitor going down opens an incident on any page that
- **Automatic** - a monitor going down opens an incident on any page that
lists it, with no action from you. These appear the moment the monitor's
state changes and close the moment it recovers.
- **Authored** an incident or maintenance window you create by hand, with
- **Authored** - an incident or maintenance window you create by hand, with
its own title, impact and a set of affected components you choose. You
post updates to it (Investigating → Identified → Monitoring → Resolved) as
the situation develops, and each update is timestamped and kept on the
page's history.
An authored incident is attached to one or more pages explicitly when you
create it it does not follow a monitor onto every page that monitor happens
create it - it does not follow a monitor onto every page that monitor happens
to be listed on.
### Scheduling maintenance
@@ -111,7 +111,7 @@ is in progress and its affected components are within the scheduled time,
those components are drawn as "under maintenance" instead of up or down.
**Maintenance changes how a day is drawn, never the uptime number itself.**
The 90-day percentage is computed from what actually happened a component
The 90-day percentage is computed from what actually happened - a component
that stayed up throughout a maintenance window still shows as up in its
history, it is only the live status pill that reads "under maintenance" for
the duration.
@@ -120,6 +120,6 @@ the duration.
A visitor's read of a page is cached for up to 30 seconds, so posting an
update or flipping Published does not necessarily change what a visitor sees
instantly though most authoring actions invalidate that cache immediately,
instantly - though most authoring actions invalidate that cache immediately,
so in practice it usually shows within a second or two. If a change genuinely
does not appear, reloading after 30 seconds always will.
+1 -1
View File
@@ -4,7 +4,7 @@ title: Workloads
sidebar_label: Workloads
---
A **workload** is one Docker container or one service a systemd unit on
A **workload** is one Docker container or one service - a systemd unit on
Linux, a Windows service on Windows. Every server reports what it is running,
and you can start, stop and restart those workloads, and read their recent
logs, without opening a console.