@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,220 @@
|
|
|
1
|
+
import type { Connector, ConnectorCallAdmissionPolicy, ConnectorCredentialConfig, ConnectorCredentialValues, ConnectorContext, ConnectorStatus, ConnectorUsageGuide, CredentialTestResult, JsonSchema, ToolAnnotations } from "../types.js";
|
|
2
|
+
export declare function defined<T extends object>(value: T): {
|
|
3
|
+
[K in keyof T]?: Exclude<T[K], undefined>;
|
|
4
|
+
};
|
|
5
|
+
export interface ApiTool {
|
|
6
|
+
name: string;
|
|
7
|
+
/**
|
|
8
|
+
* Required, non-empty. Discovery has nothing else to go on: a nameless
|
|
9
|
+
* capability costs the agent a guess, and a guess costs a wrong call.
|
|
10
|
+
*/
|
|
11
|
+
description: string;
|
|
12
|
+
/**
|
|
13
|
+
* A plain JSON Schema object describing the tool input. Optional, but what
|
|
14
|
+
* you supply must be a schema the validator can compile — `api()` refuses to
|
|
15
|
+
* construct otherwise.
|
|
16
|
+
*/
|
|
17
|
+
inputSchema?: JsonSchema;
|
|
18
|
+
/** A plain JSON Schema object describing the tool's structured output. */
|
|
19
|
+
outputSchema?: JsonSchema;
|
|
20
|
+
/**
|
|
21
|
+
* Standard MCP-style behavior hints, with an explicit `readOnlyHint`
|
|
22
|
+
* required: `true` declares a read and admits the tool to call_tool and
|
|
23
|
+
* execute_code, `false` declares work that must cross
|
|
24
|
+
* `call_destructive_tool` where a host can ask a human. Connecta never
|
|
25
|
+
* infers the classification from a name, a description, a schema, or the
|
|
26
|
+
* other annotations.
|
|
27
|
+
*/
|
|
28
|
+
annotations: ToolAnnotations & {
|
|
29
|
+
readOnlyHint: boolean;
|
|
30
|
+
};
|
|
31
|
+
handler: (args: any, ctx: ApiHandlerContext) => Promise<unknown> | unknown;
|
|
32
|
+
}
|
|
33
|
+
/** How a pre-registered client authenticates at the token endpoint. */
|
|
34
|
+
export type ApiOAuthClientAuthentication = "client_secret_basic" | "client_secret_post" | "none";
|
|
35
|
+
/**
|
|
36
|
+
* Downstream OAuth 2.0 authorization code grant for a hand-written API whose
|
|
37
|
+
* provider issues clients by hand and publishes no discovery metadata. Every
|
|
38
|
+
* endpoint is declared here; nothing is learned from the network.
|
|
39
|
+
*/
|
|
40
|
+
export interface ApiOAuthConfig {
|
|
41
|
+
/** Where the operator's browser is sent for consent. Absolute https URL. */
|
|
42
|
+
authorizationEndpoint: string;
|
|
43
|
+
/** Where codes are exchanged and refresh tokens redeemed. Absolute https URL. */
|
|
44
|
+
tokenEndpoint: string;
|
|
45
|
+
/** The client id the provider issued for this deployment. */
|
|
46
|
+
clientId: string;
|
|
47
|
+
/**
|
|
48
|
+
* The client secret, from deployment configuration (an environment variable
|
|
49
|
+
* or Worker secret), never a literal in source. Omit for a public client.
|
|
50
|
+
* It is never written to storage.
|
|
51
|
+
*/
|
|
52
|
+
clientSecret?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Token-endpoint client authentication. Defaults to `client_secret_basic`
|
|
55
|
+
* with a secret and `none` without one.
|
|
56
|
+
*/
|
|
57
|
+
tokenEndpointAuthMethod?: ApiOAuthClientAuthentication;
|
|
58
|
+
/** Space-separated scopes requested at consent. */
|
|
59
|
+
scope?: string;
|
|
60
|
+
/** PKCE with S256 (default true). Disable only for a server that rejects it. */
|
|
61
|
+
pkce?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Extra authorization-request parameters, such as a provider's
|
|
64
|
+
* `access_type`. The grant's own parameters cannot be overridden.
|
|
65
|
+
*/
|
|
66
|
+
authorizationParams?: Record<string, string>;
|
|
67
|
+
/**
|
|
68
|
+
* Headers added to every token-endpoint request — the code exchange and
|
|
69
|
+
* each refresh — for a provider whose token endpoint demands its own
|
|
70
|
+
* framing, such as Church Community Builder's
|
|
71
|
+
* `Accept: application/vnd.ccbchurch.v2+json`. They replace the grant's
|
|
72
|
+
* defaults of the same name; `Authorization`, `Content-Type`,
|
|
73
|
+
* `Content-Length`, `Cookie`, and `Host` stay the grant's.
|
|
74
|
+
*/
|
|
75
|
+
tokenRequestHeaders?: Record<string, string>;
|
|
76
|
+
/**
|
|
77
|
+
* The exact origins `ctx.oauth.fetch` sends the access token to, such as
|
|
78
|
+
* `"https://api.ccbchurch.com"`. A request anywhere else is refused before
|
|
79
|
+
* it is sent, so an untrusted URL in a response cannot carry the token off.
|
|
80
|
+
*/
|
|
81
|
+
apiOrigins: readonly string[];
|
|
82
|
+
}
|
|
83
|
+
/** A handler's use of its connector's OAuth grant, never the grant itself. */
|
|
84
|
+
export interface ApiOAuthAccess {
|
|
85
|
+
/**
|
|
86
|
+
* `fetch` with the calling owner's access token as `Authorization: Bearer`.
|
|
87
|
+
* Only to `apiOrigins`; redirects are returned unfollowed. A 401 earns one
|
|
88
|
+
* coordinated refresh and one replay; a second 401, a refused refresh, or
|
|
89
|
+
* no grant at all throws `auth_required`, and an authorization-server
|
|
90
|
+
* outage throws a retryable `unavailable`.
|
|
91
|
+
*/
|
|
92
|
+
fetch(input: string | URL, init?: RequestInit): Promise<Response>;
|
|
93
|
+
}
|
|
94
|
+
/** The context an `api()` handler receives. */
|
|
95
|
+
export interface ApiHandlerContext extends ConnectorContext {
|
|
96
|
+
/** Fetch with final outgoing credential headers and query values registered. */
|
|
97
|
+
fetch: typeof fetch;
|
|
98
|
+
/** Present exactly when the connector declares `oauth`. */
|
|
99
|
+
oauth?: ApiOAuthAccess;
|
|
100
|
+
}
|
|
101
|
+
export interface ApiOptions {
|
|
102
|
+
/** Human-readable display name; the connector id remains the address prefix. */
|
|
103
|
+
title?: string;
|
|
104
|
+
description?: string;
|
|
105
|
+
/** Downstream auth ownership. Defaults to one shared deployment grant. */
|
|
106
|
+
authScope?: "shared" | "personal";
|
|
107
|
+
/**
|
|
108
|
+
* Max inline result size (bytes) for this connector's tools before
|
|
109
|
+
* call_tool truncates and stashes the full text for connecta.result
|
|
110
|
+
* paging. Overrides the deployment's `calls.maxResultBytes`; omit to inherit
|
|
111
|
+
* it. Must be a whole number of bytes >= 1; anything else refuses to
|
|
112
|
+
* construct.
|
|
113
|
+
*/
|
|
114
|
+
maxResultBytes?: number;
|
|
115
|
+
/** Optional per-runtime downstream call-admission policy. */
|
|
116
|
+
callAdmission?: ConnectorCallAdmissionPolicy;
|
|
117
|
+
/**
|
|
118
|
+
* Optional agent-facing usage guide served by `skills` as
|
|
119
|
+
* `connector:<id>`. A string is markdown; the structured form adds bounded
|
|
120
|
+
* discovery metadata. See `Connector.usageGuide`.
|
|
121
|
+
*/
|
|
122
|
+
usageGuide?: string | ConnectorUsageGuide;
|
|
123
|
+
/** Optional operator-managed credential exposed through ctx.credential and the connection in the operator UI. */
|
|
124
|
+
credential?: ConnectorCredentialConfig;
|
|
125
|
+
/**
|
|
126
|
+
* Optional downstream OAuth, exposed to handlers as `ctx.oauth` and managed
|
|
127
|
+
* like a `remoteMcp()` grant: Connect in the operator UI, or
|
|
128
|
+
* `authorize_connector`. Exclusive with `credential` — one connector, one
|
|
129
|
+
* way to authenticate, so `auth_required` has one recovery.
|
|
130
|
+
*/
|
|
131
|
+
oauth?: ApiOAuthConfig;
|
|
132
|
+
/** Optional validation behind the connection's Test action in the operator UI. */
|
|
133
|
+
testCredential?: (value: string, ctx: ConnectorContext) => Promise<CredentialTestResult>;
|
|
134
|
+
/** Optional validation for named multi-field credentials. */
|
|
135
|
+
testCredentials?: (values: ConnectorCredentialValues, ctx: ConnectorContext) => Promise<CredentialTestResult>;
|
|
136
|
+
/**
|
|
137
|
+
* Validate call arguments against each tool's `inputSchema` before invoking
|
|
138
|
+
* the handler (default true). Mismatches fail with a non-retryable
|
|
139
|
+
* `invalid_args` ConnectorCallError instead of reaching the handler. Set
|
|
140
|
+
* false to restore the pre-validation pass-through for deployments relying
|
|
141
|
+
* on loose coercion.
|
|
142
|
+
*/
|
|
143
|
+
validateArgs?: boolean;
|
|
144
|
+
tools: ApiTool[];
|
|
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
|
+
};
|
|
194
|
+
/**
|
|
195
|
+
* The OAuth half of an `api()` connector: the hooks the callback route,
|
|
196
|
+
* operator UI, and `authorize_connector` key on, and the handler accessor.
|
|
197
|
+
* Supplied by `api()` from `src/auth/static-oauth.ts`; defined here so this
|
|
198
|
+
* module never imports the grant machinery.
|
|
199
|
+
*/
|
|
200
|
+
export interface ApiOAuthHooks {
|
|
201
|
+
status(ctx: ConnectorContext): Promise<ConnectorStatus>;
|
|
202
|
+
startAuth(ctx: ConnectorContext, opts?: {
|
|
203
|
+
force?: boolean;
|
|
204
|
+
}): Promise<ConnectorStatus>;
|
|
205
|
+
disconnectAuth(ctx: ConnectorContext): Promise<void>;
|
|
206
|
+
verifyState(state: string | null, ctx: ConnectorContext): Promise<boolean>;
|
|
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>;
|
|
211
|
+
/** The handler-facing accessor for one call's context. */
|
|
212
|
+
access(ctx: ConnectorContext): ApiOAuthAccess;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* A static connector; every tool passes {@link checkToolContract} first.
|
|
216
|
+
*
|
|
217
|
+
* The builder behind `api()`, without the downstream OAuth machinery: credential-free
|
|
218
|
+
* providers import this without paying for a grant they cannot declare.
|
|
219
|
+
*/
|
|
220
|
+
export declare function apiConnector(id: string, opts: ApiOptions, oauth?: ApiOAuthHooks): Connector;
|
|
@@ -0,0 +1,214 @@
|
|
|
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";
|
|
10
|
+
export function defined(value) {
|
|
11
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Three things a hand-written surface is refused for at construction rather
|
|
15
|
+
* than in production (#340): no description (discovery has nothing to route on
|
|
16
|
+
* and a guess costs a call), no explicit `readOnlyHint` (connecta never infers
|
|
17
|
+
* the safety class, so an unclassified tool is a deployment bug), and an
|
|
18
|
+
* `inputSchema` the validator cannot compile (declaring one is optional;
|
|
19
|
+
* declaring an unenforceable one is not). None of this reaches a proxied
|
|
20
|
+
* catalog — the contract binds the surfaces we write, not the ones we relay.
|
|
21
|
+
*/
|
|
22
|
+
function checkToolContract(id, tool) {
|
|
23
|
+
const address = `${id}.${tool.name}`;
|
|
24
|
+
if (typeof tool.description !== "string" || tool.description.trim() === "") {
|
|
25
|
+
throw new Error(`api() tool "${address}" needs a non-empty description — it is what an ` +
|
|
26
|
+
"agent reads to choose the tool (convention: imperative one-liner, " +
|
|
27
|
+
'e.g. "Send an email via Resend").');
|
|
28
|
+
}
|
|
29
|
+
if (typeof tool.annotations?.readOnlyHint !== "boolean") {
|
|
30
|
+
throw new Error(`api() tool "${address}" needs an explicit annotations.readOnlyHint: ` +
|
|
31
|
+
"true for a read, false for work that must cross " +
|
|
32
|
+
"call_destructive_tool. Connecta never infers the classification " +
|
|
33
|
+
"from a tool name, description, schema, or other annotations.");
|
|
34
|
+
}
|
|
35
|
+
if (typeof tool.handler !== "function") {
|
|
36
|
+
throw new Error(`api() tool "${address}" needs a handler function.`);
|
|
37
|
+
}
|
|
38
|
+
if (tool.inputSchema)
|
|
39
|
+
compileValidator(tool.inputSchema, { address });
|
|
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
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A static connector; every tool passes {@link checkToolContract} first.
|
|
87
|
+
*
|
|
88
|
+
* The builder behind `api()`, without the downstream OAuth machinery: credential-free
|
|
89
|
+
* providers import this without paying for a grant they cannot declare.
|
|
90
|
+
*/
|
|
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`);
|
|
94
|
+
if (opts.oauth !== undefined && oauth === undefined) {
|
|
95
|
+
throw new Error(`api() connector "${id}" declares oauth but was built without its grant; construct it with api().`);
|
|
96
|
+
}
|
|
97
|
+
const names = new Set();
|
|
98
|
+
for (const tool of opts.tools) {
|
|
99
|
+
if (names.has(tool.name)) {
|
|
100
|
+
throw new Error(`api() tool "${id}.${tool.name}" is declared more than once; discovery and dispatch must use one definition.`);
|
|
101
|
+
}
|
|
102
|
+
names.add(tool.name);
|
|
103
|
+
checkToolContract(id, tool);
|
|
104
|
+
}
|
|
105
|
+
const defs = opts.tools.map((t) => ({
|
|
106
|
+
name: t.name,
|
|
107
|
+
description: t.description,
|
|
108
|
+
...defined({
|
|
109
|
+
inputSchema: t.inputSchema,
|
|
110
|
+
outputSchema: t.outputSchema,
|
|
111
|
+
}),
|
|
112
|
+
annotations: t.annotations,
|
|
113
|
+
}));
|
|
114
|
+
const byName = new Map(opts.tools.map((t) => [t.name, t]));
|
|
115
|
+
const validateArgs = opts.validateArgs ?? true;
|
|
116
|
+
const auth = describedApiAuth(opts);
|
|
117
|
+
const connector = {
|
|
118
|
+
id,
|
|
119
|
+
...defined({ title: opts.title }),
|
|
120
|
+
kind: "api",
|
|
121
|
+
describe: () => ({ source: { kind: "api" }, auth, tools: describedTools(defs) }),
|
|
122
|
+
...defined({
|
|
123
|
+
description: opts.description,
|
|
124
|
+
authScope: opts.authScope,
|
|
125
|
+
maxResultBytes: opts.maxResultBytes,
|
|
126
|
+
callAdmission: opts.callAdmission,
|
|
127
|
+
usageGuide: opts.usageGuide,
|
|
128
|
+
credential: opts.credential,
|
|
129
|
+
testCredential: opts.testCredential,
|
|
130
|
+
testCredentials: opts.testCredentials,
|
|
131
|
+
}),
|
|
132
|
+
...(oauth
|
|
133
|
+
? {
|
|
134
|
+
status: oauth.status,
|
|
135
|
+
startAuth: oauth.startAuth,
|
|
136
|
+
disconnectAuth: oauth.disconnectAuth,
|
|
137
|
+
verifyState: oauth.verifyState,
|
|
138
|
+
verifyCallbackIssuer: oauth.verifyCallbackIssuer,
|
|
139
|
+
consumeAuthError: oauth.consumeAuthError,
|
|
140
|
+
finishAuth: oauth.finishAuth,
|
|
141
|
+
}
|
|
142
|
+
: {}),
|
|
143
|
+
staticTools: defs,
|
|
144
|
+
async listTools(ctx) {
|
|
145
|
+
return redactCatalog(ctx, defs);
|
|
146
|
+
},
|
|
147
|
+
async callTool(name, args, ctx) {
|
|
148
|
+
trackCredentialReads(ctx);
|
|
149
|
+
const tool = byName.get(name);
|
|
150
|
+
if (!tool) {
|
|
151
|
+
throw new Error(`Unknown tool "${name}" on connector "${id}"`);
|
|
152
|
+
}
|
|
153
|
+
const input = args ?? {};
|
|
154
|
+
if (validateArgs && tool.inputSchema) {
|
|
155
|
+
const invalid = validateCatalogToolInput(tool.inputSchema, input, {
|
|
156
|
+
address: `${id}.${name}`,
|
|
157
|
+
logger: ctx.logger,
|
|
158
|
+
// Always: the schema compiled at construction, so anything that
|
|
159
|
+
// fails here is a schema that cannot be enforced, and a surface we
|
|
160
|
+
// wrote ourselves does not get to admit unvalidated input quietly.
|
|
161
|
+
failClosed: true,
|
|
162
|
+
}, { connector: id, tool });
|
|
163
|
+
if (invalid)
|
|
164
|
+
throw invalid;
|
|
165
|
+
}
|
|
166
|
+
// `await` (not a bare promise return) so a handler that throws before
|
|
167
|
+
// its first await never sits handler-less for the thenable-adoption
|
|
168
|
+
// microtask — workerd and vitest both report that gap as an unhandled
|
|
169
|
+
// rejection even though the caller catches the failure.
|
|
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
|
+
});
|
|
176
|
+
try {
|
|
177
|
+
return redactSentSecrets(ctx, await tool.handler(input, handlerCtx));
|
|
178
|
+
}
|
|
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;
|
|
183
|
+
// A handler owns its destinations. ctx.baseUrl is Connecta's inbound
|
|
184
|
+
// URL, so it must never masquerade as the failed downstream host.
|
|
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);
|
|
202
|
+
}
|
|
203
|
+
},
|
|
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;
|
|
214
|
+
}
|
package/dist/connectors/api.d.ts
CHANGED
|
@@ -1,72 +1,9 @@
|
|
|
1
|
-
import type { Connector
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* capability costs the agent a guess, and a guess costs a wrong call.
|
|
10
|
-
*/
|
|
11
|
-
description: string;
|
|
12
|
-
/**
|
|
13
|
-
* A plain JSON Schema object describing the tool input. Optional, but what
|
|
14
|
-
* you supply must be a schema the validator can compile — `api()` refuses to
|
|
15
|
-
* construct otherwise.
|
|
16
|
-
*/
|
|
17
|
-
inputSchema?: JsonSchema;
|
|
18
|
-
/** A plain JSON Schema object describing the tool's structured output. */
|
|
19
|
-
outputSchema?: JsonSchema;
|
|
20
|
-
/**
|
|
21
|
-
* Standard MCP-style behavior hints, with an explicit `readOnlyHint`
|
|
22
|
-
* required: `true` declares a read and admits the tool to call_tool and
|
|
23
|
-
* execute_code, `false` declares work that must cross
|
|
24
|
-
* `call_destructive_tool` where a host can ask a human. Connecta never
|
|
25
|
-
* infers the classification from a name, a description, a schema, or the
|
|
26
|
-
* other annotations.
|
|
27
|
-
*/
|
|
28
|
-
annotations: ToolAnnotations & {
|
|
29
|
-
readOnlyHint: boolean;
|
|
30
|
-
};
|
|
31
|
-
handler: (args: any, ctx: ConnectorContext) => Promise<unknown> | unknown;
|
|
32
|
-
}
|
|
33
|
-
export interface ApiOptions {
|
|
34
|
-
/** Human-readable display name; the connector id remains the address prefix. */
|
|
35
|
-
title?: string;
|
|
36
|
-
description?: string;
|
|
37
|
-
/** Downstream auth ownership. Defaults to one shared deployment grant. */
|
|
38
|
-
authScope?: "shared" | "personal";
|
|
39
|
-
/**
|
|
40
|
-
* Max inline result size (bytes) for this connector's tools before
|
|
41
|
-
* call_tool truncates and stashes the full text for get_result
|
|
42
|
-
* paging. Overrides the deployment's `calls.maxResultBytes`; omit to inherit
|
|
43
|
-
* it. Must be a whole number of bytes >= 1; anything else warns at startup
|
|
44
|
-
* and is ignored.
|
|
45
|
-
*/
|
|
46
|
-
maxResultBytes?: number;
|
|
47
|
-
/** Optional per-runtime downstream call-admission policy. */
|
|
48
|
-
callAdmission?: ConnectorCallAdmissionPolicy;
|
|
49
|
-
/**
|
|
50
|
-
* Optional agent-facing usage guide served by `skills` as
|
|
51
|
-
* `connector:<id>`. A string is markdown; the structured form adds bounded
|
|
52
|
-
* discovery metadata. See `Connector.usageGuide`.
|
|
53
|
-
*/
|
|
54
|
-
usageGuide?: string | ConnectorUsageGuide;
|
|
55
|
-
/** Optional operator-managed credential exposed through ctx.credential and the connection in the operator UI. */
|
|
56
|
-
credential?: ConnectorCredentialConfig;
|
|
57
|
-
/** Optional validation behind the connection's Test action in the operator UI. */
|
|
58
|
-
testCredential?: (value: string, ctx: ConnectorContext) => Promise<CredentialTestResult>;
|
|
59
|
-
/** Optional validation for named multi-field credentials. */
|
|
60
|
-
testCredentials?: (values: ConnectorCredentialValues, ctx: ConnectorContext) => Promise<CredentialTestResult>;
|
|
61
|
-
/**
|
|
62
|
-
* Validate call arguments against each tool's `inputSchema` before invoking
|
|
63
|
-
* the handler (default true). Mismatches fail with a non-retryable
|
|
64
|
-
* `invalid_args` ConnectorCallError instead of reaching the handler. Set
|
|
65
|
-
* false to restore the pre-validation pass-through for deployments relying
|
|
66
|
-
* on loose coercion.
|
|
67
|
-
*/
|
|
68
|
-
validateArgs?: boolean;
|
|
69
|
-
tools: ApiTool[];
|
|
70
|
-
}
|
|
71
|
-
/** A static connector; every tool passes {@link checkToolContract} first. */
|
|
1
|
+
import type { Connector } from "../types.js";
|
|
2
|
+
import type { ApiOptions } from "./api-connector.js";
|
|
3
|
+
export type { ApiHandlerContext, ApiOAuthAccess, ApiOAuthClientAuthentication, ApiOAuthConfig, ApiOptions, ApiTool, } from "./api-connector.js";
|
|
4
|
+
/**
|
|
5
|
+
* A hand-written connector: static tools, each with a handler. With `oauth`,
|
|
6
|
+
* its handlers reach the API through the calling owner's downstream grant
|
|
7
|
+
* (`ctx.oauth`), managed exactly like a `remoteMcp()` grant.
|
|
8
|
+
*/
|
|
72
9
|
export declare function api(id: string, opts: ApiOptions): Connector;
|
package/dist/connectors/api.js
CHANGED
|
@@ -1,104 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
|
|
5
|
-
}
|
|
1
|
+
import { staticOAuth } from "../auth/static-oauth.js";
|
|
2
|
+
import { assertKnownOptions } from "../config-schema.js";
|
|
3
|
+
import { API_OPTIONS, apiConnector } from "./api-connector.js";
|
|
6
4
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the safety class, so an unclassified tool is a deployment bug), and an
|
|
11
|
-
* `inputSchema` the validator cannot compile (declaring one is optional;
|
|
12
|
-
* declaring an unenforceable one is not). None of this reaches a proxied
|
|
13
|
-
* catalog — the contract binds the surfaces we write, not the ones we relay.
|
|
5
|
+
* A hand-written connector: static tools, each with a handler. With `oauth`,
|
|
6
|
+
* its handlers reach the API through the calling owner's downstream grant
|
|
7
|
+
* (`ctx.oauth`), managed exactly like a `remoteMcp()` grant.
|
|
14
8
|
*/
|
|
15
|
-
function checkToolContract(id, tool) {
|
|
16
|
-
const address = `${id}.${tool.name}`;
|
|
17
|
-
if (typeof tool.description !== "string" || tool.description.trim() === "") {
|
|
18
|
-
throw new Error(`api() tool "${address}" needs a non-empty description — it is what an ` +
|
|
19
|
-
"agent reads to choose the tool (convention: imperative one-liner, " +
|
|
20
|
-
'e.g. "Send an email via Resend").');
|
|
21
|
-
}
|
|
22
|
-
if (typeof tool.annotations?.readOnlyHint !== "boolean") {
|
|
23
|
-
throw new Error(`api() tool "${address}" needs an explicit annotations.readOnlyHint: ` +
|
|
24
|
-
"true for a read, false for work that must cross " +
|
|
25
|
-
"call_destructive_tool. Connecta never infers the classification " +
|
|
26
|
-
"from a tool name, description, schema, or other annotations.");
|
|
27
|
-
}
|
|
28
|
-
if (tool.inputSchema)
|
|
29
|
-
compileValidator(tool.inputSchema, { address });
|
|
30
|
-
}
|
|
31
|
-
/** A static connector; every tool passes {@link checkToolContract} first. */
|
|
32
9
|
export function api(id, opts) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
checkToolContract(id, tool);
|
|
10
|
+
// Read the options once, as plain data, before choosing a grant from them.
|
|
11
|
+
opts = assertKnownOptions(opts, `api(${JSON.stringify(id)})`, API_OPTIONS);
|
|
12
|
+
if (opts.oauth !== undefined && opts.credential !== undefined) {
|
|
13
|
+
throw new Error(`api() connector "${id}" declares both oauth and credential. Declare ` +
|
|
14
|
+
"one: auth_required must name a single recovery. A provider offering " +
|
|
15
|
+
"both lets the deployment choose which one to pass.");
|
|
40
16
|
}
|
|
41
|
-
|
|
42
|
-
name: t.name,
|
|
43
|
-
description: t.description,
|
|
44
|
-
...defined({
|
|
45
|
-
inputSchema: t.inputSchema,
|
|
46
|
-
outputSchema: t.outputSchema,
|
|
47
|
-
}),
|
|
48
|
-
annotations: t.annotations,
|
|
49
|
-
}));
|
|
50
|
-
const byName = new Map(opts.tools.map((t) => [t.name, t]));
|
|
51
|
-
const validateArgs = opts.validateArgs ?? true;
|
|
52
|
-
return {
|
|
53
|
-
id,
|
|
54
|
-
...defined({ title: opts.title }),
|
|
55
|
-
kind: "api",
|
|
56
|
-
...defined({
|
|
57
|
-
description: opts.description,
|
|
58
|
-
authScope: opts.authScope,
|
|
59
|
-
maxResultBytes: opts.maxResultBytes,
|
|
60
|
-
callAdmission: opts.callAdmission,
|
|
61
|
-
usageGuide: opts.usageGuide,
|
|
62
|
-
credential: opts.credential,
|
|
63
|
-
testCredential: opts.testCredential,
|
|
64
|
-
testCredentials: opts.testCredentials,
|
|
65
|
-
}),
|
|
66
|
-
staticTools: defs,
|
|
67
|
-
async listTools() {
|
|
68
|
-
return defs;
|
|
69
|
-
},
|
|
70
|
-
async callTool(name, args, ctx) {
|
|
71
|
-
const tool = byName.get(name);
|
|
72
|
-
if (!tool) {
|
|
73
|
-
throw new Error(`Unknown tool "${name}" on connector "${id}"`);
|
|
74
|
-
}
|
|
75
|
-
const input = args ?? {};
|
|
76
|
-
if (validateArgs && tool.inputSchema) {
|
|
77
|
-
const invalid = validateToolInput(tool.inputSchema, input, {
|
|
78
|
-
address: `${id}.${name}`,
|
|
79
|
-
logger: ctx.logger,
|
|
80
|
-
// Always: the schema compiled at construction, so anything that
|
|
81
|
-
// fails here is a schema that cannot be enforced, and a surface we
|
|
82
|
-
// wrote ourselves does not get to admit unvalidated input quietly.
|
|
83
|
-
failClosed: true,
|
|
84
|
-
});
|
|
85
|
-
if (invalid)
|
|
86
|
-
throw invalid;
|
|
87
|
-
}
|
|
88
|
-
// `await` (not a bare promise return) so a handler that throws before
|
|
89
|
-
// its first await never sits handler-less for the thenable-adoption
|
|
90
|
-
// microtask — workerd and vitest both report that gap as an unhandled
|
|
91
|
-
// rejection even though the caller catches the failure.
|
|
92
|
-
try {
|
|
93
|
-
return await tool.handler(input, ctx);
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
// A handler owns its destinations. ctx.baseUrl is Connecta's inbound
|
|
97
|
-
// URL, so it must never masquerade as the failed downstream host.
|
|
98
|
-
if (error instanceof ConnectorCallError || !networkErrorCode(error))
|
|
99
|
-
throw error;
|
|
100
|
-
throw unavailableCallError(error);
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
};
|
|
17
|
+
return apiConnector(id, opts, opts.oauth !== undefined ? staticOAuth(id, opts.oauth) : undefined);
|
|
104
18
|
}
|