← Back to blog

Ask your AI assistant "was I alerted?" — okokumo's MCP server

2026-10-19

engineeringmcp

The question we get most about alerting isn't "is it down?" The dashboard answers that. It's "was I told?" Those are different questions. A check can go down, the state change can be recorded correctly, and the alert can still reach nobody, for example because the only channel was never confirmed. We added a per-check alert log for exactly this after a beta tester spent six days believing an expiry alert had gone out.

Now an assistant can ask that question for you. okokumo runs an official, hosted Model Context Protocol server at https://app.okokumo.com/mcp. It is available on every plan, Free included. This post covers what it can do, what it can't, and why the gap between those two is deliberately small.

The whole design in one sentence

Every MCP tool is a call to a /v1 route you could already make with curl.

This isn't just a loose analogy. A tool call is dispatched as an in-process request through the API's own router, at /v1/..., carrying the caller's credential. The request then goes through the same auth middleware, organization scoping, role checks, plan limits and validation as a REST request from your terminal. If we change a rule in a /v1 handler, the MCP server picks up the change automatically, because there is no second copy of the rule to forget. The MCP endpoint can do nothing your key can't already do over REST.

In practice:

  • A member's key can manage checks but can't write incidents. That's the same rule as over REST, and the assistant gets the same 403.
  • A Free organization at its 5-check limit gets plan limit reached (5 checks) from create_check, just as it would from POST /v1/checks.
  • /mcp spends your organization's /v1 rate window: 120 requests per minute, shared with REST. An assistant stuck in a loop can't exceed your budget. It can only use it up.

The 18 tools

There are 10 read tools:

Tool Calls
get_account GET /me
list_checks GET /checks
get_check GET /checks/{id}
get_check_stats GET /checks/{id}/stats (period: day, week or month)
list_check_transitions GET /checks/{id}/transitions
list_check_alerts GET /checks/{id}/alerts
get_check_channels GET /checks/{id}/channels
list_channels GET /channels (config redacted)
get_status_page GET /status-page
list_incidents GET /status-page/incidents

And 8 write tools: create_check, update_check, pause_check, resume_check, set_check_channels, create_incident, update_incident and post_incident_update.

The list leaves some things out on purpose. There are no delete tools and no API-key tools. REST has DELETE /status-page/incidents/{id}, but the MCP server doesn't expose it. Deleting anything and managing keys stay in the dashboard.

Some differences from raw REST are also deliberate:

  • Partial updates. A /v1 PUT replaces the whole record. update_check, pause_check, resume_check and update_incident read the current record first and overlay only the arguments the model passed. Without this, an assistant that sent only name would reset every other field to its default.
  • Seconds, everywhere. The arguments are interval_seconds, timeout_seconds and grace_seconds. Minutes are the classic model mistake, so the tool schemas and the server instructions both say "SECONDS". Unknown arguments are refused with the list of accepted ones, so a grace_minutes is rejected loudly rather than silently ignored.
  • No secrets in the transcript. list_channels returns id, name, type, validated_at and created_at, and nothing else. Webhook URLs, signing secrets and Telegram chat ids are credentials.
  • Destructive hints. pause_check, update_check, set_check_channels and update_incident are marked destructiveHint, so clients ask you before running them. Pausing a check and narrowing its alert routing both mean alerts that won't be sent. The tool descriptions tell the model to confirm with you first. Incidents are private drafts until someone asks for published: true.

One exchange, on the wire

Say you ask: "Did anyone get paged when nightly-backup went down last night?"

  1. The assistant calls list_checks with no arguments. The server makes an in-process GET /v1/checks and returns your checks with their state. It also adds a ready-made ping_url for heartbeat and cron checks.
  2. It finds nightly-backup and calls list_check_alerts with {"check_id": "<uuid>"}. That becomes GET /v1/checks/<uuid>/alerts, which returns the last 20 deliveries for the check, newest first: outcome (delivered, failed or skipped), channel_type, channel_name, detail and created_at.
  3. Suppose the top row is skipped with the detail none of the organization's 1 channel(s) are confirmed. The assistant can now give you the actual answer: no, nobody was paged, because your only channel was never verified. It can call list_channels to show you that validated_at is unset.

All three steps are reads. Each one cost a single unit of your rate window. Nothing changed.

Writes are just as direct. "Pause nightly-backup, I'm moving it to a new host" becomes pause_check with {"check_id": "<uuid>"}. Your client should ask you to confirm because the tool is marked destructive. The server then sends GET /v1/checks/<uuid> followed by PUT /v1/checks/<uuid> with the full current record plus enabled: false. Because it makes two /v1 calls, it costs 2 units. A paused check isn't monitored and never alerts. If you only want to mute alerts while the check keeps measuring, the MCP server has no tool for that. Maintenance windows are in the dashboard.

Connecting: an API key, or just the URL

With a key. Create one in Dashboard → Account → API keys. Only owners and admins can do this. The key acts with the role of the person who created it, and there is no read-only key yet. Use one key per client so you can revoke each one separately. Revocation takes effect at the client's next message, because the key is checked on every request. For Cursor, put this in ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project):

{
  "mcpServers": {
    "okokumo": {
      "url": "https://app.okokumo.com/mcp",
      "headers": { "Authorization": "Bearer sk_…" }
    }
  }
}

The MCP page has the equivalents for Claude Code and Claude Desktop.

With the browser. Clients that implement MCP authorization only need the URL. In Claude Desktop that's Settings → Connectors → Add custom connector, with https://app.okokumo.com/mcp. The client calls /mcp and gets a 401 whose WWW-Authenticate header points to okokumo's authorization server. It then identifies itself and opens your browser on a consent screen. That screen shows the app's name, where it will send you back to, which organization it's for, and what it gets: the same access as an API key, with your role. There is only one OAuth scope, mcp, and it is worth exactly an API key, no more. As with keys, only owners and admins can approve a connection. Only approve one you started yourself.

Access tokens last an hour. Refresh tokens last 30 days and rotate on every use. If a refresh token is presented twice, the whole connection is ended, because one of those copies is in the wrong hands. Every approved app is listed under Dashboard → Account → Connected apps, with who connected it and when it was last used. Owners and admins can revoke any of them there.

What has been tested, and what hasn't

The browser flow has been tested end to end with the official Go SDK's OAuth client, Claude Code and the MCP Inspector. claude.ai and Claude Desktop connectors, ChatGPT, Cursor and VS Code implement the same MCP authorization spec, but we haven't tried them against this server live yet. If one fails, tell us.

There is no stdio npm package. The hosted endpoint is the only way to connect.

A read-only key or scope, so an assistant can look without being able to touch anything, is the obvious next step. It isn't built yet. If you'd use it, tell us.