Authentication: OAuth 2.0, client metadata documents and scopes

Simp is both the OAuth 2.0 authorisation server and the MCP resource server: discovery metadata, Client ID Metadata Documents instead of registration, authorization code with PKCE S256, RFC 9207 issuer identification, resource-bound tokens, four scopes, rotation and revocation.

Simp is both the authorisation server and the resource server for Simp Connect. Your client discovers everything it needs from two metadata documents, identifies itself with a Client ID Metadata Document (an HTTPS URL you host), walks an authorization-code flow with PKCE, and then calls the MCP endpoint with a Bearer token bound to that endpoint. There is no registration step, no API key and no shared secret. The rules are the MCP 2026-07-28 authorization specification.

Discovery#

DocumentURL
Authorisation server metadata (RFC 8414)https://voice.simpmobile.com/.well-known/oauth-authorization-server
Protected resource metadata for the MCP endpoint (RFC 9728)https://voice.simpmobile.com/.well-known/oauth-protected-resource/mcp

An unauthenticated request to https://voice.simpmobile.com/mcp returns 401 with a WWW-Authenticate challenge that points at the protected-resource document, so a spec-compliant client bootstraps the whole flow from the MCP URL alone.

json
{
  "issuer": "https://voice.simpmobile.com",
  "authorization_endpoint": "https://voice.simpmobile.com/oauth/authorize",
  "token_endpoint": "https://voice.simpmobile.com/oauth/token",
  "revocation_endpoint": "https://voice.simpmobile.com/oauth/revoke",
  "client_id_metadata_document_supported": true,
  "authorization_response_iss_parameter_supported": true,
  "scopes_supported": ["tasks:write","tasks:read","input:respond","account:read"],
  "response_types_supported": ["code"],
  "response_modes_supported": ["query"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none", "client_secret_basic", "client_secret_post"],
  "service_documentation": "/developers"
}

There is no registration_endpoint: Simp does not support Dynamic Client Registration (RFC 7591), which the MCP specification deprecated on 2026-07-28.

The flow#

  1. Step 1Identify: your client_id is the HTTPS URL of your Client ID Metadata Document. See Client ID Metadata Documents.
  2. Step 2Authorise: send your user to GET /oauth/authorize with response_type=code, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method=S256 and resource=https://voice.simpmobile.com/mcp. Simp fetches your document, checks the redirect URI against it, and shows your user a consent screen. Approve redirects back with code, state and iss.
  3. Step 3Check iss: before you exchange the code, compare iss with the issuer you recorded from the metadata, by exact string comparison. Every authorization response carries it, errors included. See Issuer identification.
  4. Step 4Exchange: POST /oauth/token (application/x-www-form-urlencoded) with grant_type=authorization_code, the code, the same redirect_uri, the code_verifier, your client_id and (optionally) the same resource. You authenticate as a public client: send no secret and no client assertion. Returns an access token and a refresh token.
  5. Step 5Call: Authorization: Bearer <access token> on every request to https://voice.simpmobile.com/mcp.
  6. Step 6Refresh: POST /oauth/token with grant_type=refresh_token. Every refresh rotates the refresh token; store the new one.

PKCE is mandatory#

Only S256 is accepted; plain is rejected. A public client (every metadata-document client) cannot obtain an authorization code without a code_challenge.

Redirect URIs#

Matching is exact, with the one exception RFC 8252 section 7.3 requires: a loopback URI in your document (http://127.0.0.1/... or http://localhost/...) may be used on any port, which is how Claude Code, mcp-remote and other CLIs bind an ephemeral local port. Scheme, host and path still match exactly. Custom URL schemes are not accepted.

Client ID Metadata Documents#

Your client_id is an HTTPS URL with a path (for example https://agent.example.com/oauth/client.json), and that URL serves your client's registration as JSON:

json
{
  "client_id": "https://agent.example.com/oauth/client.json",
  "client_name": "My agent",
  "client_uri": "https://agent.example.com",
  "logo_uri": "https://agent.example.com/logo.png",
  "redirect_uris": ["https://agent.example.com/oauth/callback"],
  "application_type": "web",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}
FieldRules
client_idRequired. Exactly the URL the document is served from, byte for byte (no trailing-slash or case differences).
client_nameRequired. Your consent screen shows your document's HOST as who is asking (a name in a document is self-asserted), and Connected apps shows the same.
redirect_urisRequired. Each https://, or http:// on a loopback host. No fragments, no custom schemes.
application_typeweb (hosted redirects only) or native (loopback redirects only). If you omit it, Simp derives it from your redirect URIs; a mix of hosted and loopback redirects is refused.
token_endpoint_auth_method / token_endpoint_auth_methods_supportedMust allow none: Simp authenticates every metadata-document client as a public client with PKCE. If you list private_key_jwt as well, Simp uses none, and a token request that carries a client assertion or a secret anyway is refused with invalid_client.
grant_types / response_typesWhen present, must include authorization_code and code.
client_secretMust not appear.

How Simp fetches your document: HTTPS on port 443, to a host that resolves only to public addresses, with no redirects followed, at most 16 KiB, within 5 seconds. Simp honours your Cache-Control: max-age between 60 seconds and 24 hours (300 seconds when you send none) and fetches again on the next authorisation after that. A document that fails any rule refuses the authorisation, and your user sees "This app's details couldn't be checked".

If your user connects from a CLI or a desktop app whose only redirects are loopback addresses, the consent screen warns them that the app runs on their own computer, because any local program can listen on a loopback port.

Issuer identification#

Simp implements RFC 9207. Every authorization response, success and error, carries iss=https://voice.simpmobile.com, the exact value of issuer in the metadata and of authorization_servers[0] in the protected resource metadata. Record the issuer before you redirect your user, then compare iss against it with simple string comparison before you send the code anywhere; on a mismatch, discard the response and do not display its error fields. This closes the authorization-server mix-up attack. ChatGPT and Codex use their stable callback (https://chatgpt.com/connector_platform_oauth_redirect) and stable client id (https://chatgpt.com/oauth/client.json) with an authorisation server that does this.

Tokens are bound to the resource and the issuer#

Simp implements RFC 8707 resource indicators. Send resource=https://voice.simpmobile.com/mcp on the authorization request (if you omit it, the MCP endpoint is assumed). The authorization code, the access token and the refresh token all record that resource, and the MCP endpoint accepts a token only when it names the MCP endpoint. A resource on the token request must match the authorization request.

Every code and token also records the issuer that minted it, and Simp refuses one minted by any other issuer. Key the credentials you store by issuer, and never reuse them with a different authorisation server.

Scopes#

Both text columns below are the verbatim strings the user reads on the consent screen, which is why they address the user directly rather than you - quoting them is the point, so you know exactly what your users are agreeing to.

ScopeConsent screen titleConsent screen wording
tasks:writeCreate and cancel tasksSet up new call tasks for your simps, and cancel them.
tasks:readSee your tasksView your recent tasks, call progress and results.
input:respondPass along your answersRelay your responses when a simp asks for input or approval mid-call.
account:readBasic account infoYour simps, your Simp virtual numbers, and your first name if sharing allows it.

An empty or missing scope parameter requests all four (or the client's registered subset). Any unknown scope value rejects the whole parameter with invalid_scope rather than being silently dropped. Per-tool scope failures surface as in-band tool errors (insufficient_scope), not as HTTP 403 - see the Errors catalogue.

What a scope never unlocks: the user's password, their display name, About You values outside their sharing rules, raw phone numbers of third parties, or call audio. get_user_info returns the first name only when the user's sharing rules allow it, through the same gate the simps use on a call.

Tokens#

CredentialPrefixLifetime
Authorization codesimp_ac_5 minutes, single use
Access tokensimp_at_1 hour
Refresh tokensimp_rt_30 days, rotated on every use
Client secretsimp_cs_No expiry (hash-stored)

All credentials are opaque, high-entropy random strings, stored only as SHA-256 digests and compared in constant time. The prefixes make a leaked credential recognisable to secret scanners.

Rotation and reuse detection#

Every refresh rotates the refresh token. Presenting a refresh token that has already been rotated is treated as theft or leakage: the whole grant is revoked - every access and refresh token that client holds for that user - and the user is notified in Simp. The same happens if an authorization code is presented twice. Build your client to persist the new refresh token atomically before using it, and to fall back to a fresh authorisation if a refresh fails with invalid_grant.

Revocation and disconnect#

  • POST /oauth/revoke (RFC 7009) lets a client revoke its own tokens.
  • Disconnect in the Simp app (Connected apps) revokes every live token for that app immediately. The app has to go through consent again to reconnect.
  • Consent, issuance, refresh, revocation and disconnects are all written to Simp's audit ledger.

Rate limits#

LimitValue
Tool invocations per user per client60 per minute
Task creation per user per client20 per minute (on top of the tool limit)
Tool invocations per client across all users600 per minute for the platforms' own clients (ChatGPT, Claude, Claude Code), 120 for any other metadata document until raised
Task creation per client across all users200 per minute for the platforms' own clients, 40 for any other metadata document until raised

A hit is an in-band rate_limited tool error carrying retry_after_seconds; back off for that long. On top of these, every connection has a daily task cap set by its permission mode.

Pre-registered clients#

Two pre-registered confidential clients (simp_chatgpt, simp_claude) exist for an installation that cannot host a metadata document. They are web clients with hosted redirect URIs only, authenticate with client_secret_basic or client_secret_post, and their secrets are never published; ask through your usual Simp contact if you genuinely need one. Prefer a metadata document.