Back to blog
Security & Compliance

MCP Server Security: Read-only, Token Scopes and Human Approval

Agent token with read-only scope and a rejected write attempt returning status code 403

An AI agent reading your monitoring data cannot change anything in your Uptimeify account. That is not a promise, it is a property of the token: every write attempt from an agent token ends in 403 with the error code agentTokenReadOnly. So does every GET request outside the allowlist of its scope. This post shows where that boundary runs, who opens it and how you close it again in one call.

The real question: not "may the agent in", but "what can it do in there"

Most security conversations about MCP servers circle around access. That is the weaker question. Access is binary, blast radius is not. Four things decide the actual risk: which verbs the token knows, which surface it sees, how long it lives and who approved it.

The Uptimeify MCP server is stateless and runs at POST https://uptimeify.io/mcp. It exposes two groups of tools. 20 check tools work with no account and no token at all: SSL check, DNS lookup, DNS propagation, MX, SPF, DKIM, DMARC, DNSBL, WHOIS, domain expiry, HTTP headers, HSTS, redirects, port check, ping, website status, response time, IP geolocation, ASN and reverse DNS. Thirteen more tools read your own monitoring and account data and do require a token: list_monitors, monitor_status, list_incidents, check_history, uptime_summary, list_alert_channels, alert_history, list_status_pages, list_maintenance_windows, get_organization, list_users, list_customers and billing_summary. The last four are the narrowest of the set: they emit a fixed list of fields and nothing else, so no contact e-mail, no address, no VAT id, no phone number, no tariff name and no payment method leaves with them.

None of these 33 tools writes. An agent checking the TLS chain of deinkunde.com for you, or verifying that the SPF record still resolves, needs no access to your account whatsoever. That is the first and most effective cut: most everyday agent work happens outside your data.

Read-only as architecture, not as a setting

For agent tokens, read-only is not a checkbox on a form. It is what the token is. Every write call, and every GET or HEAD outside the allowlist of its scope, is rejected with 403 and data.code = agentTokenReadOnly. There is no path through which an agent token creates a monitor, edits a customer or closes an incident. Those operations stay with humans holding a login or a classic API token.

There are exactly two scopes:

ScopeHow it comes aboutWhat it reaches
tools.publicanonymous registration, active immediately, no approval neededonly the public check tools under GET /api/tools/*
api.readonly after a confirmed claim by a signed-in humanadditionally GET /api/websites*, GET /api/incidents* and GET /api/health, scoped to the approving user's organization or a single customer inside it

The registration and token endpoints are served only on the canonical host uptimeify.io. On a white-label domain of your agency they answer with 404. Your client status pages stay outside the auth flow, even when they run under your own domain.

Who grants access: the human, not the agent

Before an agent may read account data at all, a signed-in human has to confirm it. The claim flow knows two routes, and the difference between them is the interesting part.

With a service_auth registration the ceremony starts immediately. The registration sits at pending and mints no token until a signed-in user authorizes it. The agent receives the user_code and the verification_uri and displays both to its human, the way a device flow on a television does.

With an anonymous registration it is stricter. That registration works right away with tools.public and can optionally be upgraded to api.read later. On this path the agent never learns the user_code. It gets a verification URL and nothing else. The code is visible only to the signed-in user with the exact bound email address, on the claim page. An agent cannot talk itself into its own upgrade, not even when it fully controls the registration.

Two more properties make the flow auditable:

  • A registration can be claimed exactly once. A second attempt returns 409 claimed_or_in_flight.
  • The resulting scope hangs off the approving human. A token never reaches further than the user who authorized it, either across that user's organization or a single customer inside it.

That answers the most common question from client conversations before it is asked: no, an agent does not see other tenants' data, and no, it cannot grant itself the access.

Token lifetime and revocation

Short lifetimes are the second line of defence after scope. If a token lives for one hour, a leak is an incident with an expiry date instead of a standing condition.

  • The access token is opaque, starts with wsma_ and expires after 3,600 seconds. After that the agent mints a fresh one from its identity assertion.
  • The identity assertion is an EdDSA JWT valid for roughly 24 hours. It is the durable credential, not the access token.
  • The confirmation code in the claim flow and its claim token both expire after 600 seconds. Too slow means starting the ceremony again.
  • Revocation through POST /oauth2/revoke is idempotent and always returns 200, including for an unknown or long revoked token. The result is unambiguous instead of sending you into a debugging session.

On top of that come limits that slow a runaway agent down early: 120 requests per minute per IP on the MCP endpoint, 15 to 30 per minute per anonymous tool, 60 per minute per authenticated tool. Anything above returns 429. Registration is capped at 30 requests per minute, the token endpoint at 60.

What you have to treat like a password

In exactly one place the secret does not live in a header, it lives in the path. When you create an alert source in Incident Management, for Zabbix, Grafana, Datadog, Sentry, Prometheus Alertmanager or a custom webhook, Uptimeify generates an ingest URL shaped like https://<your-domain>/api/im/ingest/<token>. The token is part of the URL. The full URL therefore has to be treated like a password: not in a public repository, not in a ticket, not in a shared screenshot.

Three details that make handling it easier:

  • The URL is shown in cleartext exactly once, right after the source is created. Never again after that.
  • It can be rotated at any time. The source's settings tab issues a fresh URL and retires the old one.
  • An unknown or rotated-away token returns 404 not_found. The response never reveals whether that token ever existed. That is deliberate, and it denies a scanner the feedback it would need to guess.

The same discipline applies to classic API tokens. They start with wsm_, are shown in full only once, and can be created with a lifetime between 1 and 365 days. Scoping one to a single customer scopes the damage with it. What a token is technically, and where you use it, is covered in the glossary entry on the API token.

Where the data sits

Everything queried through the MCP server runs on the same infrastructure as the rest of the platform: European-only stack, Frankfurt, without US subprocessors. The MCP server is not a separate service with its own data store, it is a read surface on the existing API. It copies nothing to another place. The current list of providers in use is published on the subprocessors page, and the technical detail on encryption, access control and operations sits on the pages for GDPR and EU hosting and enterprise security.

Part of the data question is how long things stay. In Incident Management, alert payloads are nulled 90 days after ingest, resolved alerts are deleted 90 days after ingest, and outbound delivery records are deleted 90 days after they were sent. The incident itself and its timeline are kept in full for 12 months, after which only a daily aggregate survives, covering incident counts plus acknowledge and resolve times, for 24 months in total. An agent with api.read only ever sees the normalized view, never the raw payload of an alert source. That payload is not served by this interface at all, because it can contain whatever the operator of the source put into it, including someone else's keys.

If you find a gap in how we secure this, the route for reporting it is documented: security policy.

Checklist before you approve

Before you grant an agent api.read, walk these seven points. Together they take under ten minutes and save you the uncomfortable version of the conversation.

  1. Does the agent need account data at all? For checks against third-party domains the public tools are enough, with no registration.
  2. Who confirms the approval? The claim hangs off a named, signed-in person with a bound email address. Record who that was.
  3. Scoped to what? Organization or single customer. When in doubt, the smaller slice.
  4. Where does the agent run? A token in a local client is a different object from one in a shared automation. The one-hour expiry helps, it does not replace storage discipline.
  5. Are ingest URLs stored properly? They carry their token in the path. Visible in repos, tickets or screenshots means: rotate.
  6. Is the revocation route known? POST /oauth2/revoke, idempotent, always 200. Looking it up during an incident costs minutes you do not have.
  7. Can you evidence the data answer? Location, subprocessors and retention periods are documented. Link them in the proposal instead of describing them.

Security around agent access does not come from someone promising to be careful. It comes from the risky operation having no path at all. Everything else is trust without evidence.

Frequently asked questions

Written by
Florian Zaskoku · Co-Founder

Co-Founder of Uptimeify, responsible for all of marketing. He bridges technical development and marketing strategy: from Java, PHP and Shopware plugins to steering digital growth strategies. A certified UX Manager (IHK) and digital-marketing advisor to three non-profit organizations.

Set Uptimeify as a preferred source on Google

Google shows sources you mark as preferred more often in its answers.

More from the blog

Tool call flow between AI assistant, MCP client and MCP server running an SSL check
Monitoring

What Is an MCP Server? Definition, Function and Practice

An MCP server is the standardised interface an AI assistant uses to call real tools instead of guessing.

Florian Zaskoku10 min read
Overview of the seven European Uptimeify probe locations after the July 2026 switch, with Zurich and Prague retired and Frankfurt and Paris new
Company

Platform Update July 2026: 27 changes, two of them ask something of you

New probe locations, a full data export, TCP port monitoring and a second SMS carrier: every change from July in one post.

Florian Zaskoku7 min read
Monitor view showing a failed content check after a manipulated client homepage
Security & Compliance

Detect Defacement: Monitor Website Manipulation Before Your Clients Do

Why a hijacked client site stays unnoticed for days, and how to cut your response time down to minutes.

Florian Zaskoku9 min read

Check the boundary before you need it

Location, subprocessors, encryption and retention periods are documented in the open. When a prospect asks where the data goes, you should not have to look for the answer first.