Early bird discounts live! Claim your offer

MCP Authentication

How a client connects to the Subscriby MCP server — OAuth 2.1 by signing in, or a personal access token — and how every tool enforces its ability.

The MCP server at https://mcp.subscriby.net accepts two credentials on the same endpoint:

CredentialHow you get itActs asBest for
OAuth 2.1 access tokenThe client asks; you sign in to Subscriby and press Authorize. No token to copy.You, on the team you are working in, with every ability you hold thereClaude, Claude Code, Cursor, VS Code, ChatGPT — any OAuth-aware client
Personal access token (sbt_…)Settings → API Tokens, choosing the abilities and the team scope by handThe abilities and scope:team: / scope:project: you mintedScripts, CI, clients you configure with a header, narrow grants

Both run through the same tenant resolution and the same per-tool ability gate. Nothing about a tool changes with the credential; what changes is how broad the credential is and how it was granted.

Connecting with OAuth

Add the server URL — exactly https://mcp.subscriby.net — to a client that supports remote MCP servers and choose to connect. The client discovers the authorization server on its own, registers itself, and sends you to Subscriby:

  1. If you are not signed in to the dashboard, you sign in first (two-factor and passkeys apply as usual).
  2. Subscriby shows a consent screen naming the client, the account you are signed in as, and what the connection will be able to do.
  3. Authorize returns you to the client, which now holds a short-lived access token and a refresh token.

An access token lasts one hour. The client renews it silently with the refresh token, which lasts 30 days from its last use and is rotated on every renewal; a connection you stop using expires by itself. Disconnecting the server inside the client discards both tokens.

What an OAuth connection can do

The connection acts as you, on the team you had selected when you authorized it, with every ability your role there grants — the same reach you have in the dashboard, and the same tenant boundary. Tools still enforce their own abilities, and destructive tools still ask for confirmation inside the client before they run. If you want a connection that can only read, or only reach one project, mint a personal access token with those abilities instead and configure the client with it.

Under the hood

For anyone wiring up a client by hand, the server follows the MCP authorization specification:

  • An unauthenticated call is answered with 401 and WWW-Authenticate: Bearer resource_metadata="https://mcp.subscriby.net/.well-known/oauth-protected-resource", scope="mcp:use".
  • The protected resource metadata names the endpoint as its resource and https://app.subscriby.net as the authorization server.
  • https://app.subscriby.net/.well-known/oauth-authorization-server lists the authorization, token and dynamic registration endpoints; the grants are authorization_code and refresh_token, PKCE is S256 only, and the one scope is mcp:use.
  • Clients register dynamically (RFC 7591) as public clients; the token endpoint accepts application/x-www-form-urlencoded; a spent or rotated refresh token is refused with invalid_grant.

Connecting with a personal access token

Mint a token in Settings → API Tokens and send it on every request:

Authorization: Bearer sbt_live_<id>_<secret>

Configure the client with the header (each client page shows where). The token carries only the abilities you ticked, frozen to one team and optionally to a list of projects, exactly as on the REST API.

Per-tool abilities

Each tool enforces one concrete ability from the catalog. A personal access token carrying project:view-any can call list_projects but receives TOKEN_MISSING_ABILITY on list_subscribers; an OAuth connection satisfies every ability because the creator consented as themselves. Grant personal access tokens narrowly.

An ability means the same thing on both transports: a tool requires exactly what its REST equivalent does, so mint for the operations you intend to call rather than for the channel you intend to call them over.

ToolRequired ability
list_projectsproject:view-any
get_projectproject:view
list_subscribersproject-user:view-any
list_plansproject-subscription-plan:view-any
list_access_codesproject-access-code:view-any
list_webhook_endpointswebhook-endpoint:view-any
get_activity_logactivity:read

The tools reference carries the ability mapping for every tool.

There is no channel ability

MCP used to require a token carrying mcp:full before any tool handler ran. It was removed in 3.0.0: any personal access token whose abilities cover the tools it calls may use MCP, exactly as with REST, and nothing in the ability model got wider.

Team and project scope

A personal access token carries scope:team:<uuid> and optional scope:project:<uuid> entries, the same ones the REST API uses. An OAuth connection is scoped to the team you were working in when you authorized it and to every project on it. The MCP server runs the same tenant-resolution step either way, so every tool call is team-isolated exactly as a REST request is.

Revocation

  • Personal access tokens: revoke from Settings → API Tokens. Revocation takes effect on the next call; any client still holding the token receives AUTHENTICATION_REQUIRED.
  • OAuth connections: disconnect the server inside the client, which discards its tokens. The access token it held expires within the hour and the refresh token 30 days after its last use.

Rate limiting

Authenticated MCP calls are bucketed at 120/min per token — per personal access token, or per OAuth access token. Hitting the limit returns RATE_LIMITED with Retry-After. Claude, Cursor, ChatGPT Desktop and VS Code all pace their tool calls internally; exhausting this bucket in practice usually means a tight loop on the client side.

Unauthenticated traffic to the MCP host (probes, port scanners, misconfigured clients with no credential attached) is bucketed separately at 5/min per IP. The OAuth discovery documents and the registration endpoint have their own bucket of 60/min per IP, so a client's first handshake is never throttled by someone else's probing. See Rate limiting for the full bucket table.

CORS

Browser-based MCP clients (the hosted MCP Inspector, in-browser playgrounds, web extensions) issue cross-origin requests against mcp.subscriby.net. The server returns a permissive Access-Control-Allow-Origin: * so any browser origin can complete the handshake; Access-Control-Allow-Credentials is not set because we authenticate exclusively through Authorization: Bearer … and never accept cookies on this surface.

The CORS layer exposes the request/response headers a browser client needs to read:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, Accept, MCP-Session-Id, MCP-Protocol-Version, Last-Event-ID
Access-Control-Expose-Headers: MCP-Session-Id, X-RateLimit-Limit, X-RateLimit-Remaining, Retry-After, WWW-Authenticate
Access-Control-Max-Age: 86400

Preflight OPTIONS requests short-circuit before authentication so a browser can complete its handshake without first owning a credential. Native MCP clients ignore CORS entirely — these headers only matter when the caller is a browser.

Logging and auditing

Every MCP tool invocation is recorded in the activity log and is reachable through the activity:read ability (via either the REST GET /v1/activity endpoint or the get_activity_log MCP tool). Each entry identifies the invoking creator and the credential kind, so you can audit what an agent did on your behalf.

How is this guide?

On this page

Subscriby is a product designed by you — for you.

No boardroom full of executives deciding what we ships next. Our roadmap always shaped by you with your feedback.

Share feedback or a request