Skip to content
PraxisSign in
Knowledge sections

MCP server

The same capabilities exposed as MCP tools and resources for AI clients, with OAuth for people and API Tokens for scripts.

mcpaitoolsoauth
On this page

The MCP server is the second Capability Interface. It exposes the same supported workspace reads and mutations as REST, shaped as MCP tools and one resource, and it takes two credentials: an OAuth access token for an MCP Client a person operates (Claude, Cursor, and similar), or a workspace API Token for scripts and CI.

Tools and resources

The API Worker serves Effect's MCP server at POST /mcp using streamable HTTP and protocol 2025-11-25. Initialize a session and send its mcp-session-id with later requests. GET /mcp returns 405; discovery is at GET /mcp/discovery and through the protocol's tools/list.

Read tools include get_workspace_overview, list_members, list_notifications, list_api_tokens, list_webhooks, list_webhook_deliveries, list_webhook_delivery_attempts, and list_audit_events, with the workspace://overview resource. List tools accept optional cursor and numeric limit; delivery listing takes endpointId. The workspace always comes from the credential.

The eleven write tools call the same operation catalog as REST:

ToolJSON argumentsBehavior
create_api_tokenname, scopes, optional expiresAtCreates a token and returns its plaintext once
replace_api_tokentokenId, scopes, overlapSeconds, optional expiresAtReturns replacement plaintext once; narrows scopes/expiry and retires the old token within 24 hours; audited, do not automatically retry
delete_api_tokentokenIdRevokes the token; unknown or revoked IDs also return revoked
create_webhookurl, events, optional descriptionRegisters an endpoint; returns metadata
update_webhookendpointId, optional url, events, enabledChanges endpoint configuration
delete_webhookendpointIdRemoves an endpoint and associated data
rotate_webhook_secretendpointIdReturns a new signing secret once; old secret retains 24-hour grace
send_webhook_test_eventendpointIdSaves and enqueues an external test delivery
replay_webhook_deliverydeliveryIdCreates and enqueues a copy of a terminal delivery
request_workspace_exportnoneCreates an archive job and notifies on completion
get_workspace_export_download_linkexportIdReturns a signed URL for a ready, unexpired archive

Store plaintext tokens and signing secrets securely. Treat signed URLs as credentials; they expire within 15 minutes, capped by archive retention. Webhook creation preserves the REST contract, which omits the initial signing secret. Call rotate_webhook_secret to obtain a signing secret.

Mutations can write audit records, publish webhooks, and enqueue work. Only token revocation is marked idempotent. Tools do not retry mutations automatically. A failed webhook enqueue can leave a pending delivery; inspect deliveries before retrying. An export enqueue failure can leave a failed export. Expected refusals return MCP tool errors, including disabled or missing endpoints, invalid URLs, unavailable queues, and unavailable or expired exports.

Tool annotations describe read-only, destructive, idempotent, and external behavior. They are client hints and do not guarantee human approval. System-admin, account-deletion, impersonation, authentication-configuration, and Better Auth browser-session membership or invitation operations are not exposed.

Connect from Claude

An MCP Client connects through OAuth 2.1. You sign in once, pick the workspace, and the client acts as you, with what your role there allows.

  1. In Claude (or another MCP client that supports remote servers), add a custom connector with the server URL https://<your-api-worker>/mcp. Locally that is http://localhost:8787/mcp, with pnpm run dev running against a seeded local D1.
  2. The client fetches /.well-known/oauth-protected-resource/mcp from the API Worker, learns that the web app's /api/auth is the authorization server, and opens the browser at /api/auth/oauth2/authorize. If you are signed out you land on /sign-in; signing in resumes the authorization.
  3. On /oauth/consent you pick exactly one Workspace and see what the client asks for (mcp:read for reads, explicit mcp:write for mutations, offline_access to stay connected, and the identity scopes). Allow, and the browser returns to the client with the authorization code.
  4. The client exchanges the code for an access token bound to the API Worker's /mcp and to that workspace, and calls the tools with it. The token expires after an hour; the refresh token the client also received renews it without another sign-in.
  5. Disconnect from /account, under MCP clients: revoking deletes the consent, revokes the client's refresh and access tokens, and writes an mcp_client.consent_revoked Audit Event. Connecting wrote mcp_client.consent_granted.

The client identifies itself with a Client ID Metadata Document, an HTTPS document published by the MCP client. There is no registration step and no client secret to manage.

Connect from a script

Scripts keep using API Tokens: Authorization: Bearer bsk_live_… on POST /mcp, exactly as on the REST routes. See API tokens. A seeded local D1 accepts the documented seed token.

What the two credentials share, and where they differ

Both open the same route and draw from the mcp rate-limit bucket. Every write invocation also draws from rest_write. Each tool re-checks its own permission through the one authorize() path in @b2b-saas-starter/authz; the difference is who it authorizes as:

  • An API Token authorizes as its scopes (read, write, admin).
  • An OAuth write also requires a current matching consent granting mcp:write. Existing read-only grants never acquire writes automatically. The token authorizes as the Member: the API Worker verifies the token's signature against the web worker's JWKS, its issuer (MCP_OAUTH_ISSUER), audience (MCP_RESOURCE_URL) and expiry, reads the workspace from the token's starter_workspace_* claims, and resolves your membership in that workspace on every call. A member who leaves the workspace is refused on the next call; a role change applies on the next call. A token for workspace A cannot access workspace B. Revoked or changed consents invalidate previously issued write tokens through a consent ID/version binding. Token creation and replacement cannot grant permissions beyond the caller's authority.

REST routes stay token-only. OAuth is the interactive path, not a second REST credential.

Configuration

Two env vars, both optional. MCP_RESOURCE_URL is the API Worker's /mcp URL; the web worker binds tokens to it (locally it defaults to http://localhost:8787/mcp). MCP_OAUTH_ISSUER is the web worker's /api/auth base URL; the API Worker trusts tokens from it. With either unset on the API Worker, POST /mcp accepts API Tokens only and serves no protected-resource metadata, so a client learns there is nothing to authorize against. The decision is recorded in ADR 0068.