Token Vault
MCP OAuth

OAuth Endpoint Reference

Every OAuth 2.1 endpoint with request/response shapes and curl examples — discovery, registration, authorize, token, refresh, revoke.

All endpoints live on the API domain. Well-behaved clients never hard-code these paths — they read them from the discovery documents. Errors follow RFC 6749 §5.2: a top-level {"error", "error_description"} body. Every endpoint is rate limited; 429 responses carry a Retry-After header.

MethodPathPurpose
GET/.well-known/oauth-protected-resourceWhich AS governs the MCP endpoint
GET/.well-known/oauth-authorization-serverAS capabilities + endpoint URLs
POST/api/mcp-oauth/registerDynamic client registration
tokenvault.uk/oauth/authorizeBrowser consent page (authorization endpoint)
POST/api/mcp-oauth/tokenCode exchange + refresh
POST/api/mcp-oauth/revokeToken revocation
POST/api/mcp-oauth/introspectToken introspection — see Introspection

Discovery

curl https://api.tokenvault.uk/.well-known/oauth-protected-resource
{
  "resource": "https://api.tokenvault.uk/api/agents/mcp",
  "authorization_servers": ["https://api.tokenvault.uk"]
}
curl https://api.tokenvault.uk/.well-known/oauth-authorization-server
{
  "issuer": "https://api.tokenvault.uk",
  "authorization_endpoint": "https://tokenvault.uk/oauth/authorize",
  "registration_endpoint": "https://api.tokenvault.uk/api/mcp-oauth/register",
  "token_endpoint": "https://api.tokenvault.uk/api/mcp-oauth/token",
  "revocation_endpoint": "https://api.tokenvault.uk/api/mcp-oauth/revoke",
  "code_challenge_methods_supported": ["S256"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "response_types_supported": ["code"],
  "token_endpoint_auth_methods_supported": ["none"]
}

Note token_endpoint_auth_methods_supported: ["none"] — Token Vault only issues public clients. There is no client_secret, ever. PKCE S256 is mandatory.

Register (RFC 7591)

curl -X POST https://api.tokenvault.uk/api/mcp-oauth/register \
  -H 'Content-Type: application/json' \
  -d '{
    "client_name": "my-agent-runtime",
    "redirect_uris": ["http://127.0.0.1:43117/callback"]
  }'
201 Created
{
  "client_id": "kX3…16-byte-urlsafe…",
  "redirect_uris": ["http://127.0.0.1:43117/callback"],
  "token_endpoint_auth_method": "none"
}

Redirect URIs must be loopback (127.0.0.1, ::1, localhost — any port) or a custom scheme (myapp://callback). Plain http to any other host is rejected with invalid_redirect_uri.

Authorize (browser)

Open the authorization endpoint in a browser with standard OAuth 2.1 query params:

https://tokenvault.uk/oauth/authorize
  ?client_id=<from register>
  &redirect_uri=http://127.0.0.1:43117/callback
  &response_type=code
  &code_challenge=<BASE64URL(SHA256(verifier))>
  &code_challenge_method=S256
  &state=<random>

The user signs in, picks an agent, and consents. The browser lands on your redirect_uri with ?code=…&state=….

Authorization codes are single-use and expire after 60 seconds. Exchange immediately. A second exchange of the same code fails with invalid_grant.

Token — code exchange

Accepts application/x-www-form-urlencoded (spec) or application/json:

curl -X POST https://api.tokenvault.uk/api/mcp-oauth/token \
  -d grant_type=authorization_code \
  -d code=<code> \
  -d code_verifier=<verifier> \
  -d client_id=<client_id> \
  -d redirect_uri=http://127.0.0.1:43117/callback
200 OK
{
  "access_token": "tvsess_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "tvrefresh_…",
  "scope": ""
}

client_id, redirect_uri, and the PKCE verifier must all match the values the code was minted against, or the exchange fails with invalid_grant.

Token — refresh

curl -X POST https://api.tokenvault.uk/api/mcp-oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token=tvrefresh_…
200 OK
{
  "access_token": "tvsess_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": ""
}

Every refresh re-checks the agent before minting: a suspended agent returns 403 invalid_grant "Agent is suspended", a deleted agent 403 "Agent not found", and an agent with MCP access switched off 403 "MCP not enabled". This is the kill-switch re-check — revoking an agent in the console stops token issuance immediately.

Revoke (RFC 7009)

curl -X POST https://api.tokenvault.uk/api/mcp-oauth/revoke \
  -H 'Content-Type: application/json' \
  -d '{"token": "tvsess_…", "token_type_hint": "access_token"}'

Always returns 200, whether or not the token existed — callers can't probe token validity through revocation. token_type_hint (access_token | refresh_token) only optimizes lookup order; both stores are tried.

Using the session token

A tvsess_ works anywhere a tvagent_ key works on the MCP surface:

curl -X POST https://api.tokenvault.uk/api/agents/mcp \
  -H 'Authorization: Bearer tvsess_…' \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

The agent's grants and ABAC policies apply identically. In webhook mode, get_credential returns a signed URL and the client fetches the credential directly from your webhook — Token Vault never touches it.

Error reference

HTTPerrorWhen
400invalid_requestMissing/malformed params, non-S256 PKCE
400invalid_grantBad/expired/reused code, PKCE failure, unknown refresh token, binding mismatch
400invalid_clientUnknown client_id
400invalid_redirect_uriUnregistered or disallowed redirect URI
400unsupported_grant_typeAnything but authorization_code / refresh_token
401invalid_tokenBad Firebase session on the consent page
403access_denied / invalid_grantAgent suspended, deleted, or MCP-disabled
429rate_limitedSlow down; honor Retry-After

Ready to try it?

Sign up free with Google — your credentials stay on your own webhook, and the quickstart gets an agent fetching its first credential in about ten minutes.

On this page