MCP Security
How OAuth connections and personal access tokens are scoped, revoked and audited, and what to do when a credential leaks.
Threat model
The MCP server accepts two credentials over HTTPS — an OAuth 2.1 access token the creator granted by signing in, or a personal access token — and runs the request inside the same tenancy boundary as the REST API. The most common risks are:
- A token leaking into LLM conversation logs — Claude, Cursor, and ChatGPT Desktop all log the session. A developer sharing a transcript can leak an
Authorizationheader if the client logs it. - Over-privileged credentials — minting a personal access token with every ability ticked, or connecting over OAuth from an account with more reach than the task needs, makes a leak costly.
- Accidental writes — an eager agent executing a tool that turns out to mutate state.
We design for the first two and accept the third as a prompt-engineering issue, with the server marking every tool that changes or deletes data as destructive so a client asks before running it. Below are the controls.
OAuth connections
- Consent, not a pasted secret. The creator signs in to the dashboard, with two-factor and passkeys applying as usual, and reads a consent screen that names the client, the signed-in account, the reach of the connection and where they will be sent back to. Nothing is copied to disk.
- Short-lived access. An access token lasts one hour. The refresh token lasts 30 days from its last use and is rotated on every renewal; presenting a spent one is refused, so a copied refresh token dies the moment the client renews.
- Public clients with PKCE. Every client registers dynamically as a public client and must present an
S256code challenge; there is no client secret to leak. - One scope, the creator's own reach. The connection acts as the creator on the team they were working in, with every ability their role grants there, inside the same tenant boundary as the dashboard. A connection that should be narrower is a personal access token instead.
- Disconnect ends it. Disconnecting the server inside the client discards its tokens; the access token expires within the hour and the refresh token cannot be renewed once discarded.
Personal access tokens
Prefix scanning
All tokens start with sbt_. GitHub's secret scanning treats this as a Subscriby-specific credential and alerts on push. If a token lands in a public repo, we usually find out before the creator does.
Mandatory team scope
Every token carries scope:team:<uuid> frozen at mint time. A leaked token is constrained to exactly one team — it can never pivot. Project-level scope:project:<uuid> tuples tighten this further.
Minimum-privilege defaults
The token-minting UI pre-selects no abilities and makes you opt into each. This is deliberate friction — most tokens only need two or three abilities.
Expiry
Personal access tokens do not expire automatically. Long-lived "production" tokens (Zapier, CI, ops dashboards) should be rotated manually on your own cadence — revoke the old token in the dashboard, mint a fresh one with the same abilities, and update the client config.
Revocation
Revoke a token from Settings → API Tokens → Revoke. Revocation is immediate; any client still holding the token gets AUTHENTICATION_REQUIRED on its next call.
Audit trail
Every MCP tool invocation is recorded in the activity log with the invoking user as the causer, the subject entity, and an actor_kind field that distinguishes MCP calls from dashboard and REST actions. Review what an agent did in Settings → Activity Log, or read the log over the API via the get_activity_log MCP tool or GET /v1/activity REST endpoint.
Leaked-credential playbook
For a personal access token:
- Revoke the token in Settings → API Tokens.
- Review the audit trail for the last 24 hours under that token's
causer_id. - If writes happened you didn't authorise, contact support with the token ID (not the secret).
- Mint a replacement token with the same abilities.
- Update the client config (Claude Desktop, Cursor, etc.) with the new token.
For an OAuth connection whose client or device you no longer trust: disconnect the server inside the client, change your Subscriby password (which signs out every browser session), and review the audit trail the same way. Access the connection still held expires within the hour.
Related
How is this guide?