@every-app/sdk 0.3.0-rc.8 → 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.
- package/README.md +75 -74
- package/dist/cloudflare/catalogSettings.generated.d.ts +20 -6
- package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -1
- package/dist/cloudflare/catalogSettings.generated.js +45 -14
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/sentinel.d.ts +3 -0
- package/dist/cloudflare/sentinel.d.ts.map +1 -1
- package/dist/cloudflare/sentinel.js +10 -0
- package/dist/cloudflare/worker.d.ts +67 -23
- package/dist/cloudflare/worker.d.ts.map +1 -1
- package/dist/cloudflare/worker.js +68 -32
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/internal/aiDefault.d.ts +36 -79
- package/dist/internal/aiDefault.d.ts.map +1 -1
- package/dist/internal/aiDefault.js +47 -149
- package/dist/internal/aiProviders.d.ts +1 -1
- package/dist/internal/aiProviders.d.ts.map +1 -1
- package/dist/internal/aiProviders.js +2 -2
- package/dist/internal/appSettings.d.ts +140 -89
- package/dist/internal/appSettings.d.ts.map +1 -1
- package/dist/internal/appSettings.js +264 -156
- package/dist/internal/capabilities.d.ts +145 -503
- package/dist/internal/capabilities.d.ts.map +1 -1
- package/dist/internal/capabilities.js +29 -550
- package/dist/internal/catalog/commerce.js +1 -1
- package/dist/internal/catalog/comms.d.ts +37 -0
- package/dist/internal/catalog/comms.d.ts.map +1 -1
- package/dist/internal/catalog/comms.js +43 -0
- package/dist/internal/catalog/index.d.ts +72 -0
- package/dist/internal/catalog/index.d.ts.map +1 -1
- package/dist/internal/catalog/infra.d.ts +35 -0
- package/dist/internal/catalog/infra.d.ts.map +1 -1
- package/dist/internal/catalog/infra.js +65 -0
- package/dist/internal/devProviders.d.ts +1 -19
- package/dist/internal/devProviders.d.ts.map +1 -1
- package/dist/internal/devProviders.js +7 -1
- package/dist/internal/events.d.ts +52 -1
- package/dist/internal/events.d.ts.map +1 -1
- package/dist/internal/events.js +66 -2
- package/dist/internal/gatewaySettings.d.ts +6 -16
- package/dist/internal/gatewaySettings.d.ts.map +1 -1
- package/dist/internal/gatewaySettings.js +6 -24
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -0
- package/dist/internal/mcpOAuth.d.ts +210 -0
- package/dist/internal/mcpOAuth.d.ts.map +1 -0
- package/dist/internal/mcpOAuth.js +735 -0
- package/dist/internal/oauthClient.d.ts +7 -47
- package/dist/internal/oauthClient.d.ts.map +1 -1
- package/dist/internal/oauthClient.js +1 -115
- package/dist/internal/oauthPresets.d.ts +356 -20
- package/dist/internal/oauthPresets.d.ts.map +1 -1
- package/dist/internal/oauthPresets.js +183 -861
- package/dist/internal/oauthScopes.d.ts +43 -155
- package/dist/internal/oauthScopes.d.ts.map +1 -1
- package/dist/internal/oauthScopes.js +82 -475
- package/dist/internal/oauthSpec.d.ts +28 -71
- package/dist/internal/oauthSpec.d.ts.map +1 -1
- package/dist/internal/oauthSpec.js +172 -448
- package/dist/internal/protocol.d.ts +39 -15
- package/dist/internal/protocol.d.ts.map +1 -1
- package/dist/internal/protocol.js +40 -7
- package/dist/internal/providerApps.d.ts +2 -2
- package/dist/internal/providerApps.js +2 -2
- package/dist/internal/providerRegistry.d.ts +5 -64
- package/dist/internal/providerRegistry.d.ts.map +1 -1
- package/dist/internal/providerRegistry.js +19 -118
- package/dist/internal/providerSpec.d.ts.map +1 -1
- package/dist/internal/providerSpec.js +14 -36
- package/dist/internal/remoteMcp.d.ts +191 -47
- package/dist/internal/remoteMcp.d.ts.map +1 -1
- package/dist/internal/remoteMcp.js +338 -133
- package/dist/internal/signInProviders.d.ts +1 -1
- package/dist/internal/signInProviders.d.ts.map +1 -1
- package/dist/internal/tools.d.ts +15 -8
- package/dist/internal/tools.d.ts.map +1 -1
- package/dist/internal/tools.js +9 -8
- package/dist/manifest.d.ts +96 -28
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +1 -1
- package/dist/server/ai.d.ts +1 -93
- package/dist/server/ai.d.ts.map +1 -1
- package/dist/server/ai.js +17 -111
- package/dist/server/attribution.d.ts +10 -1
- package/dist/server/attribution.d.ts.map +1 -1
- package/dist/server/attribution.js +7 -1
- package/dist/server/connectUrl.d.ts +4 -9
- package/dist/server/connectUrl.d.ts.map +1 -1
- package/dist/server/connectUrl.js +17 -14
- package/dist/server/context.d.ts +12 -14
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js +23 -25
- package/dist/server/emit.d.ts.map +1 -1
- package/dist/server/emit.js +2 -2
- package/dist/server/env.d.ts +3 -0
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/eventsEndpoint.d.ts +48 -0
- package/dist/server/eventsEndpoint.d.ts.map +1 -0
- package/dist/server/eventsEndpoint.js +145 -0
- package/dist/server/everyApp.d.ts +16 -26
- package/dist/server/everyApp.d.ts.map +1 -1
- package/dist/server/everyApp.js +22 -46
- package/dist/server/gatewayFetch.d.ts +19 -1
- package/dist/server/gatewayFetch.d.ts.map +1 -1
- package/dist/server/gatewayFetch.js +45 -3
- package/dist/server/index.d.ts +6 -9
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -8
- package/dist/server/integrationFetch.d.ts +60 -72
- package/dist/server/integrationFetch.d.ts.map +1 -1
- package/dist/server/integrationFetch.js +124 -397
- package/dist/server/listMembers.d.ts.map +1 -1
- package/dist/server/listMembers.js +2 -2
- package/dist/server/refusal.d.ts +0 -9
- package/dist/server/refusal.d.ts.map +1 -1
- package/dist/server/refusal.js +0 -22
- package/dist/server/sendEmail.d.ts.map +1 -1
- package/dist/server/sendEmail.js +2 -2
- package/dist/server/tools.d.ts +17 -6
- package/dist/server/tools.d.ts.map +1 -1
- package/dist/server/tools.js +14 -4
- package/dist/server/toolsEndpoint.d.ts +2 -1
- package/dist/server/toolsEndpoint.d.ts.map +1 -1
- package/dist/server/toolsEndpoint.js +4 -3
- package/dist/server/webhooks.d.ts +56 -0
- package/dist/server/webhooks.d.ts.map +1 -0
- package/dist/server/webhooks.js +205 -0
- package/dist/testing/index.d.ts +2 -3
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2 -6
- package/package.json +6 -1
- package/dist/server/aiBinding.d.ts +0 -68
- package/dist/server/aiBinding.d.ts.map +0 -1
- package/dist/server/aiBinding.js +0 -294
- package/dist/server/authorizeUrl.d.ts +0 -43
- package/dist/server/authorizeUrl.d.ts.map +0 -1
- package/dist/server/authorizeUrl.js +0 -106
- package/dist/server/emailBinding.d.ts +0 -72
- package/dist/server/emailBinding.d.ts.map +0 -1
- package/dist/server/emailBinding.js +0 -124
- package/dist/server/fromMcpTools.d.ts +0 -89
- package/dist/server/fromMcpTools.d.ts.map +0 -1
- 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.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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 {
|
|
32
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
313
|
-
//
|
|
314
|
-
//
|
|
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
|
-
|
|
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.
|
|
463
|
+
setup: capability.mode === "oauth_client"
|
|
464
|
+
? oauthSetupGuide(id)
|
|
465
|
+
: capability.setup,
|
|
357
466
|
...providerHint(capability, names),
|
|
358
467
|
}));
|
|
359
468
|
}
|
|
360
|
-
|
|
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: "
|
|
363
|
-
id: "
|
|
364
|
-
label:
|
|
477
|
+
kind: "remote-mcp",
|
|
478
|
+
id: "remote",
|
|
479
|
+
label: `${label} registration`,
|
|
365
480
|
holder: "gateway",
|
|
366
|
-
names: [
|
|
481
|
+
names: [],
|
|
367
482
|
alsoReads: [],
|
|
368
483
|
need: { kind: "required" },
|
|
369
|
-
why:
|
|
370
|
-
setup: "setup/
|
|
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
|
-
|
|
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
|
-
},
|
|
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.
|
|
451
|
-
*
|
|
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
|
-
|
|
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
|
|
495
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
+
}
|