--- category: Reference title: MCP Server description: Connect openstatus to AI assistants via the Model Context Protocol --- The openstatus MCP server lets AI assistants (Claude.ai, Claude Desktop, Claude Code, ChatGPT, Cursor, and other [Model Context Protocol](https://modelcontextprotocol.io) clients) read and manage your status pages, status reports, and maintenance windows directly from a conversation. ## Endpoint ``` https://api.openstatus.dev/mcp ``` The transport is **Streamable HTTP** (stateless). The server speaks JSON-RPC 2.0 over a single endpoint that accepts `GET`, `POST`, and `DELETE`. ## Authentication The MCP server accepts two credentials. OAuth is the default for interactive clients; the API key header stays for CI and headless agents. For a guided walkthrough of registering, signing in, and verifying the connection, see [Connect openstatus to your coding agent](/docs/guides/how-to-connect-openstatus-to-your-agent). ### OAuth (recommended) Point your client at `https://api.openstatus.dev/mcp` with no header. The server answers `401` with a `WWW-Authenticate` header pointing at its protected-resource metadata, and any client that implements the [MCP authorization spec](https://modelcontextprotocol.io/specification/basic/authorization) (Claude.ai, Claude Desktop, Claude Code, ChatGPT, Cursor, VS Code) takes it from there: 1. The client registers itself once (dynamic client registration) and opens the authorization URL in your browser. 2. You sign in to openstatus if needed, pick the **workspace** to connect and the **access** level (read-only or read & write), and click **Approve**. 3. The client exchanges the code for an access token (PKCE `S256`) and starts calling tools with `Authorization: Bearer os_oat_…`. There is nothing to copy. The token is scoped to the workspace you picked at consent, so a user in several workspaces connects each one as a separate app. | | | |---|---| | Access token | `os_oat_` prefix, valid for 1 hour, refreshed automatically by the client | | Refresh token | 90 days sliding, rotated on every refresh | | Grant | one live grant per client and user; connecting again replaces it | | Revoke | **Settings > Integrations > Connected apps**; the next call fails with `401` | A connection lives in **Settings > Integrations > Connected apps** with the client name, granted access, who connected it, and when it was last used. Members revoke their own connections, owners and admins revoke any. Removing a member from the workspace revokes their connections too. ### API key (fallback) Send an openstatus API key in the `x-openstatus-key` header. This is the same key the REST API, ConnectRPC, CLI, and Terraform provider use, and the right choice when no browser is available: CI pipelines, cron jobs, or an agent running on a server. ```http x-openstatus-key: os_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` Get a key from the dashboard's **Settings > General**, in the **API Keys** card. When both credentials are present, the header wins. ### Scopes Both credentials carry a scope that controls what tools the MCP server exposes: - **Read-only** — the server only registers read tools (`list_*` and `get_*`). Mutation tools (`create_*`, `update_*`, `add_*`, `resolve_*`) are not advertised in `tools/list` and cannot be called. - **Read & write** — every tool is available. With OAuth you choose the scope on the consent screen; a client may request read & write and you can still downgrade it to read-only. On an API key the scope is set at creation time and is **immutable**: to change it, revoke the key and issue a new one. For AI agents that should only observe state — health summaries, paging on-call, drafting communication — grant **Read-only**. The server gates writes at two layers (the `tools/list` filter is the visible UX; the service-layer scope check is the boundary), so a misconfigured client can't accidentally mutate. ## Tools The server exposes 23 tools (21 on plans without the `audit-log` feature), grouped by resource: 19 scoped to the workspace tied to your credential, plus 4 public content tools that read openstatus.dev pages on any credential and any scope (see [Content](#content)). Mutations write to the audit log with `actor_type = "mcp"` (see [Audit log](#audit-log)). ### Pages | Tool | Type | Purpose | |------------------------|------|---------| | `list_status_pages` | read | List public status pages with their slug and id. Used to discover the `pageId` required by mutation tools. | | `list_page_components` | read | List components on a status page with their id, name, type (`monitor` / `static`), and linked monitor. Used to discover the `pageComponentIds` accepted by `create_status_report`, `update_status_report`, and `create_maintenance`. | ### Status reports | Tool | Type | Purpose | |----------------------------|----------|---------| | `list_status_reports` | read | List status reports newest-first. `filter: "active" \| "all"` (defaults to active = excludes resolved). Paginated via `page` (1-indexed) and `perPage`; response carries a `pagination` object with `page`, `perPage`, `totalSize`, and `totalPages`. | | `create_status_report` | mutation | Create a new status report on a status page with an initial public update. | | `add_status_report_update` | mutation | Append a public update to an existing status report and bump its status. | | `update_status_report` | mutation | Edit a report's title, status, or affected components without posting a public update. | | `resolve_status_report` | mutation | Mark a report resolved and post a final public update with the supplied message. | ### Maintenance | Tool | Type | Purpose | |----------------------|----------|---------| | `list_maintenances` | read | List maintenance windows newest-first. Paginated via `page` (1-indexed) and `perPage`; response carries a `pagination` object with `page`, `perPage`, `totalSize`, and `totalPages`. | | `create_maintenance` | mutation | Schedule a maintenance window (`from` / `to` are ISO 8601 strings). | ### Monitors | Tool | Type | Purpose | |-----------------------|------|---------| | `list_monitors` | read | List monitors newest-first, including `activeIncidentCount`. Used to discover the numeric `monitorId` the other monitor tools require. | | `get_monitor` | read | Full configuration for one monitor: URL, regions, periodicity, retry/timeout, notification channels, tags. Does not return latency or status. | | `get_monitor_status` | read | Per-region current health (active / degraded / error). No time window. | | `get_monitor_summary` | read | Aggregate success/degraded/error counts, p50–p99 latency, and `lastPingAt` over `1d` (default), `7d`, or `14d`. | | `list_response_logs` | read | Recent per-region HTTP check results — request status, status code, latency — over `1d` (default), `7d`, or `14d`. HTTP monitors only; `limit` ≤ 100. | | `get_response_log` | read | Full detail of one check: URL, timing breakdown (dns/connect/tls/ttfb/transfer), redacted response headers, error message, assertion results. | ### Notifications | Tool | Type | Purpose | |----------------------|------|---------| | `list_notifications` | read | List notification channels and the monitors each one is wired to. Channel credentials are never exposed. | ### Private locations | Tool | Type | Purpose | |--------------------------|------|---------| | `list_private_locations` | read | List the workspace's private locations with their name, status (`active` / `error`), metadata, and `lastSeenAt`. Agent tokens are never exposed. | ### Audit Registered only when the workspace plan includes the `audit-log` feature — otherwise these tools are absent from `tools/list`. | Tool | Type | Purpose | |-------------------|------|---------| | `list_audit_logs` | read | List audit-log entries (mutating actions) newest-first, bounded to the last 14 days. Optional `entityType` + `entityId` filter. | | `get_audit_log` | read | Full before/after snapshots and `changedFields` for a single audit-log entry. | ### Content Public openstatus.dev content — no workspace data, available on any credential and any scope. | Tool | Type | Purpose | |--------------------|------|---------| | `search_docs` | read | Search the documentation, guides, or changelog (`type`, default `docs`). Returns title, snippet, url, and a `path` for `get_doc_page`. | | `get_doc_page` | read | Full markdown of one docs, guide, or changelog page by `path`. | | `search_content` | read | Search every public page — product/pricing, blog, comparisons, use cases, customer stories, tooling, plus docs/guides/changelog. `type` defaults to `all`. Returns title, type, snippet, url, and a `path` for `get_content_page`. | | `get_content_page` | read | Full markdown of any public page by `path` (e.g. `pricing`, `blog/…`, `compare/…`). | The MCP client gates every tool call behind your approval — the server does not gate again. Each tool also carries [MCP annotations](https://modelcontextprotocol.io/specification/server/tools#tool-annotations) (`readOnlyHint`, `destructiveHint`, `idempotentHint`) so well-behaved clients can decide whether to confirm, cache, or surface differently. ### Resources Alongside its tools, the server exposes a small set of read-only MCP **resources** — public documents carrying no workspace data, so they are available on any credential and any scope. List them with `resources/list` and fetch one with `resources/read`: | Resource | URI | Contents | |---|---|---| | `openapi-specification` | `https://api.openstatus.dev/openapi.json` | The full OpenAPI document, served from the binary rather than fetched. | | `mcp-server-reference` | `https://www.openstatus.dev/docs/reference/mcp-server` | This page. | | `site-index` | `https://www.openstatus.dev/llms.txt` | `llms.txt`: product context, pricing, and a linked index of every page on openstatus.dev. | Remotely fetched resources are capped at a 5-second timeout and never resolve empty: on a failure the read returns a short pointer back to the same URI rather than a blank body. ### Notifying subscribers Every publishing tool — `create_status_report`, `add_status_report_update`, `resolve_status_report`, `create_maintenance` — has a **required** `notify: boolean` field, with no default. The tool's input schema rejects calls that omit it, which forces the LLM to make an explicit choice (and therefore ask the user) before firing. `update_status_report` edits metadata only and carries no `notify` field at all. This required-field behaviour is specific to MCP. The dashboard AI assistant and the Slack agent wrap these same tools in an approval step that strips `notify` from the model-facing schema and injects it from a human toggle defaulting to `false`. MCP exposes the raw schema, so the caller must supply `notify` explicitly. Notifications dispatch as part of the same call. There is no separate notify tool: if you create a status report or append an update with `notify: false`, that update will **never** reach subscribers — you cannot retroactively notify the same update later. This matches the dashboard and Slack agent semantics. The mutation and the notify dispatch are sequential, not transactional. The mutation persists first; if the notify step then throws (transient provider issue, partial outage of an integration), the response carries `notified: false` and the row stays. `notified: true` means the dispatch call returned without throwing — **not** that every subscriber received a message. If the workspace plan doesn't include subscriber notifications, the service is a silent no-op and the response will still report `notified: true`. Treat the field as "the dispatch ran cleanly," not as a delivery receipt. | Tool | What `notify: true` sends | |----------------------------|---------------------------| | `create_status_report` | Notification for the initial update | | `add_status_report_update` | Notification for the new update | | `resolve_status_report` | Resolution notification | | `create_maintenance` | Maintenance scheduled notification | | `update_status_report` | n/a — metadata-only edit, never has a notify path | The required `notify` field, combined with the mandatory draft-and-confirm workflow in each tool's description, encodes the contract that LLMs must: 1. Draft the title/status/message/components. 2. Show the draft to the user. 3. Ask **explicitly** whether to notify subscribers. 4. Only call the tool once both content and notify are confirmed. The tool's response includes a `notified: boolean` field so the assistant can confirm what actually went out. If the workspace plan doesn't include subscriber notifications, `notify: true` is a no-op inside the service (no error, just nothing dispatched). ### Lookup-before-mutate `create_status_report` and `create_maintenance` require a `pageId`, and they (plus `update_status_report`) accept an optional `pageComponentIds`. The tool descriptions instruct the model to call `list_status_pages` for the `pageId` and `list_page_components` for component ids first; **never** type a numeric id you don't know — make the assistant resolve it. Ids that don't belong to the workspace (or, for components, to the supplied page) come back as `NOT_FOUND`. ## Configure Claude Code ```bash claude mcp add --transport http --scope user openstatus https://api.openstatus.dev/mcp ``` Run `/mcp` inside Claude Code, select **openstatus**, and choose **Authenticate**. Your browser opens the consent screen; approve, and the tools appear. Step-by-step in [Connect openstatus to Claude Code](/guides/connect-openstatus-to-claude-code). For a headless setup, pass the API key instead: `--header "x-openstatus-key: os_…"`. ## Configure Claude.ai and Claude Desktop Claude.ai and Claude Desktop share one connector list. Open **Settings > Connectors > Add custom connector**, enter `https://api.openstatus.dev/mcp` as the URL, and click **Connect**. Approve on the consent screen and enable the connector in the chat's tool picker. If you need an API key instead (Claude Desktop only), bridge the server through [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) in `claude_desktop_config.json`: ```json { "mcpServers": { "openstatus": { "command": "npx", "args": [ "mcp-remote", "https://api.openstatus.dev/mcp", "--header", "x-openstatus-key: ${API_KEY}" ], "env": { "API_KEY": "os_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } } } } ``` The key is held in `env` and referenced via `${API_KEY}` because `mcp-remote` parses header args before shell expansion. Fully quit and relaunch Claude Desktop afterwards. ## Configure Cursor Add the server to `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global): ```json { "mcpServers": { "openstatus": { "url": "https://api.openstatus.dev/mcp" } } } ``` Cursor shows the server as **Needs login**; click it to run the OAuth flow. To use an API key instead, add `"headers": { "x-openstatus-key": "os_…" }` to the entry. ## Configure opencode Add the server to `opencode.json` (project) or `~/.config/opencode/opencode.json` (global), then run `opencode mcp auth openstatus` to complete the OAuth flow: ```json { "$schema": "https://opencode.ai/config.json", "mcp": { "openstatus": { "type": "remote", "url": "https://api.openstatus.dev/mcp", "enabled": true } } } ``` To use an API key instead, add `"headers": { "x-openstatus-key": "{env:OPENSTATUS_API_KEY}" }` and `"oauth": false` to the entry. Step-by-step in [Connect openstatus to opencode](/guides/connect-openstatus-to-opencode). ## Configure Codex ```bash codex mcp add openstatus --url https://api.openstatus.dev/mcp codex mcp login openstatus ``` The first command writes the server to `~/.codex/config.toml`, which the Codex CLI, the Codex IDE extension, and the ChatGPT desktop app all share; the second opens the consent screen. For a headless setup, pass the API key instead: `--header "x-openstatus-key=os_…"`, or `env_http_headers = { "x-openstatus-key" = "OPENSTATUS_API_KEY" }` in a committed `.codex/config.toml`. Step-by-step in [Connect openstatus to Codex](/guides/connect-openstatus-to-codex). ## Configure ChatGPT In ChatGPT on the web, switch on **Developer mode** under **Settings > Security and login**, then open **Settings > Connectors > Create**, enter `https://api.openstatus.dev/mcp` as the MCP server URL with **OAuth** authentication, and complete the consent screen. Enable the connector per chat from the **+** menu under **Developer mode**. Step-by-step in [Connect openstatus to ChatGPT](/guides/connect-openstatus-to-chatgpt). ## OAuth endpoints For client authors. openstatus is an OAuth 2.1 authorization server for the `/mcp` resource, with public clients only. | Endpoint | Purpose | |---|---| | `GET /.well-known/oauth-authorization-server` | Authorization server metadata (RFC 8414) | | `GET /.well-known/oauth-protected-resource/mcp` | Protected resource metadata for `/mcp` (RFC 9728) | | `POST /oauth/register` | Dynamic client registration (RFC 7591) | | `GET /oauth/authorize` | Authorization request; redirects to the consent screen | | `POST /oauth/token` | `authorization_code` and `refresh_token` grants; form or JSON body | | `POST /oauth/revoke` | Token revocation (RFC 7009); accepts either token of a grant | - `response_type=code` with PKCE `S256` is required. Scopes are `read` and `write`; an authorization request without a scope asks for `write`. - Clients are public (`token_endpoint_auth_method: none`). There are no client secrets. - Dynamic registration only accepts redirect URIs on loopback hosts, `https` URLs on `openstatus.dev`, `claude.ai`, `chatgpt.com`, `cursor.com` and their subdomains, or the `cursor://`, `vscode://`, and `vscode-insiders://` schemes. Loopback redirects match on everything but the port (RFC 8252). - Any other client can skip registration and use a **Client ID Metadata Document**: pass a public `https` URL with a path as `client_id`. The document is fetched on every authorization request, must echo its own URL as `client_id`, and its `redirect_uris` are used as-is. Domain ownership replaces the allowlist. - Errors follow RFC 6749 (`{ "error", "error_description" }`), not the openstatus API envelope. ### Self-hosting The issuer is fixed by configuration, not derived from the request host, so metadata stays stable behind proxies. Set both on the API server: ```env OAUTH_ISSUER=https://api.example.com # origin of /mcp and /oauth/* DASHBOARD_URL=https://app.example.com # serves /oauth/consent ``` Outside production `OAUTH_ISSUER` defaults to `http://localhost:3000` and `DASHBOARD_URL` to `http://localhost:3001` — the ports of `pnpm dev`. The Docker stack binds different ports; see [How to Self-Host openstatus](/docs/guides/self-hosting-openstatus) for the values that go with it. ## Errors Errors map by severity: - **Recoverable** (`NOT_FOUND`, `VALIDATION`, `CONFLICT`, `LIMIT_EXCEEDED`, `PRECONDITION_FAILED`) come back as a tool result with `isError: true`. The model can read the message and retry with corrected input. - **Transport-level** (`UNAUTHORIZED`, `FORBIDDEN`, `INTERNAL`, malformed input) come back as a JSON-RPC error. Error messages are not redacted — the consumer is an LLM that benefits from the detail. ## Limits - `list_status_reports` and `list_maintenances` use offset pagination: `page` (1-indexed, default 1) and `perPage` (default 50, max 200). The response's `pagination.totalPages` tells the LLM whether more pages exist. `list_status_pages` is unpaginated (workspaces typically have a handful). - `list_monitors` and `list_audit_logs` page via `perPage` (max 50); `list_notifications` via `perPage` (max 200); `list_private_locations` via `perPage` (max 100); `list_response_logs` via `limit` (max 100) and `offset`. - Every tool invocation is one server call and counts against the [rate limits](#rate-limits) below. ## Audit log Every mutation invoked through MCP appears in the workspace audit log with: - `actor_type = "mcp"` — slice by surface: `WHERE actor_type = 'mcp'`. - `actor_id` = the credential's stable identifier (not the workspace id). For an API key this is the key id; for an OAuth connection it is `oat_`. Trace a specific credential with `WHERE actor_id = ''`. - `actor_user_id` = the openstatus user behind the credential: who created the API key (custom keys only), or who approved the OAuth consent. Attribute mutations back to a person with a join to the `user` table. Connecting and revoking an app are audited too, as `oauth_grant.create` and `oauth_grant.delete`. ## Rate limits MCP requests share the per-token limits of the HTTP API. A rejected request gets a plain `429` with a `Retry-After` header rather than a JSON-RPC error; see [API rate limits](/docs/reference/api-rate-limits).