@zackbart/connecta 0.28.1 → 0.30.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/CHANGELOG.md +855 -5
- package/README.md +125 -116
- package/bin/connecta.mjs +121 -139
- package/bin/meta-tool-names.d.mts +2 -0
- package/bin/meta-tool-names.mjs +9 -0
- package/bin/version.mjs +3 -0
- package/dist/access-tokens.js +53 -63
- package/dist/activity-request.js +8 -0
- package/dist/activity.d.ts +76 -42
- package/dist/activity.js +110 -42
- package/dist/argument-redaction.js +230 -0
- package/dist/auth/clerk-sdk/client.d.ts +65 -0
- package/dist/auth/clerk-sdk/client.js +5 -0
- package/dist/auth/clerk-sdk/generated.js +1845 -0
- package/dist/auth/clerk-transport.js +19 -0
- package/dist/auth/clerk.d.ts +17 -7
- package/dist/auth/clerk.js +189 -107
- package/dist/auth/cloudflare-access.js +25 -26
- package/dist/auth/downstream-client-metadata.d.ts +29 -0
- package/dist/auth/downstream-client-metadata.js +133 -0
- package/dist/auth/downstream-oauth.js +1584 -1580
- package/dist/auth/downstream-revocation.js +106 -0
- package/dist/auth/oauth-token-response.js +231 -0
- package/dist/auth/oauth-v2-migration.js +116 -0
- package/dist/auth/static-oauth.js +93 -71
- package/dist/auth-elicitation.js +288 -0
- package/dist/authorization-handoff.js +111 -0
- package/dist/branding.d.ts +8 -16
- package/dist/branding.js +26 -59
- package/dist/byte-read-response.js +39 -0
- package/dist/call-admission.js +12 -16
- package/dist/catalog-cache.js +739 -0
- package/dist/catalog-drift.d.ts +50 -15
- package/dist/catalog-drift.js +365 -128
- package/dist/catalog-fingerprint.js +2 -4
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-limits.js +2 -2
- package/dist/catalog-service.js +409 -225
- package/dist/catalog.js +43 -84
- package/dist/concurrency.js +3 -4
- package/dist/config-defaults.d.ts +39 -0
- package/dist/config-defaults.js +46 -0
- package/dist/config-schema.d.ts +181 -0
- package/dist/config-schema.js +435 -0
- package/dist/config-value-sources.js +102 -0
- package/dist/config.d.ts +633 -0
- package/dist/config.js +508 -0
- package/dist/connector-access.d.ts +1 -0
- package/dist/connector-access.js +13 -15
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +15 -6
- package/dist/connector-scope.js +1 -1
- package/dist/connectors/api-connector.d.ts +59 -7
- package/dist/connectors/api-connector.js +106 -19
- package/dist/connectors/api.js +4 -1
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +87 -38
- package/dist/connectors/negotiation-cache.js +46 -0
- package/dist/connectors/option-shapes.js +27 -0
- package/dist/connectors/remote-mcp.d.ts +30 -4
- package/dist/connectors/remote-mcp.js +1774 -320
- package/dist/connectors/resource-uri.js +335 -0
- package/dist/credential-contract.d.ts +10 -0
- package/dist/credential-rules.js +1 -3
- package/dist/credential-url.js +108 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +27 -18
- package/dist/d1.d.ts +130 -0
- package/dist/d1.js +290 -0
- package/dist/describe-config.d.ts +165 -0
- package/dist/describe-config.js +315 -0
- package/dist/described.js +106 -0
- package/dist/downstream-input-context.js +128 -0
- package/dist/downstream-input.js +325 -0
- package/dist/errors.d.ts +132 -31
- package/dist/errors.js +175 -87
- package/dist/execute.js +798 -318
- package/dist/executor-admission.d.ts +1 -0
- package/dist/executor-admission.js +11 -15
- package/dist/executor-contract.d.ts +20 -0
- package/dist/executor-contract.js +48 -0
- package/dist/executor-result.js +30 -13
- package/dist/executors/quickjs-child.js +15 -9
- package/dist/executors/quickjs-protocol.js +5 -6
- package/dist/executors/quickjs-runtime.js +152 -59
- package/dist/executors/quickjs.js +49 -39
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +238 -0
- package/dist/guest-types.d.ts +218 -0
- package/dist/guest-types.js +1 -0
- package/dist/html-security.js +24 -0
- package/dist/identity.js +1 -3
- package/dist/inbound-credential.js +52 -0
- package/dist/index.d.ts +24 -256
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +262 -79
- package/dist/json-schema.d.ts +1 -1
- package/dist/mcp-client-context.js +35 -0
- package/dist/mcp-result.js +11 -7
- package/dist/meta-output.js +61 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +419 -272
- package/dist/module-contracts.d.ts +8 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -2
- package/dist/oauth-callback-outcome.js +1 -3
- package/dist/oauth-handoff.js +194 -0
- package/dist/oauth-sealing.js +20 -0
- package/dist/operator-record.d.ts +137 -0
- package/dist/operator-record.js +420 -0
- package/dist/operator-ui/assets.js +31 -0
- package/dist/operator-ui/contract.d.ts +72 -0
- package/dist/operator-ui/contract.js +1 -0
- package/dist/operator-ui/fix-prompts.d.ts +18 -0
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/model.d.ts +12 -87
- package/dist/operator-ui/model.js +2 -9
- package/dist/operator-ui/setup-commands.js +1 -3
- package/dist/operator-ui/view.d.ts +323 -0
- package/dist/operator-ui/view.js +62 -189
- package/dist/page-styles.js +2 -3
- package/dist/program-result.js +5 -0
- package/dist/program-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +60 -19
- package/dist/provider.d.ts +142 -0
- package/dist/provider.js +187 -0
- package/dist/providers/_shared/google/result-size.js +57 -0
- package/dist/providers/_shared/google/service-account.d.ts +58 -0
- package/dist/providers/_shared/google/service-account.js +500 -0
- package/dist/providers/_shared/google/workspace.d.ts +276 -0
- package/dist/providers/_shared/google/workspace.js +518 -0
- package/dist/providers/_shared/request-token.js +83 -0
- package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
- package/dist/providers/{breeze.js → breeze/index.js} +184 -140
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
- package/dist/providers/{ccb.js → ccb/index.js} +271 -116
- package/dist/providers/ccb/skill.generated.js +9 -0
- package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
- package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
- package/dist/providers/cloudflare/skill.generated.js +10 -0
- package/dist/providers/docs/index.d.ts +54 -0
- package/dist/providers/docs/index.js +1237 -0
- package/dist/providers/docs/skill.generated.js +9 -0
- package/dist/providers/drive/index.d.ts +53 -0
- package/dist/providers/drive/index.js +1523 -0
- package/dist/providers/drive/skill.generated.js +9 -0
- package/dist/providers/forms/index.d.ts +54 -0
- package/dist/providers/forms/index.js +1217 -0
- package/dist/providers/forms/skill.generated.js +11 -0
- package/dist/providers/github/auth.d.ts +32 -0
- package/dist/providers/github/auth.js +361 -0
- package/dist/providers/github/hosted.js +218 -0
- package/dist/providers/github/index.d.ts +13 -0
- package/dist/providers/github/index.js +140 -0
- package/dist/providers/github/key.js +100 -0
- package/dist/providers/github/rest.js +321 -0
- package/dist/providers/github/scope.d.ts +29 -0
- package/dist/providers/github/scope.js +124 -0
- package/dist/providers/github/skill.generated.js +8 -0
- package/dist/providers/gmail/index.d.ts +51 -0
- package/dist/providers/gmail/index.js +1436 -0
- package/dist/providers/gmail/skill.generated.js +9 -0
- package/dist/providers/infisical/index.d.ts +7 -0
- package/dist/providers/infisical/index.js +713 -0
- package/dist/providers/infisical/skill.generated.js +8 -0
- package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
- package/dist/providers/linear/index.js +352 -0
- package/dist/providers/linear/skill.generated.js +8 -0
- package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
- package/dist/providers/mixpanel/index.js +416 -0
- package/dist/providers/mixpanel/skill.generated.js +9 -0
- package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
- package/dist/providers/{notion.js → notion/index.js} +226 -239
- package/dist/providers/notion/skill.generated.js +10 -0
- package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
- package/dist/providers/{overflow.js → overflow/index.js} +74 -111
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
- package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
- package/dist/providers/planning-center/skill.generated.js +10 -0
- package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
- package/dist/providers/revenuecat/index.js +557 -0
- package/dist/providers/revenuecat/skill.generated.js +11 -0
- package/dist/providers/sheets/index.d.ts +54 -0
- package/dist/providers/sheets/index.js +1237 -0
- package/dist/providers/sheets/skill.generated.js +10 -0
- package/dist/providers/slides/index.d.ts +53 -0
- package/dist/providers/slides/index.js +2645 -0
- package/dist/providers/slides/skill.generated.js +9 -0
- package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
- package/dist/providers/stripe/index.js +266 -0
- package/dist/providers/stripe/skill.generated.js +11 -0
- package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
- package/dist/providers/{tithely.js → tithely/index.js} +60 -91
- package/dist/providers/tithely/skill.generated.js +8 -0
- package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
- package/dist/providers/vercel/index.js +1038 -0
- package/dist/providers/vercel/mcp-ownership.js +212 -0
- package/dist/providers/vercel/skill.generated.js +11 -0
- package/dist/registry.d.ts +110 -171
- package/dist/registry.js +588 -927
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +103 -0
- package/dist/result-shapes.js +17 -49
- package/dist/retired-toolkits.js +1 -1
- package/dist/routes/access-tokens.js +3 -6
- package/dist/routes/activity-context.js +21 -0
- package/dist/routes/activity-disclosure.js +40 -0
- package/dist/routes/activity.js +59 -21
- package/dist/routes/actor-labels.js +3 -50
- package/dist/routes/connect.js +221 -0
- package/dist/routes/credentials.js +15 -27
- package/dist/routes/mcp.js +365 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +110 -57
- package/dist/routes/operator.js +3 -3
- package/dist/routes/shared.d.ts +25 -64
- package/dist/routes/shared.js +160 -80
- package/dist/routes/skills.js +30 -0
- package/dist/routes/ui-config.js +225 -0
- package/dist/routes/ui-data.js +40 -35
- package/dist/routes/ui.js +66 -56
- package/dist/runtime/admission.js +4 -2
- package/dist/runtime/call-admission.js +3 -1
- package/dist/runtime/connector-scope.js +1 -4
- package/dist/runtime/run.js +10 -39
- package/dist/runtime/services.js +12 -117
- package/dist/runtime/shared-read.js +1 -3
- package/dist/runtime/storage.js +8 -1
- package/dist/sent-secrets.d.ts +93 -0
- package/dist/sent-secrets.js +1013 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +416 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +181 -0
- package/dist/storage/keys.js +443 -0
- package/dist/storage/memory.js +16 -4
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +543 -0
- package/dist/timeout.js +4 -2
- package/dist/tool-name.js +17 -0
- package/dist/tool-safety.d.ts +9 -35
- package/dist/tool-safety.js +20 -40
- package/dist/types.d.ts +336 -56
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +7 -19
- package/dist/ui.js +76 -90
- package/dist/url-safety.js +3 -6
- package/dist/usage-guide.js +146 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +309 -47
- package/dist/version.d.ts +1 -7
- package/dist/version.js +3 -7
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +435 -0
- package/documentation/README.md +63 -0
- package/documentation/architecture.md +696 -231
- package/documentation/auth.md +1069 -546
- package/documentation/code-mode.md +662 -356
- package/documentation/deploying.md +146 -0
- package/documentation/integrating.md +159 -0
- package/documentation/meta-tools.md +570 -216
- package/documentation/operating.md +135 -0
- package/documentation/operator-tests.md +97 -0
- package/documentation/operator-ui.md +132 -0
- package/documentation/provider-migration-0.29.md +65 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +293 -255
- package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
- package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
- package/examples/worker/src/connecta.config.ts +151 -0
- package/examples/worker/src/index.ts +14 -197
- package/examples/worker/wrangler.jsonc +15 -35
- package/package.json +126 -54
- package/templates/node/.dockerignore +1 -2
- package/templates/node/.env.example +20 -21
- package/templates/node/AGENTS.md +21 -17
- package/templates/node/Dockerfile +3 -5
- package/templates/node/README.md +140 -58
- package/templates/node/docker-compose.yml +11 -12
- package/templates/node/package.json +4 -2
- package/templates/node/src/connecta.config.ts +119 -0
- package/templates/node/src/index.ts +9 -154
- package/templates/node/src/provision-token.ts +17 -0
- package/AGENTS.md +0 -170
- package/dist/artifacts/connector.d.ts +0 -58
- package/dist/artifacts/connector.js +0 -730
- package/dist/artifacts/document.js +0 -86
- package/dist/artifacts/guide.js +0 -140
- package/dist/artifacts/html-scan.js +0 -329
- package/dist/artifacts/json.js +0 -78
- package/dist/artifacts/kv-store.d.ts +0 -20
- package/dist/artifacts/kv-store.js +0 -140
- package/dist/artifacts/markdown.js +0 -478
- package/dist/artifacts/module.d.ts +0 -37
- package/dist/artifacts/module.js +0 -66
- package/dist/artifacts/operations.d.ts +0 -197
- package/dist/artifacts/operations.js +0 -1002
- package/dist/artifacts/refresh.d.ts +0 -55
- package/dist/artifacts/refresh.js +0 -236
- package/dist/artifacts/routes.js +0 -206
- package/dist/artifacts/types.d.ts +0 -214
- package/dist/artifacts/types.js +0 -22
- package/dist/artifacts/validate.d.ts +0 -62
- package/dist/artifacts/validate.js +0 -651
- package/dist/artifacts.d.ts +0 -6
- package/dist/artifacts.js +0 -9
- package/dist/auth/bearer.d.ts +0 -11
- package/dist/auth/bearer.js +0 -50
- package/dist/providers/basecamp.js +0 -445
- package/dist/providers/linear.js +0 -217
- package/dist/providers/mixpanel.js +0 -247
- package/dist/providers/revenuecat.js +0 -317
- package/dist/providers/stripe.js +0 -253
- package/dist/providers/vercel.js +0 -1253
- package/dist/storage/file.d.ts +0 -15
- package/dist/storage/file.js +0 -455
- package/ethos.md +0 -93
- package/examples/worker/src/cloudflare-kv.ts +0 -43
- package/examples/worker/src/d1-activity-row.ts +0 -155
- package/examples/worker/src/d1-activity.ts +0 -147
- package/examples/worker/src/d1-storage.ts +0 -138
- package/examples/worker/src/r2-artifact-blobs.ts +0 -29
- package/templates/node/src/file-activity.ts +0 -182
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* public surface. Web APIs only.
|
|
14
14
|
*/
|
|
15
15
|
import { ConnectorCallError, unavailableCallError } from "../errors.js";
|
|
16
|
+
import { sentSecretsFor } from "../sent-secrets.js";
|
|
16
17
|
/** Parse delta-seconds or an HTTP-date into a non-negative wait window. */
|
|
17
18
|
export function retryAfterMs(headers) {
|
|
18
19
|
const raw = headers.get("retry-after");
|
|
@@ -43,25 +44,22 @@ export function retryAfterMs(headers) {
|
|
|
43
44
|
* fence, not the only one.
|
|
44
45
|
*/
|
|
45
46
|
export function oauthBearer(provider) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
return grant.fetch(url, init);
|
|
56
|
-
},
|
|
47
|
+
const send = (url, init, ctx) => {
|
|
48
|
+
const grant = ctx.oauth;
|
|
49
|
+
if (!grant) {
|
|
50
|
+
// A wiring bug, not an outage: the transport was built for a grant the
|
|
51
|
+
// connector does not have, so no request can be authenticated.
|
|
52
|
+
throw new ConnectorCallError("connector_call_failed", `The ${provider} transport authenticates through the connector's OAuth grant, and this call's context carries none.`, { retryable: false });
|
|
53
|
+
}
|
|
54
|
+
return grant.fetch(url, init);
|
|
57
55
|
};
|
|
56
|
+
return { authenticate: () => ({}), fetch: send };
|
|
58
57
|
}
|
|
59
58
|
/** Statuses that instruct a client to re-send somewhere else. Never followed. */
|
|
60
59
|
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
61
60
|
/** Statuses the HTTP spec defines as carrying no body at all. */
|
|
62
61
|
const BODILESS_STATUSES = new Set([204, 205, 304]);
|
|
63
62
|
const decoder = new TextDecoder();
|
|
64
|
-
const encoder = new TextEncoder();
|
|
65
63
|
/**
|
|
66
64
|
* A base URL is deployment configuration, so a bad one is a structural mistake
|
|
67
65
|
* and throws where it is written rather than on the first call that uses it.
|
|
@@ -72,19 +70,17 @@ function confinementBase(provider, baseUrl) {
|
|
|
72
70
|
base = new URL(baseUrl);
|
|
73
71
|
}
|
|
74
72
|
catch {
|
|
75
|
-
throw new Error(`${provider} baseUrl must be an absolute URL
|
|
73
|
+
throw new Error(`${provider} baseUrl must be an absolute URL.`);
|
|
76
74
|
}
|
|
77
|
-
const loopback = base.hostname === "localhost" ||
|
|
78
|
-
base.hostname === "127.0.0.1" ||
|
|
79
|
-
base.hostname === "[::1]";
|
|
75
|
+
const loopback = base.hostname === "localhost" || base.hostname === "127.0.0.1" || base.hostname === "[::1]";
|
|
80
76
|
if (base.protocol !== "https:" && !(base.protocol === "http:" && loopback)) {
|
|
81
|
-
throw new Error(`${provider} baseUrl must be https (http is allowed only for a loopback proxy or test double)
|
|
77
|
+
throw new Error(`${provider} baseUrl must be https (http is allowed only for a loopback proxy or test double).`);
|
|
82
78
|
}
|
|
83
79
|
if (base.username || base.password) {
|
|
84
80
|
throw new Error(`${provider} baseUrl must not embed URL credentials; authentication belongs in headers.`);
|
|
85
81
|
}
|
|
86
82
|
if (base.search || base.hash) {
|
|
87
|
-
throw new Error(`${provider} baseUrl must not carry a query or fragment
|
|
83
|
+
throw new Error(`${provider} baseUrl must not carry a query or fragment.`);
|
|
88
84
|
}
|
|
89
85
|
return base;
|
|
90
86
|
}
|
|
@@ -111,8 +107,7 @@ function confinedUrl(provider, base, basePath, request) {
|
|
|
111
107
|
catch {
|
|
112
108
|
throw new ConnectorCallError("invalid_args", `A ${provider} request path did not resolve to a valid URL.`);
|
|
113
109
|
}
|
|
114
|
-
if (url.origin !== base.origin ||
|
|
115
|
-
!`${url.pathname}/`.startsWith(`${basePath}/`)) {
|
|
110
|
+
if (url.origin !== base.origin || !`${url.pathname}/`.startsWith(`${basePath}/`)) {
|
|
116
111
|
throw new ConnectorCallError("invalid_args", `A ${provider} request path escaped the connector's base URL.`);
|
|
117
112
|
}
|
|
118
113
|
for (const [key, value] of Object.entries(request.query ?? {})) {
|
|
@@ -134,9 +129,7 @@ function oversized(provider, limit, detail) {
|
|
|
134
129
|
/** The response body as a `ReadableStream`, or null when there is none to read. */
|
|
135
130
|
function readableBody(response) {
|
|
136
131
|
const body = response.body;
|
|
137
|
-
return body && typeof body.getReader === "function"
|
|
138
|
-
? body
|
|
139
|
-
: null;
|
|
132
|
+
return body && typeof body.getReader === "function" ? body : null;
|
|
140
133
|
}
|
|
141
134
|
/** Read a stream to the ceiling and stop there — never past it. */
|
|
142
135
|
async function drain(provider, stream, limit) {
|
|
@@ -172,9 +165,9 @@ async function drain(provider, stream, limit) {
|
|
|
172
165
|
/**
|
|
173
166
|
* Every read is capped at `maxResponseBytes`: a streaming body is abandoned at
|
|
174
167
|
* the ceiling rather than buffered past it, and where a runtime gives no body
|
|
175
|
-
* stream the
|
|
176
|
-
*
|
|
177
|
-
*
|
|
168
|
+
* stream the buffered bytes are measured before they are accepted, with text
|
|
169
|
+
* and JSON decoded from them. Such a runtime still buffers internally, but
|
|
170
|
+
* cannot return an oversized body as a successful result.
|
|
178
171
|
*
|
|
179
172
|
* A mapper must re-throw `ConnectorCallError` and swallow only what it
|
|
180
173
|
* recognizes: a bare `catch` around `response.json()` eats this refusal along
|
|
@@ -192,9 +185,22 @@ function boundedResponse(provider, response, limit) {
|
|
|
192
185
|
text: async () => "",
|
|
193
186
|
json: emptyJson,
|
|
194
187
|
jsonResult: () => jsonResult(emptyJson),
|
|
188
|
+
prefix: async () => ({ bytes: new Uint8Array(), truncated: false }),
|
|
195
189
|
};
|
|
196
190
|
}
|
|
197
191
|
const stream = readableBody(response);
|
|
192
|
+
const prefix = async (maxBytes) => {
|
|
193
|
+
const keep = Math.min(Math.max(0, Math.trunc(maxBytes)), limit);
|
|
194
|
+
if (stream)
|
|
195
|
+
return await readPrefix(stream, keep);
|
|
196
|
+
// A runtime with no body stream buffers the body regardless; the
|
|
197
|
+
// ceiling still bounds what is accepted.
|
|
198
|
+
const buffer = new Uint8Array(await response.arrayBuffer());
|
|
199
|
+
if (buffer.byteLength > limit && keep === limit) {
|
|
200
|
+
throw oversized(provider, limit, `${buffer.byteLength} bytes`);
|
|
201
|
+
}
|
|
202
|
+
return { bytes: buffer.slice(0, keep), truncated: buffer.byteLength > keep };
|
|
203
|
+
};
|
|
198
204
|
let read;
|
|
199
205
|
const bytes = () => {
|
|
200
206
|
read ??= stream
|
|
@@ -209,14 +215,9 @@ function boundedResponse(provider, response, limit) {
|
|
|
209
215
|
};
|
|
210
216
|
let readText;
|
|
211
217
|
const text = () => {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const size = encoder.encode(body).length;
|
|
216
|
-
if (size > limit)
|
|
217
|
-
throw oversized(provider, limit, `${size} bytes`);
|
|
218
|
-
return body;
|
|
219
|
-
});
|
|
218
|
+
// Bytes, then UTF-8, never `response.text()`: workerd quotes a text
|
|
219
|
+
// read's non-text Content-Type in its own log (INV-6).
|
|
220
|
+
readText ??= bytes().then((body) => decoder.decode(body));
|
|
220
221
|
return readText;
|
|
221
222
|
};
|
|
222
223
|
const json = async () => {
|
|
@@ -231,8 +232,51 @@ function boundedResponse(provider, response, limit) {
|
|
|
231
232
|
text,
|
|
232
233
|
json,
|
|
233
234
|
jsonResult: () => jsonResult(json),
|
|
235
|
+
prefix,
|
|
234
236
|
};
|
|
235
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Read until a chunk crosses `keep` — at most one chunk past it — keep the
|
|
240
|
+
* first `keep` bytes, and cancel the stream: the source stops producing
|
|
241
|
+
* instead of being drained to the ceiling for bytes nobody will look at.
|
|
242
|
+
* A BYOB reader could stop at `keep + 1` exactly, but not every runtime's
|
|
243
|
+
* fetch body offers one, and one chunk is a bounded overrun.
|
|
244
|
+
*/
|
|
245
|
+
async function readPrefix(stream, keep) {
|
|
246
|
+
const reader = stream.getReader();
|
|
247
|
+
const chunks = [];
|
|
248
|
+
let total = 0;
|
|
249
|
+
let truncated = false;
|
|
250
|
+
try {
|
|
251
|
+
while (true) {
|
|
252
|
+
const { done, value } = await reader.read();
|
|
253
|
+
if (done)
|
|
254
|
+
break;
|
|
255
|
+
if (!value)
|
|
256
|
+
continue;
|
|
257
|
+
if (total + value.byteLength > keep) {
|
|
258
|
+
chunks.push(value.subarray(0, keep - total));
|
|
259
|
+
total = keep;
|
|
260
|
+
truncated = true;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
chunks.push(value);
|
|
264
|
+
total += value.byteLength;
|
|
265
|
+
}
|
|
266
|
+
if (truncated)
|
|
267
|
+
await reader.cancel().catch(() => { });
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
reader.releaseLock();
|
|
271
|
+
}
|
|
272
|
+
const bytes = new Uint8Array(total);
|
|
273
|
+
let offset = 0;
|
|
274
|
+
for (const chunk of chunks) {
|
|
275
|
+
bytes.set(chunk, offset);
|
|
276
|
+
offset += chunk.byteLength;
|
|
277
|
+
}
|
|
278
|
+
return { bytes, truncated };
|
|
279
|
+
}
|
|
236
280
|
async function jsonResult(read) {
|
|
237
281
|
try {
|
|
238
282
|
return { value: await read() };
|
|
@@ -280,10 +324,11 @@ export function guardedFetch(options) {
|
|
|
280
324
|
throw new ConnectorCallError("invalid_args", `A ${provider} request may not set the ${shadow} header; authentication is connector-owned.`);
|
|
281
325
|
}
|
|
282
326
|
headers[name] = value;
|
|
327
|
+
sentSecretsFor(ctx).header(value);
|
|
283
328
|
}
|
|
284
329
|
let response;
|
|
285
330
|
try {
|
|
286
|
-
|
|
331
|
+
const init = {
|
|
287
332
|
method: request.method,
|
|
288
333
|
headers,
|
|
289
334
|
...(request.body !== undefined
|
|
@@ -296,19 +341,23 @@ export function guardedFetch(options) {
|
|
|
296
341
|
// never one. Refused below rather than followed.
|
|
297
342
|
redirect: "manual",
|
|
298
343
|
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
299
|
-
}
|
|
344
|
+
};
|
|
345
|
+
sentSecretsFor(ctx).request(url, init);
|
|
346
|
+
response = await send(url.toString(), init, ctx);
|
|
300
347
|
}
|
|
301
348
|
catch (cause) {
|
|
302
349
|
if (cause instanceof ConnectorCallError)
|
|
303
350
|
throw cause;
|
|
304
|
-
throw unavailableCallError(cause, url.href, `Could not reach the ${provider} API
|
|
351
|
+
throw unavailableCallError(cause, url.href, `Could not reach the ${provider} API.`, ctx.signal);
|
|
305
352
|
}
|
|
306
353
|
if (REDIRECT_STATUSES.has(response.status)) {
|
|
307
354
|
await response.body?.cancel().catch(() => { });
|
|
308
355
|
throw new ConnectorCallError("connector_call_failed", `${provider} answered HTTP ${response.status} with a redirect; this connector talks to exactly one origin and never forwards its credential to another.`, { retryable: false });
|
|
309
356
|
}
|
|
310
357
|
const declared = Number(response.headers.get("content-length"));
|
|
311
|
-
|
|
358
|
+
// A prefix read takes only what it was asked for, so a long body is not
|
|
359
|
+
// a reason to refuse it; the ceiling still bounds what is read.
|
|
360
|
+
if (Number.isFinite(declared) && declared > limit && !(request.prefixOnly && response.ok)) {
|
|
312
361
|
await response.body?.cancel().catch(() => { });
|
|
313
362
|
throw oversized(provider, limit, `a declared ${declared} bytes`);
|
|
314
363
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { isSpecType } from "@modelcontextprotocol/client";
|
|
2
|
+
import { failureRecord, logFailure } from "../operator-record.js";
|
|
3
|
+
import { sentSecretsForRequest } from "../sent-secrets.js";
|
|
4
|
+
import { assertDownstreamOutputSafe } from "../downstream-input-context.js";
|
|
5
|
+
import { negotiationKeys, NEGOTIATION_TTL_SECONDS } from "../storage/keys.js";
|
|
6
|
+
/** Verdicts carry data only. Clients, signals and secrets remain request-local. */
|
|
7
|
+
export async function readNegotiation(ctx, digest) {
|
|
8
|
+
let prior;
|
|
9
|
+
try {
|
|
10
|
+
const raw = await ctx.storage.get(negotiationKeys.verdict(digest));
|
|
11
|
+
if (!raw)
|
|
12
|
+
return;
|
|
13
|
+
const value = JSON.parse(raw);
|
|
14
|
+
if (!Number.isFinite(value.expiresAt) || value.expiresAt <= Date.now())
|
|
15
|
+
return;
|
|
16
|
+
const candidate = value.prior;
|
|
17
|
+
if ((candidate?.kind === "legacy" && Object.keys(candidate).length === 1) ||
|
|
18
|
+
(candidate?.kind === "modern" && isSpecType.DiscoverResult(candidate.discover)))
|
|
19
|
+
prior = candidate;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
logFailure(ctx.logger, "negotiation cache read failed", failureRecord({}, error));
|
|
23
|
+
}
|
|
24
|
+
// Privacy refusals are not cache misses and must not enter cache diagnostics.
|
|
25
|
+
if (prior)
|
|
26
|
+
assertDownstreamOutputSafe(ctx.requestScope ?? ctx, prior);
|
|
27
|
+
return prior;
|
|
28
|
+
}
|
|
29
|
+
export async function storeNegotiation(ctx, digest, prior) {
|
|
30
|
+
if (ctx.signal?.aborted)
|
|
31
|
+
return;
|
|
32
|
+
assertDownstreamOutputSafe(ctx.requestScope ?? ctx, prior);
|
|
33
|
+
// DiscoverResult instructions, metadata and capability keys can echo auth.
|
|
34
|
+
const clean = sentSecretsForRequest(ctx.requestScope ?? ctx).redactCredentials(prior);
|
|
35
|
+
if (clean.kind === "modern" && !isSpecType.DiscoverResult(clean.discover))
|
|
36
|
+
return;
|
|
37
|
+
try {
|
|
38
|
+
await ctx.storage.set(negotiationKeys.verdict(digest), JSON.stringify({
|
|
39
|
+
prior: clean,
|
|
40
|
+
expiresAt: Date.now() + NEGOTIATION_TTL_SECONDS * 1000,
|
|
41
|
+
}), { ttlSeconds: NEGOTIATION_TTL_SECONDS });
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
logFailure(ctx.logger, "negotiation cache write failed", failureRecord({}, error));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Closed option shapes shared by the built-in connector factories: `api()`,
|
|
2
|
+
// `remoteMcp()`, and every maintained provider. Each factory walks its options
|
|
3
|
+
// against its shape with `assertKnownOptions` before reading a value, so a
|
|
4
|
+
// misspelled `maxResultByte` or `requireHTTPs` throws at construction with its
|
|
5
|
+
// path instead of silently doing nothing (INV-11). Values stay the factory's
|
|
6
|
+
// to validate; handlers, schemas, and loggers pass through as `opaque()`.
|
|
7
|
+
import { array, keys, optionsOf, strings, variants } from "../config-schema.js";
|
|
8
|
+
export { CALL_ADMISSION, PROVIDER_COMMON } from "../provider.js";
|
|
9
|
+
export const CREDENTIAL = optionsOf()({
|
|
10
|
+
...keys("label", "description", "placeholder"),
|
|
11
|
+
fields: array(optionsOf()(keys("name", "label", "description", "placeholder", "inputType"))),
|
|
12
|
+
});
|
|
13
|
+
/** The structured form; a string guide has no keys to check. */
|
|
14
|
+
export const USAGE_GUIDE = optionsOf()(keys("content", "summary", "required"));
|
|
15
|
+
/** `remoteMcp()`'s `auth`, closed per `type`. */
|
|
16
|
+
export const REMOTE_MCP_AUTH = variants("type", {
|
|
17
|
+
request: optionsOf()({ ...keys("type", "token"), headers: strings() }).shape,
|
|
18
|
+
headers: optionsOf()({ ...keys("type"), headers: strings() }).shape,
|
|
19
|
+
credential: optionsOf()({
|
|
20
|
+
...keys("type", "header", "scheme"),
|
|
21
|
+
credential: CREDENTIAL,
|
|
22
|
+
}).shape,
|
|
23
|
+
oauth: optionsOf()({
|
|
24
|
+
...keys("type", "clientMetadataUrl", "scope"),
|
|
25
|
+
client: optionsOf()(keys("issuer", "clientId", "clientSecret", "tokenEndpointAuthMethod")),
|
|
26
|
+
}).shape,
|
|
27
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { type RemoteOAuthClient } from "../auth/downstream-client-metadata.js";
|
|
1
2
|
import type { FetchLike, Transport } from "@modelcontextprotocol/client";
|
|
2
3
|
import { ConnectorCallError } from "../errors.js";
|
|
3
|
-
import type { Connector, ConnectorCallAdmissionPolicy, ConnectorContext, ConnectorCredentialConfig, ConnectorUsageGuide, Logger } from "../types.js";
|
|
4
|
+
import type { Connector, ConnectorCallAdmissionPolicy, ConnectorContext, ConnectorCredentialConfig, ConnectorUsageGuide, Logger, ToolClassification } from "../types.js";
|
|
4
5
|
/**
|
|
5
6
|
* A static downstream credential supplied through the connection in the
|
|
6
7
|
* operator UI rather than baked into deployment source.
|
|
@@ -34,12 +35,20 @@ interface RemoteMcpCredentialAuth {
|
|
|
34
35
|
scheme?: string | null;
|
|
35
36
|
}
|
|
36
37
|
export type RemoteMcpAuth = {
|
|
38
|
+
type: "request";
|
|
39
|
+
/** Resolve a Bearer token inside this request. Never persisted or described. */
|
|
40
|
+
token: (ctx: ConnectorContext) => Promise<string>;
|
|
41
|
+
/** Static protocol/catalog headers, never Authorization. */
|
|
42
|
+
headers?: Record<string, string>;
|
|
43
|
+
} | {
|
|
37
44
|
type: "headers";
|
|
38
45
|
headers: Record<string, string>;
|
|
39
46
|
} | RemoteMcpCredentialAuth | {
|
|
40
47
|
type: "oauth";
|
|
41
48
|
/** Public HTTPS client metadata document, for servers supporting URL-based client IDs. */
|
|
42
49
|
clientMetadataUrl?: string;
|
|
50
|
+
/** Pre-registered client, bound to its configured issuer; excludes clientMetadataUrl. */
|
|
51
|
+
client?: RemoteOAuthClient;
|
|
43
52
|
/** Space-separated default scopes when the resource server does not advertise them. */
|
|
44
53
|
scope?: string;
|
|
45
54
|
};
|
|
@@ -67,10 +76,10 @@ export interface RemoteMcpOptions {
|
|
|
67
76
|
authScope?: "shared" | "personal";
|
|
68
77
|
/**
|
|
69
78
|
* Max inline result size (bytes) for this connector's tools before
|
|
70
|
-
* call_tool truncates and stashes the full text for
|
|
79
|
+
* call_tool truncates and stashes the full text for connecta.result
|
|
71
80
|
* paging. Overrides the deployment's `calls.maxResultBytes`; omit to inherit
|
|
72
|
-
* it. Must be a whole number of bytes >= 1; anything else
|
|
73
|
-
*
|
|
81
|
+
* it. Must be a whole number of bytes >= 1; anything else refuses to
|
|
82
|
+
* construct.
|
|
74
83
|
*/
|
|
75
84
|
maxResultBytes?: number;
|
|
76
85
|
/** Optional per-runtime downstream call-admission policy. */
|
|
@@ -81,7 +90,24 @@ export interface RemoteMcpOptions {
|
|
|
81
90
|
* discovery metadata. See `Connector.usageGuide`.
|
|
82
91
|
*/
|
|
83
92
|
usageGuide?: string | ConnectorUsageGuide;
|
|
93
|
+
/** Opt in to downstream Skills listing and resource reads. Defaults to false. */
|
|
94
|
+
skills?: boolean;
|
|
84
95
|
auth?: RemoteMcpAuth;
|
|
96
|
+
/**
|
|
97
|
+
* Reviewed read/write verdicts for this downstream's tools, by exact name.
|
|
98
|
+
* Validated at construction. A listed read fills downstream silence but
|
|
99
|
+
* never overrules an explicit write annotation; a listed write or
|
|
100
|
+
* destructive tool stays a write whatever the downstream claims; an unlisted
|
|
101
|
+
* tool is read-only only when it says so explicitly; a listed tool whose
|
|
102
|
+
* `schemaDigest` no longer matches, or cannot be checked, is served as a
|
|
103
|
+
* write. Setting it also reports
|
|
104
|
+
* catalog drift against the list: unclassified, unserved, contradicted, and
|
|
105
|
+
* schema-changed tools, as counts. The connector carries it as
|
|
106
|
+
* `classification`, and its `listTools` returns the downstream's listing
|
|
107
|
+
* unclassified: the registry classifies every read. Omit it to keep the
|
|
108
|
+
* downstream's own annotations, which still fail closed.
|
|
109
|
+
*/
|
|
110
|
+
classify?: ToolClassification | undefined;
|
|
85
111
|
/**
|
|
86
112
|
* Downstream MCP version-negotiation mode. Defaults to `"auto"`, which
|
|
87
113
|
* probes with `server/discover` and falls back to the legacy lifecycle when
|