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/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:
{
"resource": "https://yourfirm.api.sntlabs.io/mcp",
"authorization_servers": ["https://yourfirm.api.sntlabs.io"],
"bearer_methods_supported": ["header"]
}{
"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).
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"]}'{
"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.
Authorization codes are single-use and expire after 60 seconds. Exchange them right away.
4. Exchange the code
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>{
"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:
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 |