This page is only relevant if you’re building an MCP client. End users don’t need to know any of this - their MCP client handles the OAuth flow automatically.
Overview
Mokaru’s MCP server implements OAuth 2.1 (RFC 6749, RFC 9700) with:
- PKCE (RFC 7636, S256 only -
plain is rejected)
- Dynamic Client Registration (RFC 7591)
- Authorization Server Metadata (RFC 8414)
- Refresh Token Rotation with reuse detection
Discovery
GET https://api.mokaru.ai/.well-known/oauth-protected-resource
Returns:
{
"resource": "https://api.mokaru.ai/mcp",
"authorization_servers": ["https://api.mokaru.ai"],
"bearer_methods_supported": ["header"]
}
Then:
GET https://api.mokaru.ai/.well-known/oauth-authorization-server
Returns:
{
"issuer": "https://api.mokaru.ai",
"authorization_endpoint": "https://api.mokaru.ai/oauth/authorize",
"token_endpoint": "https://api.mokaru.ai/oauth/token",
"registration_endpoint": "https://api.mokaru.ai/oauth/register",
"scopes_supported": ["mcp"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"token_endpoint_auth_methods_supported": ["none"],
"code_challenge_methods_supported": ["S256"]
}
Step 1: Register your client
POST https://api.mokaru.ai/oauth/register
Content-Type: application/json
{
"client_name": "My MCP Client",
"redirect_uris": ["https://my-client.example.com/oauth/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}
Returns:
{
"client_id": "mcp_abc123...",
"client_name": "My MCP Client",
"redirect_uris": ["..."],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none",
"client_id_issued_at": 1716220800
}
Allowed redirect URIs: https:// URIs or http://localhost / http://127.0.0.1 loopback. No http:// on public hosts.
Step 2: Authorize
Generate a PKCE pair:
const codeVerifier = base64UrlEncode(crypto.randomBytes(32)); // 43 chars
const codeChallenge = base64UrlEncode(sha256(codeVerifier)); // 43 chars
Redirect the user’s browser to:
https://api.mokaru.ai/oauth/authorize?
response_type=code
&client_id=mcp_abc123...
&redirect_uri=https://my-client.example.com/oauth/callback
&code_challenge=<challenge>
&code_challenge_method=S256
&scope=mcp
&state=<random>
Mokaru shows the consent screen. After the user approves:
https://my-client.example.com/oauth/callback?code=<code>&state=<state>
Errors: If the request is malformed or the user is not on a Plus plan, Mokaru shows an HTML error page (does not redirect, per OAuth 2.1).
Step 3: Exchange code for tokens
POST https://api.mokaru.ai/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=<code>
&redirect_uri=https://my-client.example.com/oauth/callback
&client_id=mcp_abc123...
&code_verifier=<verifier>
Returns:
{
"access_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "eyJhbGc...",
"scope": "mcp"
}
Step 4: Call /mcp
Use the access token as a Bearer:
POST https://api.mokaru.ai/mcp
Authorization: Bearer eyJhbGc...
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
Step 5: Refresh
POST https://api.mokaru.ai/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=eyJhbGc...
&client_id=mcp_abc123...
Returns a new access + refresh token pair. The old refresh token is invalidated immediately.
Reuse detection: If you try to use a refresh token that has already been rotated, Mokaru detects this as a possible compromise and revokes every refresh token for that account. Treat your refresh tokens like passwords.
Access and refresh tokens are JWTs (HS256) with these claims:
{
"iss": "mokaru-api",
"aud": "mokaru-mcp",
"sub": "<accountId>",
"client_id": "mcp_abc123...",
"scope": "mcp",
"token_use": "access",
"iat": 1716220800,
"exp": 1716224400
}
Do not attempt to verify the signature client-side - the signing key is server-only. Treat tokens as opaque.
Error responses
OAuth errors follow RFC 6749:
{ "error": "invalid_grant", "error_description": "PKCE verification failed" }
Common error codes:
| Code | Meaning |
|---|
invalid_request | Missing or malformed parameter |
invalid_client | Unknown client_id |
invalid_grant | Code/refresh expired, consumed, or PKCE failed |
unauthorized_client | Client cannot use this grant type |
unsupported_grant_type | Only authorization_code and refresh_token are supported |
unsupported_response_type | Only code is supported |
Reference implementations
- MCP TypeScript SDK handles all of this automatically when given just the server URL.
- MCP Python SDK ditto.
- Claude Desktop, Claude.ai, Cursor, Continue: built-in MCP OAuth support.