Developers Get an API key
MCP

OAuth for MCP clients

Discovery, dynamic client registration, authorization code with PKCE, and the client credentials grant.

Sentinel implements the authorization flow the MCP spec expects, so standard MCP clients connect with just the server URL. You only need this page if you're building a client or debugging a connection.

All endpoints live on your firm's API address, https://yourfirm.api.sntlabs.io.

Endpoint Purpose Standard
GET /.well-known/oauth-protected-resource Points clients at the authorization server RFC 9728
GET /.well-known/oauth-authorization-server Authorization server metadata RFC 8414
POST /oauth/register Dynamic client registration RFC 7591
GET /oauth/authorize Sentinel sign-in and consent page OAuth 2.1
POST /oauth/token Exchanges a code (or an API key) for an access token OAuth 2.1

1. Discover

An unauthenticated request to /mcp returns 401 with a WWW-Authenticate header that names the resource metadata:

http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://yourfirm.api.sntlabs.io/.well-known/oauth-protected-resource"

The resource metadata names the authorization server, which is the same host:

GET /.well-known/oauth-protected-resource
{
  "resource": "https://yourfirm.api.sntlabs.io/mcp",
  "authorization_servers": ["https://yourfirm.api.sntlabs.io"],
  "bearer_methods_supported": ["header"]
}
GET /.well-known/oauth-authorization-server
{
  "issuer": "https://yourfirm.api.sntlabs.io",
  "authorization_endpoint": "https://yourfirm.api.sntlabs.io/oauth/authorize",
  "token_endpoint": "https://yourfirm.api.sntlabs.io/oauth/token",
  "registration_endpoint": "https://yourfirm.api.sntlabs.io/oauth/register",
  "grant_types_supported": ["client_credentials", "authorization_code"],
  "response_types_supported": ["code"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic", "none"],
  "scopes_supported": ["mcp"]
}

2. Register

Clients register themselves before the first sign-in. redirect_uris is required. Send a client_name, because Sentinel names the user's connection after it (for example Claude or ChatGPT).

bash
curl -s -X POST https://yourfirm.api.sntlabs.io/oauth/register \
  -H "Content-Type: application/json" \
  -d '{"client_name":"My Assistant","redirect_uris":["https://assistant.example.com/oauth/callback"]}'
201 Created
{
  "client_id": "…",
  "client_id_issued_at": 1792771200,
  "client_name": "My Assistant",
  "redirect_uris": ["https://assistant.example.com/oauth/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code"],
  "response_types": ["code"],
  "scope": "mcp"
}

Registration issues a public client with no secret. Security comes from PKCE and the exact redirect_uri match.

3. Authorize

Send the user's browser to the authorization endpoint:

https://yourfirm.api.sntlabs.io/oauth/authorize
  ?response_type=code
  &client_id=<client_id>
  &redirect_uri=https://assistant.example.com/oauth/callback
  &code_challenge=<BASE64URL(SHA256(code_verifier))>
  &code_challenge_method=S256
  &state=<random>
  &scope=mcp
Parameter Required Notes
response_type Yes Must be code
redirect_uri Yes An http or https URL. The token request must send the same value.
code_challenge Yes PKCE challenge
code_challenge_method Yes Must be S256
state Recommended Returned unchanged
client_id, scope Optional scope is always mcp

The user signs in on a Sentinel page with their email and password, then an authenticator code if they use two-factor authentication. Sentinel then checks that the account belongs to this firm and is allowed to connect apps.

On success, the browser is redirected back with a code:

https://assistant.example.com/oauth/callback?code=<code>&state=<state>

If the user selects Deny, the redirect carries error=access_denied instead.

Note

Authorization codes are single-use and expire after 60 seconds. Exchange them right away.

4. Exchange the code

bash
curl -s -X POST https://yourfirm.api.sntlabs.io/oauth/token \
  -d grant_type=authorization_code \
  -d code=<code> \
  -d code_verifier=<code_verifier> \
  -d redirect_uri=https://assistant.example.com/oauth/callback \
  -d client_id=<client_id>
200 OK
{
  "access_token": "sk_live_…",
  "token_type": "Bearer",
  "expires_in": 31536000
}

The access token is a Sentinel API key issued for this connection. It appears in the user's Connected apps under the client's name. Send it as Authorization: Bearer <access_token> on every MCP request.

There's no refresh token. The token stays valid until the user disconnects the app or an admin revokes it. expires_in is a nominal one year; when a call returns 401, run the sign-in again.

Client credentials with an API key

Some MCP clients (Claude Enterprise's original connector, for example) can only do the client_credentials grant. Paste a Sentinel API key in as the client secret, and the token endpoint returns that same key as the access token:

bash
curl -s -X POST https://yourfirm.api.sntlabs.io/oauth/token \
  -d grant_type=client_credentials \
  -d client_secret=sk_live_...

HTTP Basic (client_secret_basic) works too. Prefer the authorization code flow where the client supports it: each user then signs in as themselves, rather than everyone sharing one person's key.

Token errors

The token endpoint returns standard OAuth errors:

error When
invalid_request Missing code, code_verifier or redirect_uri
invalid_grant The code is unknown, already used, expired, or doesn't match the redirect_uri or PKCE verifier. error_description says which.
invalid_client client_credentials without a key, or with an invalid, revoked or expired key
unsupported_grant_type Anything other than authorization_code or client_credentials
slow_down (HTTP 429) More than 20 requests a minute from one IP to register, authorize or token