MCP Server
Use Uptimeify's free check tools from any AI agent over the Model Context Protocol (MCP).
Uptimeify exposes a stateless Model Context Protocol server so AI agents can run our free checks directly.
For an overview of what the server can do and which clients it works with, see the MCP server page on uptimeify.io. This page is the technical reference.
Endpoint
POST https://uptimeify.io/mcp: Streamable HTTP transport, stateless. The anonymous check tools need no authentication; the authenticated tools require a Bearer API token (see below).
Discover the server programmatically via the MCP Server Card:
GET https://uptimeify.io/.well-known/mcp/server-card.json
Connect an MCP client
Point any MCP client that speaks the Streamable HTTP transport at the endpoint above.
- Anonymous tools work with just the URL: no token needed.
- Authenticated tools require your Uptimeify API token as an
Authorization: Bearer <token>header. Create one under Settings → API tokens: leave the customer field empty for an organization-wide token (sees all your monitors), or pick a customer to scope the token to that customer only. The token is shown once on creation: copy it right away.
Clients that support remote HTTP MCP servers with custom headers can point at the URL directly:
{
"mcpServers": {
"uptimeify": {
"url": "https://uptimeify.io/mcp",
"headers": { "Authorization": "Bearer wsm_your_token_here" }
}
}
}For clients that only speak stdio, bridge to the HTTP endpoint with mcp-remote:
{
"mcpServers": {
"uptimeify": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://uptimeify.io/mcp", "--header", "Authorization: Bearer wsm_your_token_here"]
}
}
}Omit the Authorization header entirely if you only need the anonymous check tools.
Rate limits
The anonymous tools run real network probes on your behalf, so they are capped per client IP. You do not need an account to hit these limits, and you do not need one to stay under them:
| Scope | Limit |
|---|---|
POST /mcp overall | 120 requests per minute per IP |
| Each anonymous check tool | 15-30 calls per minute per IP, depending on how expensive the probe is (whois and domain_expiry are the strictest at 15) |
| Each authenticated read tool | 60 calls per minute per IP |
| Each authenticated write tool | 10 calls per minute per organization, not per IP -- writing is rarer and costlier than reading, and a model retrying a write in a loop should hit this quickly on purpose |
The two layers apply together: 120 calls a minute spread across different tools is fine, 120 calls a minute of whois is not. Exceeding a limit returns HTTP 429 with a retryAfter value in seconds, telling you when the current window ends. Windows are fixed, not sliding, so a client that waits out retryAfter gets a full fresh allowance.
The limits are enforced per IP, not per token: an API token does not raise the ceiling, and running several agents behind one NAT address makes them share it. If you have a use case that genuinely needs more, get in touch rather than working around it.
Available tools
All tools are anonymous and read-only. They mirror our public web tools:
- check_ssl: TLS/SSL certificate inspection (
host, optionalport) - check_dns, dns_propagation, mx_lookup: DNS resolution and propagation (
domain) - spf_check, dkim_check (
domain,selector), dmarc_check: mail authentication (domain) - dnsbl_check (
ip), whois (domain), domain_expiry (domain) - http_headers (
url), hsts_check (domain), redirect_check (url) - port_check (
host,port), ping_test (host, optionalport) - website_status (
url), response_time (url) - ip_geolocation (
query), asn_lookup (query), reverse_dns (ip)
Authenticated tools (read)
These tools read your own monitoring and account data. Send your Uptimeify API token as an Authorization: Bearer <token> header on the MCP request. A customer-scoped token sees only its own customer's monitors; an organization token sees all customers in the organization. All are read-only.
- list_monitors (optional
kind): list your monitors across all eight families (website,dns,icmp,tcp,ftp,smtp,ssh,imap_pop). Omitkindfor all of them; every item carries its ownkind. Each item is limited to a fixed field list: identity and target (id,publicId,name,urlorhostnameandport,monitoringType,status,managementType), the check configuration without any credentials (interval, timeout, HTTP method, expected status codes, which checks are enabled, notice and error thresholds), state (isActive,isDown,isRecovering,hasCritical,hasWarning,inMaintenance,upSinceAt,lastCheckedAt), uptime and response-time figures, the customer's name and public id,tags(id, public id, name, colour) and timestamps. Never returned: the heartbeat token, any stored credential or encrypted setting (authorization header, basic-auth user and password, custom headers, request body, mTLS certificate and key, Playwright environment), the Playwright script, the monitor's alert phone number and e-mail, custom fields, the raw protocol configuration of the non-HTTP families, and the embedded customer record. - monitor_status (
monitor_id, optionalkind): a single monitor with its current up/down status.kinddefaults towebsitefor backward compatibility. Same field list aslist_monitors; the detail endpoint's extra secrets (heartbeat token, Playwright script and environment, credential presence flags) are withheld in the same way. - list_incidents (optional
limit): recent incidents across your monitors. The embeddedcustomercarries id, public id and company name; the customer's contact e-mail is removed. - check_history (
monitor_id, optionalkind,from,to): recent checks, optionally within an ISO date range.kinddefaults towebsite. - uptime_summary (
monitor_id): uptime and average response time for day / month / year. Website monitors only -- the other seven families do not have this endpoint. - list_alert_channels (optional
website_id): the notification channels that can raise an alert for you -- name, type (email, SMS, webhook, or one of the chat / on-call integrations), whether the channel is active, and its escalation priority and delay. Withoutwebsite_idyou get the organization-level channels; pass a website id fromlist_monitorsto get the channels that apply to that monitor, including its website-level overrides. A channel's own configuration is never returned: no recipient addresses, phone numbers, webhook targets or credentials. - alert_history (
monitor_id, optionalkind,from,to,status,page,limit): which alerts went out for one monitor, when, over which channel (name and type), and whether delivery succeeded or failed with which error. Together withlist_alert_channelsthis answers "why did nobody call me".kinddefaults towebsite,statusissentorfailed, and the result is paged: 25 entries per page by default, 100 at most. The channel's stored configuration is not returned here either.errorMessageis a different matter: it is free text from the delivery attempt itself and can quote what the receiving side said, so a failed send may name the SMTP host and port, a recipient address the mail server rejected, or the hostname of a webhook target. - list_status_pages: your status pages -- name, slug, description, the customer each belongs to, whether it is published, its visibility (
public,customer_members_onlyorpassword_protected) and whether a shared page password is set, the monitors the page deliberately hides, and the custom domain it is served on. Takes no arguments; the list follows from the token. Subscriber lists are never returned, and neither is the page's visual design or the DNS verification token of a custom domain. - list_maintenance_windows (optional
active_only): your scheduled maintenance windows -- name, start and end time, the IANA timezone the recurrence is interpreted in, whether and how it recurs, whether it is active, and which monitors it covers. A monitor inside a window does not alert, which is the usual answer to "why was there no incident". This endpoint has no date-range filter: the answer always covers every window you can see, past and future alike, so bound it by time yourself instead of assuming the list was filtered. - get_organization: your own organization -- its numeric id, public id, name, account status (
activeorsuspended) and, when a deactivated account is scheduled for deletion, the date that happens. Takes no arguments: the organization follows from the token. Those five fields are the entire answer. Billing address and cycle, payment method, discount codes, quota and price limits, the MFA policy and the organization's default notification settings are not returned, and the tool never reaches the SMTP, audit-log or data-export endpoints under/api/organization/. - list_users: the members of your organization -- name, role (
admin,editororreadonly), whether the member is still active, and when they joined, alongsidelimits.currentSeats, the number of members contained in the answer. Takes no arguments. The answer carries no member identifier at all, and that is deliberate: a member can be named but not referenced, so any follow-up that points at a person has to go through the name -- and two members who share a name cannot be told apart. No e-mail addresses, no avatars, no customer assignments either. Note the scope of the list: it is narrowed to the caller's customer scope, the same wayGET /api/usersis. A customer-scoped token sees only the members assigned to its own customer, plus itself -- andlimits.currentSeatscounts exactly those, so it is not the head count of the organization. - list_customers: the customers of your organization -- numeric id, public id, name, account status and the date the customer was created, plus how many monitors each one has (total, managed, self-service and DNSBL entries). Takes no arguments, and a customer-bound token sees only its own customer. Not returned: contact and notification e-mail, postal address, VAT id, phone number, cancellation dates, free-form custom fields, and the tariff name -- the tariff is not part of this answer at all.
- billing_summary: the SMS usage of the current month -- the organization-wide
totaland the per-customer breakdown. Takes no arguments, and SMS usage is the only billing figure it reports: never a tariff or plan, an amount of money, a currency, an invoice or billing date, a billing address, a billing e-mail or a payment method. Its answer depends on a second area: withmcp:billing:readalone, each customer in the breakdown is its public id and its usage figure and nothing else; the customer's name and status appear only whenmcp:organization:readwas granted as well. Names belong to the organization area, so a connection that declined it does not get the customer list back through the usage figures. A customer-bound token sees only its own customer.
Six tools carry a positive list and emit the fields named above and nothing else: the four account tools, plus list_monitors and monitor_status, so a column added to the endpoint later does not travel out with them. Five remove a handful of named fields from whatever the endpoint returned -- list_alert_channels, alert_history, list_status_pages, list_maintenance_windows and list_incidents -- and two, check_history and uptime_summary, hand the endpoint's answer on untouched because it contains nothing but check results. A field you do not see there is withheld on purpose, not by oversight.
Paging differs per tool, and only alert_history lets you ask for a further page. list_monitors returns at most one page per monitor family (50 entries each) per call; total is the real count of matching items across the families queried, not the number of items returned, so if the two differ the list was truncated. check_history returns the endpoint's first page, 10 checks within its default 30-day window, and reports total, page and pageCount alongside. alert_history pages on its own arguments: 25 entries per page by default, 100 at most, steered by page and limit. list_alert_channels, list_status_pages, list_maintenance_windows, list_users, list_customers and billing_summary are not paged -- each returns everything the token can see, so a short list there is a short list, not a truncated one. get_organization answers with a single record and has nothing to page.
When list_monitors runs without kind, it queries all eight families in parallel. If one of them does not answer, the result still comes back, and both the text and structuredContent.partialFailures name the sources that are missing. Treat such a list as incomplete rather than as "you have none of those".
Over OAuth these tools are gated per area: the two alert tools need mcp:alerts:read and the two status-page tools need mcp:statuspages:read, both in the Monitoring and operations group of the consent screen; get_organization, list_users and list_customers need mcp:organization:read and billing_summary needs mcp:billing:read, both in the Account and billing group (see below). A tool whose area was not granted does not appear in tools/list at all, so a client never learns to call one it cannot use. A wsm_ API token is not divided this way and reaches all 13.
If you call one of these without a token, the tool returns an error asking for the Authorization: Bearer header. Create a token under Settings → API tokens. A request that carries no Authorization header at all does not see them in the first place: tools/list then returns only the 20 anonymous tools. That is the same rule as above, applied to the caller who granted nothing -- a client is never shown a tool it cannot call.
Authenticated tools (write)
43 tools change your monitoring data instead of just reading it. They call the same /api endpoints the REST API and the dashboard use -- no separate write API exists, and no new endpoint was added to build them. Two things distinguish them from every read tool above, and both are permanent, not launch caveats:
- OAuth only. A
wsm_API token never sees a write tool, no matter how it is scoped -- the reasoning is an owner decision, see Connected apps. Only an OAuth connection holding the matching write scope can call one. - Deployment-gated. Write tools exist in the tool catalogue, but a deployment registers them only when it has explicitly turned them on. Where they are not enabled, none of the 43 appear in
tools/listand calling one by name fails as if it did not exist, regardless of which scopes your connection holds. Check with whoever runs your Uptimeify instance before assuming they are live.
A connection needs the matching write scope to see a tool at all: mcp:monitors:write unlocks the first six below, mcp:incidents:write the next four, mcp:maintenance:write the next three, mcp:statuspages:write the next eight, mcp:alerts:write the next three, mcp:organization:write the last nineteen. Checking a write scope on the consent screen also checks its read twin of the same name (for example mcp:monitors:write checks mcp:monitors:read too) -- most of these tools compare the record's current state before writing so a repeated call does not repeat the write, and that comparison needs read access to the same data. mcp:maintenance:write is the one exception: it has no read twin of its own, because list_maintenance_windows already reads under mcp:statuspages:read and a second scope for the same data would be a checkbox that decides nothing. Checking it therefore does not also grant mcp:statuspages:read -- a connection needs that scope separately before update_maintenance_window's state comparison (see below) can run. None of the three alert-channel tools makes a state comparison either -- there is no GET for a single channel through this connection, so a repeated call always writes again. Under mcp:organization:write exactly two of the nineteen compare state, update_user and update_customer, and both only when the connection also holds mcp:organization:read. With this area every one of the six write scopes now backs at least one tool; none is left empty.
Monitor tools (mcp:monitors:write), all working across the eight monitor families (website, dns, icmp, tcp, ftp, smtp, ssh, imap_pop) unless noted:
- pause_monitor (
kind,monitor_id): stop checking a monitor. While paused it raises no alerts, and the paused period shows as a gap in its uptime figures rather than downtime. Calling it on an already-paused monitor changes nothing and says so instead of writing again. - resume_monitor (
kind,monitor_id): start checking a paused monitor again. Left untouched if the monitor is in maintenance rather than paused -- ending a maintenance window is a separate, deliberate action this tool never takes. Subject to the same monitor-quota check as creating a monitor, so it can fail outright if the quota is exhausted. - rename_monitor (
kind,monitor_id,name, 1-255 characters): change the display name. Cosmetic only -- what is checked, how often, and its alert routing all stay the same. - set_check_interval (
kind,monitor_id,check_interval_minutes, 1-60): change how often a monitor is checked. The account's package sets a minimum interval per monitor; a value below it fails the call outright instead of being rounded up, so a lower interval is not always available. - create_website_monitor (
customer_id,name,url, optionalcheck_interval_minutes, optionalmonitoring_type): create a website monitor for a specific customer.customer_idis required and should never be guessed -- ask which customer the monitor belongs to if it is not already known. Subject to the account's monitor quota, so it fails outright rather than queuing if the quota is exhausted. Carries no credentials or scripts: no basic-auth password, mTLS key, custom authorization header, or Playwright script/environment, andmonitoring_typeexcludesplaywrightfor the same reason -- set those up in the dashboard. - delete_monitor (
kind,monitor_id,confirm: true): delete a monitor permanently, taking its check history, incidents, alert channels, tags and maintenance windows with it. This cannot be undone. Refused withreadonlyMayNotDeletefor a caller whose role is readonly, regardless of who owns the monitor.confirmmust betrue, and should be set only after a human asked for this specific deletion -- it is not a box a model should tick reflexively.
Incident tools (mcp:incidents:write), covering both manual (status-page) and automatic (monitor-raised) incidents unless noted, and all four requiring the admin or editor role -- a readonly or supporter caller is refused, generically, on every one:
- acknowledge_incident (
incident_id): mark an incident as being worked on. No-op if it is already acknowledged. Acknowledging a resolved incident is a genuine reopen -- Uptimeify treats bothopenandacknowledgedas "still live" -- so it schedules a status-page "reopened" event and re-notifies subscribers who were told it was fixed. Use it on a resolved incident only when the incident has genuinely recurred. - resolve_incident (
incident_id): resolve an incident. On a genuine open/acknowledged -> resolved transition this schedules a status-page "resolved" event and mails subscribers who were told about the outage. Calling it again on an already-resolved incident is a no-op and never sends a second all-clear. - post_incident_update (
incident_id,status:investigating,identified,monitoringorresolved,body, up to 5000 characters): post a new public update to an incident's timeline; it appears on the status page immediately. Not idempotent -- every call publishes a distinct, new update, so a call whose response never arrived should not be repeated, that republishes the same news a second time. Postingstatus: 'resolved'also resolves the parent incident (equivalent toresolve_incident) and mails the all-clear; posting any other status on a currently-resolved incident reopens it (equivalent toacknowledge_incident's reopen) and re-notifies subscribers. - delete_incident (
incident_id,confirm: true): delete a manual incident (a status-page announcement) permanently, taking every status-page event recorded about it with it. This cannot be undone. Refused withnotManualIncidentfor an automatic, monitor-raised incident -- those are worker-owned and can never be deleted through this tool.confirmmust betrue, set only after a human asked for this specific deletion.
Maintenance window tools (mcp:maintenance:write):
- create_maintenance_window (
name,start_time,end_time, one ofcustomer_idortag_ids, optionaldescription, optionaltimezone): schedule a maintenance window -- while it runs, the monitors it covers raise no alerts and the period shows as planned maintenance rather than downtime. Requires exactly one ofcustomer_id(every monitor of that customer) ortag_ids(every monitor carrying those tags); a call with neither fails outright. Not idempotent -- a second identical call creates a second window and publishes a second status-page announcement to subscribers. An organization-wide window (tag_idswithoutcustomer_id) requires an unrestricted scope and admin or editor role; everyone else gets a window scoped to their own customer. - update_maintenance_window (
maintenance_window_id, optionalname,description,start_time,end_time,is_active): change a scheduled window's name, description, time range or active state. Deactivating a window (is_active: false) stops it suppressing alerts without deleting it. When the connection can also read maintenance windows (holdsmcp:statuspages:read), this compares against the current window first and reports no change instead of writing when every field already matches; without that read scope the comparison is skipped and the call writes regardless. - delete_maintenance_window (
maintenance_window_id,confirm: true): delete a maintenance window permanently. If the window is currently running, its monitors stop being suppressed immediately and any failure they are in becomes a real alert.confirmmust betrue, set only after a human asked for this specific deletion.
Status page tools (mcp:statuspages:write):
- create_status_page (
customer_id,name, optionalslug,description,visibility,is_published,subscriptions_enabled,subscription_delay_minutes): create a new status page for a specific customer.customer_idis required and should never be guessed -- ask which customer the page belongs to if it is not already known. Not idempotent -- a second call, even with the same name, creates a SECOND page: the handler appends-2(then-3, and so on) to the slug rather than reusing the existing one, so do not repeat a call whose response did not arrive. Carries no password argument: a password-protected page needs a password this tool would reject a call without, so set one up in the dashboard instead. Presentation (layout, colors, which sections show) is not settable here -- callupdate_status_page_designafter creating the page. - update_status_page (
status_page_id, optionalcustomer_id,name,slug,description,visibility,is_published,subscriptions_enabled,subscription_delay_minutes): change a status page's name, slug, description, visibility, publish state, customer, or subscriber settings. There is no read endpoint for a single status page through this connection, so this tool cannot compare against the current page first: every call writes, even one that would set the same values again. Carries no password argument, for the same reasoncreate_status_pagedoes not, and noshow_recent_incidents/show_recent_maintenance-- the handler mirrors both into the page's design configuration as a side effect, which would silently collide withupdate_status_page_design, so that presentation belongs to the design tool instead. - update_status_page_design (
status_page_id, optional layout, color scheme, accent color, header style, font, card radius, page width, custom title/subtitle, and which sections show, each with its own limits and lookback windows for incidents and maintenance): change a status page's visual design. Reachable by a caller whose role is readonly, unlike the other three status-page tools, which need admin or editor. When the connection can also read status pages (holdsmcp:statuspages:read), compares against the current design first and reports "no change" without writing if every field this comparison can see already holds the requested value; without that read scope, or when the request sets a field the comparison cannot see (the history/incident/maintenance display settings), the comparison is skipped and the call writes regardless. - delete_status_page (
status_page_id,confirm: true): delete a status page permanently, taking its subscriber list with it -- every subscriber row cascades on delete. This cannot be undone.confirmmust betrue, set only after a human asked for this specific deletion. - add_status_page_domain (
status_page_id,hostname): bind a custom domain to a status page. Creates the row as pending and returns the TXT record name and value the human must publish in DNS. Publishing the record does not finish the job: callverify_status_page_domainafterwards to prove control of the zone, thenactivate_status_page_domainto start serving the hostname. Not idempotent -- a status page can hold only one domain, so a second call for a page that already has one fails rather than replacing it. - verify_status_page_domain (
domain_id): look up the DNS TXT record for a pending status page domain and, on success, promote it from pending to verified. Fails while the record is not yet visible in DNS -- this is the normal state right afteradd_status_page_domain, often for as long as DNS propagation takes, so do not call this again in a tight loop. Takesdomain_idas a plain number, unlike the id-or-public-id form the monitor and status page tools use. Requires the admin role -- an editor is refused here even though editors can call the other two domain tools. - activate_status_page_domain (
domain_id): promote a verified status page domain to active, the moment the hostname actually starts serving the page. Performs no DNS lookup of its own -- that isverify_status_page_domain's job -- but re-checks that this row still holds the claim on the hostname. Requires the admin role, the same restriction asverify_status_page_domain. - delete_status_page_domain (
status_page_id,domain_id,confirm: true): delete a status page's custom domain permanently. The status page itself is not deleted -- it becomes reachable only at its default URL again.confirmmust betrue, set only after a human asked for this specific deletion.
Alert channel tools (mcp:alerts:write):
- create_notification_channel (
channel_type:emailorsms,name,email_addressorphone_number, optionalcustomer_id,priority,delay_seconds,is_active): create a notification channel. Onlyemailandsmsare offered here -- every other channel type (Slack, Telegram, a generic webhook, and 27 more) carries credentials in its configuration and is set up in the dashboard instead. Not idempotent -- a second identical call creates a SECOND channel; there is no uniqueness on name and no state comparison, so do not repeat a call whose response did not arrive. Withoutcustomer_idthis creates an organization-wide channel and needs the admin role; withcustomer_idit creates a customer-bound channel, which a readonly caller may also create for their own customer. - update_notification_channel (
channel_id, optionalname,priority,delay_seconds,is_active): change a channel's display name, escalation priority, delay, or whether it is active. Does not touch a channel's configuration -- not its target, not its type, no credentials; change the target in the dashboard. There is noGETfor a single channel through this connection, so this tool cannot compare against the current channel first: every call writes, even one that would set the same values again. - delete_notification_channel (
channel_id,confirm: true): delete a notification channel permanently -- this cannot be undone. To pause a channel without losing it, callupdate_notification_channelwithis_active: falseinstead.confirmmust betrue, set only after a human asked for this specific deletion.
Organization tools (mcp:organization:write), nineteen of them, the only area whose tools leave the monitoring data behind and touch the account itself. Seven are destructive. Two, update_user and update_customer, compare state before writing when the connection also holds mcp:organization:read; every other one in this area writes on every call. Tags first:
- create_tag (
nameup to 24 characters,colorfrom a ten-key palette, optionalcustomer_id): create a tag for grouping monitors. Not idempotent -- tag names are not unique and there is noGETfor a single tag here, so a second identical call creates a SECOND tag. Withoutcustomer_idthe tag is organization-wide; a caller restricted to several customers who names none of them fails withtagCustomerRequired. There is no role gate beyond globalsupporter here: even a readonly caller may create a tag, which is the reverse ofcreate_custom_fieldunder the very same scope. - update_tag (
tag_id, optionalname,color): rename a tag or change its colour. Never moves it to another customer. No state comparison is possible, so every call writes. Who may change a tag is decided by the tag, not the caller's role: a global admin, its creator, an unrestricted caller, or one whose scope contains the tag's customer. - delete_tag (
tag_id,confirm: true): delete a tag everywhere, permanently. It falls off every monitor, and it is stripped from every maintenance window that used it -- a window whose scope was only this tag is then left with no scope at all and may suppress nothing. The response reports how many windows were touched inaffectedWindows, counted before the delete; check those windows afterwards.confirmmust betrue, set only after a human asked for this specific deletion. - add_monitor_tag (
kind,monitor_id,tag_id): attach an existing tag to one monitor. The endpoint is itself idempotent, but this tool reportschanged: trueeither way, so read the answer as "the tag is now on this monitor", not as "something was written". At most FIVE tags per monitor; a sixth is refused withtagLimitReached(422, carryingmax), the only 422 in this catalogue. Both ids are numeric. - remove_monitor_tag (
kind,monitor_id,tag_id,confirm: true): take a tag off ONE monitor. The tag itself stays and keeps every other monitor -- deleting it everywhere isdelete_tag, a much larger action. An assignment that never existed also answers with success.confirmis required because every delete here requires it, not because the consequence is grave: a singleadd_monitor_tagundoes it.
Custom fields, three tools, all three requiring the admin role -- stricter than create_tag under the same scope, so being allowed there says nothing about being allowed here:
- create_custom_field (
name,field_type, optionalfield_key,options,placeholder,help_text,is_required,show_in_table,sort_order): define a field on EVERY customer record of this organization at once. That is a schema change, not data on one customer, and withis_requiredit changes what other people must fill in. Not idempotent:field_keyis derived fromnamewhen omitted, so a repeated name collides withcustomFieldAlreadyExists(409), not a 400. No tool here lists custom fields, so keep the id from the response. - update_custom_field (
custom_field_id, optionalname,field_type,options,placeholder,help_text,is_required,show_in_table,sort_order,is_active): change a field definition. Never itsfield_key, which is how every value already captured is addressed. No comparison is possible, so every call writes.is_active: falsehas exactly the same effect asdelete_custom_fieldand deserves the same human confirmation even though this tool carries none. Changingfield_typedoes not convert values already stored. - delete_custom_field (
custom_field_id,confirm: true): despite the name this deletes nothing. The endpoint marks the definition inactive; the definition and every value already captured under it on every customer stay in the database, untouched. What changes is visibility: the field leaves the customer forms, the customer table, and the one listing its id could still be read from. Keep the id first, becauseupdate_custom_fieldwithis_active: trueis the way back. A retired field keeps itsfield_keyoccupied, socreate_custom_fieldwith the same name still fails withcustomFieldAlreadyExists(409). Treat it as destructive nonetheless: for the people in the dashboard, the field and its data are gone.
Scheduled reports, three tools, all three requiring the admin role. No tool here reads a report back, so nothing can compare against what already exists:
- create_report (
name,recipient_user_ids, optionalfrequency,weekday,day_of_month,scope_mode,scope_customer_ids,scope_tag_ids,monitor_types,format,enabled): create a scheduled report that mails a summary to named organization members, repeatedly, starting on its next due date with no further step from anyone. Not idempotent -- report names are not unique, so a second call creates a SECOND report that then mails the same people on the same schedule, findable only by a human. The schedule defaults to monthly, soday_of_month(1 to 28) is required unlessfrequencyis daily; weekly requiresweekday. Recipients must be members of this organization; free-form addresses are deliberately not offered here. Keep the id from the response. - update_report (
report_id, plus any of the create fields): change a report's name, schedule, scope, monitor families, format, recipients or whether it sends.report_idis the report's public id, a UUID. Every call writes; there is nothing to compare against.recipient_user_idsREPLACES the recipient list rather than adding to it. The cross-field checks run against the MERGED report, not against what you sent.enabled: falsestops the sending without losing the report and is the reversible alternative to deleting it. - delete_report (
report_id,confirm: true): delete a scheduled report permanently. Its run history goes with it, together with the stored path of each run's PDF; the PDF file itself is not removed from object storage, so it stays in the bucket with nothing left pointing at it. Already-delivered mails and the separate per-customer reports are untouched. If the intent is only to stop the sending, useupdate_reportwithenabled: falseinstead.confirmmust betrue, set only after a human asked for this specific deletion.
One tool changes a member. There is deliberately no create_user and no delete_user in this connection -- inviting someone and removing someone both happen in the dashboard, so do not try either name:
- update_user (
user_id, optionalfirst_name,last_name,is_active): change one member's profile. It changes no role and no customer assignment, so a call here can neither grant nor take away a permission. Compares before it writes when the connection also holdsmcp:organization:read. Passfirst_nameandlast_nametogether whenever you change either: the endpoint rebuilds the displayed full name out of exactly the two values in the request.is_active: falselocks the member out within seconds but deletes nothing, andis_active: trueundoes it. Requires the admin role and an unrestricted token; a customer-scoped token is refused withcustomerScopedTokenForbidden(403).list_usersdeliberately returns no member ids, souser_idhas to come from a human or the dashboard.
Customers and the two billable entries that hang off them, seven tools:
- create_customer (
name, one ofpackage_idorpackage_type, optional contact fields): create a new customer under this organization. It creates NO monitor -- the customer starts empty. Two organization-wide preconditions are checked first:quotaRequired(403) when the organization has chosen no monitor quota, andbillingSetupRequired(403) when it has no valid direct-debit mandate. A package selects quota and feature flags and carries no price. Not idempotent -- customer names are not unique. Requires admin or editor and an unrestricted token. Keep thepublicIdfrom the response: the other six all address the customer by it. - update_customer (
customer_id, optionalname, contact fields,status,package_id, alert-routing overrides, monthly-report flag): change an existing customer. Compares before it writes when the connection also holdsmcp:organization:read, which matters here because the endpoint writes one audit entry per changed field.status: inactiveis the heaviest thing this tool does: every monitor of all eight families, every monitored domain and watched IP is switched off, running maintenance windows stop, and published status pages leave the internet. Setting it back to active restores the monitors, domains and IPs but not the status pages or the windows. - delete_customer (
customer_id,confirm: true): delete a customer and the widest cascade in this catalogue -- 21 tables lose their rows with it, including its monitors, domains, IPs, incidents, status pages, maintenance windows, API tokens, notification channels and the stored PDFs of reports already produced for it. If the intent is only to stop the monitoring, useupdate_customerwithstatus: inactiveinstead. Requires the admin role, strictly more thanupdate_customerneeds. The report PDFs are removed from object storage BEFORE the row, and a failed removal fails the whole call rather than leaving personal PDFs behind with nothing pointing at them. - add_customer_domain (
customer_id,domain_name, optionalstatus,notes): watch a domain's registration expiry. This creates a billable monitor and consumes both organization and package quota. An organization at its limit fails withmonitorQuotaReached(403), unless automatic upgrading is on, in which case this very call moves it to a higher and more expensive tier; one already on the top tier fails withmonitorQuotaNoHigherTier. Only registries that answer RDAP are supported. Not idempotent and there is no uniqueness check, so a repeat creates a second billable monitor. No tool here lists these entries, so keep the id andpublicId. - delete_customer_domain (
customer_domain_id,confirm: true): delete one watched domain permanently. Its expiry status row and its tag assignments go with it; the tags themselves stay. The only route back isadd_customer_domainwith the same name, which creates a NEW entry with a new id. A readonly caller is refused withreadonlyMayNotDelete(403) whatever else it may edit.confirmmust betrue, set only after a human asked for this specific removal. - add_customer_ip (
customer_id,ip_address, optionalstatus,notes): watch an IP address on the DNSBL blocklists. This creates a billable monitor against the organization's active-IP limit, and that limit answers with a bare 403 carrying nodata.codeat all, so the message has to be read rather than the code matched. The address alone decides IPv4 or IPv6; there is deliberately no argument for it. Not idempotent and there is no uniqueness check. Keep the id andpublicIdfrom the response. - delete_customer_ip (
customer_ip_id,confirm: true): delete one watched address permanently. Its DNSBL status row, its whole listing-check history and any maintenance window bound to it go with it, and its tag assignments are removed while the tags stay. The only route back isadd_customer_ipwith the same address, which creates a NEW entry with an empty history. A readonly caller is refused withreadonlyMayNotDelete(403).confirmmust betrue.
What this area deliberately does not reach: creating or deleting a member, bulk customer maintenance, sending a report by hand, the organization's own master data, SMTP settings and API tokens. All of those stay in the dashboard.
A write tool returns { changed: true, result } for a real write, or { changed: false, reason } when a state comparison found nothing to do. pause_monitor, resume_monitor, rename_monitor, set_check_interval, acknowledge_incident, resolve_incident, update_maintenance_window, update_status_page_design, update_user and update_customer make that comparison (update_maintenance_window only when the connection also holds mcp:statuspages:read; update_status_page_design only when it holds mcp:statuspages:read and the request sets no field the comparison cannot see; update_user and update_customer only when it holds mcp:organization:read); create_website_monitor, delete_monitor, post_incident_update, delete_incident, create_maintenance_window, delete_maintenance_window, create_status_page, update_status_page, delete_status_page, add_status_page_domain, verify_status_page_domain, activate_status_page_domain, delete_status_page_domain, create_notification_channel, update_notification_channel, delete_notification_channel, create_tag, update_tag, delete_tag, add_monitor_tag, remove_monitor_tag, create_custom_field, update_custom_field, delete_custom_field, create_report, update_report, delete_report, create_customer, delete_customer, add_customer_domain, delete_customer_domain, add_customer_ip and delete_customer_ip do not -- a delete already answers idempotently at the endpoint itself, the channel, tag, custom-field and report tools have no read endpoint to compare against, and the rest are deliberately never a no-op. Errors come back the same way as for the read tools above: isError: true with a plain message, no structuredContent.
Structured output
Every tool declares an outputSchema, and every successful call returns structuredContent next to the text block. You can type-check a response against the published schema instead of parsing our JSON by hand. The text block is unchanged and still carries the full payload, so a client that ignores structured output sees exactly what it saw before.
Two properties of these schemas are deliberate:
- No field is required. The schema types the fields it knows about; it never asserts one is present. An endpoint that omits or nulls a value does not turn a check into a failed tool call.
- Extra properties are allowed. The schemas are open, so a field added later rides along in
structuredContentinstead of failing validation in a strict client.
structuredContent must be a JSON object, and four of these tools answer with a bare array: list_incidents has its array wrapped under an incidents key, list_alert_channels under channels, list_maintenance_windows under maintenanceWindows, and list_customers under customers. The text block keeps the array as-is in all four cases.
Errors carry no structuredContent. They come back as isError: true with a plain message, as before.
Tool names use only letters, digits and underscores. That is not cosmetic: the major function-calling APIs validate tool names against ^[a-zA-Z0-9_-]{1,64}$ (OpenAI) and ^[a-zA-Z0-9_-]{1,128}$ (Anthropic), so a dotted name would be rejected by any client that passes our tools through to a model.
Connect via OAuth (Claude Desktop / Claude.ai)
Uptimeify's /mcp endpoint also supports standard MCP OAuth (OAuth 2.1 + PKCE + Dynamic Client Registration): this is how Claude's remote-MCP connector authenticates without you creating an API token by hand.
- In Claude, add
https://uptimeify.io/mcpas a remote MCP server and choose Connect. - Claude registers itself automatically (Dynamic Client Registration) and opens a browser window asking you to sign in to Uptimeify and approve access.
- The consent screen names the application and the account it will be linked to, and offers each area the application requested as a separate checkbox, grouped in up to three sections: Monitoring and operations (monitors, incidents, check history, alerts, status pages -- read), Account and billing (organization, billing -- read) and Write access (monitors, incidents, maintenance windows, status pages, alert channels, organization -- see Authenticated tools (write) above; check history and billing are the two read areas with no write counterpart). Claude's remote-MCP connector requests the seven read areas, so that is the widest version of the read half of the screen you will see; an application that asks for read access without naming a specific area gets only the three operational areas that existed before this expansion -- monitors, incidents, check history -- and only those three appear, pre-checked. Whichever areas are offered, the read areas start pre-checked (account areas unchecked, because that is where personal and billing data lives) and every write area starts unchecked: nothing here is granted by default, ever. Checking a write box also checks its read twin of the same name, because the tools need to read a record before deciding whether writing it would change anything -- unchecking the write box afterwards leaves the read grant in place. Maintenance windows are the one write area with no read twin to check:
list_maintenance_windowslives under the status-pages read area instead, so check that separately if you wantupdate_maintenance_windowto compare state before writing. Clear a box and that area stays out of the connection. Outside of a checked write area the connection stays exactly as read-only as before: it inherits your own account's visibility, a customer-restricted user's Claude sees only that customer, while an org admin's Claude sees the whole organization -- and even inside a checked write area, whether a tool actually does anything depends on whether this deployment has write tools switched on at all (see above). - Once approved, Claude sees only the tools whose area you granted, and none of the others. All seven read areas carry tools, the same 13 read-only tools available to
wsm_API tokens:list_monitorsandmonitor_status(monitors),list_incidents(incidents),check_historyanduptime_summary(check history),list_alert_channelsandalert_history(alerts),list_status_pagesandlist_maintenance_windows(status pages),get_organization,list_usersandlist_customers(organization),billing_summary(billing). Billing is the one area that also reads better with a second one: granting organization alongside it puts customer names on the usage breakdown, and leaving it out keeps the breakdown to public ids. Checking the six write areas, where the deployment has them switched on, adds the 43 tools listed above --wsm_API tokens never get these, however they are scoped. The anonymous check tools are unaffected by any of this.
There is no way past the screen. An authorization request that omits prompt, or sends any other value, is answered with the consent screen regardless. prompt=none is the one request we do not rewrite: it asks for no interface, so it gets the OpenID Connect error redirect instead, carrying login_required without a session and consent_required with one. The scope names themselves are documented under Connected apps.
OAuth access tokens are short-lived (1 hour) and refresh silently while Claude keeps using the connection; the underlying grant expires automatically after 7 days of inactivity. You can review and revoke connected applications at any time under Settings → Connected apps (see Connected apps): revoking is immediate and reconnecting requires going through OAuth consent again.
OAuth and static wsm_ API tokens both work on /mcp at the same time: connecting via OAuth does not require removing an existing API-token integration.
Example (tools/list)
curl -X POST https://uptimeify.io/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Without an Authorization header this lists the 20 anonymous tools. Add an API token or an OAuth connection to see the account-bound ones your grant covers.
Example (authenticated tool call)
List your own monitors: add the Authorization header:
curl -X POST https://uptimeify.io/mcp \
-H 'Authorization: Bearer wsm_your_token_here' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_monitors","arguments":{}}}'