everyapp 0.2.2 → 0.3.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +6 -77
  2. package/bin/everyapp.js +235 -0
  3. package/dist/access-F3GKBKAW.js +1 -0
  4. package/dist/activity-4MFMH5JP.js +12 -0
  5. package/dist/apps-63K2DGVT.js +9 -0
  6. package/dist/chunk-2UFJRKEE.js +4 -0
  7. package/dist/chunk-3AAHHUJC.js +11 -0
  8. package/dist/chunk-3Z53CH4F.js +6 -0
  9. package/dist/chunk-5ZTFC6DK.js +1 -0
  10. package/dist/chunk-6O3GJS5H.js +15 -0
  11. package/dist/chunk-74EST4VW.js +2 -0
  12. package/dist/chunk-7LG3F4FF.js +10 -0
  13. package/dist/chunk-AUDGOIHE.js +1 -0
  14. package/dist/chunk-BHMRY53V.js +2 -0
  15. package/dist/chunk-DQWOXSL3.js +32 -0
  16. package/dist/chunk-E3EPK27P.js +10 -0
  17. package/dist/chunk-E7EUXUVD.js +1 -0
  18. package/dist/chunk-ECLJ5IAL.js +126 -0
  19. package/dist/chunk-EQX4GKJ2.js +4 -0
  20. package/dist/chunk-EUNGJMJ6.js +20 -0
  21. package/dist/chunk-F6IH47GT.js +4 -0
  22. package/dist/chunk-G64S77GC.js +33 -0
  23. package/dist/chunk-GD2JZMD3.js +1 -0
  24. package/dist/chunk-GRFPP4UW.js +7 -0
  25. package/dist/chunk-IA534DGW.js +10 -0
  26. package/dist/chunk-IG26HYRR.js +45 -0
  27. package/dist/chunk-IZEOAOKL.js +6 -0
  28. package/dist/chunk-JYKRUS5C.js +1 -0
  29. package/dist/chunk-KMAOZRV4.js +18 -0
  30. package/dist/chunk-MC56CUQI.js +7 -0
  31. package/dist/chunk-MDORVBPD.js +1 -0
  32. package/dist/chunk-N4E5YE4X.js +5 -0
  33. package/dist/chunk-NJFWEYX7.js +1 -0
  34. package/dist/chunk-OEOVNJAN.js +1 -0
  35. package/dist/chunk-OVKVV6MK.js +2 -0
  36. package/dist/chunk-PL6EORDJ.js +15 -0
  37. package/dist/chunk-PMPIMHKG.js +6 -0
  38. package/dist/chunk-QZWSSWMP.js +22 -0
  39. package/dist/chunk-R4CGF622.js +2 -0
  40. package/dist/chunk-RFVMR32Y.js +1 -0
  41. package/dist/chunk-RQFHVU3Y.js +25 -0
  42. package/dist/chunk-SAT3PGZU.js +57 -0
  43. package/dist/chunk-SPPR6WCI.js +1 -0
  44. package/dist/chunk-TPWSGRNT.js +30 -0
  45. package/dist/chunk-VVQRET35.js +2 -0
  46. package/dist/chunk-VXJQGJ7Y.js +2 -0
  47. package/dist/chunk-WEM3WLOC.js +1 -0
  48. package/dist/chunk-Y3VIFPN5.js +1 -0
  49. package/dist/chunk-YJBYZWSP.js +22 -0
  50. package/dist/chunk-ZYL2ZHCF.js +98 -0
  51. package/dist/configure-2KMHM22T.js +8 -0
  52. package/dist/create-ELEJMZBS.js +22 -0
  53. package/dist/db-MZIAKGBN.js +9 -0
  54. package/dist/delete-H47KPJSB.js +3 -0
  55. package/dist/deploy-HGFJLLUO.js +1 -0
  56. package/dist/dev-62VA7UEM.js +86 -0
  57. package/dist/email-NNFD3VVB.js +10 -0
  58. package/dist/events-JDY76EG6.js +15 -0
  59. package/dist/groups-NQFXDZZP.js +8 -0
  60. package/dist/index.js +92 -1
  61. package/dist/install-JTZWRZIL.js +1 -0
  62. package/dist/login-P6LYQQWP.js +1 -0
  63. package/dist/mcp-NCFWEBMD.js +1 -0
  64. package/dist/provider-WVBI2VED.js +13 -0
  65. package/dist/release-6FYSPXXA.js +1 -0
  66. package/dist/restore-XBU7DOJV.js +3 -0
  67. package/dist/secret-ML5EXS3O.js +1 -0
  68. package/dist/setup-IEXCISLP.js +1 -0
  69. package/dist/status-OPLPKJ4U.js +4 -0
  70. package/dist/teardown-3JMMLK6O.js +22 -0
  71. package/dist/update-37ZDATLR.js +1 -0
  72. package/dist/workspace-DAFZVYQ2.js +21 -0
  73. package/package.json +30 -27
  74. package/skills/build-every-app/SKILL.md +199 -0
  75. package/skills/build-every-app/patterns.md +134 -0
  76. package/skills/everyapp/SKILL.md +292 -0
  77. package/skills/everyapp/mcp.md +336 -0
  78. package/skills/everyapp/security-review.md +61 -0
  79. package/skills/everyapp/setup/README.md +263 -0
  80. package/skills/everyapp/setup/access.md +178 -0
  81. package/skills/everyapp/setup/context.md +117 -0
  82. package/skills/everyapp/setup/dataforseo.md +287 -0
  83. package/skills/everyapp/setup/email.md +366 -0
  84. package/skills/everyapp/setup/gateway.md +243 -0
  85. package/skills/everyapp/setup/google.md +360 -0
  86. package/skills/everyapp/setup/microsoft-365.md +281 -0
  87. package/skills/everyapp/setup/openai.md +318 -0
  88. package/skills/everyapp/setup/openrouter.md +309 -0
  89. package/skills/everyapp/setup/providers.md +353 -0
  90. package/skills/everyapp/setup/remote-mcp.md +199 -0
  91. package/skills/everyapp/setup/slack.md +344 -0
  92. package/skills/everyapp/setup/workers-ai.md +138 -0
  93. package/bin/__everyapp_bash_complete.js +0 -12
  94. package/dist/bash-complete.js +0 -3
  95. package/dist/chunk-7BOQJFI5.js +0 -8
  96. package/dist/chunk-7YVJFS4A.js +0 -1
  97. package/dist/chunk-BIXKPCDH.js +0 -2
  98. package/dist/chunk-DLGPLHBH.js +0 -47
  99. package/dist/chunk-FD6K2YA7.js +0 -9
  100. package/dist/chunk-GAJEQYJH.js +0 -39
  101. package/dist/chunk-O2KDOP5R.js +0 -13
  102. package/dist/chunk-OI2MBGBG.js +0 -4
  103. package/dist/chunk-POSDLD3G.js +0 -8
  104. package/dist/chunk-RB5D2R45.js +0 -1
  105. package/dist/chunk-U4LIQ5VR.js +0 -1
  106. package/dist/chunk-VEYSNPDH.js +0 -3
  107. package/dist/chunk-VP6SKW44.js +0 -7
  108. package/dist/chunk-WO3NE7ZU.js +0 -7
  109. package/dist/chunk-XTO2VTPY.js +0 -6
  110. package/dist/chunk-YJ2DKIZS.js +0 -1
  111. package/dist/create-DYQ3NBWJ.js +0 -22
  112. package/dist/deploy-PYTCMDGV.js +0 -7
  113. package/dist/deploy-RMAHOA4Z.js +0 -55
  114. package/dist/dev-SAE237FS.js +0 -0
  115. package/dist/generateConfig-46Z3ERF3.js +0 -1
  116. package/dist/login-B6EF3SMX.js +0 -1
  117. package/dist/logout-XMKZBLNV.js +0 -1
  118. package/dist/remoteD1Shell-A67JMO3H.js +0 -6
  119. package/dist/setupLocal-TZJUW5ES.js +0 -6
  120. package/gateway/every-app-gateway-build.tar.gz +0 -0
@@ -0,0 +1,292 @@
1
+ ---
2
+ name: everyapp
3
+ description: Create, run, deploy and manage private web apps on Cloudflare with everyapp (Every App). Use before running any everyapp command, to deploy an app, set a secret, query its database, check the Gateway, or answer any "how do I" question about Every App.
4
+ ---
5
+
6
+ everyapp: self-hosted platform for private web apps on Cloudflare. While
7
+ publishing is paused the command is `pnpm everyapp` (never `npx everyapp`),
8
+ run from the Every App checkout or from an app's directory (its package.json
9
+ links the CLI).
10
+
11
+ Docs: two worlds. The user docs (https://everyapp.com/docs) are short and
12
+ for the person. The agent docs are yours: https://everyapp.com/llms.txt
13
+ lists every page with a one-line summary; a page is Markdown at its URL
14
+ plus .md (https://everyapp.com/docs/agent/manifest.md); in a checkout they
15
+ are landing-page/content/agent/*.md. Pages: setup (driving setup for a
16
+ person, every handoff), cli (every command and flag), json (the --json
17
+ protocol), errors (every code and its fix), troubleshooting (by symptom),
18
+ status (the report, every line, and its fixes in order), manifest
19
+ (everyapp.config.ts), identity (headers, token, env, SDK), secrets
20
+ (secrets vs Gateway settings vs capabilities), dev (dev vs production),
21
+ email, events (webhooks, email alerts, the signature, every event type,
22
+ emit()), mcp (tools, connector, OAuth), deploy (the pipeline, what the policy
23
+ check refuses), providers (API providers: the catalog, registering one,
24
+ a spec's fields), access (Cloudflare Access), workspace (the workspace from
25
+ the terminal and from an AI client: every command and every tool), gateway
26
+ (everything a Gateway offers: apps vs MCP apps vs the compact MCP surface,
27
+ people, groups and caps, the CLI by job, what to suggest next, other MCP
28
+ servers), build (the build-every-app skill as a page), patterns (its
29
+ example-patterns index), repo/* (security model, capabilities, runbooks).
30
+ Step-by-step setup of each
31
+ integration (the Gateway, Access, email, DataForSEO, Context, OpenRouter,
32
+ OpenAI, Workers AI, Google, Microsoft 365, Slack; every catalog provider by
33
+ id in setup/providers.md): setup/README.md beside this file.
34
+ Answering "how do I": fetch the page for the subject and answer from it
35
+ with the exact command or manifest field, naming the page. A failed
36
+ command: look its code up on errors (anchor: the code, lowercased) before
37
+ trying anything else. Never guess a flag: cli has them all.
38
+ Building an app: the Gateway signs people in, so never build a login page
39
+ or auth; read the user with the SDK (identity page), and scope every query
40
+ by user.id: the Gateway admits every member of the organization. A new app
41
+ edits everyapp.config.ts for name, resources, public, providers,
42
+ capabilities, secrets and email (manifest page), and the Gateway gives it
43
+ nothing it doesn't declare there. AI: list the provider, providers:
44
+ ["openai"] (or openrouter, workers-ai), and call it with the SDK's
45
+ createGatewayFetch({ env, for: request }), a fetch for
46
+ http://every-app-gateway.internal/api/ai/<provider>/... (an OpenAI client
47
+ takes it as fetch, with http://every-app-gateway.internal/api/ai/openai/v1
48
+ as baseURL; the other two base URLs and the code are on the identity
49
+ page); the Gateway holds the key, never the app, and a provider the
50
+ manifest doesn't list is refused provider_not_allowed 403. A capability
51
+ (members, email, an OAuth client, DataForSEO) is listed under
52
+ capabilities with access and a why and called with listMembers(),
53
+ sendEmail() or integrationFetch() (secrets page); an OAuth client with
54
+ scope sets (google-workspace, microsoft-365, slack) is listed with
55
+ scopes: [...] and held to them; after a schema change run pnpm run
56
+ db:generate; a value only dev needs goes in .dev.vars, never in git (dev
57
+ page). The way back: the first row of the app's sidebar (or of its page
58
+ header when it has no sidebar) is a muted "‹ Every App" link to
59
+ GATEWAY_URL (aria-label "Back to Every App"), above the app's own name,
60
+ so every app on the Gateway reads the same; put nothing else about the
61
+ Gateway in the footer.
62
+
63
+ Pass --json. One JSON event per line; "done" = success; exit 2 = show the
64
+ user the last action_required (url, code), then run its data.rerun (the same
65
+ command, unless it names another); exit 1 = show the error; if data.authorize
66
+ is set, ask before using data.rerun. waiting: true = keep reading (5 min
67
+ for the device login, up to 15 for the owner link).
68
+
69
+ State: pnpm everyapp status --json (data.fixes says what to run when something is off;
70
+ data.next says what to do next: at most four { code, command | url, note, why }
71
+ from the Gateway's state, never repeating a fix; tell the person
72
+ each why and offer the command or page; data.apps[].setup says what each
73
+ app needs set: ready, settings, unmet (each with why and docs), and
74
+ command, pnpm everyapp configure <app>)
75
+ Gateway: pnpm everyapp setup --domain <host> --json (install, upgrade, repair;
76
+ a fresh install runs 5-20 min: build, certificate, then up to 15 min
77
+ waiting for the owner link, so give it a long timeout or background
78
+ it and keep reading; run data.rerun as given (a needsTerminal rerun
79
+ goes to the person))
80
+ New app: pnpm everyapp create ../<id> --json (from the every-app folder: the
81
+ app is its own repository beside it, linked to the checkout; a bare
82
+ <id> makes apps/<id> inside the checkout instead) then cd into it and
83
+ edit src/; the manifest is everyapp.config.ts
84
+ Run: pnpm everyapp dev (long-running, human-facing: start it in the
85
+ background and read the printed URL; no --json; --port when 8787 is
86
+ taken; stop it with Ctrl-C/SIGINT to the everyapp process, never a
87
+ pattern kill of vite or workerd; a pnpm install or deploy meanwhile
88
+ rebuilds the SDK and breaks dev for a few seconds)
89
+ Ship: pnpm everyapp deploy --json (from the app directory; safe to repeat;
90
+ also upgrades the Gateway to this checkout when it has to rebind).
91
+ No Gateway yet: action_required install_gateway; ask the user, run
92
+ its data.rerun (setup), then deploy again. done.data.publicRoutes
93
+ lists the routes the manifest opens to the internet without
94
+ sign-in; tell the person which.
95
+ Release: someone else's app, without a clone: pnpm everyapp install
96
+ https://github.com/<owner>/<repo> --json (its latest release's
97
+ everyapp-release.tar.gz; a tarball URL or file works too). NO_RELEASE:
98
+ the repository publishes none; clone it, pnpm install in it, then
99
+ this checkout's pnpm everyapp deploy from its directory. Never
100
+ build a repository from its URL yourself.
101
+ To publish one: pnpm everyapp release, then attach the
102
+ tarball to a GitHub release of the app's repository, or give
103
+ the repository the Release workflow that does it on every push
104
+ (pnpm pack-for-app <app> --workflow from this checkout;
105
+ docs/runbooks/app-releases.md).
106
+ Data: pnpm everyapp db migrate (local) pnpm everyapp db query "<sql>" [--local] --json
107
+ (a production write stops with CONFIRMATION_REQUIRED: add --yes only
108
+ if the user said so). pnpm everyapp db restore --json undoes the last
109
+ deploy's migrations (and every write since): only when the user asks.
110
+ Postgres: an app with resources.hyperdrive or migrations.dialect "postgresql"
111
+ reads POSTGRES_DATABASE_URL (and <BINDING>_ORIGIN_URL) from
112
+ .everyapp/secrets.env, which the user writes; never print, commit or
113
+ ask for its values in chat. deploy runs drizzle-kit migrate before
114
+ the upload.
115
+ Secrets: pnpm everyapp configure <app> --json sets up an app: every setting its
116
+ manifest declares, each where it is held (an AI key or a capability's
117
+ client on the Gateway, once for every app; the app's own secrets on
118
+ its Worker, after its first deploy); live at once, no deploy. At a
119
+ terminal it shows what is set (names only), asks for each required
120
+ setting that isn't with a hidden prompt (why, where to get it and its
121
+ guide above), which member of an any-of group, then offers the
122
+ optional ones; from you (no prompt): NAME=value lines on stdin (one
123
+ per line, from a command's output) or --from-env (each unset
124
+ setting from the variable of its own name; stdin isn't read then).
125
+ A value never goes on the command line (VALUE_ON_COMMAND_LINE). One
126
+ name at a time: pnpm everyapp secret set NAME [--app <id>] --json
127
+ (the value from a hidden prompt, stdin, or --from-env VAR). The name
128
+ decides where it goes. A Gateway setting (OPENAI_API_KEY, EMAIL_FROM,
129
+ GOOGLE_WORKSPACE_CLIENT_ID, …) is set on the Gateway from any
130
+ directory. An app's own secret must be listed under `secrets` in its
131
+ everyapp.config.ts and is set after its first deploy, from the app's
132
+ directory or from anywhere with --app <id> (SECRET_NOT_DECLARED: add
133
+ it there first). secret unset NAME [--app <id>]; secret list [<app>]
134
+ --json (one app's settings, where each is held and whether it is set,
135
+ with data.app.setup; elsewhere with no app named: the Gateway's names
136
+ and every app's settings).
137
+ Needs setup: read data.apps[].setup; for each unmet setting show its why and docs
138
+ and read its setup guide (.claude/skills/everyapp/<setup>); then run
139
+ setup.command, pnpm everyapp configure <app>, without a value passing
140
+ through the chat: tell the person to run it in their own terminal (it
141
+ asks for each setting with a hidden prompt and says where to get it)
142
+ and say when done; or drive it yourself, --json always: NAME=value
143
+ lines on stdin from a command's output, or --from-env for variables
144
+ the person exported before starting you. Never a value on the command
145
+ line; never ask for, print, repeat or invent a value. An app whose
146
+ setup.ready is false doesn't open (status shows "needs setup"; deploy
147
+ warns APP_NEEDS_SETUP and puts the command under next); an any-of
148
+ group (setup.groups) needs one member set, not all: configure asks
149
+ which.
150
+ Email: pnpm everyapp email set --sender "*@<domain>" --inbound <address> --json
151
+ In the app's directory, after its first deploy; nothing is deployed. --sender
152
+ replaces the From addresses the Gateway lets the app name (everyapp.config.ts
153
+ lists capabilities.email); --inbound replaces the addresses routed to its
154
+ email() handler (everyapp.config.ts declares email: { inbound: true }) and
155
+ makes the Email Routing rules now. EMAIL_ROUTING_DISABLED needs the user's
156
+ yes: rerun with --enable-email-routing only if they said so. email unset
157
+ senders|inbound clears; email show prints both.
158
+ Access: pnpm everyapp access on --json (Cloudflare Access in front of the Gateway
159
+ and its apps: finds or creates the Zero Trust team (--team NAME when the
160
+ account has none: permanent, ask the user), keeps both sign-in methods
161
+ (Sign in with Cloudflare for the account's members, the one-time PIN for
162
+ everyone else), keeps the allow policy with every member's and invitee's
163
+ email, puts two Access applications in front of the hosts with the
164
+ Gateway's MCP, OAuth and /health and each app's public routes let through
165
+ (an app host's /mcp only where its manifest sets mcp), and sets the three
166
+ CLOUDFLARE_ACCESS_* Gateway settings in one version. Rerunnable; run again
167
+ after inviting someone. access off asks (--yes); access show reports. Each
168
+ existing member then signs in once with their password through Access;
169
+ tell the user who (done.data.signIn).
170
+ Set up: what the Gateway holds for apps, end to end (the provider's side, the
171
+ key or OAuth client, configure or secret set, the check): read
172
+ setup/README.md in this skill first, then the file it names, each on
173
+ its own with the file-reading tool (together they pass a shell's
174
+ output cap). Cloudflare and the Gateway
175
+ (setup/gateway.md), Cloudflare Access (setup/access.md), email
176
+ (setup/email.md), API keys (setup/dataforseo.md, context.md,
177
+ openrouter.md, openai.md, workers-ai.md), OAuth clients
178
+ (setup/google.md, microsoft-365.md, slack.md), any other catalog
179
+ provider (setup/providers.md#<id>, generated from the catalog); a
180
+ provider registered on the Gateway has no file: pnpm everyapp
181
+ provider show <id> prints its spec. A key or client secret
182
+ never goes through the chat: README.md says the three ways it gets in.
183
+ Remove: pnpm everyapp delete <id> --json never add --purge, --force-dns,
184
+ --allow-delete-*, --allow-downgrade, or --yes to delete unless the
185
+ user asked.
186
+ MCP apps: pnpm everyapp mcp on dataforseo --json offers a data provider's
187
+ tools (dataforseo_<tool>) to members' AI clients through the Gateway's
188
+ /mcp with the Gateway's key (DATAFORSEO_API_KEY, setup/dataforseo.md);
189
+ no page, nothing to deploy (the launcher lists it under "MCP apps").
190
+ pnpm everyapp mcp add <url> --auth bearer --json does the same for
191
+ any remote MCP server (Streamable HTTP): <id>_<tool>, its credential
192
+ a Gateway setting <ID>_MCP_TOKEN set with secret set and pinned to
193
+ the URL's origin (setup/remote-mcp.md). add looks at the server once
194
+ and only warns. mcp list shows each app's kind, on/off/needs setup,
195
+ tools and key; mcp show <id> its URL, credential and every tool with
196
+ its risk; mcp tool <id> <tool> --risk read|write|destructive or --off
197
+ overrides one tool; mcp refresh <id> re-lists. mcp off <id> stops
198
+ serving and keeps access (mcp on is instant); mcp remove <id> deletes
199
+ it (the key stays; before 2026-10-25 off deleted it). The credential
200
+ never goes through the chat (setup/README.md's three ways). provider
201
+ on|off still run mcp on|off through launch week; provider list is the
202
+ API providers' list. You, owners and admins get it;
203
+ others are granted in Admin → Apps → Manage access. An owner or admin
204
+ caps an app's provider calls per month in Admin → Usage (off by
205
+ default): past it a provider tool answers a tool error naming the
206
+ cap, and an app's brokered call gets 429 app_monthly_cap_exceeded
207
+ until the next UTC month.
208
+ APIs: pnpm everyapp provider add stripe.json --json registers an API
209
+ provider the catalog doesn't have on the Gateway: a JSON spec (hosts,
210
+ auth, settings named after its id, STRIPE_…; never a value; fields
211
+ and rules on the providers page), from a file or an https URL. Then
212
+ pnpm everyapp secret set STRIPE_API_KEY (it names the provider and
213
+ the hosts the key goes to, and binds the key to them: a row replaced
214
+ with --replace needs the key set again). An app lists it under
215
+ capabilities like a catalog provider and calls integrationFetch().
216
+ provider list (the catalog and the rows), provider show <id> (the
217
+ whole spec; --save writes .everyapp/providers/<id>.json for dev),
218
+ provider remove <id> (PROVIDER_IN_USE while an app lists it).
219
+ Workspace: pnpm everyapp workspace rename "Acme Corp" --json renames the workspace
220
+ (the Gateway's organization: the name in the switcher and in invitations;
221
+ 1 to 64 characters, no line breaks). Nothing is deployed; members see it
222
+ on their next page. Owners and admins can do the same under Manage
223
+ workspace → Rename. WORKSPACE_NOT_CLAIMED: the Gateway has no owner yet.
224
+ Roles: pnpm everyapp workspace role <email> owner|admin|member --json sets a
225
+ member's role in the workspace under the Cloudflare login (the Users
226
+ page does the same). Owners are peers and the workspace always keeps
227
+ one: the last owner's role is refused (LAST_OWNER); someone still
228
+ invited isn't a member yet (MEMBER_NOT_FOUND).
229
+ Manage: pnpm everyapp workspace --help the rest of Manage workspace from the
230
+ terminal, under the Cloudflare login: show, users, invite (prints the
231
+ link; nothing is mailed), invite-link, uninvite, deactivate, reactivate,
232
+ remove (--yes), reset-link, groups, group create|set|delete|members|apps,
233
+ apps, app set|access|cap, usage, activity, tokens, token revoke,
234
+ connections, connection revoke; --json on each. --org <slug> acts on
235
+ another organization (a name two organizations share is refused:
236
+ ORG_AMBIGUOUS; scripts pass the slug or id). deactivate and remove keep
237
+ the last owner who can act (LAST_OWNER); a ban is account-wide, so
238
+ deactivate checks every organization the person owns.
239
+ Events: pnpm everyapp events add <url> --events 'cap.*,member.*' --json sends
240
+ those Gateway events to a webhook (Slack workflow, Zapier, n8n), signed;
241
+ the generated secret is data.secret, shown once: hand it to the person,
242
+ never invent or echo one. --email admins mails owners and admins instead.
243
+ events list|test <id>|deliveries --dead|redeliver <id>; events types lists
244
+ types. An app emits its own with emit() (app.<id>.<name>), declaring
245
+ nothing. Everything: /docs/agent/events.
246
+ Tools: an app's AI tools are defineTools() in src/tools.ts; the Gateway
247
+ serves them at https://<gateway>/mcp as <app>_<tool> (dev:
248
+ http://<app-id>.localhost:8787/mcp) and snapshots them at deploy.
249
+ How to write, connect and debug them: mcp.md beside this file.
250
+ Wipe: pnpm everyapp teardown --account-id <id> --dry-run --json deletes
251
+ EVERYTHING Every App made in one Cloudflare account (the Gateway too):
252
+ only on a dedicated test account the user names, never one they use.
253
+ Always --dry-run --json first and show the user data.plan. It needs
254
+ EVERYAPP_TEST_ACCOUNTS=<id> and CLOUDFLARE_API_TOKEN scoped to that
255
+ account in the environment, which the user sets. Without --confirm it
256
+ stops with TEARDOWN_CONFIRMATION_REQUIRED (authorize): never add
257
+ --confirm <id> unless the user explicitly asked for the deletion.
258
+
259
+ Security: the Gateway signs people in and passes identity to the app in a
260
+ header the SDK verifies (everyApp(), getEveryAppUser(),
261
+ requireEveryAppUser(), a tool call's identity); never read who the
262
+ user is from a client-supplied header, cookie or parameter. Routes
263
+ are private unless listed under `public` in everyapp.config.ts; a
264
+ public route is open to the internet and does its own checks.
265
+ Before opening a pull request that touches identity, public routes,
266
+ tools, secrets or queries, review the changes locally against the
267
+ app boundaries in security-review.md beside this file: in Claude
268
+ Code, /security-review (its built-in review of the branch's pending
269
+ changes) pointed at that file; any other agent reads git diff
270
+ against the list there. No CI job and no API key: the review runs
271
+ where you run. Fix what it finds, or say in the pull request why a
272
+ finding is a false positive. What the Gateway promises and what an
273
+ app must do itself: docs/threat-model.md in the Every App checkout
274
+ (https://everyapp.com/docs/agent/repo/threat-model).
275
+
276
+ Only a person can: approve the Cloudflare login, open the owner link, pick
277
+ a workers.dev subdomain, pay for Workers Paid, buy a domain. Interactive
278
+ setup optionally offers browser deployment: a Yes/No prompt (default No),
279
+ then a hidden Cloudflare API token prompt. Never ask for its value in chat.
280
+ It then offers Cloudflare Access (Yes by default; the team name asked for
281
+ when the account has none); under --json neither is asked, and --yes
282
+ answers No to both.
283
+ Handing off: stop, tell the person in one or two lines what to do (the
284
+ action's url and code, what to click, what to come back with), and resume
285
+ with data.rerun when they say it is done. Two more only a person can do:
286
+ pick the account (choose_account: data.choices, each with its rerun) and
287
+ confirm in a terminal a Worker changed outside everyapp (rerun_interactive,
288
+ data.needsTerminal: true: hand them the command without --json; adding it
289
+ back loops). https://everyapp.com/docs/agent/setup.md has the words for
290
+ each handoff and the order of a whole setup.
291
+ Full reference: https://everyapp.com/docs/agent/cli.md (every command and flag),
292
+ https://everyapp.com/llms.txt (every page).
@@ -0,0 +1,336 @@
1
+ # Agent tools (MCP)
2
+
3
+ An app never speaks MCP. It describes its tools with `defineTools()` in
4
+ src/tools.ts and passes them to `everyApp(handler, { tools })`; the Gateway
5
+ snapshots that catalog and serves AI clients at `https://<gateway>/mcp` (every
6
+ app the caller can use, tools named `<app-id>_<tool>`). An app's own host
7
+ serves `https://<app-host>/mcp` (that app alone) only when everyapp.config.ts
8
+ says `mcp: { by: "gateway", why: "..." }`; leave it out (the default), and
9
+ point clients at the Gateway. Never add an MCP server, an /mcp route, a login
10
+ or a token check to an app. Full reference: https://everyapp.com/docs/agent/mcp.md (in a
11
+ checkout landing-page/content/agent/mcp.md); the person's steps: https://everyapp.com/docs/connect-ai.
12
+
13
+ ## Define tools: src/tools.ts
14
+
15
+ The starter's shape (starter/src/tools.ts, shortened; the queries and their zod
16
+ schemas live in starter/src/server/todos.ts, shared with the server functions):
17
+
18
+ ```ts
19
+ import { ToolError, defineTools } from "@every-app/sdk/server";
20
+ import { z } from "zod";
21
+ import * as todos from "@/server/todos";
22
+
23
+ export const tools = defineTools(
24
+ {
25
+ list_todos: {
26
+ description: "List the user's todos, completed ones included.",
27
+ risk: "read",
28
+ input: z.object({}).strict(),
29
+ handler: (_input, { user }) => todos.listTodos(user.id),
30
+ },
31
+ create_todo: {
32
+ description: "Add a todo. The server assigns its id.",
33
+ risk: "write",
34
+ idempotent: false,
35
+ input: todos.createTodoInput,
36
+ handler: (input, { user }) => todos.createTodo(user.id, input),
37
+ },
38
+ delete_todo: {
39
+ description: "Delete one todo for good.",
40
+ risk: "destructive",
41
+ input: todos.deleteTodoInput,
42
+ handler: async (input, { user }) =>
43
+ (await todos.deleteTodo(user.id, input)) ?? raise("No todo with that id"),
44
+ },
45
+ },
46
+ {
47
+ instructions:
48
+ "Never guess todo ids: call list_todos first when an id isn't already known.",
49
+ },
50
+ );
51
+
52
+ /** A message the model may read; any other error reaches it as "Internal error". */
53
+ function raise(message: string): never {
54
+ throw new ToolError(message);
55
+ }
56
+ ```
57
+
58
+ Register them once, in src/entry.worker.ts:
59
+ `export default everyApp<Env>(async (request) => tanstackEntry.fetch(request), { tools });`
60
+
61
+ Names: lowercase snake_case, a letter first, at most 48 characters; on the
62
+ Gateway connector `<app-id>_<tool>` must fit 64 or the tool is skipped.
63
+ Fields: `description` (required; the model reads it; over 2048 characters the
64
+ Gateway skips the tool), `risk` (required), `input` (required: zod 4.2
65
+ or later, any Standard Schema with JSON Schema support, or a plain
66
+ `{ jsonSchema, validate }`; it must describe an object),
67
+ `handler(input, context)` (required), `idempotent` (boolean, sent as
68
+ `annotations.idempotentHint`), `title`, `output` (an object schema,
69
+ validated and sent as `structuredContent`), `openWorld`. Second
70
+ argument `{ instructions }`: guidance across the tools, at most 2048;
71
+ on the Gateway connector it is clipped to 1024 and placed after the
72
+ app list, and Claude Code shows the model only the first 4,096
73
+ characters of the whole (Codex's docs ask that the first 512 stand
74
+ alone), so put what matters first.
75
+ Risk: "read": listed and callable for every caller (`readOnlyHint: true`).
76
+ "write": hidden from read-only connections (`read_only_connection`).
77
+ "destructive": hidden from every AI client until approvals exist
78
+ (`destructive_requires_approval`): absent from tools/list, a call
79
+ answers `-32602 Unknown tool: <name>`. Keep the handler.
80
+ Context: the handler's second argument: `user` (the verified caller: `id`,
81
+ `email`, `orgId`, `orgRole`, `channel: "mcp"`, `actor.sub` of
82
+ `pat:<token id>`, `oauth:<connection id>` or `access:<email>`,
83
+ `scopes` of `["mcp:read"]` or `["mcp:read", "mcp:write"]`, and
84
+ `toolCall` with this call's `id` and `name`), `env`, `ctx`, `request`
85
+ (the internal `POST /__everyapp/tools/call`), `callId`. Scope every
86
+ read and write by `user.id`: a client never supplies a user id.
87
+ Results: a string (one text block), any JSON value (a JSON text block), a value
88
+ checked by `output` (text plus `structuredContent`), or
89
+ `toolResult({ text, structured })`. At most 1 MiB reaches the client.
90
+ Errors: throw `new ToolError("No todo with that id")` for a message the model
91
+ may read (`isError: true`). Any other error is logged in the app as
92
+ `[everyapp] tool "<name>" failed (ref <callId>): …` and reaches the
93
+ client only as `Internal error in <app-id> (ref <callId>)`.
94
+ A bad definition throws at module load (`defineTools: tool "<name>" needs a risk: "read", "write" or "destructive"`),
95
+ so the Worker fails rather than serving a catalog without the tool; a schema with
96
+ no object JSON Schema form only skips that tool with a warning (`TOOLS_CATALOG` in dev).
97
+
98
+ MCP apps: the Gateway also serves tools that aren't any app's, with no page
99
+ and nothing to deploy, on the same /mcp under the same access rules: a data
100
+ provider's built in (DataForSEO: `dataforseo_<tool>`, once the operator runs
101
+ `pnpm everyapp mcp on dataforseo` and sets DATAFORSEO_API_KEY), and any remote
102
+ MCP server added by URL (`pnpm everyapp mcp add <url> --auth bearer`, its tools
103
+ `<id>_<tool>`). For a remote server the Gateway holds the credential (the
104
+ Gateway setting `<ID>_MCP_TOKEN`, pinned to the URL's origin), forwards each
105
+ call, and decides each tool's risk from the server's annotations (no hint
106
+ counts as `write`; `mcp tool <id> <tool> --risk …` overrides one). `mcp off`
107
+ stops serving and keeps access; `mcp remove` deletes it. Never wrap a
108
+ remote MCP server in an app of your own to put it behind the Gateway: add it
109
+ (setup/remote-mcp.md, SKILL.md "MCP apps").
110
+
111
+ ## What the Gateway does with them
112
+
113
+ Snapshot: the Gateway stores each app's catalog in D1 with the app version it
114
+ came from, taken when the app is activated (the first deploy: the CLI waits
115
+ for the app to go live, so the catalog exists before anyone connects), when a
116
+ deploy through the Gateway (a browser install) finishes a new version, on the first app-host request each
117
+ isolate sees after a self-hosted redeploy, when `/mcp` or the consent page
118
+ finds a missing or outdated snapshot (synced within the request, 3 s budget),
119
+ and when a call reports a drifted `catalogHash` or `unknown_tool`. The CLI's
120
+ deploy syncs nothing itself. One sync: `POST /__everyapp/tools/list` (15 s, a
121
+ 5 MiB response; no count cap per app: a catalog is bounded by its per-tool
122
+ limits, the response size and one atomic D1 write); status `ok`, `empty`,
123
+ `unsupported` (SDK older than 0.3) or `error` (retried at most every 5
124
+ minutes); logged as `mcp.catalog.sync`.
125
+ Calls: `tools/list` never calls the app. `tools/call` checks the arguments
126
+ against the snapshot, mints a 120 s identity JWT for that one call
127
+ (`chan: "mcp"`, explicit scopes, `tc: { id, name }`), makes one
128
+ `POST /__everyapp/tools/call` (30 s) and audits one `mcp_tool_calls` row.
129
+ Built-ins (Gateway connector only; always loaded in Claude Code):
130
+ `everyapp_list_apps` (the apps this connection can use),
131
+ `everyapp_whoami` (user, org, access), `everyapp_search_tools`
132
+ (`{ query?, app?, limit? }`: finds callable tools by words, by app
133
+ id or both, with their input schemas; read-only), and the two call
134
+ wrappers `everyapp_call_read` and `everyapp_call_write`
135
+ (`{ name, arguments? }`: run one app tool by its `<app-id>_<tool>`
136
+ name; the read wrapper runs read tools only with `mcp:read` alone,
137
+ the write wrapper is hidden from read-only connections). A wrapped
138
+ call is the same one call: same policy, checks and identity, and
139
+ one audit row naming the inner tool with `via` set to the wrapper.
140
+ Workspace tools (Gateway connector only; every admin action of the UI, listed
141
+ in full mode to a caller whose role reaches each; a read-only
142
+ connection never sees a write one; a token bound to one app or a
143
+ connection consented to some apps sees none of them; never a
144
+ credential that opens the Gateway; one audit row per call under app
145
+ `everyapp`): owners and admins get
146
+ `everyapp_workspace_show`, `everyapp_workspace_users`,
147
+ `everyapp_workspace_invite` (never the link), `everyapp_workspace_uninvite`,
148
+ `everyapp_workspace_role`, `everyapp_workspace_deactivate`,
149
+ `everyapp_workspace_reactivate`, `everyapp_workspace_reset_email`,
150
+ `everyapp_workspace_groups`, `everyapp_workspace_group_create`,
151
+ `everyapp_workspace_group_set`, `everyapp_workspace_group_delete`,
152
+ `everyapp_workspace_group_members`, `everyapp_workspace_group_members_set`,
153
+ `everyapp_workspace_group_apps`, `everyapp_workspace_group_apps_set`,
154
+ `everyapp_workspace_apps`, `everyapp_workspace_app_set`,
155
+ `everyapp_workspace_app_access`, `everyapp_workspace_app_access_set`,
156
+ `everyapp_workspace_app_cap`, `everyapp_workspace_usage`,
157
+ `everyapp_workspace_activity`, `everyapp_workspace_rename`, and the
158
+ events tools `everyapp_workspace_events`, `everyapp_workspace_events_add`
159
+ (a generated signing secret comes back once, in that answer: it opens
160
+ nothing on the Gateway, so the person stores it in the receiver now),
161
+ `everyapp_workspace_events_set`, `everyapp_workspace_events_remove`
162
+ (asks `confirm: true`; Cap alerts is turned off instead),
163
+ `everyapp_workspace_events_test`, `everyapp_workspace_event_deliveries`
164
+ and `everyapp_workspace_event_redeliver`; owners alone get
165
+ `everyapp_workspace_remove` and `everyapp_workspace_app_delete`
166
+ (both ask `confirm: true`); every member gets their own
167
+ `everyapp_my_tokens`, `everyapp_revoke_token`, `everyapp_my_connections`
168
+ and `everyapp_revoke_connection`. A user is an email or id, an app a
169
+ manifest id or row id, a group a name or `grp_` id, an event
170
+ subscription a name or `sub_` id. MCP apps, the
171
+ `mcp` commands as tools: `everyapp_mcp_list`, `everyapp_mcp_show`,
172
+ `everyapp_mcp_add` (a credential's setting name, never its value; the
173
+ answer names `secret set`), `everyapp_mcp_on`, `everyapp_mcp_off`,
174
+ `everyapp_mcp_refresh`, `everyapp_mcp_tool`, and for owners
175
+ `everyapp_mcp_remove` (`confirm: true`). Full reference:
176
+ /docs/agent/mcp#workspace-tools.
177
+ Provider tools (Gateway connector only; one audit row per call like the
178
+ workspace tools): every member gets `everyapp_provider_list` (the
179
+ API providers this Gateway brokers, catalog and rows, with their
180
+ settings and hints and which of this organization's apps list
181
+ each; set-ness and the key binding for owners and admins only);
182
+ owners and admins on a self-hosted Gateway also get
183
+ `everyapp_provider_add` (`{ spec }`: the spec object, never a URL,
184
+ never a replace of a row that is there) and
185
+ `everyapp_provider_remove` (`{ id }`: refuses a built-in, a row
186
+ whose key is set, and a row an app lists). A hosted Gateway hides
187
+ both writers. No key is ever set through a tool: `everyapp secret
188
+ set NAME` from the terminal. Full reference:
189
+ /docs/agent/mcp#provider-tools.
190
+ Listing: full (the built-ins, the workspace tools, then every tool) until a connection's full list
191
+ would pass 256 tools or 1 MiB, then compact (the built-ins only:
192
+ find tools with `everyapp_search_tools`, run them through the
193
+ wrappers or by name); `X-EveryApp-Tools: full|compact` chooses
194
+ for any caller that can set a header, `?tools=` on /mcp for a
195
+ personal token, an Access caller or Claude Code (the header first
196
+ when both are given); claude.ai, Claude Desktop and ChatGPT need
197
+ the OAuth resource to equal the URL typed, so for them the query
198
+ form fails to connect and the bare /mcp gets the automatic rule.
199
+ Compact hides nothing: an unlisted tool is still callable; a
200
+ hidden one stays `Unknown tool`. A read-only connection is told
201
+ to use `everyapp_call_read` alone.
202
+ Changes: 2026-07-28 clients (Claude Code) hold a `subscriptions/listen` stream
203
+ re-read every 30 s, so a change reaches them within about half a
204
+ minute; the Gateway ends it after 30 minutes (an app host after 10,
205
+ and never past the token's expiry) and the client opens a new one,
206
+ few enough an hour that Claude Code keeps listening indefinitely;
207
+ 2025-era clients get `notifications/tools/list_changed` and a
208
+ new `Mcp-Session-Id` on their next request (a stale session's
209
+ `tools/list` answers 404, so the client initializes again).
210
+ `tools/list` carries `ttlMs: 60000` (a minute's cache).
211
+
212
+ ## Local: pnpm everyapp dev
213
+
214
+ `pnpm everyapp dev` prints `http://<app-id>.localhost:8787/mcp` under "MCP" in
215
+ its ready block: a local ToolBus with the Gateway's policy, argument checks and
216
+ identity contract, serving the app's live `defineTools()` catalog, re-read
217
+ every 2 s, so an edit shows on the next request. In the default stub mode any
218
+ bearer value acts as the seeded dev user; in `--mode mirror` it takes a
219
+ personal access token from the local Gateway. Each call prints
220
+ `MCP <tool> <outcome> <ms>ms` or `MCP <tool> blocked: <reason>` (two
221
+ spaces between parts; outcome and reason codes are printed as words: `tool
222
+ error`, `destructive requires approval`). Claude Code:
223
+
224
+ ```bash
225
+ claude mcp add --transport http todo-local http://<app-id>.localhost:8787/mcp --header "Authorization: Bearer dev"
226
+ ```
227
+
228
+ Caveat: the dev ToolBus declares `listChanged: false`, so Claude Code sees
229
+ tools added to `defineTools()` only after reconnecting (`/mcp`, the server).
230
+
231
+ ## Connecting a person
232
+
233
+ Claude Code (2.1.81 and later), then `/mcp`, choose **everyapp**, choose
234
+ **Authenticate**, allow in the browser; `everyapp_whoami` confirms the account:
235
+
236
+ ```bash
237
+ claude mcp add --transport http everyapp https://<gateway>/mcp
238
+ ```
239
+
240
+ claude.ai, Claude Desktop, ChatGPT: a custom connector with the URL
241
+ `https://<gateway>/mcp`; the client identifies itself with a Client ID
242
+ Metadata Document (CIMD), finds the OAuth server from the 401 challenge, and
243
+ the consent page opens. Cursor, Gemini CLI, MCP Inspector: the same URL, after
244
+ dynamic client registration (`/oauth/register`; `MCP_DCR_ENABLED` controls it).
245
+ Consent page: the organization, **All apps I can use, including future ones**
246
+ or **Only these apps**, read & write (default) or **Read only**, then
247
+ **Allow**. It grants `mcp:read` and, for read & write, `mcp:write`; an app
248
+ shown as `(no AI tools yet)` has a snapshot with no tools; one with no
249
+ snapshot yet shows no hint (its tools load on first use).
250
+ Scripts and anything that takes a header: a personal access token. In the
251
+ Gateway open **Admin** (the sidebar footer's **Manage workspace**; owners and
252
+ admins only), tab **Tokens**, heading **Personal access tokens**, button
253
+ **New token**: a name, **All my apps** or one app, an expiry (default
254
+ 90 days). The token is `epat_…`, shown once, read & write. Send it as
255
+ `Authorization: Bearer epat_…` to `https://<gateway>/mcp` (a token made for one
256
+ app sees that app alone):
257
+
258
+ ```bash
259
+ claude mcp add --transport http everyapp https://<gateway>/mcp --header "Authorization: Bearer $EVERYAPP_TOKEN"
260
+ ```
261
+
262
+ `https://<app-host>/mcp` takes the same header only where that app's manifest
263
+ sets `mcp: { by: "gateway", why }`; on any other app host the path is the app's
264
+ own private route and an MCP-only token gets 403 `token_scope` everywhere.
265
+
266
+ Disconnecting: **AI connections** in the account menu at the bottom of the
267
+ sidebar (`/settings/connections`) lists each OAuth connection; revoking one
268
+ makes the client's next request fail `invalid_token`. Removing or
269
+ deactivating a person ends all of theirs; tokens are revoked on Admin, Tokens.
270
+
271
+ Other servers worth having beside the Gateway's (Cloudflare's API server for
272
+ the owner, read before write; nothing else): [setup/gateway.md](setup/gateway.md),
273
+ "Optional: MCP servers worth having beside Every App". Offer one in a line;
274
+ never add it unasked.
275
+
276
+ ## Troubleshooting
277
+
278
+ Tools missing after a deploy, in order: (1) the client's cache: tools/list is
279
+ good for 60 s and a held stream polls every 30 s, so list tools again after a
280
+ minute; (2) the snapshot: read `mcp.catalog.sync` in the Gateway's logs
281
+ (`unsupported`: an SDK older than 0.3; `error`: the app's tools endpoint
282
+ failed, retried in 5 minutes; a self-hosted redeploy is noticed from the app
283
+ host's next request in each isolate); (3) hidden on purpose: destructive
284
+ tools are never listed, write tools are hidden from a read-only connection
285
+ (`everyapp_whoami` shows `access`), and a tool whose namespaced name passes 64
286
+ characters, whose description passes 2048 or whose schema isn't an object is
287
+ dropped with a warning (dev prints it as `TOOLS_CATALOG`); unlisted is not
288
+ hidden: a compact listing (the instructions' first line says the connection
289
+ "lists its tools compactly") leaves every app tool out of tools/list on
290
+ purpose, and each is found by `everyapp_search_tools` and callable by name or
291
+ through `everyapp_call_read`/`everyapp_call_write`, while a hidden tool stays
292
+ `Unknown tool` there too; (4) the person
293
+ cannot open the app: it is absent from `everyapp_list_apps`; grant access in
294
+ Admin (a connection made with **Only these apps** needs a reconnect to add one).
295
+ 401 vs 403 on `/mcp`: 401 means no credential or one that doesn't verify (an
296
+ expired or revoked token, a bearer that isn't `epat_` on an app host, the token
297
+ of a revoked connection); its body depends on the surface. On
298
+ `https://<gateway>/mcp` with OAuth on: an empty body and
299
+ `WWW-Authenticate: Bearer realm="OAuth", resource_metadata="…/.well-known/oauth-protected-resource/mcp", scope="mcp:read"`
300
+ (`error="invalid_token"` added when a token was sent); a revoked connection's
301
+ token gets `{"error":"invalid_token","error_description":"This connection was revoked or lost its access. Connect again."}`.
302
+ On `https://<app-host>/mcp` (where the manifest sets `mcp`), and on the Gateway with OAuth off:
303
+ `{"error":"unauthenticated"}` with `WWW-Authenticate: Bearer realm="every-app"`
304
+ (`error="invalid_token"` when a bearer was sent; the app host adds
305
+ `error_description="Use a personal access token here, or connect <gateway>/mcp"`).
306
+ An app host without `mcp` in its manifest answers `/mcp` as the app does (usually 404),
307
+ and one with `by: "gateway"` but no catalog to serve answers 503 `{"error":"mcp_unavailable"}`.
308
+ 403 `{"error":"forbidden","reason":…}` means the credential verified and was
309
+ refused: `token_scope` (scopes without `mcp:read` or `mcp:write`, an
310
+ MCP-only token on a page route, or any route of a host without `mcp`), `no_app_access` (app host: the person cannot
311
+ open this app), `not_connected`, `organization_unresolved`, `account_deactivated`, `organization_suspended`.
312
+ OAuth off: `MCP_OAUTH_ENABLED` is `false`, or `GATEWAY_URL` is neither https
313
+ nor http on exactly localhost or 127.x: `/oauth/*` and the discovery documents
314
+ answer 404 `oauth_disabled`, `/mcp` takes `epat_` tokens only and its 401 says
315
+ `WWW-Authenticate: Bearer realm="every-app"` with no authorization server. Turn
316
+ it on with `pnpm everyapp secret set MCP_OAUTH_ENABLED`, entering `true` (live at once).
317
+ DCR off or limited: with `MCP_DCR_ENABLED` `false`, `/oauth/register` answers
318
+ 404 `registration_disabled`; CIMD clients still connect, Cursor cannot. While
319
+ on: 5 registrations a minute and 20 a day per client address, 200 a day per
320
+ self-hosted Gateway, then `429 rate_limited` (wait, or use a CIMD client); past
321
+ 60 requests a minute per address on `/oauth/register` or `/oauth/token`, 429 `Too many attempts.`
322
+ Behind Cloudflare Access: `pnpm everyapp access on` lets `/mcp`, `/mcp/*`,
323
+ `/oauth/token`, `/oauth/register`, `/.well-known/*` and `/health` through on the Gateway
324
+ host, and `/mcp` on an app host only where its manifest sets `mcp`, so OAuth and tokens keep working; the consent
325
+ page stays behind Access. With Managed OAuth on the Gateway's Access application
326
+ instead, Access is the OAuth server: the person must have signed in to the Gateway
327
+ in a browser once (else `403 not_connected`), gets read & write to every app, no connection row.
328
+
329
+ ## Sources
330
+
331
+ - packages/sdk/src/server/tools.ts, toolsEndpoint.ts, everyApp.ts; packages/sdk/src/internal/tools.ts, protocol.ts
332
+ - packages/toolbus/src/server.ts, http.ts, builtins.ts, policy.ts, dev/index.ts
333
+ - apps/every-app-gateway/src/server/toolbus/catalog-sync.ts, gateway-mcp.ts, app-host-mcp.ts; src/server/app-activation.ts, deploy/run.ts; src/server/mcp-auth/routes.ts, config.ts, validate.ts, consent.ts, registration-limit.ts
334
+ - apps/every-app-gateway/src/routes/_app/admin/tokens.tsx, settings/connections.tsx; src/client/components/Sidebar.tsx, oauth/ConsentForm.tsx, admin/CreateUserTokenModal.tsx
335
+ - packages/perimeter/src/gateway.ts; packages/cli/src/commands/dev/devAppGateway.ts, ready.ts; packages/cli/src/commands/deploy/deployApp.ts; packages/cli/src/lib/app/skill.ts
336
+ - starter/src/tools.ts, entry.worker.ts, server/todos.ts, README.md; docs/runbooks/mcp-oauth.md; landing-page/content/agent/mcp.md; landing-page/content/docs/connect-ai.mdx