https://api.nextlevelmca.com. MCP clients discover it, register themselves, and send users through a magic-link sign-in before any token is issued. This page is the reference for what the server does; you do not need to implement any of it to use Claude or ChatGPT.
Discovery
An unauthenticated request to
/mcp returns 401 with WWW-Authenticate: Bearer resource_metadata="https://api.nextlevelmca.com/mcp/.well-known/oauth-protected-resource", which is how clients find the authorization server without configuration.
GET /.well-known/oauth-protected-resource
GET /.well-known/oauth-authorization-server
Dynamic client registration
POST /oauth/register (RFC 7591) is open, as MCP expects. Send client_name, redirect_uris and optionally logo_uri, client_uri, grant_types and token_endpoint_auth_method. Redirect URIs must be absolute https URLs (http://localhost and http://127.0.0.1 on any port are allowed for local clients), with no wildcards or fragments. The response is 201 with a client_id; clients are public (token_endpoint_auth_method: "none"), there is no client secret. Registration is limited to 10 per hour per IP address, and clients unused for 90 days are removed.
Authorization flow
- Authorize.
GET /oauth/authorizewithresponse_type=code,client_id,redirect_uri,scope,state,code_challenge,code_challenge_method=S256and optionallyresource.client_idandredirect_uriare validated first; on failure the server renders an error page and never redirects to an unvalidated URI. Only PKCE S256 is accepted;plainis rejected. Request parameters are stored server-side under an opaque request id for 30 minutes, not carried in URLs. - Sign in. The user enters their email. If it belongs to an active user, a magic link is sent; the page says “check your email” either way, so addresses cannot be enumerated. Links are single use, expire after 10 minutes, and are limited to 3 per email per 15 minutes. Opening one sets a signed,
HttpOnly,Secure,SameSite=Laxsession cookie that lives 30 minutes. - Location. Users who belong to several locations pick one; users with one location skip this. The grant is pinned to that location.
- Consent. The user sees the client, the location and each scope in plain words, then approves or denies. An existing, unrevoked consent for the same client, location and scopes skips this step.
- Code. The server issues an authorization code bound to the client, redirect URI, scopes, location and PKCE challenge. Codes expire after 60 seconds and are single use; presenting a consumed code revokes everything issued from it.
- Token.
POST /oauth/token(JSON or form-encoded) withgrant_type=authorization_code,code,redirect_uri,client_idandcode_verifierreturns an access token, a refresh token,expires_in: 3600and the grantedscope. Limited to 60 requests per minute per IP address.
Tokens
Access tokens are RS256-signed JWTs, valid for 1 hour, verified against the JWKS bykid. Claims: iss (https://api.nextlevelmca.com), sub (the user id), aud (the resource requested, by default https://api.nextlevelmca.com/mcp), location_id, scope, client_id, iat, exp and jti. The MCP server only accepts tokens whose aud is its own URL, so a token cannot be replayed against another service; /v1 accepts tokens minted for either the MCP server or the API.
Refresh tokens are opaque, stored hashed, and rotate on every use: each refresh returns a new refresh token in the same family and revokes the old one. Presenting an already-rotated token is treated as theft and revokes the whole family. Lifetime is 30 days, sliding on each rotation, with an absolute cap of 90 days from the first grant.
Every token, code and magic link is stored as a SHA-256 hash; raw values are never persisted.
Scopes and roles
Two independent checks apply to every tool call and API request:- Scopes limit breadth. They are the resources the connection may touch, chosen at consent. A tool without its scope returns a clear error naming the scope.
- Role limits depth. The connection acts as the signed-in user, with that user’s role from the app: which deals they can see, whether they may withdraw a submission or accept an offer, whether
dobis returned. Scopes can never grant more than the user’s role allows.
Tenant isolation
A token carries exactly onelocation_id, chosen at the location step. Every read and write is filtered by it; a record from another location does not exist as far as that token is concerned (it returns 404). On every request the server also checks that the user is still active in that location; a removed or deactivated user’s tokens stop working at once, regardless of their expiry.
Revoking access
- Settings → Developers → Connected apps in the app lists each consent for the location with the user, client, scopes, when it was granted and last used. Revoke deletes the consent and every refresh-token family for that user, client and location. Outstanding access tokens expire within the hour and cannot be refreshed.
- Clients can call
POST /oauth/revokewith a refresh token to revoke its family. - Removing the connector inside Claude or ChatGPT discards the client’s tokens but leaves the consent in place; revoke in the app as well when offboarding.
- Deactivating the user in the app invalidates all of their tokens immediately.