API Reference
MCP OAuth

MCP OAuth

OAuth 2.0 endpoints used by MCP clients (Claude Desktop, Cursor, OpenAI Apps, and any other MCP-compatible host) to authenticate against your Pharlo workspace.

These endpoints implement the standard OAuth 2.0 Authorization Code flow plus RFC 7591 Dynamic Client Registration (opens in a new tab), so MCP clients can register and connect automatically without manual setup.

If you are integrating an MCP client, you usually do not call these endpoints directly — the client handles the flow on your behalf. The documentation here is provided so MCP host developers and security reviewers can audit the protocol surface.

Base URL: https://pharlo.io

These endpoints are not prefixed with /api/v1 — they live under the platform root so MCP clients can discover them via standard /.well-known/ paths.


Endpoints

MethodPathDescriptionStatus
GET/.well-known/oauth-authorization-serverOAuth Authorization Server metadata (RFC 8414)200
GET/.well-known/oauth-protected-resourceProtected Resource metadata200
POST/oauth/mcp/registerDynamic Client Registration (RFC 7591)201
GET/oauth/mcp/authorizeUser consent screen302
POST/oauth/mcp/tokenExchange code or refresh token for access tokens200

Discovery

MCP clients begin by fetching the discovery documents under /.well-known/. These return JSON describing the supported grant types, scopes, and the URLs of the registration, authorization, and token endpoints.

curl https://pharlo.io/.well-known/oauth-authorization-server
curl https://pharlo.io/.well-known/oauth-protected-resource

The response follows RFC 8414 (opens in a new tab):

{
  "issuer": "https://pharlo.io",
  "registration_endpoint": "https://pharlo.io/oauth/mcp/register",
  "authorization_endpoint": "https://pharlo.io/oauth/mcp/authorize",
  "token_endpoint": "https://pharlo.io/oauth/mcp/token",
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "response_types_supported": ["code"],
  "code_challenge_methods_supported": ["S256"],
  "scopes_supported": ["mcp.read", "mcp.write"]
}

POST /oauth/mcp/register

Register a new MCP client and receive a client_id. Implements RFC 7591 Dynamic Client Registration (opens in a new tab). No prior credentials required.

Request body

{
  "client_name": "My MCP App",
  "redirect_uris": ["https://my-mcp-app.example.com/oauth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "none"
}

Response 201 Created

{
  "client_id": "mcp_3f9e2a1b4c5d6e7f",
  "client_id_issued_at": 1747576800,
  "redirect_uris": ["https://my-mcp-app.example.com/oauth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "none"
}

The client_id is permanent. Public MCP clients (token_endpoint_auth_method: none) must use PKCE on the authorization request.


GET /oauth/mcp/authorize

Browser-facing consent screen. The user reviews the requested scopes and approves or denies access. On approval, Pharlo redirects to the registered redirect_uri with an authorization code.

Query parameters

ParameterTypeRequiredDescription
client_idstringYesFrom /oauth/mcp/register
redirect_uristringYesMust exactly match one of the registered redirect_uris
response_typestringYesMust be code
scopestringYesSpace-separated: mcp.read mcp.write
code_challengestringYesPKCE challenge — base64url(SHA-256(verifier))
code_challenge_methodstringYesMust be S256
statestringRecommendedOpaque value echoed back for CSRF protection

Successful redirect

HTTP/1.1 302 Found
Location: https://my-mcp-app.example.com/oauth/callback?code=...&state=...

Error redirect

If the user denies access or the request is malformed, the redirect contains error and error_description query params per RFC 6749 §4.1.2.1 (opens in a new tab).


POST /oauth/mcp/token

Exchange an authorization code for an access token, or refresh an existing access token.

Authorization Code grant

curl -X POST https://pharlo.io/oauth/mcp/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=AUTH_CODE_FROM_AUTHORIZE" \
  -d "redirect_uri=https://my-mcp-app.example.com/oauth/callback" \
  -d "client_id=mcp_3f9e2a1b4c5d6e7f" \
  -d "code_verifier=ORIGINAL_PKCE_VERIFIER"

Refresh Token grant

curl -X POST https://pharlo.io/oauth/mcp/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=REFRESH_TOKEN" \
  -d "client_id=mcp_3f9e2a1b4c5d6e7f"

Response 200 OK

{
  "access_token": "eyJhbGciOiJSUzI1NiI...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "rt_8a9b0c1d2e3f4a5b",
  "scope": "mcp.read mcp.write"
}
FieldTypeDescription
access_tokenstringBearer token — pass as Authorization: Bearer <access_token> to Pharlo API calls
expires_inintLifetime in seconds (3600 = 1 hour)
refresh_tokenstringUse to obtain a new access_token without user interaction
scopestringGranted scopes — may be narrower than requested

Error responses

Statuserror valueReason
400invalid_requestMissing or malformed parameter
400invalid_grantAuthorization code expired, already used, or PKCE verifier mismatch
400invalid_clientUnknown client_id
401invalid_tokenRefresh token revoked or expired

For details on the MCP integration story end-to-end, see MCP Integration: Setup.


See also