OAuth for connectors
Connect Claude.ai and other OAuth-only MCP clients to VortexDeployer — discovery, dynamic client registration, authorization code + PKCE, and grants bound to your API key.
Some MCP clients — Claude.ai custom connectors among them — can only authenticate with OAuth, never with a static bearer key. For them, VortexDeployer is also an OAuth 2.1 authorization server: the standard authorization-code flow with PKCE, dynamic client registration and refresh tokens, ending in an access token that works exactly like a vd_ API key on the MCP endpoint, the REST API and the streams.
| What | Value |
|---|---|
| Issuer | https://evm.vortexdeployer.com/evm/api/v1 |
| Discovery | https://evm.vortexdeployer.com/.well-known/oauth-authorization-server (RFC 8414) and https://evm.vortexdeployer.com/.well-known/oauth-protected-resource (RFC 9728) — fetched at the API host's root, not under /api/v1 |
| Register | POST https://evm.vortexdeployer.com/evm/api/v1/oauth/register (RFC 7591) |
| Authorize | GET https://evm.vortexdeployer.com/evm/api/v1/oauth/authorize |
| Token | POST https://evm.vortexdeployer.com/evm/api/v1/oauth/token |
| Revoke | POST https://evm.vortexdeployer.com/evm/api/v1/oauth/revoke (RFC 7009) |
The one idea: a grant is a key#
At consent time you pick one of your API keys, and the OAuth grant is bound to it. The access token the client receives (vdo_…) then *is* that key for every purpose: the key's scopes, per-trade and daily caps, IP allowlist and rate limits govern everything the agent does, its calls appear in the API hub under that key, and revoking the key ends the agent's access — every token minted from it dies on the next call. Refresh tokens (vdr_…) rotate on every use; a replayed refresh token revokes the whole chain.
The agent acts as you with real funds. Bind a read-only key for research, or a trade key with maxSolPerTrade and dailySolCap. Create a dedicated key in the app under API before connecting.
For a person connecting Claude.ai#
- In the app, open API and create a key for the agent (read-only or capped — see above).
- In Claude.ai → Settings → Connectors → Add custom connector, paste
https://evm.vortexdeployer.com/evm/api/v1/mcp. - Claude registers itself, then sends your browser to VortexDeployer. Sign in, pick the key, Approve.
- Done — Claude's tools now act as that key. Disconnecting the connector (or revoking the key) ends access.
For a client developer#
The flow is textbook OAuth 2.1 — the SDK you already use (@modelcontextprotocol/sdk auth helpers, any OIDC-less OAuth client) works as-is.
Discovery. Fetch /.well-known/oauth-protected-resource at the MCP host's root (or follow the WWW-Authenticate header on a bare 401 from POST /mcp) to find the authorization server, then its /.well-known/oauth-authorization-server for the endpoints.
Register (once, public client — no secret is issued):
POST https://evm.vortexdeployer.com/evm/api/v1/oauth/register
Content-Type: application/json
{ "client_name": "My agent", "redirect_uris": ["https://myapp.example/oauth/callback"] }→ 201 with client_id, grant_types, token_endpoint_auth_method: "none". Redirect URIs must be https (or http on loopback for local development), no fragments, and the authorize request's redirect_uri must match one exactly.
Authorize. Send the browser to:
GET https://evm.vortexdeployer.com/evm/api/v1/oauth/authorize?response_type=code
&client_id=…&redirect_uri=…&state=…
&code_challenge=<base64url(sha256(verifier))>&code_challenge_method=S256S256 only. The owner signs in on the consent page, picks the API key the grant binds to, and approves; the browser comes back to your redirect_uri with code and state (or error=access_denied). A malformed request never redirects — it answers 400 in place.
Exchange (form-encoded, like every OAuth server):
POST https://evm.vortexdeployer.com/evm/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=…&redirect_uri=…&client_id=…&code_verifier=…→ { "access_token": "vdo_…", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "vdr_…" }. Codes are single-use and expire in two minutes; client, redirect URI and PKCE verifier must all match.
Refresh. grant_type=refresh_token&refresh_token=…&client_id=… — the old refresh token is rotated out; reuse revokes the chain.
Use. Authorization: Bearer vdo_… on POST https://evm.vortexdeployer.com/evm/api/v1/mcp, any REST route, and as the token field of a stream subscribe — anywhere a vd_ key works, with the bound key's scopes and limits.
Revoke. POST /oauth/revoke with token=… — always 200. Revoking the bound key in the app revokes every token minted from it.
Errors#
Protocol endpoints answer RFC 6749-style JSON, not the API's public error shape: { "error": "invalid_grant", "error_description": "…" }. The usual suspects: invalid_client_metadata (bad registration), invalid_request (unknown client, redirect mismatch, missing PKCE), invalid_grant (code or refresh token invalid, expired, used or replayed), temporarily_unavailable (rate limited — the same per-IP budgets as failed API-key auth).
Good to know#
- Lifetimes (server-configured): auth code 2 min, access token 1 h, refresh token 30 days, parked consent 10 min.
- No OAuth scopes. The
scopeparameter is accepted and ignored — the bound key's scopes are the grant's scope. - No client secrets. Every client is public; PKCE is what binds a code to its client.
- Streams accept
vdo_tokens wherever avd_key works, with the key'sstreamscope.