Managing the workspace: the CLI and the MCP tools
Everything the Gateway's admin UI does (users, invitations, roles, groups, who can open an app, caps, usage, the activity trail and the audit log, tokens, AI connections, the organization's name) as everyapp workspace commands for the operator and as everyapp_workspace_* tools an admin's AI client calls on /mcp, who may do what, and what neither surface ever returns.
The Gateway's admin UI (Manage workspace in the sidebar: Users, Groups, Apps, Tokens, Usage, Activity, Events, plus AI connections in the account menu) is one of three ways to run a workspace. The other two are for agents:
| Surface | Who acts | Authority | Where it shows |
|---|---|---|---|
pnpm everyapp workspace … |
the operator: the Cloudflare login (or CLOUDFLARE_API_TOKEN) that owns the Gateway's account |
root. The commands write the Gateway's D1 directly, like email set and provider on (ADR-0003 in the checkout, adr/), with the same guards the UI holds: never the last owner, never an owner by an admin. Nothing is asked of the Gateway over HTTPS, so they work while it is down. |
the next request to the Gateway reads the change; nothing is written to Admin → Activity (Cloudflare's audit log has the D1 writes) |
everyapp_workspace_* tools on https://<gateway>/mcp |
the person behind an OAuth connection, a personal access token (epat_) or a Cloudflare Access assertion |
their live role in the organization (re-read on every request) and the connection's access. A tool is hidden below its role; a write tool is hidden from, and refused for, a read-only connection like any app write tool. The services' own rules apply on top and come back as tool errors. | one row in Admin → Activity per call, app everyapp, with the client's name, like any tool call |
Every row of the parity matrix (which UI action is which command and which tool, and the few with none) is in the checkout at docs/design/2026-10-agent-parity.md; a test (packages/cli/src/docs/parity.test.ts) fails when a UI action gets no twin and no reason.
The CLI: everyapp workspace
Run from anywhere, with --json when an agent drives it (the protocol). Every command takes --gateway and --account-id, and all but rename and role take --org <slug|id|name>; without it a command acts on the install organization (the one the first deploy made). --yes skips the confirmation of remove and group delete. People are named by email or user id, apps by their manifest id (crm) or row id, groups by name or grp_ id, invitations by the invited email or id.
| Command | What it does |
|---|---|
workspace show |
The organization (name, slug, id), its owners, whether the Gateway can send mail, and counts of members, pending invitations, groups and apps. |
workspace users |
Members with role and state (active, deactivated), and pending invitations with role and expiry. |
workspace invite <email> [--role admin] |
Create the invitation (good for 72 hours) and print its link (https://<gateway>/accept-invitation?token=einv_…). The CLI never mails it: hand the link over. The invitation records the operator as its inviter, so the operator's login email must be a verified member (WORKSPACE_NOT_A_MEMBER otherwise). |
workspace invite-link <email|id> |
A new link for a pending invitation; earlier links keep working until it is used, canceled or expires. |
workspace uninvite <email|id> |
Cancel a pending invitation. |
workspace role <email> <owner|admin|member> |
Change a member's role. An owner is made or demoted only by the operator or, in the UI, by an owner; the last owner can never be demoted. |
workspace rename <name> |
Rename the organization (the slug, and so every Worker name, stays). |
workspace deactivate <email> |
Ban the account: no sign-in, every session ended, personal access tokens and AI connections revoked, in one batch. Never the last live owner. |
workspace reactivate <email> |
Lift the ban. Revoked tokens and connections stay revoked. |
workspace remove <email> |
Remove the membership, their app grants, group memberships, tokens and connections in this organization, and the account itself when it belongs to no other organization. Like the UI, someone Cloudflare Access would let straight back in is deactivated instead (decided from whether an Access auto-join setting is set, since its value is a secret the CLI cannot read; the output says how to delete anyway). Never the last live owner. |
workspace reset-link <email> |
A one-hour, single-use password-reset link to hand over (https://<gateway>/reset-password?token=…), for a member of this organization alone and not deactivated. Unlike the UI it works for an owner: the operator is root, and this is how a sole owner who lost their password gets back in. Mailing one is the UI's or the MCP tool's. |
workspace groups |
The groups with member and app counts. |
workspace group create <name> [--description], workspace group set <group> [--name] [--description], workspace group delete <group> |
A group's name is unique in the organization. Deleting a group takes its app grants with it. |
workspace group members <group> [--add <email>…] [--remove <email>…] [--set <email>…] |
Who is in the group; or change it. --set replaces the whole list. |
workspace group apps <group> [--add <app>…] [--remove <app>…] [--set <app>…] |
Which apps the group opens; or change it. |
workspace apps |
Every app of the organization with who can open it (everyone, or how many people and groups), its setup state and status. status is the operator's view of the same apps (Workers, versions); this is the admin's. |
workspace app set <app> [--name] [--description] [--everyone on|off] |
The catalog entry: the name and description people see, and whether every member can open it. Turning --everyone off keeps the people who already have access. |
workspace app access <app> [--add <email>…] [--remove <email>…] [--set <email>…] |
Who can open the app directly (groups grant it too); or change it. |
workspace app cap <app> [<n|off>] [--usd <dollars|off>] [--unlimited] |
The organization's monthly caps on the app's provider calls: a number of calls (a whole number up to a billion) and --usd dollars of provider-reported cost (0.01 to 1,000,000, whole cents), each a value, off (none of its own: the workspace default of that kind binds it, and without one it is uncapped), or left out to keep the app's own (so --usd 20 alone keeps the call cap); or --unlimited alone (no cap of either kind, even with workspace defaults). A cap of either kind ends --unlimited, the other kind then following its default. The dollar cap counts only the cost a provider reports (OpenRouter's usage.cost, DataForSEO's cost); other providers' calls count toward the call cap alone. The row holds caps or unlimited, never both, every column written in one statement; both caps off deletes it. The semantics are the Usage page's (Manage workspace → Usage). An app past either cap that binds it is refused with app_monthly_cap_exceeded. As in Admin → Usage, the organization's first cap makes the built-in Cap alerts subscription (owners and admins emailed at 80% and at the cap; pnpm everyapp events list shows it, done.data.capAlerts names it the run it is made), and cap.raised is queued when a cap that binds the app ends higher or gone, judged per kind on the effective cap before and after (its own, else the default): a cap set under the default is no raise, --unlimited under a default is a removal, off under a default raises to it; a lowered cap is no event. done.data carries cap, costCapMicros, unlimited, was, wasCostCapMicros, wasUnlimited, effectiveCap, capSource, effectiveCostCapMicros and costCapSource (app, default or null; dollars in millionths). --unlimited with a number, off or --usd is INVALID_VALUE; none of them is MISSING_ARGUMENT; dollars out of range or finer than a cent are INVALID_CAP. On a Gateway from before the workspace default (migration 0014) --unlimited is refused with GATEWAY_PREDATES_DEFAULT_CAPS; a number and off still work. On one from before dollar caps (migration 0018) --usd is refused with GATEWAY_PREDATES_COST_CAPS. |
workspace cap default <n|off> |
The organization's default monthly cap: what binds every app without a cap or --unlimited of its own (Manage workspace → Usage → Default monthly cap). off removes it (the org_settings row goes). The Gateway holds every such app to it within 30 seconds. As in the Gateway, the first cap (an app's or the default) makes the Cap alerts subscription, and the default first set, raised or removed queues cap.raised with no app and source: "default" (a lowered one doesn't), all in the write's own batch. done.data carries cap, was and capAlerts. Refused on a Gateway from before migration 0014 with GATEWAY_PREDATES_DEFAULT_CAPS (run pnpm everyapp update first). |
workspace usage [--month YYYY-MM] |
What the organization's apps spent in a UTC month: per app, per member and per provider. done.data.defaultMonthlyCallCap is the workspace default (or null) and each app row carries its own cap (monthlyCallCap), capUnlimited, the cap that binds it (effectiveMonthlyCallCap) and its source (capSource: app, default or null), and paused (it has reached that cap this month; always false for a past month). The table's CAP column shows the binding cap, (default) when inherited, none (unlimited) for an exempt app and paused. Refused on a Gateway from before migration 0014 with GATEWAY_PREDATES_DEFAULT_CAPS. |
workspace activity [--app <app>] [--user <email>] [--decision allowed|blocked] [--risk read|write|destructive] [--since <time>] [--limit <n>] [--before <cursor>] |
The tool-call audit trail, newest first: who called which tool of which app, through which client, allowed or blocked, and the outcome; the Activity page's filters, and --before takes the previous page's cursor (done.data.nextBefore under --json). Never the arguments or the result. |
workspace audit [--actor <who>] [--type <type>] [--since <time>] [--limit <n>] [--before <cursor>] |
The audit log, newest first: every event the Gateway recorded (members, apps, caps, connections, tokens, Access, MCP apps, providers, apps' own events), who did it (a member, agent · <AI app>, CLI · <operator>, app · <id>, Gateway), the type in plain words and the subject; kept 90 days. --actor is a member by email or id, else an actor id as the rows hold it (a connection's, a token's, an app's, an operator's email); --type one type, a prefix (member.*, app.crm.*) or * (pings and tool calls too; out otherwise); --before the previous page's cursor (done.data.nextBefore). Never an event's data. GATEWAY_OUTDATED before migration 0015. |
workspace tokens [--user <email|id>] [--all] |
Personal access tokens: name, scopes, app, expiry, last use; --all lists revoked and expired ones too. Never a value. The operator sees every member's. |
workspace token revoke <id> |
Revoke one. The next request with it is refused. |
workspace connections [--user <email>] |
AI connections (the OAuth clients people connected): client, access, apps, last use. |
workspace connection revoke <id> |
Revoke one. |
What the CLI does not do, and why: it cannot create a personal access token (the Gateway hashes tokens with BETTER_AUTH_SECRET, a Worker secret the CLI never holds; create one in Admin → Tokens), and it sends no mail (the Gateway's sender does; the CLI prints links instead). everyapp delete <app> removes an app's Worker and catalog row, so there is no workspace app delete.
The MCP tools: everyapp_workspace_*
Listed on the Gateway's /mcp to owners and admins beside everyapp_list_apps, everyapp_whoami and everyapp_search_tools (the connector). A member sees none of them and a call answers "Unknown tool". A read-only connection sees the read tools only. A credential narrowed to apps (a personal access token bound to one app, a connection consented for selected apps) sees none of them: it administers nothing. They are listed in the full listing; in the compact listing (a connection past 256 tools or 1 MiB, or X-EveryApp-Tools: compact) a client finds them with everyapp_search_tools and runs them through everyapp_call_read or everyapp_call_write, like app tools. Every call leaves one row in Admin → Activity.
Four tools are for every member, about their own things: everyapp_my_tokens and everyapp_revoke_token (the caller's personal access tokens, never a value), everyapp_my_connections and everyapp_revoke_connection (the caller's AI connections in every organization; revoking the one in use ends it on the next request).
| Tool | Reads or writes | Role | What it does |
|---|---|---|---|
everyapp_workspace_show |
reads | admin | The organization, your role, whether the Gateway can send mail, and counts. |
everyapp_workspace_users |
reads | admin | Members and pending invitations. |
everyapp_workspace_invite |
writes | admin | email, role (member or admin). Creates the invitation and says whether the Gateway mailed it (emailed). It never returns the link: where the Gateway has no email, the admin copies the link in Admin → Users or the operator runs everyapp workspace invite-link. |
everyapp_workspace_uninvite |
writes | admin | invitation (id or email): cancel a pending invitation. |
everyapp_workspace_role |
writes | admin | user, role (member, admin, owner). Only an owner changes an admin or makes an owner; nobody changes their own role; the last owner stays. |
everyapp_workspace_deactivate, everyapp_workspace_reactivate |
writes | admin | user. Deactivation ends sessions and revokes tokens and connections; only for accounts in this organization alone. |
everyapp_workspace_remove |
writes | admin | user, confirm: true. Removes the membership (deactivates instead where Cloudflare Access would let them back in, and says so). |
everyapp_workspace_reset_email |
writes | admin | user: mail a password-reset link to a member (the link goes to them, never to the caller). |
everyapp_workspace_groups, everyapp_workspace_group_create, everyapp_workspace_group_set, everyapp_workspace_group_delete |
reads / writes | admin | The groups; name, description?; group plus the fields to change; group. |
everyapp_workspace_group_members, everyapp_workspace_group_members_set |
reads / writes | admin | group; then users (the whole list) or add / remove. |
everyapp_workspace_group_apps, everyapp_workspace_group_apps_set |
reads / writes | admin | group; then apps (the whole list) or add / remove. |
everyapp_workspace_apps |
reads | admin | Every app with who can open it, its setup state and status. |
everyapp_workspace_app_set |
writes | admin | app, name?, description?, everyone? (available to every member). |
everyapp_workspace_app_access, everyapp_workspace_app_access_set |
reads / writes | admin | app; then users (the whole list) or add / remove. |
everyapp_workspace_app_cap |
writes | admin | app, cap? (whole calls per month, or null for no call cap of its own), costUsd? (dollars of provider-reported cost per month, 0.01 to 1,000,000 in whole cents, or null for no dollar cap of its own), unlimited? (boolean, alone: no cap of either kind even under workspace defaults). A cap left out is kept, so costUsd alone keeps the call cap; give at least one of the three. A cap and unlimited together are refused (INVALID_INPUT), as is a fraction of a cent. Answers monthlyCallCap, monthlyCostCapMicros, capUnlimited, effectiveMonthlyCallCap and capSource, effectiveMonthlyCostCapMicros and costCapSource (app, default or null). |
everyapp_workspace_cap_default |
writes | admin | cap? (whole calls per month for every app without a call cap or unlimited of its own, or null for no default), costUsd? (dollars per month the same way, or null); a field left out is kept, give at least one. Answers defaultMonthlyCallCap and defaultMonthlyCostCapMicros. The same rules as Admin → Usage → Default monthly cap: the first cap makes the Cap alerts subscription; set, raised or removed queues cap.raised with no app and source: "default". |
everyapp_ai_show |
reads | member | {}. What the default model provider resolves to now (provider, model, source, via, the candidates in order); configured and settings for owners and admins only. See /docs/agent/ai. |
everyapp_ai_default |
writes | admin | provider, model?, or clear: true. The same setting as Admin → Usage → AI default and everyapp ai default: a provider the default can't be is refused (INVALID_INPUT); a change queues ai.default_changed. |
everyapp_workspace_app_delete |
writes | admin | app, confirm: true. Removes the catalog row; the Worker stays until the operator runs everyapp delete. |
everyapp_workspace_usage |
reads | admin | month? (YYYY-MM, UTC). Answers defaultMonthlyCallCap and, per app, monthlyCallCap, capUnlimited, effectiveMonthlyCallCap, capSource and paused, as the Usage page shows them. |
everyapp_workspace_activity |
reads | admin | app?, user?, decision?, risk?, since?, limit?, before? (the previous page's cursor). |
everyapp_workspace_audit |
reads | admin | actor?, type? (a type, member.*, or *), since?, limit?, before? (the previous page's cursor): the audit log, 90 days. |
everyapp_workspace_rename |
writes | admin | name. |
everyapp_workspace_events |
reads | admin | The event subscriptions (Admin → Events): where each delivers (a webhook's host and path, or email), the events it takes, whether it is on, its last delivery. Never a signing secret. |
everyapp_workspace_events_add |
writes | admin | url or email (owners or admins), events?, name?, secret?. Without secret the Gateway generates one and returns it once, in that answer: it signs deliveries to the URL and opens nothing on the Gateway, so the person stores it in the receiver now. |
everyapp_workspace_events_set, everyapp_workspace_events_remove |
writes | admin | subscription (id or name) plus enabled?, events?, name?; subscription, confirm: true (a built-in subscription, Cap alerts, is turned off instead, and the answer says so). |
everyapp_workspace_events_test |
writes | admin | subscription: sends a test event to that one subscription and answers the first attempt (the HTTP status, or the error and the retry). |
everyapp_workspace_event_deliveries, everyapp_workspace_event_redeliver |
reads / writes | admin | subscription?, dead?, limit?, before?; delivery: send a failed or dead one again now. |
Rules every tool holds (toolbus.workspace-tools-role-gated and toolbus.workspace-tools-no-credentials in the threat model's register):
- No tool returns a credential that opens the Gateway: no invite link, no password-reset link, no token value. None creates a personal access token: a connection that can be revoked must not mint itself a credential that outlives it. The CLI and the UI do those. The one secret a tool returns is the webhook signing secret
everyapp_workspace_events_addgenerates, once: it opens nothing on the Gateway. - A refusal is a tool error,
CODE: message, with the UI's codes (OWNER_REQUIRED,CANNOT_MANAGE_SELF,CANNOT_MANAGE_OWNER,USER_IN_OTHER_ORGANIZATION,USER_NOT_FOUND,APP_NOT_FOUND,INVALID_INPUT); a tool hidden by role or access is not listed and is "Unknown tool" on call, so a member learns nothing about them. remove,app_deleteandmcp_removeare an owner's or admin's (since 2026-10-25; nothing on the connector is owner-only) and takeconfirm: true;events_removetakes it too; without it they refuse and say so. What stays an owner's is inside the services: an admin never removes an owner (OWNER_REQUIRED), never makes or demotes one.- The arguments are checked against each tool's own schema whatever their size, and every call is audited with an argument digest and a byte count, never the arguments themselves.
Which to use
- An agent on the operator's machine (the one that ran
setup): the CLI. It needs no account on the Gateway except to invite. - An agent connected as a person (claude.ai, Claude Code, Cursor, a script with a personal token): the tools, with that person's role.
- Creating a personal access token, copying an invite or reset link, approving an AI app's connection: the UI, by the person.
Sign-in providers: Continue with Google, Microsoft, GitHub, Apple, Cloudflare or SSO
How a Gateway offers Continue with Google, Microsoft, GitHub, Apple, Cloudflare or any OpenID Connect issuer beside passwords: the catalog and every <ID>_SIGNIN_<FIELD> setting, the redirect URL, every everyapp signin command, flag, --json shape and code, who gets in and how existing accounts connect, Microsoft's tenant rule, Apple's POST and expiring secret, a private Cloudflare client versus Access's Sign in with Cloudflare, the generic OIDC issuer, Manage workspace → Sign-in, the read-only MCP tools, turning passwords off, and the threat model's rows.
Security Model
The platform's security rules: trust zones, deployment modes, what the perimeter enforces, what apps receive, hosted sign-up, MCP, secrets handling.