On this page

    Access and authentication

    What an agent can and cannot see, how tokens and OAuth sign-in work, and how to revoke access.

    Agents3 min read

    Rules agents inherit

    An agent follows the same rules as a person in the web app, and a few of its own.

    • Access is identical. Every tool resolves the workspace role, per-folder and per-document grants, and locks through lib/access.ts (CONCEPT.md §23b). A document you cannot open does not exist to the agent: not in search, not in listings, and not as the far end of a relationship.
    • Denied reads as not found. The agent never learns that a restricted item exists.
    • One workspace per connection. IDs from another workspace, even one you belong to, come back as not found.
    • Notes outrank summaries. A note is written by a person. Tools return notes separately from AI summaries, and a folder's note is included with everything inside it.
    • Agent-drawn links are origin: "ai". A model decided the edge, even though your credentials carried the request (§9). They show up as AI suggestions in the web app, and a pair someone dismissed cannot be proposed again through MCP.
    • Edits are versioned. Each change creates a document version that people can restore, and it re-runs the AI pipeline.
    • The live editor wins. If someone has the document open in the collaborative editor, edit_document refuses and tells the agent who. Otherwise it clears the editor's CRDT state, and the next editor to open loads the new markdown.

    Authentication

    Both ways of connecting produce the same thing: a row in the agentTokens table.

    FieldMeaning
    userIdWho the agent acts as
    workspaceIdThe one workspace it can reach
    scoperead or write
    kindpersonal (token) or oauth (sign-in)
    tokenHash, refreshHashSHA-256 of the tokens. No plaintext is stored.

    On every request the gateway hashes the bearer token, looks up the row, checks expiry, and re-checks workspace membership. Removing someone from a workspace cuts off their agents without anyone revoking tokens. Revoking in Settings → Agents deletes the row, and the next request is refused. Resetting your password deletes every token and connection you hold, in every workspace.

    Each tool call is recorded with its tool name, ids, outcome and time, kept for 30 days. What an agent writes or asks (search queries, document text, notes, reasons, names) is not kept.

    Anonymous requests get 401 with WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/mcp", which starts OAuth discovery in clients that support it.

    Personal access tokens

    • Format ctx_pat_…, 256 bits of randomness, minted in an action (not a mutation, which replays deterministically).
    • Expiry: 30 days, 90 days, 1 year, or never.
    • A viewer cannot create a write-scoped token.

    OAuth 2.1

    Implemented in convex/oauth.ts as Convex HTTP actions. It is not a separate auth service.

    EndpointStandard
    /.well-known/oauth-protected-resource[/mcp]RFC 9728
    /.well-known/oauth-authorization-server[/mcp]RFC 8414
    /oauth/registerRFC 7591
    /oauth/authorize → web app /oauth/consentOAuth 2.1
    /oauth/token (authorization_code, refresh_token)OAuth 2.1
    /oauth/revokeRFC 7009
    • Public clients only. PKCE with S256 is required. No client secrets, and no implicit or password grant.
    • Client identity:
      • A Client ID Metadata Document URL (the method the 2026-07-28 spec prefers; Claude Code uses https://claude.ai/oauth/claude-code-client-metadata). The server fetches it, and the document must name itself as the client_id.
      • Or Dynamic Client Registration, which the spec deprecates but most clients still use.
    • Redirect URIs: matched exactly, except that loopback addresses may use any port (RFC 8252 §7.3). Native clients bind whatever port is free, and their metadata cannot know it in advance.
    • Authorization responses include iss (RFC 9207). A resource parameter (RFC 8707) must name this server.
    • Consent happens in the web app, where your session already exists. Everything shown about the client is re-derived on the server from its id, never read from the URL. The page sends frame-ancestors 'none', so it cannot be framed to steal a click.
    • Lifetimes: code 5 minutes, single use. Access token 1 hour. Refresh token 90 days, rotated on every use (the old refresh and access tokens stop working).
    • Requires the SITE_URL env var on the Convex deployment, pointing at the web app. Convex Auth already needs it.
    MoveOpen