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.
- package/README.md +6 -77
- package/bin/everyapp.js +235 -0
- package/dist/access-F3GKBKAW.js +1 -0
- package/dist/activity-4MFMH5JP.js +12 -0
- package/dist/apps-63K2DGVT.js +9 -0
- package/dist/chunk-2UFJRKEE.js +4 -0
- package/dist/chunk-3AAHHUJC.js +11 -0
- package/dist/chunk-3Z53CH4F.js +6 -0
- package/dist/chunk-5ZTFC6DK.js +1 -0
- package/dist/chunk-6O3GJS5H.js +15 -0
- package/dist/chunk-74EST4VW.js +2 -0
- package/dist/chunk-7LG3F4FF.js +10 -0
- package/dist/chunk-AUDGOIHE.js +1 -0
- package/dist/chunk-BHMRY53V.js +2 -0
- package/dist/chunk-DQWOXSL3.js +32 -0
- package/dist/chunk-E3EPK27P.js +10 -0
- package/dist/chunk-E7EUXUVD.js +1 -0
- package/dist/chunk-ECLJ5IAL.js +126 -0
- package/dist/chunk-EQX4GKJ2.js +4 -0
- package/dist/chunk-EUNGJMJ6.js +20 -0
- package/dist/chunk-F6IH47GT.js +4 -0
- package/dist/chunk-G64S77GC.js +33 -0
- package/dist/chunk-GD2JZMD3.js +1 -0
- package/dist/chunk-GRFPP4UW.js +7 -0
- package/dist/chunk-IA534DGW.js +10 -0
- package/dist/chunk-IG26HYRR.js +45 -0
- package/dist/chunk-IZEOAOKL.js +6 -0
- package/dist/chunk-JYKRUS5C.js +1 -0
- package/dist/chunk-KMAOZRV4.js +18 -0
- package/dist/chunk-MC56CUQI.js +7 -0
- package/dist/chunk-MDORVBPD.js +1 -0
- package/dist/chunk-N4E5YE4X.js +5 -0
- package/dist/chunk-NJFWEYX7.js +1 -0
- package/dist/chunk-OEOVNJAN.js +1 -0
- package/dist/chunk-OVKVV6MK.js +2 -0
- package/dist/chunk-PL6EORDJ.js +15 -0
- package/dist/chunk-PMPIMHKG.js +6 -0
- package/dist/chunk-QZWSSWMP.js +22 -0
- package/dist/chunk-R4CGF622.js +2 -0
- package/dist/chunk-RFVMR32Y.js +1 -0
- package/dist/chunk-RQFHVU3Y.js +25 -0
- package/dist/chunk-SAT3PGZU.js +57 -0
- package/dist/chunk-SPPR6WCI.js +1 -0
- package/dist/chunk-TPWSGRNT.js +30 -0
- package/dist/chunk-VVQRET35.js +2 -0
- package/dist/chunk-VXJQGJ7Y.js +2 -0
- package/dist/chunk-WEM3WLOC.js +1 -0
- package/dist/chunk-Y3VIFPN5.js +1 -0
- package/dist/chunk-YJBYZWSP.js +22 -0
- package/dist/chunk-ZYL2ZHCF.js +98 -0
- package/dist/configure-2KMHM22T.js +8 -0
- package/dist/create-ELEJMZBS.js +22 -0
- package/dist/db-MZIAKGBN.js +9 -0
- package/dist/delete-H47KPJSB.js +3 -0
- package/dist/deploy-HGFJLLUO.js +1 -0
- package/dist/dev-62VA7UEM.js +86 -0
- package/dist/email-NNFD3VVB.js +10 -0
- package/dist/events-JDY76EG6.js +15 -0
- package/dist/groups-NQFXDZZP.js +8 -0
- package/dist/index.js +92 -1
- package/dist/install-JTZWRZIL.js +1 -0
- package/dist/login-P6LYQQWP.js +1 -0
- package/dist/mcp-NCFWEBMD.js +1 -0
- package/dist/provider-WVBI2VED.js +13 -0
- package/dist/release-6FYSPXXA.js +1 -0
- package/dist/restore-XBU7DOJV.js +3 -0
- package/dist/secret-ML5EXS3O.js +1 -0
- package/dist/setup-IEXCISLP.js +1 -0
- package/dist/status-OPLPKJ4U.js +4 -0
- package/dist/teardown-3JMMLK6O.js +22 -0
- package/dist/update-37ZDATLR.js +1 -0
- package/dist/workspace-DAFZVYQ2.js +21 -0
- package/package.json +30 -27
- package/skills/build-every-app/SKILL.md +199 -0
- package/skills/build-every-app/patterns.md +134 -0
- package/skills/everyapp/SKILL.md +292 -0
- package/skills/everyapp/mcp.md +336 -0
- package/skills/everyapp/security-review.md +61 -0
- package/skills/everyapp/setup/README.md +263 -0
- package/skills/everyapp/setup/access.md +178 -0
- package/skills/everyapp/setup/context.md +117 -0
- package/skills/everyapp/setup/dataforseo.md +287 -0
- package/skills/everyapp/setup/email.md +366 -0
- package/skills/everyapp/setup/gateway.md +243 -0
- package/skills/everyapp/setup/google.md +360 -0
- package/skills/everyapp/setup/microsoft-365.md +281 -0
- package/skills/everyapp/setup/openai.md +318 -0
- package/skills/everyapp/setup/openrouter.md +309 -0
- package/skills/everyapp/setup/providers.md +353 -0
- package/skills/everyapp/setup/remote-mcp.md +199 -0
- package/skills/everyapp/setup/slack.md +344 -0
- package/skills/everyapp/setup/workers-ai.md +138 -0
- package/bin/__everyapp_bash_complete.js +0 -12
- package/dist/bash-complete.js +0 -3
- package/dist/chunk-7BOQJFI5.js +0 -8
- package/dist/chunk-7YVJFS4A.js +0 -1
- package/dist/chunk-BIXKPCDH.js +0 -2
- package/dist/chunk-DLGPLHBH.js +0 -47
- package/dist/chunk-FD6K2YA7.js +0 -9
- package/dist/chunk-GAJEQYJH.js +0 -39
- package/dist/chunk-O2KDOP5R.js +0 -13
- package/dist/chunk-OI2MBGBG.js +0 -4
- package/dist/chunk-POSDLD3G.js +0 -8
- package/dist/chunk-RB5D2R45.js +0 -1
- package/dist/chunk-U4LIQ5VR.js +0 -1
- package/dist/chunk-VEYSNPDH.js +0 -3
- package/dist/chunk-VP6SKW44.js +0 -7
- package/dist/chunk-WO3NE7ZU.js +0 -7
- package/dist/chunk-XTO2VTPY.js +0 -6
- package/dist/chunk-YJ2DKIZS.js +0 -1
- package/dist/create-DYQ3NBWJ.js +0 -22
- package/dist/deploy-PYTCMDGV.js +0 -7
- package/dist/deploy-RMAHOA4Z.js +0 -55
- package/dist/dev-SAE237FS.js +0 -0
- package/dist/generateConfig-46Z3ERF3.js +0 -1
- package/dist/login-B6EF3SMX.js +0 -1
- package/dist/logout-XMKZBLNV.js +0 -1
- package/dist/remoteD1Shell-A67JMO3H.js +0 -6
- package/dist/setupLocal-TZJUW5ES.js +0 -6
- 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
|