MCP Authentication & API
Two credential types work interchangeably at https://api.agently.dev/mcp, both sent as a standard bearer token:
Authorization: Bearer <credential>
API keys (agently_sk_...) | OAuth 2.1 (agently_at_...) | |
|---|---|---|
| Best for | Developer tools you configure by hand: Claude Code, Cursor, VS Code, scripts | Directory clients: Claude Desktop / claude.ai connectors, ChatGPT |
| Setup | Mint in Settings → API keys, paste into config | Automatic — the client discovers the flow, you approve in the browser |
| Workspace | Chosen when the key is minted | Chosen on the consent screen |
| Lifetime | Until revoked | Access 1 h, auto-refreshed (refresh 30 d, rotating) |
Every credential is scoped to one workspace, decided when it's created. The server derives the workspace from the credential on every request — tools never accept a workspace parameter.
API keys
Minted in Settings → API keys by any workspace member.
- Read-only by default. Enable Allow writes at mint time to unlock the
remembertool. - Shown once. Only a hash is stored — if you lose a key, revoke it and mint a new one.
- Limits: 10 active keys per workspace, 120 requests/minute per key.
- Revocation is immediate, from the same settings page.
Key management API
The settings page is a UI over three REST endpoints (signed-in Agently user with workspace membership required):
GET /api/v1/workspaces/{workspaceId}/api-keys
POST /api/v1/workspaces/{workspaceId}/api-keys { "name": "...", "canWrite": false }
DELETE /api/v1/workspaces/{workspaceId}/api-keys/{keyId}
POST returns 201 with the key metadata plus "key": "agently_sk_..." — the only time the raw key is returned. 409 when the workspace already has 10 active keys. Listing never returns secrets; revoked keys stay listed for audit.
OAuth 2.1
Agently runs a spec-complete OAuth 2.1 authorization server for MCP clients. If your client supports MCP authorization, everything below happens automatically — this section is for implementers.
Discovery (RFC 9728 + RFC 8414): an unauthenticated request to /mcp returns 401 with
WWW-Authenticate: Bearer resource_metadata="https://api.agently.dev/.well-known/oauth-protected-resource"
and the metadata documents live at:
https://api.agently.dev/.well-known/oauth-protected-resourcehttps://api.agently.dev/.well-known/oauth-authorization-server
Client registration (RFC 7591, no pre-approval needed):
curl -X POST https://api.agently.dev/oauth/register \
-H 'Content-Type: application/json' \
-d '{ "client_name": "My Agent", "redirect_uris": ["https://myapp.example/callback"] }'
Returns a client_id. Public clients only — there are no client secrets; PKCE (S256) is mandatory. Redirect URIs must be https, loopback http (localhost / 127.0.0.1), or a native app scheme.
Authorization: send the browser to
https://api.agently.dev/oauth/authorize
?client_id=...&redirect_uri=...&response_type=code
&scope=brain:read brain:write
&code_challenge=...&code_challenge_method=S256&state=...
The user signs in, picks a workspace, chooses whether to grant write access, and your redirect URI receives ?code=...&state=.... Codes are single-use and expire in 10 minutes.
Token exchange:
curl -X POST https://api.agently.dev/oauth/token \
-d grant_type=authorization_code \
-d code=... -d client_id=... \
-d redirect_uri=... -d code_verifier=...
{
"access_token": "agently_at_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "agently_rt_...",
"scope": "brain:read brain:write"
}
Refresh with grant_type=refresh_token. Refresh tokens are rotating and single-use — each refresh returns a new pair and retires the old one, including the old access token.
Scopes: brain:read grants the five read tools (always granted); brain:write grants remember (the user can decline it on the consent screen).
The MCP endpoint
POST https://api.agently.dev/mcp
Speaks MCP Streamable HTTP (spec revision 2026-07-28, stateless). Send JSON-RPC with Content-Type: application/json and Accept: application/json, text/event-stream; responses stream as Server-Sent Events. There are no sessions — every request is self-contained, and GET / DELETE on /mcp return 405.
Errors & limits
| Status | Meaning | What to do |
|---|---|---|
401 | Missing, invalid, expired, or revoked credential | Check the bearer token; OAuth clients follow the WWW-Authenticate pointer to re-authorize |
405 | GET/DELETE on /mcp | The server is stateless — use POST |
429 | Rate limit exceeded | Back off — 120 requests/minute per credential, with RateLimit-* headers |
400 (OAuth) | { "error": "...", "error_description": "..." } | invalid_grant on expired/replayed codes and rotated refresh tokens — restart the flow |
Tool-level failures return a normal MCP result with isError: true and a short explanation. Not-found cases (unknown entity, empty brain) are not errors — they return guidance text so agents can self-correct.
Writes via remember count against the workspace's Brain ingest quota (Settings → Usage). At the plan cap, writes are rejected with a clear message; reads are unaffected.