@every-app/sdk 0.2.0 → 0.3.0-rc.10

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 (189) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +609 -3
  3. package/dist/cloudflare/catalogSettings.generated.d.ts +81 -0
  4. package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -0
  5. package/dist/cloudflare/catalogSettings.generated.js +174 -0
  6. package/dist/cloudflare/index.d.ts +23 -0
  7. package/dist/cloudflare/index.d.ts.map +1 -0
  8. package/dist/cloudflare/index.js +24 -0
  9. package/dist/cloudflare/sentinel.d.ts +127 -0
  10. package/dist/cloudflare/sentinel.d.ts.map +1 -0
  11. package/dist/cloudflare/sentinel.js +85 -0
  12. package/dist/cloudflare/worker.d.ts +805 -0
  13. package/dist/cloudflare/worker.d.ts.map +1 -0
  14. package/dist/cloudflare/worker.js +997 -0
  15. package/dist/dev/index.d.ts +8 -0
  16. package/dist/dev/index.d.ts.map +1 -0
  17. package/dist/dev/index.js +7 -0
  18. package/dist/dev/keypair.d.ts +26 -0
  19. package/dist/dev/keypair.d.ts.map +1 -0
  20. package/dist/dev/keypair.js +62 -0
  21. package/dist/index.d.ts +6 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +5 -0
  24. package/dist/internal/aiDefault.d.ts +204 -0
  25. package/dist/internal/aiDefault.d.ts.map +1 -0
  26. package/dist/internal/aiDefault.js +334 -0
  27. package/dist/internal/aiProviders.d.ts +72 -0
  28. package/dist/internal/aiProviders.d.ts.map +1 -0
  29. package/dist/internal/aiProviders.js +233 -0
  30. package/dist/internal/appSettings.d.ts +315 -0
  31. package/dist/internal/appSettings.d.ts.map +1 -0
  32. package/dist/internal/appSettings.js +549 -0
  33. package/dist/internal/audit.d.ts +108 -0
  34. package/dist/internal/audit.d.ts.map +1 -0
  35. package/dist/internal/audit.js +235 -0
  36. package/dist/internal/capabilities.d.ts +3174 -0
  37. package/dist/internal/capabilities.d.ts.map +1 -0
  38. package/dist/internal/capabilities.js +1110 -0
  39. package/dist/internal/catalog/commerce.d.ts +394 -0
  40. package/dist/internal/catalog/commerce.d.ts.map +1 -0
  41. package/dist/internal/catalog/commerce.js +477 -0
  42. package/dist/internal/catalog/comms.d.ts +375 -0
  43. package/dist/internal/catalog/comms.d.ts.map +1 -0
  44. package/dist/internal/catalog/comms.js +520 -0
  45. package/dist/internal/catalog/index.d.ts +1973 -0
  46. package/dist/internal/catalog/index.d.ts.map +1 -0
  47. package/dist/internal/catalog/index.js +19 -0
  48. package/dist/internal/catalog/inference.d.ts +393 -0
  49. package/dist/internal/catalog/inference.d.ts.map +1 -0
  50. package/dist/internal/catalog/inference.js +460 -0
  51. package/dist/internal/catalog/infra.d.ts +423 -0
  52. package/dist/internal/catalog/infra.d.ts.map +1 -0
  53. package/dist/internal/catalog/infra.js +543 -0
  54. package/dist/internal/catalog/search.d.ts +400 -0
  55. package/dist/internal/catalog/search.d.ts.map +1 -0
  56. package/dist/internal/catalog/search.js +463 -0
  57. package/dist/internal/devProviders.d.ts +49 -0
  58. package/dist/internal/devProviders.d.ts.map +1 -0
  59. package/dist/internal/devProviders.js +80 -0
  60. package/dist/internal/email.d.ts +280 -0
  61. package/dist/internal/email.d.ts.map +1 -0
  62. package/dist/internal/email.js +613 -0
  63. package/dist/internal/events.d.ts +193 -0
  64. package/dist/internal/events.d.ts.map +1 -0
  65. package/dist/internal/events.js +367 -0
  66. package/dist/internal/gatewaySettings.d.ts +83 -0
  67. package/dist/internal/gatewaySettings.d.ts.map +1 -0
  68. package/dist/internal/gatewaySettings.js +215 -0
  69. package/dist/internal/identity.d.ts +57 -5
  70. package/dist/internal/identity.d.ts.map +1 -1
  71. package/dist/internal/identity.js +99 -22
  72. package/dist/internal/index.d.ts +22 -1
  73. package/dist/internal/index.d.ts.map +1 -1
  74. package/dist/internal/index.js +22 -1
  75. package/dist/internal/members.d.ts +36 -0
  76. package/dist/internal/members.d.ts.map +1 -0
  77. package/dist/internal/members.js +7 -0
  78. package/dist/internal/oauthClient.d.ts +61 -0
  79. package/dist/internal/oauthClient.d.ts.map +1 -0
  80. package/dist/internal/oauthClient.js +133 -0
  81. package/dist/internal/oauthPresets.d.ts +33 -0
  82. package/dist/internal/oauthPresets.d.ts.map +1 -0
  83. package/dist/internal/oauthPresets.js +1064 -0
  84. package/dist/internal/oauthScopes.d.ts +172 -0
  85. package/dist/internal/oauthScopes.d.ts.map +1 -0
  86. package/dist/internal/oauthScopes.js +510 -0
  87. package/dist/internal/oauthSpec.d.ts +102 -0
  88. package/dist/internal/oauthSpec.d.ts.map +1 -0
  89. package/dist/internal/oauthSpec.js +789 -0
  90. package/dist/internal/organizationName.d.ts +18 -0
  91. package/dist/internal/organizationName.d.ts.map +1 -0
  92. package/dist/internal/organizationName.js +24 -0
  93. package/dist/internal/protocol.d.ts +150 -3
  94. package/dist/internal/protocol.d.ts.map +1 -1
  95. package/dist/internal/protocol.js +163 -2
  96. package/dist/internal/providerApps.d.ts +75 -0
  97. package/dist/internal/providerApps.d.ts.map +1 -0
  98. package/dist/internal/providerApps.js +59 -0
  99. package/dist/internal/providerRegistry.d.ts +218 -0
  100. package/dist/internal/providerRegistry.d.ts.map +1 -0
  101. package/dist/internal/providerRegistry.js +432 -0
  102. package/dist/internal/providerSpec.d.ts +66 -0
  103. package/dist/internal/providerSpec.d.ts.map +1 -0
  104. package/dist/internal/providerSpec.js +1154 -0
  105. package/dist/internal/remoteMcp.d.ts +188 -0
  106. package/dist/internal/remoteMcp.d.ts.map +1 -0
  107. package/dist/internal/remoteMcp.js +543 -0
  108. package/dist/internal/signInProviders.d.ts +104 -0
  109. package/dist/internal/signInProviders.d.ts.map +1 -0
  110. package/dist/internal/signInProviders.js +245 -0
  111. package/dist/internal/tools.d.ts +115 -0
  112. package/dist/internal/tools.d.ts.map +1 -0
  113. package/dist/internal/tools.js +80 -0
  114. package/dist/manifest.d.ts +238 -0
  115. package/dist/manifest.d.ts.map +1 -0
  116. package/dist/manifest.js +30 -0
  117. package/dist/server/ai.d.ts +111 -0
  118. package/dist/server/ai.d.ts.map +1 -0
  119. package/dist/server/ai.js +145 -0
  120. package/dist/server/aiBinding.d.ts +68 -0
  121. package/dist/server/aiBinding.d.ts.map +1 -0
  122. package/dist/server/aiBinding.js +294 -0
  123. package/dist/server/attribution.d.ts +43 -0
  124. package/dist/server/attribution.d.ts.map +1 -0
  125. package/dist/server/attribution.js +75 -0
  126. package/dist/server/authorizeUrl.d.ts +43 -0
  127. package/dist/server/authorizeUrl.d.ts.map +1 -0
  128. package/dist/server/authorizeUrl.js +106 -0
  129. package/dist/server/connectUrl.d.ts +21 -0
  130. package/dist/server/connectUrl.d.ts.map +1 -0
  131. package/dist/server/connectUrl.js +37 -0
  132. package/dist/server/context.d.ts +68 -0
  133. package/dist/server/context.d.ts.map +1 -0
  134. package/dist/server/context.js +117 -0
  135. package/dist/server/emailBinding.d.ts +72 -0
  136. package/dist/server/emailBinding.d.ts.map +1 -0
  137. package/dist/server/emailBinding.js +124 -0
  138. package/dist/server/emit.d.ts +63 -0
  139. package/dist/server/emit.d.ts.map +1 -0
  140. package/dist/server/emit.js +101 -0
  141. package/dist/server/env.d.ts +44 -0
  142. package/dist/server/env.d.ts.map +1 -0
  143. package/dist/server/env.js +1 -0
  144. package/dist/server/everyApp.d.ts +65 -19
  145. package/dist/server/everyApp.d.ts.map +1 -1
  146. package/dist/server/everyApp.js +195 -30
  147. package/dist/server/fromMcpTools.d.ts +89 -0
  148. package/dist/server/fromMcpTools.d.ts.map +1 -0
  149. package/dist/server/fromMcpTools.js +192 -0
  150. package/dist/server/gatewayFetch.d.ts +54 -14
  151. package/dist/server/gatewayFetch.d.ts.map +1 -1
  152. package/dist/server/gatewayFetch.js +201 -37
  153. package/dist/server/gatewayUrl.d.ts +13 -0
  154. package/dist/server/gatewayUrl.d.ts.map +1 -0
  155. package/dist/server/gatewayUrl.js +34 -0
  156. package/dist/server/index.d.ts +23 -3
  157. package/dist/server/index.d.ts.map +1 -1
  158. package/dist/server/index.js +19 -2
  159. package/dist/server/integrationFetch.d.ts +162 -0
  160. package/dist/server/integrationFetch.d.ts.map +1 -0
  161. package/dist/server/integrationFetch.js +688 -0
  162. package/dist/server/listMembers.d.ts +32 -0
  163. package/dist/server/listMembers.d.ts.map +1 -0
  164. package/dist/server/listMembers.js +71 -0
  165. package/dist/server/nodeServer.d.ts +48 -0
  166. package/dist/server/nodeServer.d.ts.map +1 -0
  167. package/dist/server/nodeServer.js +45 -0
  168. package/dist/server/refusal.d.ts +55 -0
  169. package/dist/server/refusal.d.ts.map +1 -0
  170. package/dist/server/refusal.js +193 -0
  171. package/dist/server/sendEmail.d.ts +39 -0
  172. package/dist/server/sendEmail.d.ts.map +1 -0
  173. package/dist/server/sendEmail.js +114 -0
  174. package/dist/server/tools.d.ts +184 -0
  175. package/dist/server/tools.d.ts.map +1 -0
  176. package/dist/server/tools.js +347 -0
  177. package/dist/server/toolsEndpoint.d.ts +32 -0
  178. package/dist/server/toolsEndpoint.d.ts.map +1 -0
  179. package/dist/server/toolsEndpoint.js +237 -0
  180. package/dist/tanstack-start/index.d.ts +9 -0
  181. package/dist/tanstack-start/index.d.ts.map +1 -0
  182. package/dist/tanstack-start/index.js +29 -0
  183. package/dist/testing/index.d.ts +93 -0
  184. package/dist/testing/index.d.ts.map +1 -0
  185. package/dist/testing/index.js +156 -0
  186. package/package.json +42 -8
  187. package/dist/server/mcp.d.ts +0 -66
  188. package/dist/server/mcp.d.ts.map +0 -1
  189. package/dist/server/mcp.js +0 -147
@@ -0,0 +1,543 @@
1
+ /**
2
+ * Remote MCP apps: any remote MCP server registered on a gateway by URL
3
+ * (`everyapp mcp add <url>`, Admin → Apps → Add app → "Add an MCP server",
4
+ * `everyapp_mcp_add`), served to members' AI clients as an MCP app of the
5
+ * Gateway's own, like a built-in provider app (./providerApps.ts) but with
6
+ * its tools read from the server and each call forwarded to it with a
7
+ * credential the Gateway holds (docs/design/2026-10-mcp-by-url.md).
8
+ *
9
+ * The registration record is the app row's manifest: `id`, `name`,
10
+ * `description` and a `remote` block ({@link RemoteMcpBlock}): the one URL
11
+ * the Gateway ever fetches, how it authenticates ({@link RemoteMcpAuth},
12
+ * the credential always the NAME of a Gateway setting, never a value), how
13
+ * a tool's risk is decided when the server's annotations don't say, the
14
+ * operator's per-tool overrides, and a revision the writers bump so the
15
+ * Gateway re-lists the tools on first use after a change.
16
+ *
17
+ * Everything here is pure and shared by the CLI, the perimeter's registry
18
+ * rules, the Gateway (which serves the row and validates again at every
19
+ * fetch) and the settings model (./appSettings.ts), so the rules cannot
20
+ * drift: what the URL may be, what the credential may be called, how a
21
+ * server's tool names become the ToolBus's, and what risk a tool gets.
22
+ */
23
+ import { CAPABILITIES } from "./capabilities.js";
24
+ import { isRemoteMcpSecretName } from "./gatewaySettings.js";
25
+ import { isProviderAppId } from "./providerApps.js";
26
+ import { TOOL_NAME_PATTERN, isToolRisk } from "./tools.js";
27
+ export const REMOTE_MCP_AUTH_KINDS = [
28
+ "none",
29
+ "bearer",
30
+ "header",
31
+ "basic",
32
+ "oauth",
33
+ ];
34
+ /**
35
+ * How a tool's risk is decided for the app. `annotations`: from the
36
+ * server's hints ({@link remoteToolRisk}); `read` or `write`: every tool,
37
+ * whatever it says. A per-tool override wins over all three.
38
+ */
39
+ export const REMOTE_MCP_RISK_DEFAULTS = [
40
+ "annotations",
41
+ "read",
42
+ "write",
43
+ ];
44
+ const ID_PATTERN = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
45
+ const ID_MAX_LENGTH = 32;
46
+ /** App ids a remote server can't take: the Gateway's own tool prefix. */
47
+ const RESERVED_IDS = ["everyapp"];
48
+ const SECRET_MAX_LENGTH = 64;
49
+ const HEADER_NAME_PATTERN = /^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,100}$/;
50
+ const PREFIX_MAX_LENGTH = 32;
51
+ const URL_MAX_LENGTH = 2048;
52
+ const NAME_MAX_LENGTH = 255;
53
+ const DESCRIPTION_MAX_LENGTH = 1000;
54
+ const OVERRIDES_MAX = 500;
55
+ const UPSTREAM_NAME_MAX = 256;
56
+ /**
57
+ * Headers the `header` kind may not name: the ones the Gateway sets on the
58
+ * request, the ones that route it, and the ones that would carry something
59
+ * other than this app's credential.
60
+ */
61
+ const FORBIDDEN_AUTH_HEADERS = new Set([
62
+ "authorization",
63
+ "cookie",
64
+ "host",
65
+ "content-type",
66
+ "content-length",
67
+ "accept",
68
+ "connection",
69
+ "transfer-encoding",
70
+ "mcp-session-id",
71
+ "mcp-protocol-version",
72
+ "mcp-method",
73
+ "mcp-name",
74
+ ]);
75
+ /** The default credential name for an app id: `treg` → `TREG_MCP_TOKEN`. */
76
+ export function remoteMcpSecretName(id) {
77
+ return `${id.toUpperCase().replace(/-/g, "_")}_MCP_TOKEN`;
78
+ }
79
+ /** Whether `id` can be a remote MCP app's id (kebab-case, at most 32, not reserved, not a built-in's). */
80
+ export function remoteMcpIdProblem(id) {
81
+ if (typeof id !== "string" || !ID_PATTERN.test(id)) {
82
+ return "the id must be kebab-case: lowercase letters, digits and hyphens, starting with a letter";
83
+ }
84
+ if (id.length > ID_MAX_LENGTH) {
85
+ return `the id must be at most ${ID_MAX_LENGTH} characters`;
86
+ }
87
+ if (RESERVED_IDS.includes(id))
88
+ return `"${id}" is reserved`;
89
+ if (isProviderAppId(id)) {
90
+ return `"${id}" is the Gateway's built-in MCP app; a remote server can't take its id`;
91
+ }
92
+ return null;
93
+ }
94
+ /**
95
+ * An app id from the server's URL: the host's registrable name, `www`,
96
+ * `mcp` and `api` dropped (`https://mcp.deepwiki.com/mcp` → `deepwiki`,
97
+ * `https://treg.to/mcp/` → `treg`); null when nothing usable is left (an
98
+ * IP, `localhost`), in which case the caller asks for `--name`.
99
+ */
100
+ export function remoteMcpIdFromUrl(url) {
101
+ let host;
102
+ try {
103
+ host = new URL(url).hostname.toLowerCase().replace(/\.$/, "");
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ if (!host || /^[\d.]+$/.test(host) || host.includes(":"))
109
+ return null;
110
+ const labels = host.split(".").filter((label) => label.length > 0);
111
+ if (labels.length < 2)
112
+ return null;
113
+ // The label before the public suffix, roughly: the last label that
114
+ // isn't a known two-level suffix part (co.uk, com.au).
115
+ const SECOND_LEVEL = new Set(["co", "com", "net", "org", "gov", "edu", "ac"]);
116
+ let index = labels.length - 2;
117
+ if (index > 0 &&
118
+ SECOND_LEVEL.has(labels[index]) &&
119
+ labels[index + 1].length === 2) {
120
+ index -= 1;
121
+ }
122
+ const candidate = labels[index] ?? "";
123
+ const id = candidate
124
+ .replace(/[^a-z0-9-]/g, "-")
125
+ .replace(/^-+|-+$/g, "")
126
+ .replace(/-{2,}/g, "-")
127
+ .slice(0, ID_MAX_LENGTH)
128
+ .replace(/-+$/g, "");
129
+ if (!id || !ID_PATTERN.test(id))
130
+ return null;
131
+ return remoteMcpIdProblem(id) ? null : id;
132
+ }
133
+ function isSameOrSubdomain(host, domain) {
134
+ return host === domain || host.endsWith(`.${domain}`);
135
+ }
136
+ /** The hosts whose keys the Gateway holds: a remote server is never one of them. */
137
+ export function reservedUpstreamHosts() {
138
+ const hosts = new Set([
139
+ "api.openai.com",
140
+ "openrouter.ai",
141
+ "gateway.ai.cloudflare.com",
142
+ "api.cloudflare.com",
143
+ ]);
144
+ for (const capability of Object.values(CAPABILITIES)) {
145
+ if (capability.mode === "api_key") {
146
+ for (const host of capability.hosts)
147
+ hosts.add(host);
148
+ }
149
+ else if (capability.mode === "oauth_client") {
150
+ for (const endpoint of capability.token) {
151
+ if ("host" in endpoint && typeof endpoint.host === "string") {
152
+ hosts.add(endpoint.host);
153
+ }
154
+ }
155
+ }
156
+ }
157
+ return [...hosts].sort();
158
+ }
159
+ /**
160
+ * Why `url` can't be a remote MCP server's address, or null when it can:
161
+ * `https:` (or `http:` and loopback hosts only while the Gateway itself
162
+ * runs on `http://`, local dev), a host name rather than an address, no
163
+ * credentials or fragment in it, not the Gateway's own origin or one of
164
+ * its app hosts, and not a provider whose key the Gateway holds. The
165
+ * Gateway checks the stored URL again before every fetch, so a row edited
166
+ * by hand is refused the same way.
167
+ */
168
+ export function remoteMcpUrlProblem(url, env) {
169
+ if (typeof url !== "string" || !url.trim())
170
+ return "give the server's URL";
171
+ if (url.length > URL_MAX_LENGTH)
172
+ return "the URL is too long";
173
+ let parsed;
174
+ try {
175
+ parsed = new URL(url);
176
+ }
177
+ catch {
178
+ return "the URL doesn't parse";
179
+ }
180
+ const local = env.gatewayUrl.trim().toLowerCase().startsWith("http://");
181
+ if (parsed.username || parsed.password) {
182
+ return "the URL must not carry credentials; the Gateway sends the credential from its own setting";
183
+ }
184
+ if (parsed.hash)
185
+ return "the URL must not have a fragment";
186
+ const host = parsed.hostname.toLowerCase().replace(/\.$/, "");
187
+ const loopback = host === "localhost" ||
188
+ host.endsWith(".localhost") ||
189
+ host === "127.0.0.1" ||
190
+ host === "[::1]" ||
191
+ host === "::1";
192
+ if (parsed.protocol !== "https:") {
193
+ if (parsed.protocol === "http:" && local && loopback) {
194
+ // Local dev: a fake or local server on this machine.
195
+ }
196
+ else {
197
+ return local
198
+ ? "the URL must be https://, or http:// on localhost while the Gateway runs locally"
199
+ : "the URL must be https://";
200
+ }
201
+ }
202
+ if (!host)
203
+ return "the URL has no host";
204
+ if (!loopback && (/^[\d.]+$/.test(host) || host.startsWith("["))) {
205
+ return "the URL must name a host, not an IP address";
206
+ }
207
+ if (loopback && !local) {
208
+ return "the URL must name a public host";
209
+ }
210
+ // The Gateway's own host: any port and every subdomain of it in
211
+ // production (a Worker on the same zone would be the gateway or an app);
212
+ // in local dev the exact origin only, so a fake server on another
213
+ // localhost port is fine.
214
+ let gatewayHost = "";
215
+ let gatewayOrigin = "";
216
+ try {
217
+ const gateway = new URL(env.gatewayUrl);
218
+ gatewayHost = gateway.hostname.toLowerCase().replace(/\.$/, "");
219
+ gatewayOrigin = gateway.origin.toLowerCase();
220
+ }
221
+ catch {
222
+ gatewayHost = "";
223
+ }
224
+ if (gatewayHost) {
225
+ const ownHost = local
226
+ ? parsed.origin.toLowerCase() === gatewayOrigin
227
+ : isSameOrSubdomain(host, gatewayHost);
228
+ if (ownHost)
229
+ return "the URL is this Gateway's own address";
230
+ }
231
+ for (const domain of [env.appBaseDomain, env.thirdPartyDomain]) {
232
+ const normalized = (domain ?? "").trim().toLowerCase().replace(/\.$/, "");
233
+ if (normalized && isSameOrSubdomain(host, normalized)) {
234
+ return `the URL is on this Gateway's app domain (${normalized})`;
235
+ }
236
+ }
237
+ if (reservedUpstreamHosts().includes(host)) {
238
+ return `${host} is a provider this Gateway holds a key for; apps reach it through the Gateway's capabilities, not as an MCP app`;
239
+ }
240
+ return null;
241
+ }
242
+ function isRecord(value) {
243
+ return typeof value === "object" && value !== null && !Array.isArray(value);
244
+ }
245
+ /** The credential name's problem, or null: in the family, and bounded. */
246
+ export function remoteMcpSecretProblem(name) {
247
+ if (typeof name !== "string" || !name)
248
+ return "give the credential's setting name";
249
+ if (name.length > SECRET_MAX_LENGTH)
250
+ return "the credential's name is too long";
251
+ if (!isRemoteMcpSecretName(name)) {
252
+ return `the credential's name must end in _MCP_TOKEN (letters, digits and underscores), like ${remoteMcpSecretName("treg")}`;
253
+ }
254
+ return null;
255
+ }
256
+ /** The auth block's problem, or the block as typed. */
257
+ export function parseRemoteMcpAuth(raw) {
258
+ if (!isRecord(raw) || typeof raw["kind"] !== "string") {
259
+ return { ok: false, problem: "auth must name a kind" };
260
+ }
261
+ const kind = raw["kind"];
262
+ if (!REMOTE_MCP_AUTH_KINDS.includes(kind)) {
263
+ return {
264
+ ok: false,
265
+ problem: `auth.kind must be one of ${REMOTE_MCP_AUTH_KINDS.join(", ")}`,
266
+ };
267
+ }
268
+ if (kind === "none" || kind === "oauth") {
269
+ return { ok: true, auth: { kind } };
270
+ }
271
+ const secretProblem = remoteMcpSecretProblem(raw["secret"]);
272
+ if (secretProblem)
273
+ return { ok: false, problem: `auth.secret: ${secretProblem}` };
274
+ const secret = raw["secret"];
275
+ if (kind === "bearer" || kind === "basic") {
276
+ return { ok: true, auth: { kind, secret } };
277
+ }
278
+ const header = raw["header"];
279
+ if (typeof header !== "string" || !HEADER_NAME_PATTERN.test(header)) {
280
+ return { ok: false, problem: "auth.header must be an HTTP header name" };
281
+ }
282
+ if (FORBIDDEN_AUTH_HEADERS.has(header.toLowerCase())) {
283
+ return {
284
+ ok: false,
285
+ problem: `auth.header can't be ${header}; use kind "bearer" or "basic" for Authorization`,
286
+ };
287
+ }
288
+ const prefix = raw["prefix"];
289
+ if (prefix !== undefined) {
290
+ if (typeof prefix !== "string" ||
291
+ prefix.length > PREFIX_MAX_LENGTH ||
292
+ /[\r\n]/.test(prefix)) {
293
+ return { ok: false, problem: "auth.prefix must be a short string" };
294
+ }
295
+ }
296
+ return {
297
+ ok: true,
298
+ auth: {
299
+ kind: "header",
300
+ secret,
301
+ header,
302
+ ...(typeof prefix === "string" && prefix ? { prefix } : {}),
303
+ },
304
+ };
305
+ }
306
+ /**
307
+ * The `remote` block of a stored manifest, checked field by field (a row
308
+ * edited by hand must not make the Gateway send a credential anywhere the
309
+ * rules refuse), or why it can't be read. The URL is checked against the
310
+ * Gateway's environment by the caller ({@link remoteMcpUrlProblem}).
311
+ */
312
+ export function parseRemoteMcpBlock(raw) {
313
+ if (!isRecord(raw))
314
+ return { ok: false, problem: "remote must be an object" };
315
+ const url = raw["url"];
316
+ if (typeof url !== "string" || !url || url.length > URL_MAX_LENGTH) {
317
+ return { ok: false, problem: "remote.url must be a URL" };
318
+ }
319
+ const auth = parseRemoteMcpAuth(raw["auth"]);
320
+ if (!auth.ok)
321
+ return auth;
322
+ const risk = raw["risk"] ?? "annotations";
323
+ if (!REMOTE_MCP_RISK_DEFAULTS.includes(risk)) {
324
+ return {
325
+ ok: false,
326
+ problem: `remote.risk must be one of ${REMOTE_MCP_RISK_DEFAULTS.join(", ")}`,
327
+ };
328
+ }
329
+ const tools = {};
330
+ const rawTools = raw["tools"] ?? {};
331
+ if (!isRecord(rawTools))
332
+ return { ok: false, problem: "remote.tools must be an object" };
333
+ const entries = Object.entries(rawTools);
334
+ if (entries.length > OVERRIDES_MAX) {
335
+ return {
336
+ ok: false,
337
+ problem: `remote.tools has more than ${OVERRIDES_MAX} entries`,
338
+ };
339
+ }
340
+ for (const [name, override] of entries) {
341
+ if (!name || name.length > UPSTREAM_NAME_MAX) {
342
+ return {
343
+ ok: false,
344
+ problem: "remote.tools has a tool name that is empty or too long",
345
+ };
346
+ }
347
+ if (!isRecord(override)) {
348
+ return { ok: false, problem: `remote.tools.${name} must be an object` };
349
+ }
350
+ const out = {};
351
+ if (override["risk"] !== undefined) {
352
+ if (!isToolRisk(override["risk"])) {
353
+ return {
354
+ ok: false,
355
+ problem: `remote.tools.${name}.risk must be read, write or destructive`,
356
+ };
357
+ }
358
+ out.risk = override["risk"];
359
+ }
360
+ if (override["enabled"] !== undefined) {
361
+ if (typeof override["enabled"] !== "boolean") {
362
+ return {
363
+ ok: false,
364
+ problem: `remote.tools.${name}.enabled must be true or false`,
365
+ };
366
+ }
367
+ out.enabled = override["enabled"];
368
+ }
369
+ tools[name] = out;
370
+ }
371
+ const rev = raw["rev"] ?? 1;
372
+ if (typeof rev !== "number" || !Number.isInteger(rev) || rev < 1) {
373
+ return { ok: false, problem: "remote.rev must be a positive integer" };
374
+ }
375
+ return {
376
+ ok: true,
377
+ block: {
378
+ url,
379
+ auth: auth.auth,
380
+ risk: risk,
381
+ tools,
382
+ rev,
383
+ },
384
+ };
385
+ }
386
+ /**
387
+ * The `remote` block of a stored manifest, or null when the manifest has
388
+ * none (a built-in provider app's, a deployed app's) or it can't be read
389
+ * (such a row is a provider row the Gateway serves nothing for and lists
390
+ * as needing setup, see app-setup).
391
+ */
392
+ export function remoteMcpOf(manifest) {
393
+ if (!isRecord(manifest) || manifest["remote"] === undefined)
394
+ return null;
395
+ const parsed = parseRemoteMcpBlock(manifest["remote"]);
396
+ return parsed.ok ? parsed.block : null;
397
+ }
398
+ /** Whether a stored manifest declares a remote MCP server at all (well-formed or not). */
399
+ export function hasRemoteMcpBlock(manifest) {
400
+ return isRecord(manifest) && manifest["remote"] !== undefined;
401
+ }
402
+ /** The credential's setting name the block declares, if its kind takes one. */
403
+ export function remoteMcpSecretOf(auth) {
404
+ return "secret" in auth ? auth.secret : null;
405
+ }
406
+ /**
407
+ * A description for a row that gave none: the server's origin, read as a
408
+ * sentence. The origin only: a path or query may carry something the
409
+ * registrar meant for the server alone, and the description reaches every
410
+ * member with access and every AI client.
411
+ */
412
+ export function remoteMcpDefaultDescription(name, url) {
413
+ let where = "";
414
+ try {
415
+ where = new URL(url).origin;
416
+ }
417
+ catch {
418
+ where = "";
419
+ }
420
+ return `${name}'s MCP server${where ? ` at ${where}` : ""}, served to your AI clients through this Gateway.`;
421
+ }
422
+ /**
423
+ * The operator's override for a server tool: by the server's name, or by
424
+ * a name that becomes the same ToolBus name ({@link normalizeRemoteToolName}),
425
+ * so a server that renames `send-mail` to `send_mail` can't bring a tool the
426
+ * operator turned off back under the same listed name.
427
+ */
428
+ export function remoteToolOverride(block, upstreamName) {
429
+ const exact = block.tools[upstreamName];
430
+ if (exact)
431
+ return exact;
432
+ const local = normalizeRemoteToolName(upstreamName);
433
+ if (!local)
434
+ return undefined;
435
+ for (const [key, override] of Object.entries(block.tools)) {
436
+ if (normalizeRemoteToolName(key) === local)
437
+ return override;
438
+ }
439
+ return undefined;
440
+ }
441
+ /** Whether a server's tool name can travel in an HTTP header (`Mcp-Name`): visible ASCII, bounded. */
442
+ export function isHeaderSafeToolName(name) {
443
+ return /^[\x21-\x7e]{1,256}$/.test(name);
444
+ }
445
+ /** The manifest a remote MCP app's row stores, from what the registrar gave. */
446
+ export function remoteMcpAppManifest(input) {
447
+ const name = (input.name ?? "").trim().slice(0, NAME_MAX_LENGTH) || input.id;
448
+ const description = (input.description ?? "").trim().slice(0, DESCRIPTION_MAX_LENGTH) ||
449
+ remoteMcpDefaultDescription(name, input.remote.url);
450
+ return { id: input.id, name, description, remote: input.remote };
451
+ }
452
+ /**
453
+ * A tool's risk for the Gateway. An override wins; then the app's `read`
454
+ * or `write` default; then the server's hints, read conservatively: an
455
+ * explicit `readOnlyHint: true` is `read`, an explicit `destructiveHint:
456
+ * true` is `destructive` (hidden until approvals exist), and anything
457
+ * else, a missing hint included, is `write`. The spec's missing-means-true
458
+ * default for `destructiveHint` is not followed: it would hide every
459
+ * unhinted tool.
460
+ */
461
+ export function remoteToolRisk(hints, block, upstreamName) {
462
+ const override = remoteToolOverride(block, upstreamName)?.risk;
463
+ if (override)
464
+ return { risk: override, source: "override" };
465
+ if (block.risk === "read" || block.risk === "write") {
466
+ return { risk: block.risk, source: "app" };
467
+ }
468
+ if (hints?.readOnlyHint === true)
469
+ return { risk: "read", source: "hint" };
470
+ if (hints?.destructiveHint === true) {
471
+ return { risk: "destructive", source: "hint" };
472
+ }
473
+ return { risk: "write", source: "default" };
474
+ }
475
+ /** Whether the operator left the tool on (the default). */
476
+ export function remoteToolEnabled(block, upstreamName) {
477
+ return remoteToolOverride(block, upstreamName)?.enabled !== false;
478
+ }
479
+ /**
480
+ * A server's tool name as the ToolBus lists it: lowercase snake_case, at
481
+ * most 48 characters (`TOOL_NAME_PATTERN`). camelCase is split
482
+ * (`getUser` → `get_user`), `-`, `.`, `/` and spaces become `_`, anything
483
+ * else is dropped, runs of `_` collapse, and a leading digit gets a `t_`
484
+ * in front. Null when nothing usable is left.
485
+ */
486
+ export function normalizeRemoteToolName(name) {
487
+ if (typeof name !== "string")
488
+ return null;
489
+ let out = name
490
+ .trim()
491
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
492
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
493
+ .toLowerCase()
494
+ .replace(/[\s./:-]+/g, "_")
495
+ .replace(/[^a-z0-9_]/g, "")
496
+ .replace(/_{2,}/g, "_")
497
+ .replace(/^_+|_+$/g, "");
498
+ if (!out)
499
+ return null;
500
+ if (/^[0-9]/.test(out))
501
+ out = `t_${out}`;
502
+ out = out.slice(0, 48).replace(/_+$/g, "");
503
+ return TOOL_NAME_PATTERN.test(out) ? out : null;
504
+ }
505
+ /**
506
+ * The request headers that carry the credential for `auth`, from the
507
+ * setting's value. The value never appears anywhere else.
508
+ */
509
+ export function remoteMcpAuthHeaders(auth, value) {
510
+ switch (auth.kind) {
511
+ case "none":
512
+ case "oauth":
513
+ return {};
514
+ case "bearer":
515
+ return { authorization: `Bearer ${value}` };
516
+ case "basic":
517
+ return { authorization: `Basic ${base64(value)}` };
518
+ case "header":
519
+ return { [auth.header]: `${auth.prefix ?? ""}${value}` };
520
+ }
521
+ }
522
+ function base64(text) {
523
+ const bytes = new TextEncoder().encode(text);
524
+ let binary = "";
525
+ for (const byte of bytes)
526
+ binary += String.fromCharCode(byte);
527
+ return btoa(binary);
528
+ }
529
+ /** Plain words for an auth kind, for lists and dialogs. */
530
+ export function remoteMcpAuthLabel(auth) {
531
+ switch (auth.kind) {
532
+ case "none":
533
+ return "no credential";
534
+ case "bearer":
535
+ return "bearer token";
536
+ case "basic":
537
+ return "user and password";
538
+ case "header":
539
+ return `header ${auth.header}`;
540
+ case "oauth":
541
+ return "OAuth (not supported yet)";
542
+ }
543
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The sign-in providers a Gateway can offer beside passwords ("Continue
3
+ * with Google"), as data: what each one is called, the Worker secrets it
4
+ * reads, where the OAuth app is made, the scopes asked for and which claim
5
+ * becomes the email. One catalog serves the Gateway (which providers are
6
+ * on, the sign-in page's buttons, Manage workspace → Sign-in), the CLI
7
+ * (`everyapp signin add|list|show|remove`, `secret set`), the manifest
8
+ * validator (an app's own secret can never take one of these names) and
9
+ * the docs' drift tests.
10
+ *
11
+ * Naming rule, agreed with the provider registry's conventions
12
+ * (docs/design/2026-10-provider-registry.md, section 6): a sign-in
13
+ * provider's settings are `<ID>_SIGNIN_<FIELD>`, with FIELD one of
14
+ * CLIENT_ID, CLIENT_SECRET, ISSUER, LABEL or TENANT. The `_SIGNIN_` infix is
15
+ * what keeps the family apart from the API providers' `<ID>_API_KEY`,
16
+ * `<ID>_CLIENT_ID` (the OAuth clients the Gateway holds for apps) and the
17
+ * MCP apps' `<ID>_MCP_TOKEN`: `GOOGLE_SIGNIN_CLIENT_ID` is the sign-in
18
+ * provider's, `GOOGLE_WORKSPACE_CLIENT_ID` the OAuth client apps use, and
19
+ * a registered provider row or an app manifest can never claim a name in
20
+ * the family (isSignInSettingName).
21
+ *
22
+ * The settings are Worker secrets, set under the Cloudflare login (`everyapp
23
+ * signin add`, or `secret set`), never from the admin UI or an agent
24
+ * connection: a stolen session can't add a way in.
25
+ */
26
+ export declare const SIGN_IN_PROVIDER_IDS: readonly ["google", "microsoft", "github", "apple", "cloudflare", "oidc"];
27
+ export type SignInProviderId = (typeof SIGN_IN_PROVIDER_IDS)[number];
28
+ /** What a setting of a sign-in provider holds. */
29
+ export type SignInSettingRole = "clientId" | "clientSecret" | "issuer" | "label" | "tenant";
30
+ export interface SignInSetting {
31
+ /** `GOOGLE_SIGNIN_CLIENT_ID`. */
32
+ readonly name: string;
33
+ readonly role: SignInSettingRole;
34
+ /** Without it the provider is off (an incomplete set is reported). */
35
+ readonly required: boolean;
36
+ /**
37
+ * A plain value (an id, a URL, a word) `everyapp secret set` may show as
38
+ * typed; a credential stays hidden.
39
+ */
40
+ readonly plain: boolean;
41
+ /** One line: what to paste here. */
42
+ readonly what: string;
43
+ }
44
+ export interface SignInProvider {
45
+ readonly id: SignInProviderId;
46
+ /** The button: "Continue with <label>". The `oidc` entry's label is a setting. */
47
+ readonly label: string;
48
+ /** Where the OAuth app is created. */
49
+ readonly console: string;
50
+ /** What the OAuth app is called there ("OAuth client ID", "App registration"). */
51
+ readonly appNoun: string;
52
+ readonly settings: readonly SignInSetting[];
53
+ /** The scopes requested: identity only, nothing an app could act with. */
54
+ readonly scopes: readonly string[];
55
+ /** Which claim becomes the email, and what makes it count as verified. */
56
+ readonly emailClaim: string;
57
+ /** The everyapp skill's setup guide, relative to the skill. */
58
+ readonly setup: `setup/signin-${SignInProviderId}.md`;
59
+ /** Caveats worth a line in every listing. */
60
+ readonly notes: readonly string[];
61
+ }
62
+ /** The path Better Auth serves the provider's callback on, under the Gateway's URL. */
63
+ export declare function signInCallbackPath(id: SignInProviderId): string;
64
+ /** The redirect URL to register with the provider, for a Gateway at `gatewayUrl`. */
65
+ export declare function signInCallbackUrl(gatewayUrl: string, id: SignInProviderId): string;
66
+ export declare const SIGN_IN_PROVIDERS: Readonly<Record<SignInProviderId, SignInProvider>>;
67
+ /** The catalog in listing order. */
68
+ export declare const SIGN_IN_PROVIDER_LIST: readonly SignInProvider[];
69
+ export declare function isSignInProviderId(value: string): value is SignInProviderId;
70
+ /**
71
+ * Every setting a sign-in provider reads, in catalog order: what `everyapp
72
+ * secret set` accepts for the Gateway, and what the Gateway's dev config
73
+ * declares.
74
+ */
75
+ export declare const SIGN_IN_GATEWAY_SETTINGS: readonly string[];
76
+ /** The plain (non-credential) ones among them: ids, the issuer, the label, the tenant. */
77
+ export declare const PLAIN_SIGN_IN_SETTINGS: readonly string[];
78
+ /**
79
+ * The family: any name of the shape `<ID>_SIGNIN_<FIELD>`. Reserved for the
80
+ * catalog above, whether or not an entry reads the name today, so no
81
+ * registered provider row and no app manifest can ever take one.
82
+ */
83
+ export declare const SIGN_IN_SETTING_PATTERN: RegExp;
84
+ export declare function isSignInSettingName(name: string): boolean;
85
+ /** The provider a setting belongs to, if one reads it. */
86
+ export declare function signInProviderOfSetting(name: string): SignInProvider | null;
87
+ /** Whether a provider is on, off, or half set up, from which names are set. */
88
+ export type SignInProviderStatus = "on" | "off" | "incomplete";
89
+ export interface SignInProviderState {
90
+ readonly id: SignInProviderId;
91
+ readonly status: SignInProviderStatus;
92
+ /** The required settings not set (empty when on or off). */
93
+ readonly missing: readonly string[];
94
+ /** The settings set, required or not. */
95
+ readonly set: readonly string[];
96
+ }
97
+ /**
98
+ * A provider's state from which of its settings exist: on when every
99
+ * required one is set, off when none at all is, incomplete in between (the
100
+ * Gateway refuses to half-configure a sign-in: the provider is off and the
101
+ * problem is reported).
102
+ */
103
+ export declare function signInProviderState(provider: SignInProvider, isSet: (name: string) => boolean): SignInProviderState;
104
+ //# sourceMappingURL=signInProviders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signInProviders.d.ts","sourceRoot":"","sources":["../../src/internal/signInProviders.ts"],"names":[],"mappings":"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"}