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#
| Document | URL |
|---|---|
| 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.
{
"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#
Identify: your client_idis the HTTPS URL of your Client ID Metadata Document. See Client ID Metadata Documents.
Authorise: send your user to GET /oauth/authorizewithresponse_type=code,client_id,redirect_uri,scope,state,code_challenge,code_challenge_method=S256andresource=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 withcode,stateandiss.
Check iss: before you exchange the code, compareisswith theissueryou recorded from the metadata, by exact string comparison. Every authorization response carries it, errors included. See Issuer identification.
Exchange: POST /oauth/token(application/x-www-form-urlencoded) withgrant_type=authorization_code, thecode, the sameredirect_uri, thecode_verifier, yourclient_idand (optionally) the sameresource. You authenticate as a public client: send no secret and no client assertion. Returns an access token and a refresh token.
Call: Authorization: Bearer <access token>on every request tohttps://voice.simpmobile.com/mcp.
Refresh: POST /oauth/tokenwithgrant_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:
{
"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"
}| Field | Rules |
|---|---|
client_id | Required. Exactly the URL the document is served from, byte for byte (no trailing-slash or case differences). |
client_name | Required. 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_uris | Required. Each https://, or http:// on a loopback host. No fragments, no custom schemes. |
application_type | web (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_supported | Must 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_types | When present, must include authorization_code and code. |
client_secret | Must 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.
| Scope | Consent screen title | Consent screen wording |
|---|---|---|
tasks:write | Create and cancel tasks | Set up new call tasks for your simps, and cancel them. |
tasks:read | See your tasks | View your recent tasks, call progress and results. |
input:respond | Pass along your answers | Relay your responses when a simp asks for input or approval mid-call. |
account:read | Basic account info | Your 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#
| Credential | Prefix | Lifetime |
|---|---|---|
| Authorization code | simp_ac_ | 5 minutes, single use |
| Access token | simp_at_ | 1 hour |
| Refresh token | simp_rt_ | 30 days, rotated on every use |
| Client secret | simp_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#
| Limit | Value |
|---|---|
| Tool invocations per user per client | 60 per minute |
| Task creation per user per client | 20 per minute (on top of the tool limit) |
| Tool invocations per client across all users | 600 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 users | 200 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.
