MCP server
Authentication
How MCP clients authenticate to Arachne: personal access tokens (the recommended path), OAuth access tokens from Arachne's Cognito pool, and the limits and error codes that protect the endpoint.
Every request to https://arachne.withaadhi.com/mcp carries Authorization: Bearer <token>. Two kinds of
token are accepted:
| Token | Looks like | Use it when |
|---|---|---|
| Personal access token (PAT) | arpat_ + 43 characters | Almost always. Works in every client that can set a header. |
| OAuth access token | a JWT issued by Arachne's Cognito user pool | Your client does OAuth with a pre-registered client id (advanced). |
Personal access tokens#
Create one#
- Dashboard → MCP access tokensSign in to the app; the section is at the bottom of the Dashboard.
- Label it and click Create tokenUse a label that names the client and machine, so you know what to revoke later.
- Copy it onceThe full token is displayed once. Arachne stores only its SHA-256 hash, so it can never be shown again; if you lose it, revoke it and create another.
Use it#
POST /mcp HTTP/1.1
Host: arachne.withaadhi.com
Authorization: Bearer arpat_REPLACE_WITH_YOUR_TOKEN
Content-Type: application/json
Keep it in an environment variable or your client's secret storage and reference it from config:
export ARACHNE_TOKEN="arpat_REPLACE_WITH_YOUR_TOKEN" # e.g. in ~/.zshrc, or load it from your password manager's CLI
[Environment]::SetEnvironmentVariable("ARACHNE_TOKEN", "arpat_REPLACE_WITH_YOUR_TOKEN", "User")
What a token can do#
- It acts as you, in the organisation it was created in (your personal one by default).
- Its permissions follow your live role. A token has no scopes of its own: on every request Arachne looks up your current role in that organisation and grants the matching tool scopes. Promote, demote or remove a person and their existing tokens change immediately; there is nothing to reissue.
- It can be revoked at any time from the same dashboard section. A revoked token gets
401on its next request. The list shows when each token was last used. - Up to 25 active tokens per person. Creating a 26th returns
token_limit. - Tokens do not expire. Rotate them yourself: create a new token, update your client, revoke the old one. Doing this every few months, and whenever a laptop or CI secret changes hands, is a good habit.
- Tokens are managed only from a signed-in browser session. A token cannot create, list or revoke tokens.
Never commit a token
Do not paste tokens into repositories, issue trackers, chat or prompts. The arpat_ prefix exists so secret
scanners can spot a leaked one; if that happens, revoke it straight away.
How a request is checked#
sequenceDiagram
autonumber
participant C as MCP client
participant S as Arachne /mcp
participant DB as Token + membership store
C->>S: POST /mcp, Authorization: Bearer arpat_...
S->>S: per-IP rate limit and lockout (before any token work)
S->>S: body at most 256 KiB
S->>S: prefix arpat_ means PAT, never parsed as a JWT
S->>DB: look up SHA-256(token)
alt unknown, malformed or revoked
DB-->>S: none
S-->>C: 401 invalid_token (counts as an IP auth failure)
else live token
DB-->>S: account + organisation
S->>DB: live role of that person in that organisation
alt no longer a member
S-->>C: 403 insufficient_scope
else member
S->>S: tool scopes granted from the role, last-used bumped
S->>S: per-person rate limit, then tool scope + role check
S-->>C: 200 JSON-RPC result
end
endOAuth with Cognito#
Clients that implement OAuth can use an access token from Arachne's Cognito user pool instead of a PAT. This path exists for clients that support pre-registered OAuth clients; it is not the recommended way to connect today.
| Setting | Value |
|---|---|
| Protected-resource metadata | https://arachne.withaadhi.com/mcp/.well-known/oauth-protected-resource (also named in every 401's WWW-Authenticate header) |
| Authorization server | the Cognito user pool issuer listed in that metadata (authorization_servers) |
| Client | a public client (no secret), authorization code + PKCE. Ask the operator for its client id. |
| Redirect URI | must match exactly; registered: http://localhost:8976/callback (no wildcard ports) |
| Scopes to request | openid email https://arachne.withaadhi.com/mcp/invoke |
| Token lifetime | access token 60 minutes; refresh token 7 days |
What the server does with such a token: it verifies the signature against the pool's keys, the issuer and the expiry;
a Cognito access token has no audience, so it must carry the …/mcp/invoke scope. Arachne then fetches your email from
the pool's UserInfo endpoint with the same token (nothing is stored), signs you in exactly like a web login (same identity,
first use creates the account), and acts in your personal organisation with your role's tool scopes.
For Claude Code, which supports pre-registered clients, the configuration would look like this (replace the client id):
claude mcp add-json arachne '{"type":"http","url":"https://arachne.withaadhi.com/mcp",
"oauth":{"clientId":"YOUR_MCP_CLIENT_ID","callbackPort":8976,
"scopes":"openid email https://arachne.withaadhi.com/mcp/invoke"}}'
# then, inside Claude Code: /mcp -> arachne -> authenticate in the browser
Limits of the OAuth path
- Cognito does not support dynamic client registration, so clients that rely on it ("sign in when needed" with automatic registration,
mcp-remotewithout a header) cannot complete sign-in. - The metadata's
scopes_supportedlists Arachne's tool scopes (mcp:advisories.read, …), which are not Cognito scopes; a client that requests those literally is refused by Cognito. Set the scopes above explicitly. - This flow has not been verified end to end with each client listed in Install & connect. If it gives you trouble, use a PAT.
Limits and error codes#
| Limit | Value | When exceeded |
|---|---|---|
| Per source IP, before any token check | 120 requests / minute | HTTP 429 {"error":"rate_limited"} with Retry-After |
| Failed authentications per IP | 30 in 5 minutes | the IP is locked out for the rest of the window (429) |
| Per person, all tokens together | 60 requests / minute | HTTP 429 for protocol calls; a tools/call gets a tool error rate_limited with retry_after_seconds |
Action tools (fix_submit + pr_open) per person | 10 / hour | tool error rate_limited |
| Request body | 256 KiB | HTTP 413 |
| Status | Meaning | What to do |
|---|---|---|
401 invalid_token | No bearer token, an unknown, malformed or revoked PAT, or an expired / wrong-audience OAuth token. | Check the header; create a new token if it was revoked. |
403 insufficient_scope | You are no longer a member of the token's organisation, or (on tools/call) your role does not allow that tool. The JSON-RPC error names the scope and role permission needed. | Ask an owner or admin for the role, or use a tool your role allows. |
quota (web: 402) | Your monthly allowance is used up. Over MCP this is a tool result with isError: true and {"error":"quota_exhausted","resets_at":…,"options":["upgrade","request_more","wait"]}, not an HTTP 402. | Request more uses in the app, or wait for resets_at. |
429 rate_limited | One of the rate limits above. | Back off for Retry-After seconds. |
413 | Request body over 256 KiB. | Send smaller arguments. |