@every-app/sdk 0.3.0-rc.11 → 0.3.0-rc.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/cloudflare/worker.d.ts +1 -1
- package/dist/cloudflare/worker.d.ts.map +1 -1
- package/dist/cloudflare/worker.js +1 -2
- package/dist/internal/aiDefault.d.ts +32 -76
- package/dist/internal/aiDefault.d.ts.map +1 -1
- package/dist/internal/aiDefault.js +41 -144
- package/dist/internal/appSettings.d.ts +26 -19
- package/dist/internal/appSettings.d.ts.map +1 -1
- package/dist/internal/appSettings.js +24 -31
- package/dist/internal/capabilities.d.ts +21 -8
- package/dist/internal/capabilities.d.ts.map +1 -1
- package/dist/internal/capabilities.js +2 -2
- package/dist/internal/catalog/commerce.js +1 -1
- package/dist/internal/gatewaySettings.d.ts +6 -16
- package/dist/internal/gatewaySettings.d.ts.map +1 -1
- package/dist/internal/gatewaySettings.js +5 -23
- 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/oauthBuiltIns.d.ts +1 -1
- package/dist/internal/oauthBuiltIns.js +1 -1
- package/dist/internal/oauthSpec.d.ts +1 -1
- package/dist/internal/oauthSpec.d.ts.map +1 -1
- package/dist/internal/oauthSpec.js +51 -1
- package/dist/internal/protocol.d.ts +5 -24
- package/dist/internal/protocol.d.ts.map +1 -1
- package/dist/internal/protocol.js +0 -19
- package/dist/internal/providerRegistry.d.ts +4 -3
- package/dist/internal/providerRegistry.d.ts.map +1 -1
- package/dist/internal/providerRegistry.js +11 -3
- package/dist/internal/remoteMcp.d.ts +162 -47
- package/dist/internal/remoteMcp.d.ts.map +1 -1
- package/dist/internal/remoteMcp.js +261 -124
- package/dist/internal/tools.d.ts +11 -7
- package/dist/internal/tools.d.ts.map +1 -1
- package/dist/internal/tools.js +8 -8
- package/dist/manifest.d.ts +9 -25
- package/dist/manifest.d.ts.map +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 +1 -1
- 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/everyApp.d.ts +4 -23
- package/dist/server/everyApp.d.ts.map +1 -1
- package/dist/server/everyApp.js +13 -46
- package/dist/server/gatewayFetch.d.ts +1 -1
- package/dist/server/gatewayFetch.js +1 -1
- package/dist/server/index.d.ts +3 -7
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -7
- package/dist/server/integrationFetch.d.ts +1 -1
- 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/tools.d.ts +11 -8
- package/dist/server/tools.d.ts.map +1 -1
- package/dist/server/tools.js +10 -4
- package/dist/server/toolsEndpoint.d.ts +2 -3
- package/dist/server/toolsEndpoint.d.ts.map +1 -1
- package/dist/server/toolsEndpoint.js +6 -11
- 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 +1 -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/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
|
@@ -3,34 +3,53 @@
|
|
|
3
3
|
* (`everyapp mcp add <url>`, Admin → Apps → Add app → "Add an MCP server",
|
|
4
4
|
* `everyapp_mcp_add`), served to members' AI clients as an MCP app of the
|
|
5
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
|
|
7
|
-
*
|
|
6
|
+
* its tools read from the server and each call forwarded to it
|
|
7
|
+
* (docs/design/2026-10-mcp-by-url.md, docs/design/2026-11-mcp-apps-auth.md).
|
|
8
8
|
*
|
|
9
9
|
* The registration record is the app row's manifest: `id`, `name`,
|
|
10
10
|
* `description` and a `remote` block ({@link RemoteMcpBlock}): the one URL
|
|
11
|
-
* the Gateway ever fetches,
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* the Gateway ever fetches, its credential ({@link RemoteMcpAuth}: none, or
|
|
12
|
+
* a provider of the registry, whose key or OAuth connection the Gateway
|
|
13
|
+
* holds and binds to the provider's hosts), how a tool's risk is decided
|
|
14
|
+
* when the server's annotations don't say, the operator's per-tool
|
|
15
|
+
* overrides, and a revision the writers bump so the Gateway re-lists the
|
|
16
|
+
* tools on first use after a change. An app with a provider also lists it
|
|
17
|
+
* under `capabilities` ({@link remoteMcpAppManifest}), so the settings
|
|
18
|
+
* model treats its key or client as any app's.
|
|
19
|
+
*
|
|
20
|
+
* A credential the registrar gives at `mcp add` becomes a provider row of
|
|
21
|
+
* the app's own, `<appId>-mcp` ({@link remoteMcpProviderId}; the specs are
|
|
22
|
+
* built in ./mcpOAuth.ts): an API-key row for a static value
|
|
23
|
+
* (`<APPID>_MCP_TOKEN`), a discovered `oauth2-user` row for OAuth
|
|
24
|
+
* (`<APPID>_MCP_CLIENT_ID`, `_SECRET`).
|
|
16
25
|
*
|
|
17
26
|
* Everything here is pure and shared by the CLI, the perimeter's registry
|
|
18
27
|
* rules, the Gateway (which serves the row and validates again at every
|
|
19
28
|
* fetch) and the settings model (./appSettings.ts), so the rules cannot
|
|
20
|
-
* drift: what the URL may be, what the credential may be
|
|
21
|
-
*
|
|
29
|
+
* drift: what the URL may be, what the credential may be, how a server's
|
|
30
|
+
* tool names become the ToolBus's, and what risk a tool gets.
|
|
22
31
|
*/
|
|
23
|
-
import { CAPABILITIES } from "./capabilities.js";
|
|
24
|
-
import { isRemoteMcpSecretName } from "./gatewaySettings.js";
|
|
32
|
+
import { CAPABILITIES, apiKeyHosts, } from "./capabilities.js";
|
|
25
33
|
import { isProviderAppId } from "./providerApps.js";
|
|
34
|
+
import { PROVIDER_ID } from "./providerSpec.js";
|
|
26
35
|
import { TOOL_NAME_PATTERN, isToolRisk } from "./tools.js";
|
|
27
|
-
|
|
36
|
+
/** The kinds a stored `remote.auth` block may have. */
|
|
37
|
+
export const REMOTE_MCP_AUTH_KINDS = ["none", "provider"];
|
|
38
|
+
/**
|
|
39
|
+
* What the registrar may ask for at `mcp add` (`--auth`): none, a static
|
|
40
|
+
* credential the add turns into a synthesized API-key provider row, or
|
|
41
|
+
* OAuth, a discovered `oauth2-user` row (./mcpOAuth.ts).
|
|
42
|
+
*/
|
|
43
|
+
export const REMOTE_MCP_CREDENTIAL_KINDS = [
|
|
28
44
|
"none",
|
|
29
45
|
"bearer",
|
|
30
46
|
"header",
|
|
47
|
+
"query",
|
|
31
48
|
"basic",
|
|
32
49
|
"oauth",
|
|
33
50
|
];
|
|
51
|
+
/** Why an old registration (`bearer`, `header`, `basic`, `oauth` with a `secret`) no longer parses. */
|
|
52
|
+
export const REMOTE_MCP_AUTH_PREDATES = "this registration predates provider credentials; remove it and add it again";
|
|
34
53
|
/**
|
|
35
54
|
* How a tool's risk is decided for the app. `annotations`: from the
|
|
36
55
|
* server's hints ({@link remoteToolRisk}); `read` or `write`: every tool,
|
|
@@ -45,18 +64,17 @@ const ID_PATTERN = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
|
|
|
45
64
|
const ID_MAX_LENGTH = 32;
|
|
46
65
|
/** App ids a remote server can't take: the Gateway's own tool prefix. */
|
|
47
66
|
const RESERVED_IDS = ["everyapp"];
|
|
48
|
-
const
|
|
49
|
-
const HEADER_NAME_PATTERN = /^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,100}$/;
|
|
50
|
-
const PREFIX_MAX_LENGTH = 32;
|
|
67
|
+
const HEADER_NAME_PATTERN = /^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,64}$/;
|
|
51
68
|
const URL_MAX_LENGTH = 2048;
|
|
52
69
|
const NAME_MAX_LENGTH = 255;
|
|
53
70
|
const DESCRIPTION_MAX_LENGTH = 1000;
|
|
54
71
|
const OVERRIDES_MAX = 500;
|
|
55
72
|
const UPSTREAM_NAME_MAX = 256;
|
|
56
73
|
/**
|
|
57
|
-
* Headers
|
|
58
|
-
* request, the ones that route it, and the ones that would
|
|
59
|
-
* other than this app's credential
|
|
74
|
+
* Headers a static `header` credential may not name: the ones the Gateway
|
|
75
|
+
* sets on the request, the ones that route it, and the ones that would
|
|
76
|
+
* carry something other than this app's credential (`Authorization` is
|
|
77
|
+
* the `bearer` kind's; `--prefix` gives it another scheme).
|
|
60
78
|
*/
|
|
61
79
|
const FORBIDDEN_AUTH_HEADERS = new Set([
|
|
62
80
|
"authorization",
|
|
@@ -71,10 +89,99 @@ const FORBIDDEN_AUTH_HEADERS = new Set([
|
|
|
71
89
|
"mcp-protocol-version",
|
|
72
90
|
"mcp-method",
|
|
73
91
|
"mcp-name",
|
|
92
|
+
"last-event-id",
|
|
74
93
|
]);
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Why `header` can't carry a remote MCP server's static credential, or
|
|
96
|
+
* null when it can: an HTTP header name the MCP transport and the Gateway
|
|
97
|
+
* don't set themselves.
|
|
98
|
+
*/
|
|
99
|
+
export function remoteMcpHeaderProblem(header) {
|
|
100
|
+
if (typeof header !== "string" || !HEADER_NAME_PATTERN.test(header)) {
|
|
101
|
+
return "the header must be an HTTP header name";
|
|
102
|
+
}
|
|
103
|
+
if (FORBIDDEN_AUTH_HEADERS.has(header.toLowerCase())) {
|
|
104
|
+
return `the header can't be ${header}; use --auth bearer for Authorization`;
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
/** The settings prefix of an app id's synthesized row: `my-server` → `MY_SERVER_MCP_`. */
|
|
109
|
+
function settingsPrefix(appId) {
|
|
110
|
+
return `${appId.toUpperCase().replace(/-/g, "_")}_MCP_`;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The provider row `everyapp mcp add` writes for an app's own credential:
|
|
114
|
+
* `<appId>-mcp` (`treg` → `treg-mcp`), its settings `TREG_MCP_*`.
|
|
115
|
+
*/
|
|
116
|
+
export function remoteMcpProviderId(appId) {
|
|
117
|
+
return `${appId}-mcp`;
|
|
118
|
+
}
|
|
119
|
+
/** Whether `providerId` is `appId`'s synthesized row ({@link remoteMcpProviderId}). */
|
|
120
|
+
export function isRemoteMcpProviderIdOf(providerId, appId) {
|
|
121
|
+
return providerId === remoteMcpProviderId(appId);
|
|
122
|
+
}
|
|
123
|
+
/** The static credential's setting on the synthesized row: `treg` → `TREG_MCP_TOKEN`. */
|
|
124
|
+
export function remoteMcpTokenSetting(appId) {
|
|
125
|
+
return `${settingsPrefix(appId)}TOKEN`;
|
|
126
|
+
}
|
|
127
|
+
/** The OAuth client id's setting on the synthesized row: `treg` → `TREG_MCP_CLIENT_ID`. */
|
|
128
|
+
export function remoteMcpClientIdSetting(appId) {
|
|
129
|
+
return `${settingsPrefix(appId)}CLIENT_ID`;
|
|
130
|
+
}
|
|
131
|
+
/** The OAuth client secret's setting on the synthesized row: `treg` → `TREG_MCP_CLIENT_SECRET`. */
|
|
132
|
+
export function remoteMcpClientSecretSetting(appId) {
|
|
133
|
+
return `${settingsPrefix(appId)}CLIENT_SECRET`;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The redirect URI of an app's synthesized OAuth row, what a server's
|
|
137
|
+
* client is registered with: `<gateway origin>/connect/<appId>-mcp/callback`.
|
|
138
|
+
*/
|
|
139
|
+
export function remoteMcpRedirectUri(gatewayUrl, appId) {
|
|
140
|
+
let origin = gatewayUrl.trim().replace(/\/+$/, "");
|
|
141
|
+
try {
|
|
142
|
+
origin = new URL(gatewayUrl).origin;
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Not a URL: used as given, without a trailing slash.
|
|
146
|
+
}
|
|
147
|
+
return `${origin}/connect/${remoteMcpProviderId(appId)}/callback`;
|
|
148
|
+
}
|
|
149
|
+
/** Where the Gateway serves its OAuth client metadata documents (public, bypassed behind Access). */
|
|
150
|
+
export const REMOTE_MCP_CLIENT_METADATA_PREFIX = "/oauth-clients/";
|
|
151
|
+
/**
|
|
152
|
+
* The Gateway's client id at a server that takes OAuth client ID metadata
|
|
153
|
+
* documents (CIMD) instead of registration: the URL of the document that
|
|
154
|
+
* describes the client, `<gateway origin>/oauth-clients/<appId>-mcp`.
|
|
155
|
+
* Null for a Gateway that isn't on https (a local one): an authorization
|
|
156
|
+
* server fetches the document, and only from https.
|
|
157
|
+
*/
|
|
158
|
+
export function remoteMcpClientMetadataUrl(gatewayUrl, appId) {
|
|
159
|
+
let origin;
|
|
160
|
+
try {
|
|
161
|
+
const parsed = new URL(gatewayUrl);
|
|
162
|
+
if (parsed.protocol !== "https:")
|
|
163
|
+
return null;
|
|
164
|
+
origin = parsed.origin;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return `${origin}${REMOTE_MCP_CLIENT_METADATA_PREFIX}${remoteMcpProviderId(appId)}`;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The hosts a provider sends its credential to, the hosts an MCP URL
|
|
173
|
+
* using it must be on: an API-key provider's `hosts` (and its host
|
|
174
|
+
* setting's value, given `settings`), an OAuth provider's API hosts. Null
|
|
175
|
+
* for a provider a remote MCP app can't use: an AI provider, a service,
|
|
176
|
+
* or an OAuth client the Gateway runs no flow for.
|
|
177
|
+
*/
|
|
178
|
+
export function remoteMcpProviderHosts(capability, settings) {
|
|
179
|
+
if (capability.mode === "api_key")
|
|
180
|
+
return apiKeyHosts(capability, settings);
|
|
181
|
+
if (capability.mode === "oauth_client" && capability.flow) {
|
|
182
|
+
return [...capability.api];
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
78
185
|
}
|
|
79
186
|
/** Whether `id` can be a remote MCP app's id (kebab-case, at most 32, not reserved, not a built-in's). */
|
|
80
187
|
export function remoteMcpIdProblem(id) {
|
|
@@ -157,11 +264,16 @@ export function reservedUpstreamHosts() {
|
|
|
157
264
|
* `https:` (or `http:` and loopback hosts only while the Gateway itself
|
|
158
265
|
* runs on `http://`, local dev), a host name rather than an address, no
|
|
159
266
|
* credentials or fragment in it, not the Gateway's own origin or one of
|
|
160
|
-
* its app hosts, and not a provider whose key the Gateway holds
|
|
161
|
-
*
|
|
162
|
-
*
|
|
267
|
+
* its app hosts, and not a provider whose key the Gateway holds
|
|
268
|
+
* ({@link reservedUpstreamHosts}) unless the app names that provider:
|
|
269
|
+
* `providerHosts` are the hosts of the provider the app's credential is
|
|
270
|
+
* (never an app's own synthesized row), and a reserved host among them is
|
|
271
|
+
* allowed (`mcp add https://api.elevenlabs.io/v1/mcp --provider
|
|
272
|
+
* elevenlabs`). The Gateway's own origin, its app domains and IPs stay
|
|
273
|
+
* refused always. The Gateway checks the stored URL again before every
|
|
274
|
+
* fetch, so a row edited by hand is refused the same way.
|
|
163
275
|
*/
|
|
164
|
-
export function remoteMcpUrlProblem(url, env) {
|
|
276
|
+
export function remoteMcpUrlProblem(url, env, options = {}) {
|
|
165
277
|
if (typeof url !== "string" || !url.trim())
|
|
166
278
|
return "give the server's URL";
|
|
167
279
|
if (url.length > URL_MAX_LENGTH)
|
|
@@ -230,74 +342,67 @@ export function remoteMcpUrlProblem(url, env) {
|
|
|
230
342
|
return `the URL is on this Gateway's app domain (${normalized})`;
|
|
231
343
|
}
|
|
232
344
|
}
|
|
233
|
-
|
|
234
|
-
|
|
345
|
+
const named = (options.providerHosts ?? []).map((each) => each.toLowerCase().replace(/\.$/, ""));
|
|
346
|
+
if (reservedUpstreamHosts().includes(host) && !named.includes(host)) {
|
|
347
|
+
return `${host} is a provider this Gateway holds a key for; add it with --provider <that provider> so the app uses its key, or reach it through the Gateway's capabilities`;
|
|
235
348
|
}
|
|
236
349
|
return null;
|
|
237
350
|
}
|
|
238
351
|
function isRecord(value) {
|
|
239
352
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
240
353
|
}
|
|
241
|
-
/**
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return `the credential's name must end in _MCP_TOKEN (letters, digits and underscores), like ${remoteMcpSecretName("treg")}`;
|
|
249
|
-
}
|
|
250
|
-
return null;
|
|
251
|
-
}
|
|
252
|
-
/** The auth block's problem, or the block as typed. */
|
|
354
|
+
/**
|
|
355
|
+
* The auth block's problem, or the block as typed: `{ kind: "none" }`,
|
|
356
|
+
* or `{ kind: "provider", provider }` naming a provider id. A
|
|
357
|
+
* block of the kinds before provider credentials (`bearer`, `header`,
|
|
358
|
+
* `basic`, `oauth`) is refused with {@link REMOTE_MCP_AUTH_PREDATES}: such
|
|
359
|
+
* a row serves nothing and reads as needing setup.
|
|
360
|
+
*/
|
|
253
361
|
export function parseRemoteMcpAuth(raw) {
|
|
254
362
|
if (!isRecord(raw) || typeof raw["kind"] !== "string") {
|
|
255
363
|
return { ok: false, problem: "auth must name a kind" };
|
|
256
364
|
}
|
|
257
365
|
const kind = raw["kind"];
|
|
258
|
-
if (
|
|
366
|
+
if (["bearer", "header", "basic", "oauth"].includes(kind)) {
|
|
367
|
+
return { ok: false, problem: REMOTE_MCP_AUTH_PREDATES };
|
|
368
|
+
}
|
|
369
|
+
if (kind === "none") {
|
|
370
|
+
for (const field of Object.keys(raw)) {
|
|
371
|
+
if (field !== "kind") {
|
|
372
|
+
return {
|
|
373
|
+
ok: false,
|
|
374
|
+
problem: `auth.${field} is not a field of kind none`,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return { ok: true, auth: { kind } };
|
|
379
|
+
}
|
|
380
|
+
if (kind !== "provider") {
|
|
259
381
|
return {
|
|
260
382
|
ok: false,
|
|
261
383
|
problem: `auth.kind must be one of ${REMOTE_MCP_AUTH_KINDS.join(", ")}`,
|
|
262
384
|
};
|
|
263
385
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (kind === "bearer" || kind === "basic") {
|
|
272
|
-
return { ok: true, auth: { kind, secret } };
|
|
273
|
-
}
|
|
274
|
-
const header = raw["header"];
|
|
275
|
-
if (typeof header !== "string" || !HEADER_NAME_PATTERN.test(header)) {
|
|
276
|
-
return { ok: false, problem: "auth.header must be an HTTP header name" };
|
|
386
|
+
for (const field of Object.keys(raw)) {
|
|
387
|
+
if (!["kind", "provider"].includes(field)) {
|
|
388
|
+
return {
|
|
389
|
+
ok: false,
|
|
390
|
+
problem: `auth.${field} is not a field of kind provider`,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
277
393
|
}
|
|
278
|
-
|
|
394
|
+
const provider = raw["provider"];
|
|
395
|
+
if (typeof provider !== "string" || !PROVIDER_ID.test(provider)) {
|
|
279
396
|
return {
|
|
280
397
|
ok: false,
|
|
281
|
-
problem:
|
|
398
|
+
problem: "auth.provider must be a provider id: lowercase letters, digits and hyphens, a letter first",
|
|
282
399
|
};
|
|
283
400
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
return { ok: false, problem: "auth.prefix must be a short string" };
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
return {
|
|
293
|
-
ok: true,
|
|
294
|
-
auth: {
|
|
295
|
-
kind: "header",
|
|
296
|
-
secret,
|
|
297
|
-
header,
|
|
298
|
-
...(typeof prefix === "string" && prefix ? { prefix } : {}),
|
|
299
|
-
},
|
|
300
|
-
};
|
|
401
|
+
return { ok: true, auth: { kind: "provider", provider } };
|
|
402
|
+
}
|
|
403
|
+
/** The provider the block names, or null for `none`. */
|
|
404
|
+
export function remoteMcpProviderOf(auth) {
|
|
405
|
+
return auth.kind === "provider" ? auth.provider : null;
|
|
301
406
|
}
|
|
302
407
|
/**
|
|
303
408
|
* The `remote` block of a stored manifest, checked field by field (a row
|
|
@@ -348,7 +453,7 @@ export function parseRemoteMcpBlock(raw) {
|
|
|
348
453
|
if (!isToolRisk(override["risk"])) {
|
|
349
454
|
return {
|
|
350
455
|
ok: false,
|
|
351
|
-
problem: `remote.tools.${name}.risk must be read
|
|
456
|
+
problem: `remote.tools.${name}.risk must be read or write`,
|
|
352
457
|
};
|
|
353
458
|
}
|
|
354
459
|
out.risk = override["risk"];
|
|
@@ -391,14 +496,21 @@ export function remoteMcpOf(manifest) {
|
|
|
391
496
|
const parsed = parseRemoteMcpBlock(manifest["remote"]);
|
|
392
497
|
return parsed.ok ? parsed.block : null;
|
|
393
498
|
}
|
|
499
|
+
/**
|
|
500
|
+
* Why a stored manifest's `remote` block can't be read, or null when it
|
|
501
|
+
* can or there is none: {@link REMOTE_MCP_AUTH_PREDATES} for a block from
|
|
502
|
+
* before provider credentials.
|
|
503
|
+
*/
|
|
504
|
+
export function remoteMcpBlockProblem(manifest) {
|
|
505
|
+
if (!isRecord(manifest) || manifest["remote"] === undefined)
|
|
506
|
+
return null;
|
|
507
|
+
const parsed = parseRemoteMcpBlock(manifest["remote"]);
|
|
508
|
+
return parsed.ok ? null : parsed.problem;
|
|
509
|
+
}
|
|
394
510
|
/** Whether a stored manifest declares a remote MCP server at all (well-formed or not). */
|
|
395
511
|
export function hasRemoteMcpBlock(manifest) {
|
|
396
512
|
return isRecord(manifest) && manifest["remote"] !== undefined;
|
|
397
513
|
}
|
|
398
|
-
/** The credential's setting name the block declares, if its kind takes one. */
|
|
399
|
-
export function remoteMcpSecretOf(auth) {
|
|
400
|
-
return "secret" in auth ? auth.secret : null;
|
|
401
|
-
}
|
|
402
514
|
/**
|
|
403
515
|
* A description for a row that gave none: the server's origin, read as a
|
|
404
516
|
* sentence. The origin only: a path or query may carry something the
|
|
@@ -438,21 +550,52 @@ export function remoteToolOverride(block, upstreamName) {
|
|
|
438
550
|
export function isHeaderSafeToolName(name) {
|
|
439
551
|
return /^[\x21-\x7e]{1,256}$/.test(name);
|
|
440
552
|
}
|
|
441
|
-
/**
|
|
553
|
+
/**
|
|
554
|
+
* The manifest a remote MCP app's row stores, from what the registrar
|
|
555
|
+
* gave. An app with a provider credential lists the provider under
|
|
556
|
+
* `capabilities` like a deployed app that uses it, so the settings model
|
|
557
|
+
* (./appSettings.ts) asks for its key or client, `secret list` names it
|
|
558
|
+
* and the connect flow asks for its scopes: nothing about a remote app's
|
|
559
|
+
* credential is special there. `providerCapability` is the provider as
|
|
560
|
+
* the registry resolves it (the synthesized row's, before it is written);
|
|
561
|
+
* for an OAuth provider the entry lists `scopes`, the server's own scope
|
|
562
|
+
* strings the connect asks for (discovery's, or none: the server decides).
|
|
563
|
+
*/
|
|
442
564
|
export function remoteMcpAppManifest(input) {
|
|
443
565
|
const name = (input.name ?? "").trim().slice(0, NAME_MAX_LENGTH) || input.id;
|
|
444
566
|
const description = (input.description ?? "").trim().slice(0, DESCRIPTION_MAX_LENGTH) ||
|
|
445
567
|
remoteMcpDefaultDescription(name, input.remote.url);
|
|
446
|
-
|
|
568
|
+
const manifest = {
|
|
569
|
+
id: input.id,
|
|
570
|
+
name,
|
|
571
|
+
description,
|
|
572
|
+
remote: input.remote,
|
|
573
|
+
};
|
|
574
|
+
const auth = input.remote.auth;
|
|
575
|
+
if (auth.kind !== "provider")
|
|
576
|
+
return manifest;
|
|
577
|
+
let where = "";
|
|
578
|
+
try {
|
|
579
|
+
where = new URL(input.remote.url).origin;
|
|
580
|
+
}
|
|
581
|
+
catch {
|
|
582
|
+
where = "the server";
|
|
583
|
+
}
|
|
584
|
+
const why = `${name}'s MCP server at ${where} takes this credential; the Gateway sends it there when a member calls the app's tools.`;
|
|
585
|
+
const shortWhy = why.length > 200 ? `${why.slice(0, 199)}…` : why;
|
|
586
|
+
manifest.capabilities = {
|
|
587
|
+
[auth.provider]: input.providerCapability?.mode === "oauth_client"
|
|
588
|
+
? { scopes: [...(input.scopes ?? [])], why: shortWhy }
|
|
589
|
+
: { access: "read", why: shortWhy },
|
|
590
|
+
};
|
|
591
|
+
return manifest;
|
|
447
592
|
}
|
|
448
593
|
/**
|
|
449
594
|
* A tool's risk for the Gateway. An override wins; then the app's `read`
|
|
450
|
-
* or `write` default; then the server's hints
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
* default for `destructiveHint` is not followed: it would hide every
|
|
455
|
-
* unhinted tool.
|
|
595
|
+
* or `write` default; then the server's hints: an explicit `readOnlyHint:
|
|
596
|
+
* true` is `read`, and anything else, a missing hint included, is `write`.
|
|
597
|
+
* A write tool keeps the server's `destructiveHint`, so clients still ask
|
|
598
|
+
* before calling it.
|
|
456
599
|
*/
|
|
457
600
|
export function remoteToolRisk(hints, block, upstreamName) {
|
|
458
601
|
const override = remoteToolOverride(block, upstreamName)?.risk;
|
|
@@ -463,9 +606,8 @@ export function remoteToolRisk(hints, block, upstreamName) {
|
|
|
463
606
|
}
|
|
464
607
|
if (hints?.readOnlyHint === true)
|
|
465
608
|
return { risk: "read", source: "hint" };
|
|
466
|
-
if (hints?.destructiveHint === true)
|
|
467
|
-
return { risk: "
|
|
468
|
-
}
|
|
609
|
+
if (hints?.destructiveHint === true)
|
|
610
|
+
return { risk: "write", source: "hint" };
|
|
469
611
|
return { risk: "write", source: "default" };
|
|
470
612
|
}
|
|
471
613
|
/** Whether the operator left the tool on (the default). */
|
|
@@ -499,41 +641,36 @@ export function normalizeRemoteToolName(name) {
|
|
|
499
641
|
return TOOL_NAME_PATTERN.test(out) ? out : null;
|
|
500
642
|
}
|
|
501
643
|
/**
|
|
502
|
-
*
|
|
503
|
-
*
|
|
644
|
+
* Plain words for how the app authenticates, for lists and dialogs. With
|
|
645
|
+
* the provider's capability as the registry resolves it: "bearer token",
|
|
646
|
+
* "header X-Api-Key", "query parameter key", "user and password" (an
|
|
647
|
+
* API-key provider; "<kind>, <provider>'s key" when the provider isn't
|
|
648
|
+
* the app's own synthesized row), or "OAuth, each member connects" (an
|
|
649
|
+
* OAuth provider). Without it, the provider's id.
|
|
504
650
|
*/
|
|
505
|
-
export function
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
case "bearer":
|
|
531
|
-
return "bearer token";
|
|
532
|
-
case "basic":
|
|
533
|
-
return "user and password";
|
|
534
|
-
case "header":
|
|
535
|
-
return `header ${auth.header}`;
|
|
536
|
-
case "oauth":
|
|
537
|
-
return "OAuth (not supported yet)";
|
|
538
|
-
}
|
|
651
|
+
export function remoteMcpAuthLabel(auth, capability) {
|
|
652
|
+
if (auth.kind === "none")
|
|
653
|
+
return "no credential";
|
|
654
|
+
if (!capability)
|
|
655
|
+
return `provider ${auth.provider}`;
|
|
656
|
+
if (capability.mode === "oauth_client") {
|
|
657
|
+
return "OAuth, each member connects";
|
|
658
|
+
}
|
|
659
|
+
if (capability.mode !== "api_key")
|
|
660
|
+
return `provider ${auth.provider}`;
|
|
661
|
+
const placement = capability.auth;
|
|
662
|
+
const words = placement.kind === "bearer"
|
|
663
|
+
? placement.scheme && placement.scheme.toLowerCase() !== "bearer"
|
|
664
|
+
? `${placement.scheme} token`
|
|
665
|
+
: "bearer token"
|
|
666
|
+
: placement.kind === "header"
|
|
667
|
+
? `header ${placement.header}`
|
|
668
|
+
: placement.kind === "query"
|
|
669
|
+
? `query parameter ${placement.param}`
|
|
670
|
+
: placement.as === "pair"
|
|
671
|
+
? "user and password"
|
|
672
|
+
: "basic auth";
|
|
673
|
+
return auth.provider.endsWith("-mcp")
|
|
674
|
+
? words
|
|
675
|
+
: `${words}, ${capability.label}'s key`;
|
|
539
676
|
}
|
package/dist/internal/tools.d.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* gateway's service binding and never public:
|
|
5
5
|
*
|
|
6
6
|
* POST /__everyapp/tools/list x-everyapp-system JWT (purpose "tools.list")
|
|
7
|
-
* POST /__everyapp/tools/call x-everyapp-identity JWT, chan "mcp"
|
|
8
|
-
*
|
|
7
|
+
* POST /__everyapp/tools/call x-everyapp-identity JWT, chan "mcp"
|
|
8
|
+
* + tc claim
|
|
9
9
|
*
|
|
10
10
|
* MCP itself (protocol eras, headers, envelopes) stays in the gateway; apps
|
|
11
11
|
* only ever speak this small versioned JSON shape. Both sides import the
|
|
@@ -15,19 +15,23 @@ export declare const TOOLS_PROTOCOL_VERSION = 1;
|
|
|
15
15
|
export declare const TOOLS_PATH_PREFIX = "/__everyapp/tools";
|
|
16
16
|
export declare const TOOLS_LIST_PATH = "/__everyapp/tools/list";
|
|
17
17
|
export declare const TOOLS_CALL_PATH = "/__everyapp/tools/call";
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* A tool reads or writes. A write tool that deletes or can't be undone also
|
|
20
|
+
* says `destructiveHint` in its annotations, so clients ask before calling.
|
|
21
|
+
*/
|
|
22
|
+
export declare const TOOL_RISKS: readonly ["read", "write"];
|
|
19
23
|
export type ToolRisk = (typeof TOOL_RISKS)[number];
|
|
20
|
-
/** Scopes the gateway mints on tool-call identities (`chan: "mcp"`
|
|
24
|
+
/** Scopes the gateway mints on tool-call identities (`chan: "mcp"`), one per risk. */
|
|
21
25
|
export declare const MCP_READ_SCOPE = "mcp:read";
|
|
22
26
|
export declare const MCP_WRITE_SCOPE = "mcp:write";
|
|
23
|
-
/** Never minted in v1: destructive tools are hidden until approvals exist. */
|
|
24
|
-
export declare const MCP_DESTRUCTIVE_SCOPE = "mcp:destructive";
|
|
25
27
|
/** Local tool names: lowercase snake_case, at most 48 characters. */
|
|
26
28
|
export declare const TOOL_NAME_PATTERN: RegExp;
|
|
27
29
|
export declare function isToolRisk(value: unknown): value is ToolRisk;
|
|
28
30
|
/** The exact scope a tool-call identity must carry to call a tool. */
|
|
29
31
|
export declare function requiredScopeForRisk(risk: ToolRisk): string;
|
|
30
32
|
export interface ToolAnnotationsV1 {
|
|
33
|
+
/** A write tool deletes or can't be undone: clients ask before calling. */
|
|
34
|
+
destructiveHint?: boolean;
|
|
31
35
|
idempotentHint?: boolean;
|
|
32
36
|
openWorldHint?: boolean;
|
|
33
37
|
}
|
|
@@ -56,7 +60,7 @@ export interface ToolsListResponseV1 {
|
|
|
56
60
|
/** Tools the SDK left out because it can't describe them, and why. */
|
|
57
61
|
warnings?: string[];
|
|
58
62
|
}
|
|
59
|
-
/** The `tc` identity claim: binds a tool-call token (`chan: "mcp"`
|
|
63
|
+
/** The `tc` identity claim: binds a tool-call token (`chan: "mcp"`) to one call. */
|
|
60
64
|
export interface ToolCallClaim {
|
|
61
65
|
id: string;
|
|
62
66
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/internal/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AACrD,eAAO,MAAM,eAAe,2BAA8B,CAAC;AAC3D,eAAO,MAAM,eAAe,2BAA8B,CAAC;AAE3D,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/internal/tools.ts"],"names":[],"mappings":"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"}
|
package/dist/internal/tools.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* gateway's service binding and never public:
|
|
5
5
|
*
|
|
6
6
|
* POST /__everyapp/tools/list x-everyapp-system JWT (purpose "tools.list")
|
|
7
|
-
* POST /__everyapp/tools/call x-everyapp-identity JWT, chan "mcp"
|
|
8
|
-
*
|
|
7
|
+
* POST /__everyapp/tools/call x-everyapp-identity JWT, chan "mcp"
|
|
8
|
+
* + tc claim
|
|
9
9
|
*
|
|
10
10
|
* MCP itself (protocol eras, headers, envelopes) stays in the gateway; apps
|
|
11
11
|
* only ever speak this small versioned JSON shape. Both sides import the
|
|
@@ -15,12 +15,14 @@ export const TOOLS_PROTOCOL_VERSION = 1;
|
|
|
15
15
|
export const TOOLS_PATH_PREFIX = "/__everyapp/tools";
|
|
16
16
|
export const TOOLS_LIST_PATH = `${TOOLS_PATH_PREFIX}/list`;
|
|
17
17
|
export const TOOLS_CALL_PATH = `${TOOLS_PATH_PREFIX}/call`;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* A tool reads or writes. A write tool that deletes or can't be undone also
|
|
20
|
+
* says `destructiveHint` in its annotations, so clients ask before calling.
|
|
21
|
+
*/
|
|
22
|
+
export const TOOL_RISKS = ["read", "write"];
|
|
23
|
+
/** Scopes the gateway mints on tool-call identities (`chan: "mcp"`), one per risk. */
|
|
20
24
|
export const MCP_READ_SCOPE = "mcp:read";
|
|
21
25
|
export const MCP_WRITE_SCOPE = "mcp:write";
|
|
22
|
-
/** Never minted in v1: destructive tools are hidden until approvals exist. */
|
|
23
|
-
export const MCP_DESTRUCTIVE_SCOPE = "mcp:destructive";
|
|
24
26
|
/** Local tool names: lowercase snake_case, at most 48 characters. */
|
|
25
27
|
export const TOOL_NAME_PATTERN = /^[a-z][a-z0-9_]{0,47}$/;
|
|
26
28
|
export function isToolRisk(value) {
|
|
@@ -34,8 +36,6 @@ export function requiredScopeForRisk(risk) {
|
|
|
34
36
|
return MCP_READ_SCOPE;
|
|
35
37
|
case "write":
|
|
36
38
|
return MCP_WRITE_SCOPE;
|
|
37
|
-
case "destructive":
|
|
38
|
-
return MCP_DESTRUCTIVE_SCOPE;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
/**
|