Fettle

Uptime monitoring your AI agent can set up: HTTP, SSL, ping, TCP and DNS monitors from multiple regions, incidents, cron heartbeats, maintenance windows and status pages.

Hosted MCP Server

npx add-mcp 'https://api.fettle.sh/mcp'

Installs into Claude Code, Codex, Cursor and more

Documentation

fettle-mcp

Website: fettle.sh · MCP docs: fettle.sh/docs/mcp · Dashboard: dashboard.fettle.sh · Support: support@fettle.sh

MCP server for Fettle — uptime monitoring you can drive from Claude Code, Codex, Claude Desktop, Cursor, Windsurf or anything else that speaks the Model Context Protocol.

Ask your assistant to create a monitor, look up why one went down, acknowledge an incident or pause checks before a deploy — it calls the same public API you would.

"Add an HTTP monitor for https://api.acme.dev, check it every 5 minutes from Europe."
"Which of my monitors had incidents this week?"
"Pause the checkout monitor — we're deploying for the next 20 minutes."

In the official MCP registry this server is sh.fettle/mcp — one entry covering both channels, for clients that install from the registry rather than from a hand-edited config file.

Requirements

  • Node.js 20 or newer
  • A Fettle account and an API key (Settings → API Keys at https://fettle.sh)

An API key belongs to one organization and carries either read or write scope. Read-scope keys can list and inspect; the write tools below need a write-scope key.

Install

There is nothing to install — MCP clients run the package with npx on demand. Add one entry to your client's config and restart it.

Claude Code

claude mcp add fettle --env FETTLE_API_KEY=fettle_your_key_here -- npx -y fettle-mcp

Codex

codex mcp add fettle --env FETTLE_API_KEY=fettle_your_key_here -- npx -y fettle-mcp

Or by hand in ~/.codex/config.toml (.codex/config.toml for one trusted project):

[mcp_servers.fettle]
command = "npx"
args = ["-y", "fettle-mcp"]

[mcp_servers.fettle.env]
FETTLE_API_KEY = "fettle_your_key_here"

Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows:

{
  "mcpServers": {
    "fettle": {
      "command": "npx",
      "args": ["-y", "fettle-mcp"],
      "env": { "FETTLE_API_KEY": "fettle_your_key_here" }
    }
  }
}

Cursor

~/.cursor/mcp.json (global) or .cursor/mcp.json (per project):

{
  "mcpServers": {
    "fettle": {
      "command": "npx",
      "args": ["-y", "fettle-mcp"],
      "env": { "FETTLE_API_KEY": "fettle_your_key_here" }
    }
  }
}

Windsurf

~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "fettle": {
      "command": "npx",
      "args": ["-y", "fettle-mcp"],
      "env": { "FETTLE_API_KEY": "fettle_your_key_here" }
    }
  }
}

Configuration

VariableRequiredDefaultWhat it does
FETTLE_API_KEYyes—Your API key. The server refuses to start without it.
FETTLE_API_URLnohttps://api.fettle.shPoint the server at a different API. You will not normally set this.
FETTLE_TIMEOUT_MSno30000How long one tool call may take before it is abandoned.

Tools

49 tools, grouped by what they work on. Read-scope keys can call the read ones; everything marked write needs a write-scope key.

Every tool carries the MCP hints readOnlyHint, destructiveHint, idempotentHint and openWorldHint, so a client can tell reads from changes without parsing names and ask for confirmation only where it matters. Only the five delete_* tools are destructive. preview_maintenance_window and check_target are marked read-only although they need a write-scope key: they change nothing. check_now and check_target reach the target host, so they are open-world.

Every tool whose result is a JSON object declares an outputSchema and returns the result as structuredContent as well as text, so a client can read typed fields instead of parsing the text. The four exceptions return the text only: list_monitors (its compact rows) and the three tools whose result is a list — list_active_maintenance, preview_maintenance_window and list_channels.

Monitors

ToolScopeWhat it does
list_monitorsreadList monitors compactly (status, target, tags, SSL expiry), filtered by type, status, tags.
get_monitorreadRead one monitor's full configuration and current status by id.
check_targetwriteProbe a target once from one region before creating a monitor for it.
create_monitorwriteCreate a monitor that checks a URL, host or port on a schedule.
bulk_create_monitorswriteCreate up to 100 monitors in one call — all of them or none.
update_monitorwriteChange an existing monitor's settings.
bulk_update_monitorswriteSet the same interval, regions, channels or thresholds on up to 100 monitors, all or nothing.
pause_monitorwriteStop checking a monitor without deleting it — use before planned downtime.
bulk_pause_monitorswritePause up to 100 monitors in one call, all or nothing.
resume_monitorwriteResume checks on a paused monitor.
bulk_resume_monitorswriteResume up to 100 paused monitors in one call, all or nothing.
delete_monitorwriteSoft-delete a monitor: it stops being checked and leaves the monitor list.
check_nowwriteRe-arm a monitor to run on the next scheduler tick instead of waiting out its interval.
get_exposed_ports_presetreadDatabase and admin ports that should not be reachable from the internet.

Results and uptime

ToolScopeWhat it does
get_uptime_summaryreadUptime, average response time and incident counts over 24h / 7d / 30d at once.
get_uptime_dailyreadA monitor's uptime for each UTC day, oldest first.
get_response_time_by_regionreadA monitor's average response time over 24h, 7d or 30d, one series per region.
list_check_resultsreadIndividual check results of a monitor, newest first, filtered by region, status or period.

Incidents

ToolScopeWhat it does
list_incidentsreadList incidents, optionally filtered by monitor, status, cause or period.
get_incidentreadRead one incident with its cause and every alert attempt.
acknowledge_incidentwriteAcknowledge an open incident so repeat notifications stop.

Heartbeats

ToolScopeWhat it does
list_heartbeatsreadList heartbeat (cron / dead-man's-switch) monitors and when each was last pinged.
get_heartbeatreadRead one heartbeat, including its ping URL and when it will be reported late.
create_heartbeatwriteCreate a heartbeat monitor and get the URL a scheduled job should ping.
update_heartbeatwriteChange a heartbeat's name, period, grace, channels or tags.
pause_heartbeatwritePause a heartbeat: it is not reported late and its pings are ignored.
resume_heartbeatwriteResume a paused heartbeat.
delete_heartbeatwriteDelete a heartbeat for good; its ping URL stops working.

Maintenance windows

ToolScopeWhat it does
list_maintenance_windowsreadList maintenance windows (one-time or recurring, organization-wide or per monitor).
list_active_maintenancereadThe maintenance windows in effect right now.
get_maintenance_windowreadRead one maintenance window with its next three occurrences.
preview_maintenance_windowwriteShow the next three occurrences a schedule would produce, without saving it.
create_maintenance_windowwriteCreate a window that silences alerts, once or on an iCal RRULE schedule.
update_maintenance_windowwriteChange a maintenance window.
delete_maintenance_windowwriteDelete a maintenance window; alerts resume at once.

Tags

ToolScopeWhat it does
list_tagsreadList every tag in the organization with how many monitors and heartbeats carry it.
bulk_update_monitor_tagswriteAdd and/or remove tags across up to 300 monitors in one call.
rename_tagwriteRename a tag on every monitor and heartbeat that carries it.
delete_tagwriteRemove a tag from every monitor and heartbeat that carries it.

Status pages

ToolScopeWhat it does
list_status_pagesreadList status pages, with each page's slug and whether it is published.
get_status_pagereadRead one status page with its groups and components.
list_status_page_incidentsreadList a status page's incidents, posted by a person or opened by Fettle, with their timelines.
create_status_page_incidentwriteOpen an incident on a status page with a first message.
post_status_page_updatewriteAdd a message to a status page incident and set its status; RESOLVED closes it.
update_status_page_incidentwriteRename a status page incident; subscribers are not notified.
delete_status_page_incidentwriteRemove an incident and its timeline from a status page.

Organization and platform

ToolScopeWhat it does
get_plan_usagereadThe organization's plan, its limits, and how much of each is used.
list_channelsreadList notification channels (id, type, label, verified) to bind to monitors.
list_regionsreadList the check regions and the codes the regions argument takes.

The list is not written by hand. It is generated in the Fettle backend from one registry and served at /public-api/mcp-tools.json; this package vendors that file and builds its tools from it at startup, so the two MCP channels can never offer different tools.

Agent guide

The server sends this guide to the client in initialize (instructions), so an agent connected through either channel already has it. It is reproduced here for humans and for clients that do not surface server instructions.

Fettle is an uptime monitoring service. The tools see only the API key's organization: monitors, heartbeats, maintenance windows, incidents, status page incidents, tags, channels (read), regions. Channels, status pages, members, API keys and billing are managed only in the dashboard (https://dashboard.fettle.sh). Read a tool's description (arguments, limits, errors) before its first call.

FINDING THINGS
- list_monitors gives one short row per monitor; use it to find ids. Filters are JSON arrays. Broken now: status ["DOWN", "DEGRADED"]. Certificates expiring first: type ["SSL"], sort "ssl_expiry".
- tag filters lists; tag_match "all" (default) needs every tag, "any" one of them.
- Lists are paginated: page from 1, size 1..100 (default 25).

PLAN LIMITS
Call get_plan_usage before creating anything: limits (null means none) and usage. A broken limit or an unknown region is a 403 naming it: change the request or tell the user; never retry it unchanged.

CREATING MONITORS
- check_target probes a target once before create_monitor. list_regions names the region codes.
- bulk_create_monitors creates up to 100, all or nothing.
- type and target never change after create.
- channel_ids: ids from list_channels. Empty or omitted on create alerts every verified, active channel. Email is not a channel: every alert also goes to the account email of whoever created the monitor or heartbeat.
- update tools change only the fields you pass. Never send null; omit a field to keep it. A monitor's config is replaced whole: send every config field you want to keep.
- Exposed ports on a server: follow get_exposed_ports_preset.

MANY MONITORS AT ONCE
Find ids with list_monitors, then bulk_pause_monitors, bulk_resume_monitors or bulk_update_monitors (interval, timeout, regions, channels, threshold, cooldown) for up to 100 ids, all or nothing. bulk_update_monitor_tags adds or removes tags on up to 300. There is no bulk delete.

TAGS
Call list_tags first and reuse names. Tags are flat labels such as env:prod, trimmed, lowercased and deduplicated; at most 10 per item. rename_tag and delete_tag act on every monitor and heartbeat.

HEARTBEATS
A job calls ping_url when it succeeds. Job every N seconds, alert after T seconds of silence: period_seconds = N, grace_seconds = T - N. Never late before its first success ping. ping_url + "/start" restarts the clock, ping_url + "/fail" marks it DOWN at once.

MAINTENANCE
Before a deploy or reboot, create_maintenance_window (scope ORG or MONITOR) instead of pausing monitors: checks keep running, but no incident opens and no alert goes out. Not for heartbeats. preview_maintenance_window shows a schedule's next occurrences; list_active_maintenance shows what is silenced now.

OUTAGES
list_monitors status ["DOWN"] -> list_check_results (status code, error, region per check) -> get_incident. acknowledge_incident stops repeat alerts; the recovery alert still goes out. create_status_page_incident and post_status_page_update can email the page's subscribers: confirm the text with the user first.

ERRORS (start of a failed call's text)
- "Authentication failed": the key is missing, revoked or expired, the address is not allowed, or its creator lost access. Do not retry; tell the user.
- "This tool requires/needs an API key with write scope": the key is read-only; tell the user.
- "Invalid argument(s)": names each failing field. "Invalid request:": the rule broken. Fix, retry.
- "Forbidden:": a plan limit, an unknown region, or the key's user lacks the permission; the reason says which.
- "Not found:": no such id in this organization.
- "Rate limited": wait the seconds given, then retry. All API key calls share the organization's per-minute budget.
- "Tool '<name>' failed: the API returned 5xx": server fault, retry once later.
- "Tool '<name>' does not accept argument(s)": drop the ones named.
- "Request failed with status N": read the reason, do not retry unchanged.

The other channel: remote MCP

If your client supports remote MCP servers over HTTP, you do not need this package at all — point it at https://api.fettle.sh/mcp with the same API key as a Bearer token. Same tools, same behaviour, nothing to install or update.

Claude Code adds the remote channel from the command line — a different flag set from the stdio one above, with the key as a header rather than an environment variable:

claude mcp add fettle --transport http https://api.fettle.sh/mcp \
  --header "Authorization: Bearer fettle_your_key_here"

Clients configured through a JSON file take the same two fields:

{
  "mcpServers": {
    "fettle": {
      "url": "https://api.fettle.sh/mcp",
      "headers": { "Authorization": "Bearer fettle_your_key_here" }
    }
  }
}

Codex takes the remote channel in ~/.codex/config.toml and reads the key from an environment variable, so it never sits in the file:

[mcp_servers.fettle]
url = "https://api.fettle.sh/mcp"
bearer_token_env_var = "FETTLE_API_KEY"

Use this package when your client speaks only stdio, or when you would rather the credential stayed in a local config file than in a client's cloud configuration.

There is no OAuth on either channel yet; both authenticate with an API key.

Rate limits and plans

Tool calls spend the same per-organization request budget as the REST API — the plan's limit, not a separate one. A rate-limited call comes back saying how many seconds to wait. check_now also has its own cap of 10 calls per minute per calling address. get_plan_usage shows the organization's request budget as limits.api_rate_limit_per_minute. Plan limits are listed at https://fettle.sh/pricing.

Troubleshooting

FETTLE_API_KEY is not set — the client started the server without the variable. Check the env block of the config above, then fully restart the client: most clients read MCP config only at launch.

Authentication failed — the key is wrong, revoked, or from a deleted organization. Create a fresh one in Settings → API Keys.

This tool requires an API key with write scope — the key is read-only. Create a write-scoped key; there is no way to widen an existing key's scope.

The assistant does not see the tools — restart the client, then confirm Node.js 20+ with node -v. Run npx -y fettle-mcp in a terminal: it should print the missing-key message and exit, which proves the package itself runs.

Development

pnpm install
pnpm run typecheck
pnpm test
pnpm run build
pnpm run contract:check        # vendored contract vs. production

Adding a tool is a change in the backend's registry, not here: regenerate the contract there, then re-vendor it with pnpm run contract:check -- --write and commit the result. No source file in this package describes a tool.

server.json is the entry the official MCP registry serves, and it repeats the version — a test fails if it stops matching package.json, so bump both together.

A release is a version tag: pushing vX.Y.Z runs .github/workflows/publish.yml, which tests, builds and publishes the package to npm with provenance.

License

MIT © Fettle