imsu

View this page as Markdown

Authentication for MCP clients

#

imsu.io's MCP server at https://imsu.io/mcp is protected by an OAuth 2.1 authorization server at https://imsu.io. This page is for people who build MCP clients, and for agents that want to know what their client does. If you only want to connect a client, see MCP server; MCP clients do all of this for you.

What imsu.io supports, in short:

  • The MCP authorization spec (revisions 2025-06-18, 2025-11-25 and 2026-07-28): a 401 with resource_metadata, Protected Resource Metadata (RFC 9728) and Authorization Server Metadata (RFC 8414).
  • The authorization code grant with PKCE (S256 only) and refresh tokens. Public clients only: there are no client secrets.
  • Clients identify themselves with a Client ID Metadata Document (an HTTPS URL as client_id) or register with Dynamic Client Registration (RFC 7591).
  • Resource indicators (RFC 8707): tokens are issued for https://imsu.io/mcp only.
  • iss in the authorization response (RFC 9207).
  • No implicit flow, password grant, plain PKCE, client secrets, JWT access tokens or OpenID Connect.

Scripts and headless agents don't need any of this: they can send an API key (REST API), which /mcp accepts too.

Metadata URLs

#
Document URL
Protected Resource Metadata of the MCP server https://imsu.io/.well-known/oauth-protected-resource/mcp
The same, for clients that look at the root https://imsu.io/.well-known/oauth-protected-resource
Authorization Server Metadata https://imsu.io/.well-known/oauth-authorization-server

They need no credential, may be read from any origin, and are cacheable for an hour (Cache-Control: public, max-age=3600). There is no openid-configuration.

The flow, request by request

#

1. Call the MCP server without a token

#
POST /mcp HTTP/1.1
Host: imsu.io
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"my-agent","version":"1.0"}}}
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://imsu.io/.well-known/oauth-protected-resource/mcp", scope="monitors:read monitors:write"
Content-Type: application/problem+json

The body is described in MCP server.

2. Read the protected resource metadata

#
GET /.well-known/oauth-protected-resource/mcp HTTP/1.1
Host: imsu.io
{
  "resource": "https://imsu.io/mcp",
  "authorization_servers": ["https://imsu.io"],
  "scopes_supported": ["monitors:read", "monitors:write"],
  "bearer_methods_supported": ["header"],
  "resource_name": "imsu.io",
  "resource_documentation": "https://imsu.io/docs/mcp"
}

3. Read the authorization server metadata

#
GET /.well-known/oauth-authorization-server HTTP/1.1
Host: imsu.io
{
  "issuer": "https://imsu.io",
  "authorization_endpoint": "https://imsu.io/oauth/authorize",
  "token_endpoint": "https://imsu.io/oauth/token",
  "registration_endpoint": "https://imsu.io/oauth/register",
  "revocation_endpoint": "https://imsu.io/oauth/revoke",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"],
  "revocation_endpoint_auth_methods_supported": ["none"],
  "scopes_supported": ["monitors:read", "monitors:write"],
  "client_id_metadata_document_supported": true,
  "authorization_response_iss_parameter_supported": true,
  "service_documentation": "https://imsu.io/docs/authentication"
}

4. Get a client id

#

Use the URL of your client metadata document as client_id, or register. Registering a native app that listens on a loopback port:

POST /oauth/register HTTP/1.1
Host: imsu.io
Content-Type: application/json

{"client_name":"My agent","redirect_uris":["http://127.0.0.1:33418/callback"]}
HTTP/1.1 201 Created
Cache-Control: no-store
Content-Type: application/json; charset=utf-8
{
  "client_id": "imsu_client_6nYmKDC0DnLQnAtPUhFKCM",
  "client_id_issued_at": 1794337800,
  "client_name": "My agent",
  "redirect_uris": ["http://127.0.0.1:33418/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

5. Make a PKCE pair

#

The code_verifier is 43 to 128 characters of A-Z a-z 0-9 - . _ ~, e.g. 32 random bytes in base64url. The code_challenge is BASE64URL(SHA-256(code_verifier)) without padding. The pair from RFC 7636:

code_verifier  = dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
code_challenge = E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM

6. Send the user's browser to the authorization endpoint

#
https://imsu.io/oauth/authorize?response_type=code&client_id=imsu_client_6nYmKDC0DnLQnAtPUhFKCM&redirect_uri=http%3A%2F%2F127.0.0.1%3A33418%2Fcallback&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256&scope=monitors%3Aread%20monitors%3Awrite&state=af0ifjsldkj&resource=https%3A%2F%2Fimsu.io%2Fmcp
Parameter
response_type Required: code
client_id Required: a registered id or a metadata document URL
redirect_uri Required: one of the client's redirect URIs (how they match)
code_challenge Required: the S256 challenge
code_challenge_method Required: S256
scope Optional: monitors:read, monitors:write or both, space-separated (scopes)
state Optional; sent back unchanged. PKCE already ties the answer to the request
resource Optional; if sent, https://imsu.io/mcp

Each parameter may be sent once (resource may repeat with the same value). imsu.io checks the request and sends the browser to its consent page, inside a sealed handle that is valid for 10 minutes:

HTTP/1.1 302 Found
Location: https://imsu.io/authorize?request=CfDJ8EMIfNIR89tLlY866dBoCic4aI0Fq2yp95Dzogt9H3O…

If the request is wrong, see errors at the authorization endpoint.

7. The user signs in and allows access

#

If the user isn't signed in, imsu.io asks them to sign in (or sign up) first and then brings them back. The consent page names the app, says where the browser will return to, and lists what the app may do:

  • See your monitors and their status (monitors:read, always included)
  • Create, change, pause and delete monitors (monitors:write, if asked for; the user may untick it)

Apps registered with Dynamic Client Registration are marked "imsu.io hasn't verified this app", because anyone can register an app under any name. Apps known by their metadata URL show that URL's host instead. No logos are shown.

When the user clicks Allow, the browser goes back to the client with a single-use code:

http://127.0.0.1:33418/callback?code=imsu_ac_MoogF2zjfJ7JQKXxgg3YtY97erq1xcrkmUw02JEchmj&state=af0ifjsldkj&iss=https%3A%2F%2Fimsu.io

Check that iss is https://imsu.io. When the user clicks Cancel, the client gets error=access_denied with state and iss.

8. Exchange the code for tokens

#

The code works once, within 60 seconds.

POST /oauth/token HTTP/1.1
Host: imsu.io
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=imsu_ac_MoogF2zjfJ7JQKXxgg3YtY97erq1xcrkmUw02JEchmj&client_id=imsu_client_6nYmKDC0DnLQnAtPUhFKCM&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk&resource=https%3A%2F%2Fimsu.io%2Fmcp
HTTP/1.1 200 OK
Cache-Control: no-store
Content-Type: application/json; charset=utf-8
{
  "access_token": "imsu_at_H4zN5w3m47PM075l0Hpaj8thgf5yjNtKndxdfJtEORw",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "imsu_rt_amp3sWE4h6uIq3OZUHRrHA2CLHnLN9doHtRIbiyrCJH",
  "scope": "monitors:read monitors:write"
}

redirect_uri may be left out, as OAuth 2.1 allows; if you send it, it must be the one from step 6. scope says what the user allowed, which may be less than you asked for.

9. Call the MCP server with the access token

#
POST /mcp HTTP/1.1
Host: imsu.io
Authorization: Bearer imsu_at_H4zN5w3m47PM075l0Hpaj8thgf5yjNtKndxdfJtEORw
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"my-agent","version":"1.0"}}}
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache,no-store
event: message
data: {"result":{"protocolVersion":"2025-11-25","capabilities":{"logging":{},"tools":{}},"serverInfo":{"name":"imsu.io","title":"imsu.io","version":"1.0.0"},"instructions":"imsu.io checks websites (http), TCP ports (port) and cron jobs (heartbeat) on a schedule … Ask the user before deleting."},"id":1,"jsonrpc":"2.0"}

The answer may come as an event stream of one event whose data: line holds the JSON-RPC message, or as plain JSON; accept both. There is no Mcp-Session-Id: the server keeps no session, so send the access token and MCP-Protocol-Version with every later request.

The token works only in the Authorization header and only at https://imsu.io/mcp; the REST API at /api/v1 refuses it with 401.

10. Refresh the access token

#

Access tokens last 1 hour. Before or after one expires, trade the refresh token for new tokens:

POST /oauth/token HTTP/1.1
Host: imsu.io
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=imsu_rt_amp3sWE4h6uIq3OZUHRrHA2CLHnLN9doHtRIbiyrCJH&client_id=imsu_client_6nYmKDC0DnLQnAtPUhFKCM
{
  "access_token": "imsu_at_IFFtxW7u0V8QwjJdVILXm7c3SCQeoYA74NpajGgEeIJ",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "imsu_rt_o1owcrDfMAIclUkzXPdP5sANxYwIa8GfxrCK3oR65fM",
  "scope": "monitors:read monitors:write"
}

Keep the new refresh token and forget the old one: see refresh token rotation. Optional parameters: scope, to narrow the new access token (the refresh token keeps what was granted), and resource (https://imsu.io/mcp).

11. Revoke tokens when the user disconnects

#
POST /oauth/revoke HTTP/1.1
Host: imsu.io
Content-Type: application/x-www-form-urlencoded

token=imsu_rt_o1owcrDfMAIclUkzXPdP5sANxYwIa8GfxrCK3oR65fM&client_id=imsu_client_6nYmKDC0DnLQnAtPUhFKCM&token_type_hint=refresh_token
HTTP/1.1 200 OK
Cache-Control: no-store

Revoking a refresh token ends the whole connection, with every token issued for it; revoking an access token ends only that token. Any well-formed request gets an empty 200, also for a token that is unknown, already revoked or another client's (then nothing is revoked). A request without token or client_id gets 400 invalid_request.

Client ID Metadata Documents

#

A client can use an HTTPS URL as its client_id, pointing at a JSON document it publishes. imsu.io then needs no registration, and the consent page shows the URL's host, so users can tell who the app is.

{
  "client_id": "https://app.example.com/oauth/client.json",
  "client_name": "Example Agent",
  "client_uri": "https://app.example.com",
  "redirect_uris": ["https://app.example.com/oauth/callback", "http://127.0.0.1/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}
  • The URL must be HTTPS with a path, without a fragment, user name, password or ./.. segments, and at most 2048 characters.
  • imsu.io fetches it with Accept: application/json, from public addresses only, without following redirects, within 5 seconds. The document must be a JSON object of at most 5 KB.
  • client_id must equal the URL exactly; redirect_uris must hold 1 to 10 valid redirect URIs; token_endpoint_auth_method must be absent or none. client_name defaults to the URL's host.
  • The document is cached as its Cache-Control: max-age says, at least 5 minutes and at most 24 hours. When the cached copy has expired and can't be fetched again, the authorization fails (client_metadata_unavailable); a stale copy is never used.
  • Hosts are shown in their ASCII form (xn--… for internationalized names), so a look-alike name can't pass for another.

Dynamic Client Registration

#

POST https://imsu.io/oauth/register takes a JSON body of at most 16 KB and registers a public client (example).

  • redirect_uris (required): 1 to 10 redirect URIs.
  • client_name: trimmed, without control characters, at most 100 characters; "Unnamed app" if missing.
  • client_uri: kept only if it is HTTPS. logo_uri is ignored: imsu.io shows no logos.
  • grant_types and response_types may only list authorization_code, refresh_token and code.
  • token_endpoint_auth_method is always registered as none; no secret is issued.
  • Registered apps are marked unverified on the consent page. Registrations that are never used to connect are deleted after 7 days.
  • Each address may register 10 clients an hour; then 429 with Retry-After.

Errors are 400 with { "error", "error_description" }: invalid_redirect_uri for a redirect URI that isn't allowed, invalid_client_metadata for anything else.

{
  "error": "invalid_redirect_uri",
  "error_description": "An http redirect URI is only allowed on a loopback host (127.0.0.1, [::1] or localhost). Use https."
}

Redirect URIs

#

A redirect URI is an absolute URI without a fragment, and one of:

  • HTTPS, e.g. https://app.example.com/oauth/callback;
  • HTTP on a loopback host: http://127.0.0.1, http://[::1] or http://localhost;
  • an app's own scheme, e.g. cursor://anysphere.cursor-mcp/oauth/callback or com.example.app:/callback.

Schemes such as javascript:, data:, file:, ws: and ftp: are refused. The redirect_uri of an authorization request must match a registered one exactly, except that on a loopback host any port matches, because native apps listen on a port they pick at run time. Scheme, host, path and query must still match.

PKCE

#

PKCE is required, with code_challenge_method=S256; plain is refused. The token request must send the code_verifier whose S256 hash is the code_challenge of the authorization request (example).

Resource

#

The only resource is https://imsu.io/mcp. resource is optional in the authorization and token requests; if sent, it must be that value, or the answer is invalid_target. Every token is issued for it, and other resources refuse it.

Scopes

#
Scope Allows
monitors:read List and read monitors and the plan's limits
monitors:write Create, change, pause, resume and delete monitors; includes monitors:read

Scopes imsu.io doesn't know (openid, offline_access, …) are ignored, and asking for none of these two, or leaving scope out, asks for both. The user may allow reading only. Such a connection gets a 403 insufficient_scope challenge when it calls a tool that changes monitors; ask again with both scopes to step up (details).

Token lifetimes

#
Format Lifetime
Authorization code imsu_ac_… 60 seconds, once
Access token imsu_at_… 1 hour (expires_in: 3600)
Refresh token imsu_rt_… 30 days, replaced on every use
Consent request sealed request handle 10 minutes to sign in and allow
Connection (grant) Until the user disconnects it, the app revokes it, or a reused code or refresh token ends it

Tokens are opaque: don't parse them. imsu.io stores only their SHA-256 hashes. Connecting the same app again for the same user and account replaces the old connection and its tokens.

Refresh token rotation

#

Every refresh returns a new refresh token, and the old one stops working. If an old refresh token is presented again, imsu.io assumes it was stolen and ends the connection with all its tokens:

{
  "error": "invalid_grant",
  "error_description": "The refresh token was already used, so the access it gave was revoked. Connect the app again."
}

The same happens when an authorization code is used twice. Store the newest refresh token before using the new access token, and don't refresh from two places at once.

Disconnecting

#

The user sees every connected app at https://imsu.io/settings, under Connected apps, with what it may do and what it changed recently, and can disconnect it there. Disconnecting, or the app revoking its refresh token, ends all of the connection's tokens at once: the next request gets 401. While the account is suspended, or once the user is no longer a member of it, its tokens stop working too.

Errors at the authorization endpoint

#

While imsu.io can't trust where to send the browser (the client or its redirect URI is unknown or doesn't match), it shows its own error page instead: https://imsu.io/authorize?error=<code>.

Code Meaning
invalid_request client_id or redirect_uri is missing or sent twice
unknown_client No registered client has this client_id
client_metadata_unavailable The client metadata document couldn't be fetched, or isn't valid
invalid_redirect_uri redirect_uri isn't one of the client's

Once client and redirect URI are known, other errors go back to a native app's redirect URI (loopback HTTP or its own scheme) with error, error_description, state and iss; an HTTPS redirect URI gets the error page instead, so imsu.io can't be used to send people to any site:

http://127.0.0.1:33418/callback?error=invalid_request&error_description=code_challenge_method%20must%20be%20S256;%20plain%20is%20not%20supported.&state=af0ifjsldkj&iss=https%3A%2F%2Fimsu.io
error Meaning
invalid_request PKCE is missing or malformed, the method isn't S256, response_type is missing, or a parameter was sent twice
unsupported_response_type response_type isn't code
invalid_target resource isn't https://imsu.io/mcp
access_denied The user clicked Cancel

Errors at the token endpoint

#

The token endpoint takes form-encoded bodies of at most 16 KB and answers errors as { "error", "error_description" } with Cache-Control: no-store:

Status and error Meaning
400 invalid_request A parameter is missing or sent twice, the body isn't form-encoded or is too large, or the code_verifier is malformed
400 unsupported_grant_type grant_type isn't authorization_code or refresh_token
401 invalid_client client_id is unknown
400 invalid_grant The code or refresh token is unknown, expired, revoked, already used, or another client's; the code_verifier doesn't match; redirect_uri differs from the authorization request's
400 invalid_target resource isn't the token's resource
400 invalid_scope A refresh asked for a scope the user didn't grant

Rate limits

#

Per client address: 60 requests a minute to each of /oauth/authorize, /oauth/token and /oauth/revoke, and 10 registrations an hour at /oauth/register. Over a limit, the answer is 429 with Retry-After in seconds. The MCP server's own limit is per credential (details).

Troubleshooting

#
Problem Likely cause Fix
The browser shows "imsu.io doesn't know this app" The client_id was never registered, or the registration was deleted after 7 days unused Register again, or use a client metadata document
"This app asked to return to an address it didn't register" redirect_uri doesn't match a registered one Send a registered redirect URI; on loopback only the port may differ
"imsu.io couldn't read this app's details" The metadata document is unreachable, too large, slow, or invalid Serve valid JSON of at most 5 KB from a public address, without redirects
"This sign-in request has expired" More than 10 minutes passed between the authorization request and Allow Start the authorization again
invalid_grant when exchanging the code The code expired (60 seconds), was already used, or the code_verifier doesn't match Exchange the code right away, once, with the verifier of the same request
invalid_grant "already used, so the access it gave was revoked" A refresh token or code was used twice Connect again, and keep only the newest refresh token
401 from /mcp with error="invalid_token" The access token expired or was revoked Refresh it; if that fails, connect again
401 "Token not accepted here" from /api/v1 OAuth tokens only work at /mcp Use an API key for the REST API
403 insufficient_scope from /mcp The user allowed reading only Ask for monitors:read monitors:write again
429 Too many requests from one address or credential Wait Retry-After seconds