@every-app/sdk 0.3.0-rc.9 → 0.3.0

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 (131) hide show
  1. package/README.md +75 -74
  2. package/dist/cloudflare/catalogSettings.generated.d.ts +19 -6
  3. package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -1
  4. package/dist/cloudflare/catalogSettings.generated.js +43 -14
  5. package/dist/cloudflare/index.d.ts +1 -1
  6. package/dist/cloudflare/index.d.ts.map +1 -1
  7. package/dist/cloudflare/worker.d.ts +63 -9
  8. package/dist/cloudflare/worker.d.ts.map +1 -1
  9. package/dist/cloudflare/worker.js +45 -9
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/internal/aiDefault.d.ts +36 -79
  13. package/dist/internal/aiDefault.d.ts.map +1 -1
  14. package/dist/internal/aiDefault.js +47 -149
  15. package/dist/internal/aiProviders.d.ts +1 -1
  16. package/dist/internal/aiProviders.d.ts.map +1 -1
  17. package/dist/internal/aiProviders.js +2 -2
  18. package/dist/internal/appSettings.d.ts +140 -89
  19. package/dist/internal/appSettings.d.ts.map +1 -1
  20. package/dist/internal/appSettings.js +264 -156
  21. package/dist/internal/capabilities.d.ts +108 -503
  22. package/dist/internal/capabilities.d.ts.map +1 -1
  23. package/dist/internal/capabilities.js +29 -550
  24. package/dist/internal/catalog/commerce.js +1 -1
  25. package/dist/internal/catalog/index.d.ts +35 -0
  26. package/dist/internal/catalog/index.d.ts.map +1 -1
  27. package/dist/internal/catalog/infra.d.ts +35 -0
  28. package/dist/internal/catalog/infra.d.ts.map +1 -1
  29. package/dist/internal/catalog/infra.js +65 -0
  30. package/dist/internal/devProviders.d.ts +1 -19
  31. package/dist/internal/devProviders.d.ts.map +1 -1
  32. package/dist/internal/devProviders.js +7 -1
  33. package/dist/internal/events.d.ts +52 -1
  34. package/dist/internal/events.d.ts.map +1 -1
  35. package/dist/internal/events.js +65 -3
  36. package/dist/internal/gatewaySettings.d.ts +6 -16
  37. package/dist/internal/gatewaySettings.d.ts.map +1 -1
  38. package/dist/internal/gatewaySettings.js +5 -23
  39. package/dist/internal/index.d.ts +1 -0
  40. package/dist/internal/index.d.ts.map +1 -1
  41. package/dist/internal/index.js +1 -0
  42. package/dist/internal/mcpOAuth.d.ts +210 -0
  43. package/dist/internal/mcpOAuth.d.ts.map +1 -0
  44. package/dist/internal/mcpOAuth.js +735 -0
  45. package/dist/internal/oauthClient.d.ts +7 -47
  46. package/dist/internal/oauthClient.d.ts.map +1 -1
  47. package/dist/internal/oauthClient.js +1 -115
  48. package/dist/internal/oauthPresets.d.ts +356 -20
  49. package/dist/internal/oauthPresets.d.ts.map +1 -1
  50. package/dist/internal/oauthPresets.js +183 -861
  51. package/dist/internal/oauthScopes.d.ts +43 -155
  52. package/dist/internal/oauthScopes.d.ts.map +1 -1
  53. package/dist/internal/oauthScopes.js +82 -475
  54. package/dist/internal/oauthSpec.d.ts +28 -71
  55. package/dist/internal/oauthSpec.d.ts.map +1 -1
  56. package/dist/internal/oauthSpec.js +172 -448
  57. package/dist/internal/protocol.d.ts +24 -14
  58. package/dist/internal/protocol.d.ts.map +1 -1
  59. package/dist/internal/protocol.js +26 -7
  60. package/dist/internal/providerApps.d.ts +2 -2
  61. package/dist/internal/providerApps.js +2 -2
  62. package/dist/internal/providerRegistry.d.ts +5 -64
  63. package/dist/internal/providerRegistry.d.ts.map +1 -1
  64. package/dist/internal/providerRegistry.js +19 -118
  65. package/dist/internal/providerSpec.d.ts.map +1 -1
  66. package/dist/internal/providerSpec.js +14 -36
  67. package/dist/internal/remoteMcp.d.ts +191 -47
  68. package/dist/internal/remoteMcp.d.ts.map +1 -1
  69. package/dist/internal/remoteMcp.js +338 -133
  70. package/dist/internal/signInProviders.d.ts +1 -1
  71. package/dist/internal/signInProviders.d.ts.map +1 -1
  72. package/dist/internal/tools.d.ts +15 -8
  73. package/dist/internal/tools.d.ts.map +1 -1
  74. package/dist/internal/tools.js +9 -8
  75. package/dist/manifest.d.ts +96 -40
  76. package/dist/manifest.d.ts.map +1 -1
  77. package/dist/manifest.js +1 -1
  78. package/dist/server/ai.d.ts +1 -93
  79. package/dist/server/ai.d.ts.map +1 -1
  80. package/dist/server/ai.js +17 -111
  81. package/dist/server/attribution.d.ts +10 -1
  82. package/dist/server/attribution.d.ts.map +1 -1
  83. package/dist/server/attribution.js +7 -1
  84. package/dist/server/connectUrl.d.ts +4 -9
  85. package/dist/server/connectUrl.d.ts.map +1 -1
  86. package/dist/server/connectUrl.js +17 -14
  87. package/dist/server/context.d.ts +12 -14
  88. package/dist/server/context.d.ts.map +1 -1
  89. package/dist/server/context.js +23 -25
  90. package/dist/server/eventsEndpoint.d.ts +48 -0
  91. package/dist/server/eventsEndpoint.d.ts.map +1 -0
  92. package/dist/server/eventsEndpoint.js +145 -0
  93. package/dist/server/everyApp.d.ts +16 -26
  94. package/dist/server/everyApp.d.ts.map +1 -1
  95. package/dist/server/everyApp.js +22 -46
  96. package/dist/server/gatewayFetch.d.ts +1 -1
  97. package/dist/server/gatewayFetch.js +1 -1
  98. package/dist/server/index.d.ts +6 -9
  99. package/dist/server/index.d.ts.map +1 -1
  100. package/dist/server/index.js +4 -8
  101. package/dist/server/integrationFetch.d.ts +60 -72
  102. package/dist/server/integrationFetch.d.ts.map +1 -1
  103. package/dist/server/integrationFetch.js +120 -393
  104. package/dist/server/refusal.d.ts +0 -9
  105. package/dist/server/refusal.d.ts.map +1 -1
  106. package/dist/server/refusal.js +0 -22
  107. package/dist/server/tools.d.ts +17 -6
  108. package/dist/server/tools.d.ts.map +1 -1
  109. package/dist/server/tools.js +14 -4
  110. package/dist/server/toolsEndpoint.d.ts +2 -1
  111. package/dist/server/toolsEndpoint.d.ts.map +1 -1
  112. package/dist/server/toolsEndpoint.js +4 -3
  113. package/dist/server/webhooks.d.ts +56 -0
  114. package/dist/server/webhooks.d.ts.map +1 -0
  115. package/dist/server/webhooks.js +205 -0
  116. package/dist/testing/index.d.ts +2 -3
  117. package/dist/testing/index.d.ts.map +1 -1
  118. package/dist/testing/index.js +2 -6
  119. package/package.json +6 -1
  120. package/dist/server/aiBinding.d.ts +0 -68
  121. package/dist/server/aiBinding.d.ts.map +0 -1
  122. package/dist/server/aiBinding.js +0 -294
  123. package/dist/server/authorizeUrl.d.ts +0 -43
  124. package/dist/server/authorizeUrl.d.ts.map +0 -1
  125. package/dist/server/authorizeUrl.js +0 -106
  126. package/dist/server/emailBinding.d.ts +0 -72
  127. package/dist/server/emailBinding.d.ts.map +0 -1
  128. package/dist/server/emailBinding.js +0 -124
  129. package/dist/server/fromMcpTools.d.ts +0 -89
  130. package/dist/server/fromMcpTools.d.ts.map +0 -1
  131. package/dist/server/fromMcpTools.js +0 -192
@@ -8,17 +8,20 @@
8
8
  * (`providers`) and a capability (`capabilities`) are the Gateway's: their
9
9
  * keys and clients live on the Gateway Worker, set once for every app
10
10
  * with `everyapp secret set <NAME>`. The app's own secrets (`secrets`)
11
- * live on its Worker, set from its directory. Each declaration carries a
12
- * need: required (the default; the app doesn't open until it is set),
13
- * optional (the app opens, and the feature says what is missing where it
14
- * is used), or a member of a named any-of group (the app opens once any
15
- * member is set; only for an app that picks among the set members at run
16
- * time). Each may say `why` it is needed and where it comes from
17
- * (`docs`, an https URL); for providers and capabilities the registries
18
- * here supply a default. An app's plain settings (`settings`: a host, a
19
- * project id, a region) are held like its secrets, on its Worker, but are
20
- * values rather than keys: each says what it takes (a URL, a number, one
21
- * of a list) and may give a default.
11
+ * live on its Worker, set from its directory. Everything the manifest
12
+ * lists at its top level is required: the app doesn't open until it is
13
+ * set. The rest is grouped into features (`features`): each a named set of
14
+ * secrets, settings and the Gateway capabilities and providers it needs,
15
+ * with numbered setup steps. A required feature's values are required; an
16
+ * optional feature is all or nothing, never blocking: off while none of
17
+ * its own values is set, incomplete while some are, ready once all are.
18
+ * Each declaration may say `why` it is needed, where it comes from
19
+ * (`docs`, an https URL) and, for an app's own value, `how` to get it;
20
+ * for providers and capabilities the registries here supply a default. An
21
+ * app's plain settings (`settings`: a host, a project id, a region) are
22
+ * held like its secrets, on its Worker, but are values rather than keys:
23
+ * each says what it takes (a URL, a number, one of a list) and may give a
24
+ * default.
22
25
  *
23
26
  * Everything in this module is pure and tenant-neutral: a manifest ships
24
27
  * in a release, and what is set is asked of the caller (`isSet`), which
@@ -28,18 +31,8 @@
28
31
  import { CATALOG, capabilitySettings, } from "./capabilities.js";
29
32
  import { aiDefaultSetupHint, aiRequiredSettings, resolveAiDefault, } from "./aiDefault.js";
30
33
  import { isInstallOwnedGatewaySetting, isSettableGatewaySetting, } from "./gatewaySettings.js";
31
- import { remoteMcpAuthLabel, remoteMcpOf, remoteMcpSecretOf, } from "./remoteMcp.js";
32
- /** The need an entry declares; required unless it says otherwise. */
33
- export function needOf(entry) {
34
- if (!entry)
35
- return { kind: "required" };
36
- if (typeof entry.anyOf === "string" && entry.anyOf.trim()) {
37
- return { kind: "anyOf", group: entry.anyOf.trim() };
38
- }
39
- if (entry.optional === true)
40
- return { kind: "optional" };
41
- return { kind: "required" };
42
- }
34
+ import { oauthSetupGuide } from "./oauthPresets.js";
35
+ import { hasRemoteMcpBlock, remoteMcpBlockProblem } from "./remoteMcp.js";
43
36
  function needFields(entry) {
44
37
  if (!entry || typeof entry !== "object")
45
38
  return {};
@@ -47,11 +40,66 @@ function needFields(entry) {
47
40
  return {
48
41
  ...(typeof record["why"] === "string" ? { why: record["why"] } : {}),
49
42
  ...(typeof record["docs"] === "string" ? { docs: record["docs"] } : {}),
50
- ...(typeof record["optional"] === "boolean"
51
- ? { optional: record["optional"] }
52
- : {}),
53
- ...(typeof record["setup"] === "string" ? { setup: record["setup"] } : {}),
54
- ...(typeof record["anyOf"] === "string" ? { anyOf: record["anyOf"] } : {}),
43
+ };
44
+ }
45
+ /** `how`, `generate` and `file` as a stored entry has them. */
46
+ function ownValueFields(entry) {
47
+ if (!entry || typeof entry !== "object")
48
+ return {};
49
+ const record = entry;
50
+ return {
51
+ ...(typeof record["how"] === "string" ? { how: record["how"] } : {}),
52
+ ...(record["generate"] === true ? { generate: true } : {}),
53
+ ...(record["file"] === true ? { file: true } : {}),
54
+ };
55
+ }
56
+ const stringList = (value) => Array.isArray(value)
57
+ ? value.filter((each) => typeof each === "string")
58
+ : [];
59
+ /** The features the manifest declares, in order, each read whatever a stored row holds. */
60
+ export function featureEntries(manifest) {
61
+ const features = manifest.features;
62
+ if (!features || typeof features !== "object")
63
+ return [];
64
+ return Object.entries(features).flatMap(([id, raw]) => {
65
+ if (!raw || typeof raw !== "object")
66
+ return [];
67
+ const record = raw;
68
+ return [
69
+ {
70
+ id,
71
+ name: typeof record["name"] === "string" && record["name"].trim()
72
+ ? record["name"]
73
+ : id,
74
+ required: record["required"] === true,
75
+ ...(typeof record["why"] === "string" ? { why: record["why"] } : {}),
76
+ steps: stringList(record["steps"]),
77
+ capabilities: stringList(record["capabilities"]),
78
+ providers: stringList(record["providers"]),
79
+ },
80
+ ];
81
+ });
82
+ }
83
+ /** One feature's raw `secrets` or `settings` list, with the feature id. */
84
+ function featureLists(manifest, key) {
85
+ const features = manifest.features;
86
+ if (!features || typeof features !== "object")
87
+ return [];
88
+ return Object.entries(features).flatMap(([feature, raw]) => {
89
+ const list = raw && typeof raw === "object"
90
+ ? raw[key]
91
+ : undefined;
92
+ return Array.isArray(list) ? [{ feature, list }] : [];
93
+ });
94
+ }
95
+ /** The need of a capability or provider: optional when an optional feature lists it, else required. */
96
+ function gatewayNeedOf(features, kind, id) {
97
+ const owner = features.find((feature) => feature[kind].includes(id));
98
+ if (!owner)
99
+ return { need: { kind: "required" } };
100
+ return {
101
+ need: { kind: owner.required ? "required" : "optional" },
102
+ feature: owner.id,
55
103
  };
56
104
  }
57
105
  /** The providers the manifest lists, each as an entry whatever form it took. */
@@ -69,50 +117,68 @@ export function providerEntries(manifest) {
69
117
  export function providerIds(manifest) {
70
118
  return providerEntries(manifest).map((entry) => entry.id);
71
119
  }
72
- /** The app's own secrets the manifest lists, each as an entry whatever form it took. */
73
- export function secretEntries(manifest) {
74
- return (manifest.secrets ?? []).flatMap((entry) => {
75
- if (typeof entry === "string")
76
- return [{ name: entry }];
77
- if (entry && typeof entry === "object" && typeof entry.name === "string") {
78
- return [{ name: entry.name, ...needFields(entry) }];
120
+ function secretEntryOf(entry, feature) {
121
+ const tag = feature === undefined ? {} : { feature };
122
+ if (typeof entry === "string")
123
+ return [{ name: entry, ...tag }];
124
+ if (entry && typeof entry === "object") {
125
+ const name = entry.name;
126
+ if (typeof name === "string") {
127
+ return [{ name, ...needFields(entry), ...ownValueFields(entry), ...tag }];
79
128
  }
80
- return [];
81
- });
129
+ }
130
+ return [];
131
+ }
132
+ /**
133
+ * The app's own secrets the manifest lists, each as an entry whatever form
134
+ * it took: the top-level ones, then each feature's (tagged with it).
135
+ */
136
+ export function secretEntries(manifest) {
137
+ return [
138
+ ...(manifest.secrets ?? []).flatMap((entry) => secretEntryOf(entry)),
139
+ ...featureLists(manifest, "secrets").flatMap(({ feature, list }) => list.flatMap((entry) => secretEntryOf(entry, feature))),
140
+ ];
82
141
  }
83
142
  /** The names of the app's own secrets, in order. */
84
143
  export function secretNames(manifest) {
85
144
  return secretEntries(manifest).map((entry) => entry.name);
86
145
  }
87
- /** The app's plain settings the manifest lists, each as an entry. */
146
+ function settingEntryOf(entry, feature) {
147
+ if (!entry || typeof entry !== "object")
148
+ return [];
149
+ const record = entry;
150
+ if (typeof record["name"] !== "string")
151
+ return [];
152
+ const type = record["type"];
153
+ const values = record["values"];
154
+ return [
155
+ {
156
+ name: record["name"],
157
+ ...needFields(entry),
158
+ ...(typeof record["how"] === "string" ? { how: record["how"] } : {}),
159
+ ...(type === "url" ||
160
+ type === "number" ||
161
+ type === "boolean" ||
162
+ type === "text"
163
+ ? { type }
164
+ : {}),
165
+ ...(Array.isArray(values) &&
166
+ values.every((value) => typeof value === "string")
167
+ ? { values: values }
168
+ : {}),
169
+ ...(typeof record["default"] === "string"
170
+ ? { default: record["default"] }
171
+ : {}),
172
+ ...(feature === undefined ? {} : { feature }),
173
+ },
174
+ ];
175
+ }
176
+ /** The app's plain settings the manifest lists, each as an entry: the top-level ones, then each feature's. */
88
177
  export function settingEntries(manifest) {
89
- return (manifest.settings ?? []).flatMap((entry) => {
90
- if (!entry || typeof entry !== "object" || typeof entry.name !== "string") {
91
- return [];
92
- }
93
- const record = entry;
94
- const type = record["type"];
95
- const values = record["values"];
96
- return [
97
- {
98
- name: entry.name,
99
- ...needFields(entry),
100
- ...(type === "url" ||
101
- type === "number" ||
102
- type === "boolean" ||
103
- type === "text"
104
- ? { type }
105
- : {}),
106
- ...(Array.isArray(values) &&
107
- values.every((value) => typeof value === "string")
108
- ? { values: values }
109
- : {}),
110
- ...(typeof record["default"] === "string"
111
- ? { default: record["default"] }
112
- : {}),
113
- },
114
- ];
115
- });
178
+ return [
179
+ ...(manifest.settings ?? []).flatMap((entry) => settingEntryOf(entry)),
180
+ ...featureLists(manifest, "settings").flatMap(({ feature, list }) => list.flatMap((entry) => settingEntryOf(entry, feature))),
181
+ ];
116
182
  }
117
183
  /** The names of the app's plain settings, in order. */
118
184
  export function settingNames(manifest) {
@@ -256,23 +322,62 @@ function providerHint(capability, names) {
256
322
  ];
257
323
  return lines.length > 0 ? { hint: lines.join(" ") } : {};
258
324
  }
259
- function withNeed(setting, entry, defaults = {}) {
325
+ function withNeed(setting, entry, need, defaults = {}) {
260
326
  const docs = entry?.docs ?? defaults.docs;
261
- // The registry's guide for a Gateway setting; the manifest's for the app's own secret.
262
- const setup = setting.holder === "gateway" ? defaults.setup : entry?.setup;
263
327
  return {
264
328
  ...setting,
265
- need: needOf(entry),
329
+ need: need.need,
330
+ ...(need.feature === undefined ? {} : { feature: need.feature }),
266
331
  ...(entry?.why ? { why: entry.why } : {}),
267
332
  ...(docs ? { docs } : {}),
268
- ...(setup ? { setup } : {}),
333
+ ...(defaults.setup ? { setup: defaults.setup } : {}),
269
334
  ...(defaults.hint ? { hint: defaults.hint } : {}),
270
335
  };
271
336
  }
337
+ /**
338
+ * A stored manifest from before features (deployed before 0.3.0-rc.14) marks a
339
+ * value `optional: true`. The strict schema refuses that key now, but a stored
340
+ * row keeps it until the app is redeployed, so it still reads as optional:
341
+ * updating the Gateway never puts a working app into Needs setup.
342
+ */
343
+ function storedOptional(need, raw) {
344
+ if (need.feature !== undefined || need.need.kind === "optional")
345
+ return need;
346
+ const optional = raw !== null &&
347
+ typeof raw === "object" &&
348
+ raw["optional"] === true;
349
+ return optional ? { need: { kind: "optional" } } : need;
350
+ }
351
+ /** The raw entry named `name` in a stored list (`secrets`, `settings`, `providers`). */
352
+ function rawEntry(list, name, key) {
353
+ if (!Array.isArray(list))
354
+ return undefined;
355
+ return list.find((entry) => entry !== null &&
356
+ typeof entry === "object" &&
357
+ entry[key] === name);
358
+ }
359
+ /** An app's own value's need: its feature's (required, or optional), or required at the top level. */
360
+ function ownNeedOf(features, feature) {
361
+ if (feature === undefined)
362
+ return { need: { kind: "required" } };
363
+ const owner = features.find((each) => each.id === feature);
364
+ return {
365
+ need: { kind: owner?.required ? "required" : "optional" },
366
+ feature,
367
+ };
368
+ }
369
+ /** `how`, `generate` and `file` onto an app's own setting, when its entry says them. */
370
+ function withOwnValue(setting, entry) {
371
+ return {
372
+ ...setting,
373
+ ...(entry.how ? { how: entry.how } : {}),
374
+ ...(entry.generate ? { generate: true } : {}),
375
+ ...(entry.file ? { file: true } : {}),
376
+ };
377
+ }
272
378
  /**
273
379
  * Every setting the manifest declares, in manifest order: its AI
274
- * providers, its capabilities, the AI proxy's extra routes when `ai.routes`
275
- * asks for any, then the app's own secrets. A provider or capability the
380
+ * providers, its capabilities, then the app's own secrets. A provider or capability the
276
381
  * gateway doesn't offer contributes nothing (the manifest validator refuses
277
382
  * it elsewhere); so does a capability with nothing to set (`members`),
278
383
  * which is always set. `lookup` is where ids are found: the catalog alone
@@ -280,6 +385,7 @@ function withNeed(setting, entry, defaults = {}) {
280
385
  */
281
386
  export function appSettings(manifest, lookup = CATALOG, options = {}) {
282
387
  const settings = [];
388
+ const features = featureEntries(manifest);
283
389
  const chat = manifest.ai?.chat;
284
390
  if (chat === true || (chat && typeof chat === "object")) {
285
391
  // The default resolves on what is set: with nothing set, the first of
@@ -287,7 +393,7 @@ export function appSettings(manifest, lookup = CATALOG, options = {}) {
287
393
  // ask for; with one configured, that provider's. `isSet` here only
288
394
  // orders the candidates; evaluateAppSettings decides what is set.
289
395
  const resolution = resolveAiDefault({
290
- provider: options.aiDefault ?? null,
396
+ choice: null,
291
397
  isSet: options.isSet ?? (() => false),
292
398
  lookup,
293
399
  });
@@ -303,24 +409,25 @@ export function appSettings(manifest, lookup = CATALOG, options = {}) {
303
409
  alternatives: resolution.candidates
304
410
  .map((candidate) => settable(candidate.settings))
305
411
  .filter((names) => names.length > 0),
306
- }, chat === true ? undefined : chat, {
412
+ }, chat === true ? undefined : needFields(chat), storedOptional({ need: { kind: "required" } }, chat), {
307
413
  ...(provider?.mode === "ai" ? { docs: provider.docs } : {}),
308
414
  setup: "setup/ai.md",
309
415
  hint: aiDefaultSetupHint(resolution),
310
416
  }));
311
417
  }
312
- // The default's settings: an optional pin that needs only those is the
313
- // same row (one key, set once), so it isn't listed twice. A required
314
- // pin stays: it needs that provider's key even while another provider
315
- // serves the default.
418
+ // The default's settings: an optional pin (a provider an optional
419
+ // feature lists) that needs only those is the same row (one key, set
420
+ // once), so it isn't listed twice. A required pin stays: it needs that
421
+ // provider's key even while another provider serves the default.
316
422
  const defaultNames = settings.find((setting) => setting.kind === "ai-default")?.names;
317
423
  for (const entry of providerEntries(manifest)) {
318
424
  const capability = lookup.get(entry.id);
319
425
  if (capability?.mode !== "ai")
320
426
  continue;
321
427
  const names = providerNames(capability);
428
+ const need = storedOptional(gatewayNeedOf(features, "providers", entry.id), rawEntry(manifest.providers, entry.id, "id"));
322
429
  if (defaultNames &&
323
- needOf(entry).kind === "optional" &&
430
+ need.need.kind === "optional" &&
324
431
  names.length > 0 &&
325
432
  names.every((name) => defaultNames.includes(name))) {
326
433
  continue;
@@ -332,7 +439,7 @@ export function appSettings(manifest, lookup = CATALOG, options = {}) {
332
439
  holder: "gateway",
333
440
  names,
334
441
  alsoReads: [],
335
- }, entry, {
442
+ }, entry, need, {
336
443
  docs: capability.docs,
337
444
  setup: capability.setup,
338
445
  ...providerHint(capability, names),
@@ -351,67 +458,58 @@ export function appSettings(manifest, lookup = CATALOG, options = {}) {
351
458
  holder: "gateway",
352
459
  names,
353
460
  alsoReads,
354
- }, needFields(entry), {
461
+ }, needFields(entry), storedOptional(gatewayNeedOf(features, "capabilities", id), entry), {
355
462
  docs: capabilityDocs(capability),
356
- setup: capability.setup,
463
+ setup: capability.mode === "oauth_client"
464
+ ? oauthSetupGuide(id)
465
+ : capability.setup,
357
466
  ...providerHint(capability, names),
358
467
  }));
359
468
  }
360
- if (manifest.ai?.routes?.length) {
469
+ const remoteProblem = hasRemoteMcpBlock(manifest)
470
+ ? remoteMcpBlockProblem(manifest)
471
+ : null;
472
+ if (remoteProblem) {
473
+ const label = typeof manifest.name === "string" && manifest.name.trim()
474
+ ? manifest.name.trim()
475
+ : "the MCP server";
361
476
  settings.push({
362
- kind: "ai-routes",
363
- id: "ai-routes",
364
- label: "AI proxy routes",
477
+ kind: "remote-mcp",
478
+ id: "remote",
479
+ label: `${label} registration`,
365
480
  holder: "gateway",
366
- names: ["AI_PROXY_EXTRA_ROUTES"],
481
+ names: [],
367
482
  alsoReads: [],
368
483
  need: { kind: "required" },
369
- why: `Lets the app call ${manifest.ai.routes.join(", ")} through the AI proxy.`,
370
- setup: "setup/openai.md",
484
+ why: `${label}'s registration can't be read: ${remoteProblem}.`,
485
+ setup: "setup/remote-mcp.md",
371
486
  });
372
487
  }
373
- const remote = remoteMcpOf(manifest);
374
- if (remote) {
375
- const secret = remoteMcpSecretOf(remote.auth);
376
- if (secret) {
377
- const label = typeof manifest.name === "string" && manifest.name.trim()
378
- ? manifest.name.trim()
379
- : "the MCP server";
380
- settings.push({
381
- kind: "remote-mcp",
382
- id: secret,
383
- label: `${label} credential`,
384
- holder: "gateway",
385
- names: [secret],
386
- alsoReads: [],
387
- need: { kind: "required" },
388
- why: `What ${label} expects: ${remoteMcpAuthLabel(remote.auth)}. The Gateway sends it to ${remote.url} and nowhere else.`,
389
- setup: "setup/remote-mcp.md",
390
- });
391
- }
392
- }
393
488
  for (const entry of secretEntries(manifest)) {
394
- settings.push(withNeed({
489
+ settings.push(withOwnValue(withNeed({
395
490
  kind: "secret",
396
491
  id: entry.name,
397
492
  label: entry.name,
398
493
  holder: "app",
399
494
  names: [entry.name],
400
495
  alsoReads: [],
401
- }, entry));
496
+ }, entry, storedOptional(ownNeedOf(features, entry.feature), rawEntry(manifest.secrets, entry.name, "name"))), entry));
402
497
  }
403
498
  for (const entry of settingEntries(manifest)) {
404
- // A setting with a default is never unset for long: the deploy writes it.
405
- const need = entry.default !== undefined ? { ...entry, optional: true } : entry;
499
+ const own = storedOptional(ownNeedOf(features, entry.feature), rawEntry(manifest.settings, entry.name, "name"));
406
500
  settings.push({
407
- ...withNeed({
501
+ ...withOwnValue(withNeed({
408
502
  kind: "setting",
409
503
  id: entry.name,
410
504
  label: entry.name,
411
505
  holder: "app",
412
506
  names: [entry.name],
413
507
  alsoReads: [],
414
- }, need),
508
+ }, entry,
509
+ // A setting with a default is never unset for long: the deploy writes it.
510
+ entry.default !== undefined
511
+ ? { ...own, need: { kind: "optional" } }
512
+ : own), entry),
415
513
  value: settingValueRule(entry),
416
514
  });
417
515
  }
@@ -419,6 +517,10 @@ export function appSettings(manifest, lookup = CATALOG, options = {}) {
419
517
  }
420
518
  /** Whether each name of `setting` is set: all → true, one unset → false, else unknown. */
421
519
  function settingIsSet(setting, isSet) {
520
+ // An unreadable remote registration has nothing to set: it is fixed by
521
+ // adding the app again, never by a value.
522
+ if (setting.kind === "remote-mcp")
523
+ return false;
422
524
  const groupIsSet = (names) => {
423
525
  let unknown = false;
424
526
  for (const name of names) {
@@ -444,12 +546,24 @@ function settingIsSet(setting, isSet) {
444
546
  }
445
547
  return unknown ? null : false;
446
548
  }
549
+ /**
550
+ * A feature's state from its settings (see {@link FeatureState}). Whether
551
+ * it is started is read from its own values that have no default (a
552
+ * default is written by the deploy, so it says nothing), or, for a
553
+ * feature of Gateway settings alone, from those.
554
+ */
555
+ function featureState(settings) {
556
+ if (settings.every((setting) => setting.set !== false))
557
+ return "ready";
558
+ const own = settings.filter((setting) => setting.holder === "app" && setting.value?.default === undefined);
559
+ const started = (own.length > 0 ? own : settings).some((setting) => setting.set === true);
560
+ return started ? "incomplete" : "off";
561
+ }
447
562
  /**
448
563
  * Every setting against what is set. `isSet` answers per holder and name:
449
564
  * true, false, or null when it can't tell (the registry never recorded
450
- * the app Worker's secret names), which never blocks. An any-of group is
451
- * met once any member is set; while none is and none is unknown, every
452
- * member blocks.
565
+ * the app Worker's secret names), which never blocks. A required setting
566
+ * that is unset blocks; an optional one (an optional feature's) never does.
453
567
  */
454
568
  export function evaluateAppSettings(settings, isSet, options = {}) {
455
569
  const evaluated = settings.map((setting) => {
@@ -460,51 +574,38 @@ export function evaluateAppSettings(settings, isSet, options = {}) {
460
574
  blocking: false,
461
575
  };
462
576
  });
463
- const groups = [];
464
- for (const setting of evaluated) {
465
- if (setting.need.kind !== "anyOf")
466
- continue;
467
- const group = setting.need.group;
468
- let found = groups.find((each) => each.group === group);
469
- if (!found) {
470
- found = { group, members: [], met: false };
471
- groups.push(found);
472
- }
473
- found.members.push(setting);
474
- }
475
- for (const group of groups) {
476
- group.met = group.members.some((member) => member.set === true);
477
- const unknown = group.members.some((member) => member.set === null);
478
- if (!group.met && !unknown) {
479
- for (const member of group.members)
480
- member.blocking = true;
481
- }
482
- }
483
577
  for (const setting of evaluated) {
484
578
  if (setting.need.kind === "required" && setting.set === false) {
485
579
  setting.blocking = true;
486
580
  }
487
581
  }
488
582
  const unmet = evaluated.filter((setting) => setting.blocking);
489
- return { ready: unmet.length === 0, settings: evaluated, groups, unmet };
583
+ const features = (options.features ?? []).map((feature) => {
584
+ const own = evaluated.filter((setting) => setting.feature === feature.id);
585
+ return { ...feature, settings: own, state: featureState(own) };
586
+ });
587
+ return { ready: unmet.length === 0, settings: evaluated, unmet, features };
490
588
  }
491
589
  /**
492
590
  * What to run for an app that needs setup: `everyapp configure <app>`,
493
591
  * which reads the app's manifest (from the registry, so it runs from
494
- * anywhere), prompts for each unmet setting (one member of an any-of group
495
- * is enough: it asks which) and sets them where they are held. Null when
592
+ * anywhere), prompts for each unmet setting, feature by feature, and sets
593
+ * them where they are held. Null when
496
594
  * the app is ready. `cli` is how the operator runs everyapp (`pnpm
497
595
  * everyapp` from a checkout). Scripts that set one name at a time use
498
596
  * `everyapp secret set NAME [--app <id>]` instead.
499
597
  *
500
598
  * A provider app (`provider`: the gateway's own, with no Worker and no
501
599
  * directory; `everyapp mcp on <id>`, an MCP app) has nothing to configure: its
502
- * command is the `secret set` of its Gateway key (the first member of an
503
- * unmet any-of group), as `mcp list` says it.
600
+ * command is the `secret set` of its Gateway key, as `mcp list` says it.
504
601
  */
505
602
  export function setupCommand(report, { cli, appId, provider = false, }) {
506
603
  if (report.unmet.length === 0)
507
604
  return null;
605
+ if (report.unmet.some((setting) => setting.kind === "remote-mcp")) {
606
+ // Nothing to set: the registration predates provider credentials.
607
+ return { command: `${cli} mcp remove ${appId}` };
608
+ }
508
609
  if (!provider)
509
610
  return { command: `${cli} configure ${appId}` };
510
611
  const names = [];
@@ -513,15 +614,8 @@ export function setupCommand(report, { cli, appId, provider = false, }) {
513
614
  if (!names.includes(name))
514
615
  names.push(name);
515
616
  };
516
- for (const setting of report.unmet) {
517
- if (setting.need.kind === "required")
518
- add(setting);
519
- }
520
- for (const group of report.groups) {
521
- const first = group.members.find((member) => member.blocking);
522
- if (first)
523
- add(first);
524
- }
617
+ for (const setting of report.unmet)
618
+ add(setting);
525
619
  return { command: `${cli} secret set ${names.join(" ")}` };
526
620
  }
527
621
  export function appSetupOf(report, options) {
@@ -530,10 +624,9 @@ export function appSetupOf(report, options) {
530
624
  ready: report.ready,
531
625
  settings: report.settings.map(strip),
532
626
  unmet: report.unmet.map(strip),
533
- groups: report.groups.map((group) => ({
534
- group: group.group,
535
- met: group.met,
536
- members: group.members.map((member) => member.id),
627
+ features: report.features.map((feature) => ({
628
+ ...feature,
629
+ settings: feature.settings.map((setting) => setting.id),
537
630
  })),
538
631
  command: options.withCommand === false ? null : setupCommand(report, options),
539
632
  };
@@ -547,3 +640,18 @@ export function apiKeyCapabilityOf(id, lookup = CATALOG) {
547
640
  const capability = lookup.get(id);
548
641
  return capability?.mode === "api_key" ? capability : undefined;
549
642
  }
643
+ /**
644
+ * A fresh value for a secret the manifest marks `generate: true`: 32
645
+ * bytes from the platform's cryptographic random source, base64url
646
+ * without padding (43 characters).
647
+ */
648
+ export function generatedSecret() {
649
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
650
+ let binary = "";
651
+ for (const byte of bytes)
652
+ binary += String.fromCharCode(byte);
653
+ return btoa(binary)
654
+ .replace(/\+/g, "-")
655
+ .replace(/\//g, "_")
656
+ .replace(/=+$/, "");
657
+ }