@zackbart/connecta 0.28.1 → 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 +788 -5
- package/README.md +62 -42
- 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 +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 +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 +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 +84 -35
- 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 +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 +24 -256
- 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-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.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/{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 +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 +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 +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 +664 -231
- package/documentation/auth.md +936 -546
- 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 +120 -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 +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/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
|
@@ -93,6 +93,8 @@ export interface ApiOAuthAccess {
|
|
|
93
93
|
}
|
|
94
94
|
/** The context an `api()` handler receives. */
|
|
95
95
|
export interface ApiHandlerContext extends ConnectorContext {
|
|
96
|
+
/** Fetch with final outgoing credential headers and query values registered. */
|
|
97
|
+
fetch: typeof fetch;
|
|
96
98
|
/** Present exactly when the connector declares `oauth`. */
|
|
97
99
|
oauth?: ApiOAuthAccess;
|
|
98
100
|
}
|
|
@@ -104,10 +106,10 @@ export interface ApiOptions {
|
|
|
104
106
|
authScope?: "shared" | "personal";
|
|
105
107
|
/**
|
|
106
108
|
* Max inline result size (bytes) for this connector's tools before
|
|
107
|
-
* call_tool truncates and stashes the full text for
|
|
109
|
+
* call_tool truncates and stashes the full text for connecta.result
|
|
108
110
|
* paging. Overrides the deployment's `calls.maxResultBytes`; omit to inherit
|
|
109
|
-
* it. Must be a whole number of bytes >= 1; anything else
|
|
110
|
-
*
|
|
111
|
+
* it. Must be a whole number of bytes >= 1; anything else refuses to
|
|
112
|
+
* construct.
|
|
111
113
|
*/
|
|
112
114
|
maxResultBytes?: number;
|
|
113
115
|
/** Optional per-runtime downstream call-admission policy. */
|
|
@@ -141,6 +143,54 @@ export interface ApiOptions {
|
|
|
141
143
|
validateArgs?: boolean;
|
|
142
144
|
tools: ApiTool[];
|
|
143
145
|
}
|
|
146
|
+
/** The closed options api() accepts; see `assertKnownOptions`. */
|
|
147
|
+
export declare const API_OPTIONS: import("../config-schema.js").Field<unknown, unknown, true> & {
|
|
148
|
+
readonly shape: {
|
|
149
|
+
callAdmission: import("../config-schema.js").Field<unknown, unknown, true> & {
|
|
150
|
+
readonly shape: {
|
|
151
|
+
rules: import("../config-schema.js").Field<unknown[], readonly unknown[] | undefined, true>;
|
|
152
|
+
maxPartitions: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
usageGuide: import("../config-schema.js").Field<unknown, unknown, true> & {
|
|
156
|
+
readonly shape: {
|
|
157
|
+
readonly required: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
158
|
+
readonly content: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
159
|
+
readonly summary: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
credential: import("../config-schema.js").Field<unknown, unknown, true> & {
|
|
163
|
+
readonly shape: {
|
|
164
|
+
fields: import("../config-schema.js").Field<unknown[], readonly unknown[] | undefined, true>;
|
|
165
|
+
label: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
166
|
+
description: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
167
|
+
placeholder: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
oauth: import("../config-schema.js").Field<unknown, unknown, true> & {
|
|
171
|
+
readonly shape: {
|
|
172
|
+
authorizationParams: import("../config-schema.js").Field<Record<string, string>, Readonly<Record<string, string>> | undefined, true>;
|
|
173
|
+
tokenRequestHeaders: import("../config-schema.js").Field<Record<string, string>, Readonly<Record<string, string>> | undefined, true>;
|
|
174
|
+
scope: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
175
|
+
authorizationEndpoint: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
176
|
+
tokenEndpoint: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
177
|
+
apiOrigins: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
178
|
+
tokenEndpointAuthMethod: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
179
|
+
pkce: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
180
|
+
clientId: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
181
|
+
clientSecret: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
tools: import("../config-schema.js").Field<unknown[], readonly unknown[] | undefined, true>;
|
|
185
|
+
title: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
186
|
+
description: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
187
|
+
testCredential: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
188
|
+
testCredentials: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
189
|
+
authScope: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
190
|
+
maxResultBytes: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
191
|
+
validateArgs: import("../config-schema.js").Field<unknown, unknown, true>;
|
|
192
|
+
};
|
|
193
|
+
};
|
|
144
194
|
/**
|
|
145
195
|
* The OAuth half of an `api()` connector: the hooks the callback route,
|
|
146
196
|
* operator UI, and `authorize_connector` key on, and the handler accessor.
|
|
@@ -154,15 +204,17 @@ export interface ApiOAuthHooks {
|
|
|
154
204
|
}): Promise<ConnectorStatus>;
|
|
155
205
|
disconnectAuth(ctx: ConnectorContext): Promise<void>;
|
|
156
206
|
verifyState(state: string | null, ctx: ConnectorContext): Promise<boolean>;
|
|
157
|
-
|
|
207
|
+
verifyCallbackIssuer(issuer: string | null, ctx: ConnectorContext): Promise<boolean>;
|
|
208
|
+
consumeAuthError(ctx: ConnectorContext): Promise<void>;
|
|
209
|
+
/** Requires callbackParams with a nonempty state, including without PKCE. */
|
|
210
|
+
finishAuth(code: string, ctx: ConnectorContext, callbackParams: URLSearchParams): Promise<void>;
|
|
158
211
|
/** The handler-facing accessor for one call's context. */
|
|
159
212
|
access(ctx: ConnectorContext): ApiOAuthAccess;
|
|
160
213
|
}
|
|
161
214
|
/**
|
|
162
215
|
* A static connector; every tool passes {@link checkToolContract} first.
|
|
163
216
|
*
|
|
164
|
-
* The builder behind `api()`, without the downstream OAuth machinery:
|
|
165
|
-
*
|
|
166
|
-
* connector) imports this and pays nothing for a grant it cannot declare.
|
|
217
|
+
* The builder behind `api()`, without the downstream OAuth machinery: credential-free
|
|
218
|
+
* providers import this without paying for a grant they cannot declare.
|
|
167
219
|
*/
|
|
168
220
|
export declare function apiConnector(id: string, opts: ApiOptions, oauth?: ApiOAuthHooks): Connector;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import { ConnectorCallError, networkErrorCode, unavailableCallError, } from "../errors.js";
|
|
2
|
-
import {
|
|
1
|
+
import { ConnectorCallError, networkErrorCode, unavailableCallError, WithheldTextError } from "../errors.js";
|
|
2
|
+
import { attachFailureFacts, carryFailureFacts, errorLabel } from "../operator-record.js";
|
|
3
|
+
import { compileValidator, validateCatalogToolInput } from "../validate.js";
|
|
4
|
+
import { array, assertKnownOptions, instance, keys, optionsOf, strings } from "../config-schema.js";
|
|
5
|
+
import { describedEndpoint, describedOrigin, describedTools } from "../described.js";
|
|
6
|
+
import { CALL_ADMISSION, CREDENTIAL, USAGE_GUIDE } from "./option-shapes.js";
|
|
7
|
+
import { assertStaticToolNames } from "../tool-name.js";
|
|
8
|
+
import { registerInvocationAuth } from "../invocation-auth.js";
|
|
9
|
+
import { redactCatalog, redactSentSecrets, sentSecretsFetch, trackCredentialReads } from "../sent-secrets.js";
|
|
3
10
|
export function defined(value) {
|
|
4
11
|
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
5
12
|
}
|
|
@@ -25,17 +32,65 @@ function checkToolContract(id, tool) {
|
|
|
25
32
|
"call_destructive_tool. Connecta never infers the classification " +
|
|
26
33
|
"from a tool name, description, schema, or other annotations.");
|
|
27
34
|
}
|
|
35
|
+
if (typeof tool.handler !== "function") {
|
|
36
|
+
throw new Error(`api() tool "${address}" needs a handler function.`);
|
|
37
|
+
}
|
|
28
38
|
if (tool.inputSchema)
|
|
29
39
|
compileValidator(tool.inputSchema, { address });
|
|
30
40
|
}
|
|
41
|
+
/** The closed options api() accepts; see `assertKnownOptions`. */
|
|
42
|
+
export const API_OPTIONS = optionsOf()({
|
|
43
|
+
...keys("title", "description", "authScope", "maxResultBytes", "testCredential", "testCredentials", "validateArgs"),
|
|
44
|
+
callAdmission: CALL_ADMISSION,
|
|
45
|
+
usageGuide: USAGE_GUIDE,
|
|
46
|
+
credential: CREDENTIAL,
|
|
47
|
+
oauth: optionsOf()({
|
|
48
|
+
...keys("authorizationEndpoint", "tokenEndpoint", "clientId", "clientSecret", "tokenEndpointAuthMethod", "scope", "pkce", "apiOrigins"),
|
|
49
|
+
authorizationParams: strings(),
|
|
50
|
+
tokenRequestHeaders: strings(),
|
|
51
|
+
}),
|
|
52
|
+
// A tool's annotations stay open: MCP lets a tool carry hints Connecta
|
|
53
|
+
// does not interpret, and its schemas are JSON Schema, not options. A tool
|
|
54
|
+
// carries its handler, so it is checked in place and kept, never copied.
|
|
55
|
+
tools: array(instance(optionsOf()(keys("name", "description", "inputSchema", "outputSchema", "annotations", "handler")))),
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* How an `api()` connector authenticates, as names and public endpoints. The
|
|
59
|
+
* client id and secret, extra parameter values, and token-request header
|
|
60
|
+
* values never leave the options.
|
|
61
|
+
*/
|
|
62
|
+
function describedApiAuth(opts) {
|
|
63
|
+
const oauth = opts.oauth;
|
|
64
|
+
if (!oauth)
|
|
65
|
+
return { mode: opts.credential ? "credential" : "none" };
|
|
66
|
+
const confidential = typeof oauth.clientSecret === "string" && oauth.clientSecret !== "";
|
|
67
|
+
const authorizationEndpoint = describedEndpoint(oauth.authorizationEndpoint);
|
|
68
|
+
const tokenEndpoint = describedEndpoint(oauth.tokenEndpoint);
|
|
69
|
+
const apiOrigins = Array.isArray(oauth.apiOrigins)
|
|
70
|
+
? oauth.apiOrigins.flatMap((origin) => describedOrigin(origin) ?? [])
|
|
71
|
+
: [];
|
|
72
|
+
return {
|
|
73
|
+
mode: "oauth",
|
|
74
|
+
...(authorizationEndpoint ? { authorizationEndpoint } : {}),
|
|
75
|
+
...(tokenEndpoint ? { tokenEndpoint } : {}),
|
|
76
|
+
apiOrigins,
|
|
77
|
+
tokenEndpointAuthMethod: oauth.tokenEndpointAuthMethod ?? (confidential ? "client_secret_basic" : "none"),
|
|
78
|
+
confidentialClient: confidential,
|
|
79
|
+
pkce: oauth.pkce ?? true,
|
|
80
|
+
...(oauth.scope !== undefined ? { scope: oauth.scope } : {}),
|
|
81
|
+
...(oauth.authorizationParams ? { authorizationParamNames: Object.keys(oauth.authorizationParams) } : {}),
|
|
82
|
+
...(oauth.tokenRequestHeaders ? { tokenRequestHeaderNames: Object.keys(oauth.tokenRequestHeaders) } : {}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
31
85
|
/**
|
|
32
86
|
* A static connector; every tool passes {@link checkToolContract} first.
|
|
33
87
|
*
|
|
34
|
-
* The builder behind `api()`, without the downstream OAuth machinery:
|
|
35
|
-
*
|
|
36
|
-
* connector) imports this and pays nothing for a grant it cannot declare.
|
|
88
|
+
* The builder behind `api()`, without the downstream OAuth machinery: credential-free
|
|
89
|
+
* providers import this without paying for a grant they cannot declare.
|
|
37
90
|
*/
|
|
38
91
|
export function apiConnector(id, opts, oauth) {
|
|
92
|
+
opts = assertKnownOptions(opts, `api(${JSON.stringify(id)})`, API_OPTIONS);
|
|
93
|
+
assertStaticToolNames(opts.tools, `api(${JSON.stringify(id)}).tools`);
|
|
39
94
|
if (opts.oauth !== undefined && oauth === undefined) {
|
|
40
95
|
throw new Error(`api() connector "${id}" declares oauth but was built without its grant; construct it with api().`);
|
|
41
96
|
}
|
|
@@ -58,10 +113,12 @@ export function apiConnector(id, opts, oauth) {
|
|
|
58
113
|
}));
|
|
59
114
|
const byName = new Map(opts.tools.map((t) => [t.name, t]));
|
|
60
115
|
const validateArgs = opts.validateArgs ?? true;
|
|
61
|
-
|
|
116
|
+
const auth = describedApiAuth(opts);
|
|
117
|
+
const connector = {
|
|
62
118
|
id,
|
|
63
119
|
...defined({ title: opts.title }),
|
|
64
120
|
kind: "api",
|
|
121
|
+
describe: () => ({ source: { kind: "api" }, auth, tools: describedTools(defs) }),
|
|
65
122
|
...defined({
|
|
66
123
|
description: opts.description,
|
|
67
124
|
authScope: opts.authScope,
|
|
@@ -78,28 +135,31 @@ export function apiConnector(id, opts, oauth) {
|
|
|
78
135
|
startAuth: oauth.startAuth,
|
|
79
136
|
disconnectAuth: oauth.disconnectAuth,
|
|
80
137
|
verifyState: oauth.verifyState,
|
|
138
|
+
verifyCallbackIssuer: oauth.verifyCallbackIssuer,
|
|
139
|
+
consumeAuthError: oauth.consumeAuthError,
|
|
81
140
|
finishAuth: oauth.finishAuth,
|
|
82
141
|
}
|
|
83
142
|
: {}),
|
|
84
143
|
staticTools: defs,
|
|
85
|
-
async listTools() {
|
|
86
|
-
return defs;
|
|
144
|
+
async listTools(ctx) {
|
|
145
|
+
return redactCatalog(ctx, defs);
|
|
87
146
|
},
|
|
88
147
|
async callTool(name, args, ctx) {
|
|
148
|
+
trackCredentialReads(ctx);
|
|
89
149
|
const tool = byName.get(name);
|
|
90
150
|
if (!tool) {
|
|
91
151
|
throw new Error(`Unknown tool "${name}" on connector "${id}"`);
|
|
92
152
|
}
|
|
93
153
|
const input = args ?? {};
|
|
94
154
|
if (validateArgs && tool.inputSchema) {
|
|
95
|
-
const invalid =
|
|
155
|
+
const invalid = validateCatalogToolInput(tool.inputSchema, input, {
|
|
96
156
|
address: `${id}.${name}`,
|
|
97
157
|
logger: ctx.logger,
|
|
98
158
|
// Always: the schema compiled at construction, so anything that
|
|
99
159
|
// fails here is a schema that cannot be enforced, and a surface we
|
|
100
160
|
// wrote ourselves does not get to admit unvalidated input quietly.
|
|
101
161
|
failClosed: true,
|
|
102
|
-
});
|
|
162
|
+
}, { connector: id, tool });
|
|
103
163
|
if (invalid)
|
|
104
164
|
throw invalid;
|
|
105
165
|
}
|
|
@@ -107,21 +167,48 @@ export function apiConnector(id, opts, oauth) {
|
|
|
107
167
|
// its first await never sits handler-less for the thenable-adoption
|
|
108
168
|
// microtask — workerd and vitest both report that gap as an unhandled
|
|
109
169
|
// rejection even though the caller catches the failure.
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
const handlerCtx =
|
|
113
|
-
|
|
114
|
-
: ctx
|
|
170
|
+
// Preserve the registry's context identity: private caller/provenance
|
|
171
|
+
// markers belong to it, as does the grant its accessor reaches.
|
|
172
|
+
const handlerCtx = Object.assign(ctx, {
|
|
173
|
+
fetch: sentSecretsFetch(ctx),
|
|
174
|
+
...(oauth ? { oauth: oauth.access(ctx) } : {}),
|
|
175
|
+
});
|
|
115
176
|
try {
|
|
116
|
-
return await tool.handler(input, handlerCtx);
|
|
177
|
+
return redactSentSecrets(ctx, await tool.handler(input, handlerCtx));
|
|
117
178
|
}
|
|
118
179
|
catch (error) {
|
|
180
|
+
// The request's own abort reason, by identity, is the caller's.
|
|
181
|
+
if (ctx.signal?.aborted === true && error === ctx.signal.reason)
|
|
182
|
+
throw error;
|
|
119
183
|
// A handler owns its destinations. ctx.baseUrl is Connecta's inbound
|
|
120
184
|
// URL, so it must never masquerade as the failed downstream host.
|
|
121
|
-
if (error instanceof ConnectorCallError
|
|
122
|
-
throw error;
|
|
123
|
-
|
|
185
|
+
if (error instanceof ConnectorCallError)
|
|
186
|
+
throw redactSentSecrets(ctx, error);
|
|
187
|
+
if (!networkErrorCode(error)) {
|
|
188
|
+
// Anything else is a runtime's, parser's, or stream's account of
|
|
189
|
+
// what the handler read (a JSON parser quotes the reply it choked
|
|
190
|
+
// on; a body stream rejects with whatever the runtime says), so it
|
|
191
|
+
// is told in connecta's words, classified as the original would
|
|
192
|
+
// have been, and rebuilt without its cause or nested errors. A
|
|
193
|
+
// handler that means the agent to read its words throws a
|
|
194
|
+
// ConnectorCallError. No host is named: connecta does not know
|
|
195
|
+
// which destination the handler read.
|
|
196
|
+
const kind = errorLabel(error);
|
|
197
|
+
throw attachFailureFacts(carryFailureFacts(error, new WithheldTextError(`Connector "${id}" tool "${name}" handler failed` +
|
|
198
|
+
`${kind ? ` (${kind})` : ""}. Its text is withheld because it ` +
|
|
199
|
+
"can quote what the downstream sent.", error)), { step: "handler" });
|
|
200
|
+
}
|
|
201
|
+
throw unavailableCallError(error, undefined, undefined, ctx.signal);
|
|
124
202
|
}
|
|
125
203
|
},
|
|
126
204
|
};
|
|
205
|
+
if (oauth)
|
|
206
|
+
registerInvocationAuth(connector, async (ctx) => {
|
|
207
|
+
// The built-in static OAuth status only reads the stored grant. It never
|
|
208
|
+
// refreshes, invokes a handler, or sends a downstream request.
|
|
209
|
+
if ((await oauth.status(ctx)).state === "auth_required") {
|
|
210
|
+
throw new ConnectorCallError("downstream_oauth_required", `Connector "${id}" requires authorization. Call authorize_connector.`);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
return connector;
|
|
127
214
|
}
|
package/dist/connectors/api.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { staticOAuth } from "../auth/static-oauth.js";
|
|
2
|
-
import {
|
|
2
|
+
import { assertKnownOptions } from "../config-schema.js";
|
|
3
|
+
import { API_OPTIONS, apiConnector } from "./api-connector.js";
|
|
3
4
|
/**
|
|
4
5
|
* A hand-written connector: static tools, each with a handler. With `oauth`,
|
|
5
6
|
* its handlers reach the API through the calling owner's downstream grant
|
|
6
7
|
* (`ctx.oauth`), managed exactly like a `remoteMcp()` grant.
|
|
7
8
|
*/
|
|
8
9
|
export function api(id, opts) {
|
|
10
|
+
// Read the options once, as plain data, before choosing a grant from them.
|
|
11
|
+
opts = assertKnownOptions(opts, `api(${JSON.stringify(id)})`, API_OPTIONS);
|
|
9
12
|
if (opts.oauth !== undefined && opts.credential !== undefined) {
|
|
10
13
|
throw new Error(`api() connector "${id}" declares both oauth and credential. Declare ` +
|
|
11
14
|
"one: auth_required must name a single recovery. A provider offering " +
|
|
@@ -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 {};
|