Skip to main content
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.

Token format

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:
CodeMeaning
invalid_requestMissing or malformed parameter
invalid_clientUnknown client_id
invalid_grantCode/refresh expired, consumed, or PKCE failed
unauthorized_clientClient cannot use this grant type
unsupported_grant_typeOnly authorization_code and refresh_token are supported
unsupported_response_typeOnly 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.