MCP for agents
Point an MCP client at Perstat and it can answer “what is broken and who owns it” from your
live data, then act within its authorization. The full catalogue has fifteen tools: seven read
state, two act on an incident, and six manage projects and monitors. The connected server’s
tools/list is authoritative for the credential in front of you. No defined tool deletes a
record.
Connecting
Section titled “Connecting”| Endpoint | https://api.perstat.io/mcp |
| Transport | Streamable HTTP, stateless |
| Protocol versions | 2025-06-18 (preferred), 2025-03-26, 2024-11-05 |
One JSON-RPC message in by POST, one JSON response out. GET /mcp answers 405: this server
opens no server-initiated event stream. JSON-RPC batches were dropped in 2025-06-18 and are
rejected with a clear message.
In Claude Code:
claude mcp add --transport http perstat https://api.perstat.io/mcp \ --header "Authorization: Bearer pst_…"This form works in clients that accept a custom HTTP authorization header. Note that the key ends up in your shell history this way.
Or install the plugin
Section titled “Or install the plugin”For Claude Code there is a plugin. It wires the same server and adds four skills that turn the tools into workflows: handling an incident, managing monitors, and reading the current state.
claude plugin marketplace add datargo/perstat-pluginclaude plugin install perstat@datargoOn the first Perstat tool call Claude opens the OAuth sign-in in your browser. Choose the
organization the connection should use; there is no key to copy or shell profile to edit.
The connection is bound to that organization and your current role. Check it with
claude mcp list; Perstat should report Connected.
Use the API-key one-liner above for CI, cron, or a deliberately project-narrowed credential. Use the plugin for an interactive, browser-authenticated workflow.
Authentication
Section titled “Authentication”Perstat accepts a scoped organization API key as a bearer credential:
Authorization: Bearer pst_…A client that supports OAuth can instead use Perstat’s published Authorization Code flow with
PKCE. Discovery is available at
https://api.perstat.io/.well-known/oauth-protected-resource/mcp and
https://api.perstat.io/.well-known/oauth-authorization-server; refresh and revocation
endpoints are published there as well. A browser session cookie alone does not authenticate
MCP: the OAuth flow ends in a bearer token.
OAuth clients are accepted from Claude, Claude Code and ChatGPT, identified by their client
documents under https://claude.ai/oauth/ and https://chatgpt.com/oauth/. The consent screen names the host the client comes from, not only
the name it gives itself, and a client only receives the permissions its tools need:
monitors and incidents, each read or write.
An organization API key belongs to exactly one organization and carries scopes. Together that makes its rights predictable: it can never do more than the key says.
Owners and admins create keys in the app under Integrations, API keys.
The tools
Section titled “The tools”tools/list shows only what the current credential is allowed to do. tools/call checks the
same scope again, because a list is not a safeguard.
The full catalogue has fifteen tools. Browser OAuth currently grants monitor and incident
scopes, so plugin users do not receive list_status_pages; an API key with
status-pages:read does. The connected server’s tools/list is the practical truth for the
credential in front of you.
| Tool | Scope | What it does |
|---|---|---|
get_organization_summary |
monitors:read |
The whole picture in one call: services by state, open incidents |
list_projects |
monitors:read |
Projects with their monitor counts. The entry point for create_monitor |
list_monitors |
monitors:read |
Services with their last confirmed state, filterable. Pass archived: true to see the archive |
get_monitor |
monitors:read |
One service with configuration, regions, and recent incidents. Custom request header values come back as [redacted] |
list_incidents |
incidents:read |
Open and resolved incidents, newest first |
get_incident |
incidents:read |
One incident in detail |
acknowledge_incident |
incidents:write |
Take the incident, which stops the alert and the escalation |
resolve_incident |
incidents:write |
Close an open incident by hand |
list_status_pages |
status-pages:read |
The organization’s status pages |
create_project |
monitors:write |
Create the folder monitors live in. Organization-wide credentials only |
create_monitor |
monitors:write |
Start monitoring a new service. Organization-wide keys only |
update_monitor |
monitors:write |
Change the name, settings, check interval, or regions |
set_monitor_enabled |
monitors:write |
Pause a monitor or put it back into service |
archive_monitor |
monitors:write |
Take a service out of service and free its slot in your plan |
restore_monitor |
monitors:write |
Bring an archived service back. It returns paused and needs a free plan slot |
write implies read on the same resource, never across resources.
Everything the agent sees is English: tool names, descriptions, schema text, and error reasons. The activity log inside the app is unaffected, because people read that one.
create_project and create_monitor are the two tools that are not idempotent, and they
declare that to the client. Repeating either call can create a second resource or fail on the
name. Look first with list_projects or list_monitors.
Tools that change or end existing state (acknowledge_incident, resolve_incident,
update_monitor, set_monitor_enabled, archive_monitor) are marked as destructive, so a
client may ask before it calls them. Creating and restoring only add.
Custom request headers of a monitor are credentials and are never read back through a key or
an agent: get_monitor shows their names with the value [redacted]. Send a configuration
back through update_monitor with the placeholder in place and the stored value is kept, as
long as the URL and the method stay exactly the same. After any change to either, the placeholder
is refused, so a credential never follows a request its author did not configure. The web app still
shows the values to signed-in members.
Archived through MCP, not deleted through MCP
Section titled “Archived through MCP, not deleted through MCP”An agent can archive a service. It can never delete one, and the reason is not caution.
Deleting a monitor cascades through eleven tables and takes the incident history, the daily SLA rollups, and the exclusion windows with it. For a product whose whole point is the record, that is not something an agent call may reach. So the tool that would do it does not exist.
archive_monitor stops the service, removes it from active lists, and frees its slot in your
plan while keeping the resource restorable. Archiving itself does not delete its history, but
normal object-specific retention rules still apply; it is not permanent storage.
restore_monitor brings it back, paused, and claims a plan slot again: on a full plan the restore
is refused with the same message as create_monitor until another monitor is archived or the plan
grows. The name is released on archive, so a new monitor can take it.
This matters for your quota, and the two are not the same: pausing does not free a slot, the monitor is still there and still meant. Archiving does. If you truly want something gone for good, delete it in the app, where a person is looking at the consequences.
Getting back must not depend on memory. list_monitors with archived: true shows the
archive, and get_monitor finds archived services too, so you can look before restoring.
Without that, an archived monitor would be unreachable over MCP the moment someone lost the ID.
What does not work on an archived monitor is changing it or switching it on: update_monitor
and set_monitor_enabled reject it with “is archived, call restore_monitor first” rather than
with “not found”, because it is not missing.
Also out of write reach over MCP: status-page changes, connectors, API keys, members, and your
plan. list_status_pages is a read-only exception for API keys with status-pages:read; browser
OAuth does not currently receive that scope. Operational writes stay inside monitoring and
incidents.
Every action has a name
Section titled “Every action has a name”An action over MCP is attributed to the credential’s human identity, the API-key creator or the OAuth subject, so taking ownership during an incident carries a name and not just a token. The incident timeline additionally records that it came in over MCP. Monitors an agent creates or changes appear in your organization’s activity log the same way, under that identity’s name.
Because that identity is borrowed for attribution, it also governs permission. On every single write the server re-checks that the identity is still a member of the organization and still carries the role the action needs. The threshold matches the app: handling incidents needs whoever is allowed to respond, down to a responder, while managing monitors needs whoever is allowed to manage projects, so an owner, admin, or developer. Downgrade or remove that identity and its credential stops writing from the next call onward. Reading is unaffected and depends on the credential’s scopes. If that person’s account has been deleted, the server refuses the write as well, and says why.
Narrowed keys
Section titled “Narrowed keys”A key restricted to specific projects or monitors sees only its own resources in
list_monitors and list_incidents. Everything else simply does not exist for it, with no
way to probe whether it might. The same holds when writing: update_monitor and
set_monitor_enabled report that there is no monitor with that ID rather than that it is
forbidden.
Organization-wide tools, get_organization_summary, list_status_pages, create_project, and
create_monitor, are not offered to a narrowed key at all. The two create tools would otherwise
let it write into projects it knows nothing about.
Errors
Section titled “Errors”The list tools report total_matching and truncated alongside the rows, so a shortened answer
is recognizable as one instead of looking like a complete result.
A failure inside a tool is not a protocol error. It comes back as a result marked as an error with a plain-language reason, so the model can see what went wrong and try something else. Protocol errors stay reserved for real malformed requests.