@zackbart/connecta 0.28.0 → 0.29.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 +961 -5
- package/README.md +62 -39
- package/bin/connecta.mjs +110 -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/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 +1632 -1509
- 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 +526 -0
- package/dist/auth-elicitation.js +283 -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 +368 -224
- 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 +631 -0
- package/dist/config.js +503 -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 +220 -0
- package/dist/connectors/api-connector.js +214 -0
- package/dist/connectors/api.d.ts +8 -71
- package/dist/connectors/api.js +13 -99
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +108 -25
- 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 +1780 -351
- 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 +119 -0
- package/dist/downstream-input.js +323 -0
- package/dist/errors.d.ts +97 -20
- package/dist/errors.js +158 -80
- package/dist/execute.js +453 -305
- 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 +24 -9
- package/dist/executors/quickjs-child.js +3 -4
- package/dist/executors/quickjs-runtime.js +147 -59
- package/dist/executors/quickjs.js +29 -35
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +188 -0
- package/dist/guest-types.d.ts +176 -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 +25 -257
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +230 -73
- 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 +76 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +285 -209
- 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 +117 -0
- package/dist/oauth-partition.js +17 -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-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +55 -18
- 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/basecamp/index.d.ts +43 -0
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/breeze/index.d.ts +34 -0
- package/dist/providers/breeze/index.js +1446 -0
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/ccb/index.d.ts +103 -0
- package/dist/providers/ccb/index.js +1725 -0
- 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/{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/index.d.ts +60 -0
- package/dist/providers/overflow/index.js +1535 -0
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/planning-center/index.d.ts +59 -0
- package/dist/providers/planning-center/index.js +2199 -0
- 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/index.d.ts +49 -0
- package/dist/providers/tithely/index.js +1003 -0
- 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 +108 -171
- package/dist/registry.js +513 -925
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +100 -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 +356 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +136 -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 +52 -0
- package/dist/sent-secrets.js +474 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +406 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +166 -0
- package/dist/storage/keys.js +428 -0
- package/dist/storage/memory.js +7 -0
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +534 -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 +320 -54
- 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 +137 -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 +434 -0
- package/documentation/architecture.md +665 -230
- package/documentation/auth.md +962 -458
- package/documentation/code-mode.md +501 -312
- package/documentation/meta-tools.md +449 -200
- package/documentation/operator-tests.md +93 -0
- package/documentation/provider-migration-0.29.md +60 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +286 -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 +125 -35
- 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 +133 -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/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
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { ConnectorContext } from "../types.js";
|
|
2
|
+
/** The methods a hand-written provider surface actually uses. */
|
|
3
|
+
type GuardedMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
4
|
+
export interface GuardedRequest {
|
|
5
|
+
method: GuardedMethod;
|
|
6
|
+
/**
|
|
7
|
+
* Provider-relative path beginning with one `/`, resolved against the base
|
|
8
|
+
* URL's own path. Never an absolute URL, and never carrying `?` or `#` —
|
|
9
|
+
* query parameters go in `query`, where they are encoded rather than parsed.
|
|
10
|
+
*/
|
|
11
|
+
path: string;
|
|
12
|
+
/**
|
|
13
|
+
* Search parameters; `undefined` values are dropped, others stringified. An
|
|
14
|
+
* array repeats its key once per element, in order — the spelling of the key
|
|
15
|
+
* itself (`ids` or `ids[]`) is the provider's convention, not the helper's.
|
|
16
|
+
*/
|
|
17
|
+
query?: Record<string, string | number | boolean | readonly (string | number | boolean)[] | undefined>;
|
|
18
|
+
/**
|
|
19
|
+
* Per-request headers. `undefined` values are dropped, and a header that
|
|
20
|
+
* collides with an authentication header is refused rather than allowed to
|
|
21
|
+
* shadow it.
|
|
22
|
+
*/
|
|
23
|
+
headers?: Record<string, string | undefined>;
|
|
24
|
+
/** JSON request body. Serialized here, with the `Content-Type` to match. */
|
|
25
|
+
body?: unknown;
|
|
26
|
+
/**
|
|
27
|
+
* A pre-framed body — `FormData`, text, bytes. Mutually exclusive with
|
|
28
|
+
* `body`, and no `Content-Type` is supplied: multipart needs `fetch` to pick
|
|
29
|
+
* the boundary, and anything else knows its own type.
|
|
30
|
+
*/
|
|
31
|
+
rawBody?: BodyInit;
|
|
32
|
+
/**
|
|
33
|
+
* The mapper reads only a bounded prefix of a successful body, with
|
|
34
|
+
* `response.prefix()`, so a declared length past the ceiling is not refused
|
|
35
|
+
* up front: the ceiling still bounds what is read, never what is sent.
|
|
36
|
+
*/
|
|
37
|
+
prefixOnly?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* One downstream response, read under the connector's byte ceiling.
|
|
41
|
+
*
|
|
42
|
+
* The accessors are the whole read surface — there is no escape to the
|
|
43
|
+
* underlying `Response`, because an unbounded `.text()` on it is exactly the
|
|
44
|
+
* mistake this type exists to remove. `status`, `ok`, and `headers` are the
|
|
45
|
+
* raw facts a provider mapper needs to make its own classification.
|
|
46
|
+
*/
|
|
47
|
+
interface GuardedResponse {
|
|
48
|
+
readonly status: number;
|
|
49
|
+
readonly ok: boolean;
|
|
50
|
+
readonly headers: Headers;
|
|
51
|
+
/** The body as bytes. */
|
|
52
|
+
bytes(): Promise<Uint8Array>;
|
|
53
|
+
/** The body decoded as UTF-8. */
|
|
54
|
+
text(): Promise<string>;
|
|
55
|
+
/** The body parsed as JSON; `undefined` for an empty body, throws on junk. */
|
|
56
|
+
json(): Promise<unknown>;
|
|
57
|
+
/** Parse JSON while distinguishing malformed content from transport failure. */
|
|
58
|
+
jsonResult(): Promise<{
|
|
59
|
+
value: unknown;
|
|
60
|
+
} | {
|
|
61
|
+
parseError: unknown;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* At most `maxBytes` of the body (never past the ceiling), then the rest of
|
|
65
|
+
* the stream cancelled unread. Exactly: it retains at most `maxBytes` bytes,
|
|
66
|
+
* and consumes from the transport at most `maxBytes` plus one chunk — the
|
|
67
|
+
* chunk that crosses the bound, whose overflow is how it learns there was
|
|
68
|
+
* more (`truncated`). Chunks are whatever size the runtime delivers (64 KiB
|
|
69
|
+
* is common), so that one chunk is the whole of the overrun. Exclusive with
|
|
70
|
+
* the other readers: a body is read once.
|
|
71
|
+
*/
|
|
72
|
+
prefix(maxBytes: number): Promise<{
|
|
73
|
+
bytes: Uint8Array;
|
|
74
|
+
truncated: boolean;
|
|
75
|
+
}>;
|
|
76
|
+
}
|
|
77
|
+
/** Parse delta-seconds or an HTTP-date into a non-negative wait window. */
|
|
78
|
+
export declare function retryAfterMs(headers: Headers): number | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Turn one response into the provider's own result, or throw the provider's
|
|
81
|
+
* own typed failure. This is where status codes acquire meaning, and it is
|
|
82
|
+
* always the provider's code.
|
|
83
|
+
*/
|
|
84
|
+
type GuardedResponseMapper<T> = (response: GuardedResponse, ctx: ConnectorContext) => T | Promise<T>;
|
|
85
|
+
export interface GuardedFetchOptions {
|
|
86
|
+
/**
|
|
87
|
+
* Provider name as it appears in normalized failure prose — "Cloudflare",
|
|
88
|
+
* "Notion". Used for messages only; it is never parsed.
|
|
89
|
+
*/
|
|
90
|
+
provider: string;
|
|
91
|
+
/**
|
|
92
|
+
* Absolute base URL. Its origin and path prefix are the confinement: every
|
|
93
|
+
* request resolves beneath it or is refused.
|
|
94
|
+
*/
|
|
95
|
+
baseUrl: string;
|
|
96
|
+
/**
|
|
97
|
+
* Headers proving the caller's identity, resolved once per request. Throwing
|
|
98
|
+
* a typed `auth_required` from here is the documented way to report a
|
|
99
|
+
* missing credential — the helper has no opinion about what a credential is.
|
|
100
|
+
*/
|
|
101
|
+
authenticate: (ctx: ConnectorContext) => Record<string, string> | Promise<Record<string, string>>;
|
|
102
|
+
/**
|
|
103
|
+
* What actually sends a request, after confinement, framing, and
|
|
104
|
+
* authentication. Defaults to the global `fetch`. A connector that declares
|
|
105
|
+
* `oauth` supplies {@link oauthBearer}'s, so the calling owner's grant — not
|
|
106
|
+
* a header this helper assembled — authenticates the request. A rejection
|
|
107
|
+
* that is already a `ConnectorCallError` passes through unchanged; any other
|
|
108
|
+
* is normalized to `unavailable`, exactly as a global `fetch` failure is.
|
|
109
|
+
*/
|
|
110
|
+
fetch?: GuardedFetcher;
|
|
111
|
+
/** Constant headers sent with every request — `Accept`, an API version. */
|
|
112
|
+
headers?: Record<string, string>;
|
|
113
|
+
/**
|
|
114
|
+
* Ceiling on the response body, in bytes. Required, because a default here
|
|
115
|
+
* would be the helper guessing on a provider's behalf: what counts as an
|
|
116
|
+
* absurd response is a fact about the API, not about HTTP.
|
|
117
|
+
*/
|
|
118
|
+
maxResponseBytes: number;
|
|
119
|
+
}
|
|
120
|
+
/** Sends one already-guarded request on behalf of one call's context. */
|
|
121
|
+
type GuardedFetcher = (url: string, init: RequestInit, ctx: ConnectorContext) => Promise<Response>;
|
|
122
|
+
/**
|
|
123
|
+
* The transport half of a guarded connector that authenticates through its
|
|
124
|
+
* own `api()` OAuth grant: no header of the helper's making, and every request
|
|
125
|
+
* sent through `ctx.oauth.fetch`, which adds the calling owner's bearer token,
|
|
126
|
+
* refuses any origin outside the grant's `apiOrigins`, and spends one
|
|
127
|
+
* coordinated refresh and one replay on a 401 before answering
|
|
128
|
+
* `auth_required`. Spread it into the options:
|
|
129
|
+
* `guardedFetch({ provider, baseUrl, maxResponseBytes, ...oauthBearer(provider) })`.
|
|
130
|
+
*
|
|
131
|
+
* Confinement still happens here first, so a request reaches the grant only
|
|
132
|
+
* once it is provably beneath `baseUrl`; the grant's origin check is the second
|
|
133
|
+
* fence, not the only one.
|
|
134
|
+
*/
|
|
135
|
+
export declare function oauthBearer(provider: string): Pick<GuardedFetchOptions, "authenticate" | "fetch">;
|
|
136
|
+
/** Send one guarded request and map its response with provider knowledge. */
|
|
137
|
+
export type GuardedTransport = <T>(request: GuardedRequest, ctx: ConnectorContext, map: GuardedResponseMapper<T>) => Promise<T>;
|
|
138
|
+
/**
|
|
139
|
+
* Build the transport a connector sends every request through. `maxResponseBytes`
|
|
140
|
+
* is required rather than defaulted: what counts as an absurd response is a fact
|
|
141
|
+
* about the API, not about HTTP.
|
|
142
|
+
*/
|
|
143
|
+
export declare function guardedFetch(options: GuardedFetchOptions): GuardedTransport;
|
|
144
|
+
export {};
|
|
@@ -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");
|
|
@@ -29,12 +30,36 @@ export function retryAfterMs(headers) {
|
|
|
29
30
|
const date = Date.parse(raw);
|
|
30
31
|
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : undefined;
|
|
31
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The transport half of a guarded connector that authenticates through its
|
|
35
|
+
* own `api()` OAuth grant: no header of the helper's making, and every request
|
|
36
|
+
* sent through `ctx.oauth.fetch`, which adds the calling owner's bearer token,
|
|
37
|
+
* refuses any origin outside the grant's `apiOrigins`, and spends one
|
|
38
|
+
* coordinated refresh and one replay on a 401 before answering
|
|
39
|
+
* `auth_required`. Spread it into the options:
|
|
40
|
+
* `guardedFetch({ provider, baseUrl, maxResponseBytes, ...oauthBearer(provider) })`.
|
|
41
|
+
*
|
|
42
|
+
* Confinement still happens here first, so a request reaches the grant only
|
|
43
|
+
* once it is provably beneath `baseUrl`; the grant's origin check is the second
|
|
44
|
+
* fence, not the only one.
|
|
45
|
+
*/
|
|
46
|
+
export function oauthBearer(provider) {
|
|
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);
|
|
55
|
+
};
|
|
56
|
+
return { authenticate: () => ({}), fetch: send };
|
|
57
|
+
}
|
|
32
58
|
/** Statuses that instruct a client to re-send somewhere else. Never followed. */
|
|
33
59
|
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
34
60
|
/** Statuses the HTTP spec defines as carrying no body at all. */
|
|
35
61
|
const BODILESS_STATUSES = new Set([204, 205, 304]);
|
|
36
62
|
const decoder = new TextDecoder();
|
|
37
|
-
const encoder = new TextEncoder();
|
|
38
63
|
/**
|
|
39
64
|
* A base URL is deployment configuration, so a bad one is a structural mistake
|
|
40
65
|
* and throws where it is written rather than on the first call that uses it.
|
|
@@ -47,9 +72,7 @@ function confinementBase(provider, baseUrl) {
|
|
|
47
72
|
catch {
|
|
48
73
|
throw new Error(`${provider} baseUrl must be an absolute URL: ${baseUrl}`);
|
|
49
74
|
}
|
|
50
|
-
const loopback = base.hostname === "localhost" ||
|
|
51
|
-
base.hostname === "127.0.0.1" ||
|
|
52
|
-
base.hostname === "[::1]";
|
|
75
|
+
const loopback = base.hostname === "localhost" || base.hostname === "127.0.0.1" || base.hostname === "[::1]";
|
|
53
76
|
if (base.protocol !== "https:" && !(base.protocol === "http:" && loopback)) {
|
|
54
77
|
throw new Error(`${provider} baseUrl must be https (http is allowed only for a loopback proxy or test double): ${baseUrl}`);
|
|
55
78
|
}
|
|
@@ -84,13 +107,18 @@ function confinedUrl(provider, base, basePath, request) {
|
|
|
84
107
|
catch {
|
|
85
108
|
throw new ConnectorCallError("invalid_args", `A ${provider} request path did not resolve to a valid URL.`);
|
|
86
109
|
}
|
|
87
|
-
if (url.origin !== base.origin ||
|
|
88
|
-
!`${url.pathname}/`.startsWith(`${basePath}/`)) {
|
|
110
|
+
if (url.origin !== base.origin || !`${url.pathname}/`.startsWith(`${basePath}/`)) {
|
|
89
111
|
throw new ConnectorCallError("invalid_args", `A ${provider} request path escaped the connector's base URL.`);
|
|
90
112
|
}
|
|
91
113
|
for (const [key, value] of Object.entries(request.query ?? {})) {
|
|
92
114
|
if (value === undefined)
|
|
93
115
|
continue;
|
|
116
|
+
if (Array.isArray(value)) {
|
|
117
|
+
url.searchParams.delete(key);
|
|
118
|
+
for (const item of value)
|
|
119
|
+
url.searchParams.append(key, String(item));
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
94
122
|
url.searchParams.set(key, String(value));
|
|
95
123
|
}
|
|
96
124
|
return url;
|
|
@@ -101,9 +129,7 @@ function oversized(provider, limit, detail) {
|
|
|
101
129
|
/** The response body as a `ReadableStream`, or null when there is none to read. */
|
|
102
130
|
function readableBody(response) {
|
|
103
131
|
const body = response.body;
|
|
104
|
-
return body && typeof body.getReader === "function"
|
|
105
|
-
? body
|
|
106
|
-
: null;
|
|
132
|
+
return body && typeof body.getReader === "function" ? body : null;
|
|
107
133
|
}
|
|
108
134
|
/** Read a stream to the ceiling and stop there — never past it. */
|
|
109
135
|
async function drain(provider, stream, limit) {
|
|
@@ -139,9 +165,9 @@ async function drain(provider, stream, limit) {
|
|
|
139
165
|
/**
|
|
140
166
|
* Every read is capped at `maxResponseBytes`: a streaming body is abandoned at
|
|
141
167
|
* the ceiling rather than buffered past it, and where a runtime gives no body
|
|
142
|
-
* stream the
|
|
143
|
-
*
|
|
144
|
-
*
|
|
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.
|
|
145
171
|
*
|
|
146
172
|
* A mapper must re-throw `ConnectorCallError` and swallow only what it
|
|
147
173
|
* recognizes: a bare `catch` around `response.json()` eats this refusal along
|
|
@@ -159,9 +185,22 @@ function boundedResponse(provider, response, limit) {
|
|
|
159
185
|
text: async () => "",
|
|
160
186
|
json: emptyJson,
|
|
161
187
|
jsonResult: () => jsonResult(emptyJson),
|
|
188
|
+
prefix: async () => ({ bytes: new Uint8Array(), truncated: false }),
|
|
162
189
|
};
|
|
163
190
|
}
|
|
164
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
|
+
};
|
|
165
204
|
let read;
|
|
166
205
|
const bytes = () => {
|
|
167
206
|
read ??= stream
|
|
@@ -176,14 +215,9 @@ function boundedResponse(provider, response, limit) {
|
|
|
176
215
|
};
|
|
177
216
|
let readText;
|
|
178
217
|
const text = () => {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const size = encoder.encode(body).length;
|
|
183
|
-
if (size > limit)
|
|
184
|
-
throw oversized(provider, limit, `${size} bytes`);
|
|
185
|
-
return body;
|
|
186
|
-
});
|
|
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));
|
|
187
221
|
return readText;
|
|
188
222
|
};
|
|
189
223
|
const json = async () => {
|
|
@@ -198,8 +232,51 @@ function boundedResponse(provider, response, limit) {
|
|
|
198
232
|
text,
|
|
199
233
|
json,
|
|
200
234
|
jsonResult: () => jsonResult(json),
|
|
235
|
+
prefix,
|
|
201
236
|
};
|
|
202
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
|
+
}
|
|
203
280
|
async function jsonResult(read) {
|
|
204
281
|
try {
|
|
205
282
|
return { value: await read() };
|
|
@@ -224,7 +301,8 @@ export function guardedFetch(options) {
|
|
|
224
301
|
// "" for a root base, so joining never doubles the separator.
|
|
225
302
|
const basePath = base.pathname.replace(/\/+$/, "");
|
|
226
303
|
const constantHeaders = { ...options.headers };
|
|
227
|
-
|
|
304
|
+
const send = options.fetch ?? ((url, init) => fetch(url, init));
|
|
305
|
+
return async function guarded(request, ctx, map) {
|
|
228
306
|
if (request.body !== undefined && request.rawBody !== undefined) {
|
|
229
307
|
throw new Error(`A ${provider} request cannot carry both a JSON body and a raw body.`);
|
|
230
308
|
}
|
|
@@ -246,10 +324,11 @@ export function guardedFetch(options) {
|
|
|
246
324
|
throw new ConnectorCallError("invalid_args", `A ${provider} request may not set the ${shadow} header; authentication is connector-owned.`);
|
|
247
325
|
}
|
|
248
326
|
headers[name] = value;
|
|
327
|
+
sentSecretsFor(ctx).header(value);
|
|
249
328
|
}
|
|
250
329
|
let response;
|
|
251
330
|
try {
|
|
252
|
-
|
|
331
|
+
const init = {
|
|
253
332
|
method: request.method,
|
|
254
333
|
headers,
|
|
255
334
|
...(request.body !== undefined
|
|
@@ -262,19 +341,23 @@ export function guardedFetch(options) {
|
|
|
262
341
|
// never one. Refused below rather than followed.
|
|
263
342
|
redirect: "manual",
|
|
264
343
|
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
265
|
-
}
|
|
344
|
+
};
|
|
345
|
+
sentSecretsFor(ctx).request(url, init);
|
|
346
|
+
response = await send(url.toString(), init, ctx);
|
|
266
347
|
}
|
|
267
348
|
catch (cause) {
|
|
268
349
|
if (cause instanceof ConnectorCallError)
|
|
269
350
|
throw cause;
|
|
270
|
-
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);
|
|
271
352
|
}
|
|
272
353
|
if (REDIRECT_STATUSES.has(response.status)) {
|
|
273
354
|
await response.body?.cancel().catch(() => { });
|
|
274
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 });
|
|
275
356
|
}
|
|
276
357
|
const declared = Number(response.headers.get("content-length"));
|
|
277
|
-
|
|
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)) {
|
|
278
361
|
await response.body?.cancel().catch(() => { });
|
|
279
362
|
throw oversized(provider, limit, `a declared ${declared} bytes`);
|
|
280
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).redact(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
|