@zackbart/connecta 0.30.0 → 0.31.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 +32 -0
- package/README.md +6 -3
- package/bin/version.mjs +1 -1
- package/dist/call-recovery.js +25 -0
- package/dist/config-schema.d.ts +14 -7
- package/dist/config-schema.js +29 -10
- package/dist/connectors/guarded-fetch.d.ts +9 -1
- package/dist/connectors/guarded-fetch.js +14 -2
- package/dist/errors.d.ts +2 -0
- package/dist/invocation.js +32 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/view.d.ts +9 -0
- package/dist/operator-ui/view.js +11 -0
- package/dist/program-writes.js +2 -47
- package/dist/provider.d.ts +7 -5
- package/dist/provider.js +3 -4
- package/dist/providers/_shared/rest/dispatch.js +41 -0
- package/dist/providers/_shared/rest/operation-index.d.ts +133 -0
- package/dist/providers/_shared/rest/operation-index.js +527 -0
- package/dist/providers/_shared/rest/tools.d.ts +141 -0
- package/dist/providers/_shared/rest/tools.js +569 -0
- package/dist/providers/_shared/rest/value-safety.d.ts +151 -0
- package/dist/providers/_shared/rest/value-safety.js +580 -0
- package/dist/providers/cloudflare/graphql.js +385 -0
- package/dist/providers/cloudflare/index.d.ts +47 -51
- package/dist/providers/cloudflare/index.js +188 -1997
- package/dist/providers/cloudflare/openapi.generated.js +3347 -0
- package/dist/providers/cloudflare/rest.d.ts +37 -0
- package/dist/providers/cloudflare/rest.js +1056 -0
- package/dist/providers/cloudflare/skill.generated.js +7 -4
- package/dist/providers/cloudflare/value-safety.js +1247 -0
- package/dist/providers/notion/index.d.ts +35 -31
- package/dist/providers/notion/index.js +60 -1499
- package/dist/providers/notion/openapi.generated.js +75 -0
- package/dist/providers/notion/rest.d.ts +24 -0
- package/dist/providers/notion/rest.js +1067 -0
- package/dist/providers/notion/skill.generated.js +5 -4
- package/dist/providers/notion/value-safety.js +31 -0
- package/dist/providers/stripe/index.d.ts +26 -20
- package/dist/providers/stripe/index.js +103 -130
- package/dist/providers/stripe/openapi.generated.js +649 -0
- package/dist/providers/stripe/rest.d.ts +14 -0
- package/dist/providers/stripe/rest.js +395 -0
- package/dist/providers/stripe/skill.generated.js +7 -5
- package/dist/providers/stripe/value-safety.js +705 -0
- package/dist/providers/vercel/index.d.ts +31 -22
- package/dist/providers/vercel/index.js +96 -853
- package/dist/providers/vercel/openapi.generated.js +456 -0
- package/dist/providers/vercel/rest.d.ts +20 -0
- package/dist/providers/vercel/rest.js +1002 -0
- package/dist/providers/vercel/skill.generated.js +4 -5
- package/dist/providers/vercel/value-safety.js +328 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/write-outcome.js +47 -0
- package/documentation/README.md +1 -0
- package/documentation/architecture.md +120 -8
- package/documentation/integrating.md +16 -5
- package/documentation/meta-tools.md +9 -2
- package/documentation/provider-auth-migration.md +307 -0
- package/documentation/provider-migration-0.29.md +17 -5
- package/examples/worker/README.md +2 -2
- package/package.json +3 -2
- package/templates/node/README.md +2 -2
- package/templates/node/package.json +1 -1
- package/dist/providers/vercel/mcp-ownership.js +0 -212
|
@@ -3,6 +3,15 @@ import type { CatalogDriftReport } from "../types.js";
|
|
|
3
3
|
import type { CredentialManagementCapability, UiConnector, UiData, UiProblem } from "./model.js";
|
|
4
4
|
import type { FixPromptKind } from "./fix-prompts.js";
|
|
5
5
|
export declare function registrationPathLabel(path: NonNullable<UiConnector["registrationPath"]>): string;
|
|
6
|
+
/**
|
|
7
|
+
* A connector's source column: the maintained provider with the
|
|
8
|
+
* implementation it uses, since a dual provider's `auth` selects either the
|
|
9
|
+
* vendor's hosted MCP server or Connecta's REST connector.
|
|
10
|
+
*/
|
|
11
|
+
export declare function connectorSourceLabel(source: {
|
|
12
|
+
kind: string;
|
|
13
|
+
provider?: string;
|
|
14
|
+
}): string;
|
|
6
15
|
export type OperatorPage = "overview" | "tools" | "access" | "config" | "connector" | "tokens" | "connections" | "activity";
|
|
7
16
|
/** Pages the nav lists. */
|
|
8
17
|
export declare const OPERATOR_PAGES: readonly OperatorPage[];
|
package/dist/operator-ui/view.js
CHANGED
|
@@ -14,6 +14,17 @@ const REGISTRATION_LABELS = {
|
|
|
14
14
|
export function registrationPathLabel(path) {
|
|
15
15
|
return REGISTRATION_LABELS[path];
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* A connector's source column: the maintained provider with the
|
|
19
|
+
* implementation it uses, since a dual provider's `auth` selects either the
|
|
20
|
+
* vendor's hosted MCP server or Connecta's REST connector.
|
|
21
|
+
*/
|
|
22
|
+
export function connectorSourceLabel(source) {
|
|
23
|
+
if (!source.provider)
|
|
24
|
+
return source.kind;
|
|
25
|
+
const implementation = source.kind === "remote-mcp" ? "hosted MCP" : source.kind === "api" ? "REST" : source.kind;
|
|
26
|
+
return `${source.provider} · ${implementation}`;
|
|
27
|
+
}
|
|
17
28
|
/** Pages the nav lists. */
|
|
18
29
|
export const OPERATOR_PAGES = [
|
|
19
30
|
"overview",
|
package/dist/program-writes.js
CHANGED
|
@@ -3,55 +3,10 @@
|
|
|
3
3
|
import { CONFIG_DEFAULTS } from "./config-defaults.js";
|
|
4
4
|
import { Effect } from "effect";
|
|
5
5
|
import { jsonResult } from "./meta-tools.js";
|
|
6
|
+
import { classifyWriteOutcome } from "./write-outcome.js";
|
|
7
|
+
export { classifyWriteOutcome } from "./write-outcome.js";
|
|
6
8
|
/** Writes one program may send (`execute.maxWrites`), unless configured. */
|
|
7
9
|
export const DEFAULT_MAX_WRITES = CONFIG_DEFAULTS.execute.maxWrites;
|
|
8
|
-
/**
|
|
9
|
-
* Whether a write landed, as far as anyone can know.
|
|
10
|
-
*
|
|
11
|
-
* `unknown` is the outcome a result must never paper over: the call left
|
|
12
|
-
* connecta and no answer came back, so sending it again could do it twice and
|
|
13
|
-
* not sending it could leave it undone. That is a dispatched call that timed
|
|
14
|
-
* out, was cancelled, found the service unavailable, or failed any way that
|
|
15
|
-
* is not a verdict. A refusal code (`REFUSALS`) or a downstream tool's own
|
|
16
|
-
* `isError` is an answer, so it is `failed`. Response text does not decide
|
|
17
|
-
* whether an outcome is unknown. A call that was never dispatched is
|
|
18
|
-
* `failed`: nothing was sent.
|
|
19
|
-
* `result_processing_failed` means the downstream call completed.
|
|
20
|
-
*/
|
|
21
|
-
export function classifyWriteOutcome(outcome) {
|
|
22
|
-
if (outcome.ok)
|
|
23
|
-
return "ok";
|
|
24
|
-
if (!outcome.dispatched)
|
|
25
|
-
return "failed";
|
|
26
|
-
const code = outcome.error?.code;
|
|
27
|
-
if (code === "result_processing_failed")
|
|
28
|
-
return "ok";
|
|
29
|
-
if (code === "timeout" || code === "cancelled" || code === "unavailable") {
|
|
30
|
-
return "unknown";
|
|
31
|
-
}
|
|
32
|
-
if (code !== undefined && REFUSALS.has(code))
|
|
33
|
-
return "failed";
|
|
34
|
-
return outcome.answered === true ? "failed" : "unknown";
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Codes that say the other side refused the call rather than acted on it:
|
|
38
|
-
* the credential, the arguments, the resource, the rate, or a base version
|
|
39
|
-
* someone else already moved past (`conflict`). Anything else a
|
|
40
|
-
* connector reports after dispatch — `connector_call_failed` from a response
|
|
41
|
-
* too large to read, a redirect it would not follow, a body it could not
|
|
42
|
-
* parse, a 5xx — may come after the write landed, so only a downstream
|
|
43
|
-
* tool's own `isError` answer makes it a known failure.
|
|
44
|
-
*/
|
|
45
|
-
const REFUSALS = new Set([
|
|
46
|
-
"auth_required",
|
|
47
|
-
"downstream_oauth_required",
|
|
48
|
-
"provider_permission_denied",
|
|
49
|
-
"invalid_args",
|
|
50
|
-
"not_found",
|
|
51
|
-
"rate_limited",
|
|
52
|
-
"input_required_unsupported",
|
|
53
|
-
"conflict",
|
|
54
|
-
]);
|
|
55
10
|
/** What a dispatched write's outcome says about whether it landed. */
|
|
56
11
|
export function writeStateOf(outcome) {
|
|
57
12
|
const state = classifyWriteOutcome(outcome.ok
|
package/dist/provider.d.ts
CHANGED
|
@@ -2,10 +2,12 @@ import { type Field } from "./config-schema.js";
|
|
|
2
2
|
import type { Connector, ConnectorCallAdmissionPolicy, ConnectorUsageGuide, ToolClassification } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* How a provider reaches its vendor: the vendor's hosted MCP server, an
|
|
5
|
-
* `api()` connector over the vendor's HTTP API,
|
|
6
|
-
*
|
|
5
|
+
* `api()` connector over the vendor's HTTP API, one connector composing both
|
|
6
|
+
* with an explicit operation ownership map, or `"dual"`: the configured
|
|
7
|
+
* `auth.type` selects the implementation, OAuth reaching the hosted MCP server
|
|
8
|
+
* and a key reaching Connecta's REST connector.
|
|
7
9
|
*/
|
|
8
|
-
export type ProviderKind = "mcp" | "api" | "composed";
|
|
10
|
+
export type ProviderKind = "mcp" | "api" | "composed" | "dual";
|
|
9
11
|
/** Options every provider accepts, validated before `create` runs. */
|
|
10
12
|
export interface ProviderOptions {
|
|
11
13
|
/** What this connection is for and which decisions it answers. Required. */
|
|
@@ -105,8 +107,8 @@ export interface ProviderDefinition<O extends ProviderOptions> {
|
|
|
105
107
|
/** Closed factory options, declared with `optionsOf<O>()` for key parity. */
|
|
106
108
|
options: Field<unknown, unknown>;
|
|
107
109
|
/**
|
|
108
|
-
* Reviewed classification of the vendor's hosted MCP catalog. Hosted
|
|
109
|
-
* composed providers pass it to `remoteMcp({ classify })` through
|
|
110
|
+
* Reviewed classification of the vendor's hosted MCP catalog. Hosted,
|
|
111
|
+
* composed, and dual providers pass it to `remoteMcp({ classify })` through
|
|
110
112
|
* `ProviderContext.classify`, so the verdicts that classify live tools and
|
|
111
113
|
* the record a drift check reads cannot disagree. `api()` providers annotate
|
|
112
114
|
* each tool they author and must omit it.
|
package/dist/provider.js
CHANGED
|
@@ -20,7 +20,7 @@ export const PROVIDER_COMMON = {
|
|
|
20
20
|
callAdmission: CALL_ADMISSION,
|
|
21
21
|
};
|
|
22
22
|
const PROVIDER_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
23
|
-
const KINDS = new Set(["mcp", "api", "composed"]);
|
|
23
|
+
const KINDS = new Set(["mcp", "api", "composed", "dual"]);
|
|
24
24
|
function nonEmpty(value) {
|
|
25
25
|
return typeof value === "string" && value.trim() !== "";
|
|
26
26
|
}
|
|
@@ -40,7 +40,7 @@ export function defineProvider(definition) {
|
|
|
40
40
|
throw new Error(`${label} requires a non-empty title.`);
|
|
41
41
|
}
|
|
42
42
|
if (!KINDS.has(definition.kind)) {
|
|
43
|
-
throw new Error(`${label} kind must be "mcp", "api", or "
|
|
43
|
+
throw new Error(`${label} kind must be "mcp", "api", "composed", or "dual".`);
|
|
44
44
|
}
|
|
45
45
|
if (!nonEmpty(definition.skill?.content) || !nonEmpty(definition.skill.instructionsHeading)) {
|
|
46
46
|
throw new Error(`${label} skill requires non-empty content and instructionsHeading.`);
|
|
@@ -180,8 +180,7 @@ function providerConstructionError(provider, id, error) {
|
|
|
180
180
|
.replace(/^requires\s+/, "")
|
|
181
181
|
.replace(/^must be /, "")
|
|
182
182
|
.replace(/^(.+) must be /, "$1 to be ")
|
|
183
|
-
.replace(/^declares /, "a consistent declaration of ")
|
|
184
|
-
.replace(/^with headers or credential auth requires /, "headers or credential auth to declare ");
|
|
183
|
+
.replace(/^declares /, "a consistent declaration of ");
|
|
185
184
|
error.message = `${at} requires ${detail}`;
|
|
186
185
|
return error;
|
|
187
186
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Auth selects the implementation (decisions/0005): a `"dual"` provider sends
|
|
2
|
+
// OAuth to the vendor's hosted MCP server and a key to Connecta's own REST
|
|
3
|
+
// connector. The choice is made once, at construction, from `auth.type`; one
|
|
4
|
+
// connector id is one implementation, and a failure on one is never replayed
|
|
5
|
+
// against the other (INV-9).
|
|
6
|
+
import { remoteMcp } from "../../../connectors/remote-mcp.js";
|
|
7
|
+
/**
|
|
8
|
+
* A provider `create` that dispatches on `options.auth.type`. Pair it with
|
|
9
|
+
* `variants(["auth", "type"], …)` options, which refuse a missing or unknown
|
|
10
|
+
* type, and options the selected case does not accept, before this runs.
|
|
11
|
+
*/
|
|
12
|
+
export function byAuth(cases) {
|
|
13
|
+
return (id, options, provider) => {
|
|
14
|
+
const type = options.auth?.type;
|
|
15
|
+
const create = typeof type === "string" && Object.hasOwn(cases, type) ? cases[type] : undefined;
|
|
16
|
+
if (!create) {
|
|
17
|
+
throw new Error(`auth.type must be one of ${Object.keys(cases)
|
|
18
|
+
.map((name) => JSON.stringify(name))
|
|
19
|
+
.join(", ")}.`);
|
|
20
|
+
}
|
|
21
|
+
return create(id, options, provider);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The vendor's hosted MCP server, OAuth only: no bearer token, header, or
|
|
26
|
+
* operator credential reaches a hosted endpoint. The provider's reviewed
|
|
27
|
+
* classification classifies its catalog.
|
|
28
|
+
*/
|
|
29
|
+
export function hostedOAuth(id, provider, options) {
|
|
30
|
+
return remoteMcp(id, {
|
|
31
|
+
url: options.url,
|
|
32
|
+
...provider.connectorOptions,
|
|
33
|
+
title: options.title,
|
|
34
|
+
description: options.description,
|
|
35
|
+
auth: { type: "oauth" },
|
|
36
|
+
requireHttps: true,
|
|
37
|
+
classify: provider.classify,
|
|
38
|
+
...(options.callAdmission ? { callAdmission: options.callAdmission } : {}),
|
|
39
|
+
usageGuide: options.usageGuide,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import type { JsonSchema } from "../../../types.js";
|
|
2
|
+
export type RestMethod = "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
3
|
+
/**
|
|
4
|
+
* One request-side schema, compacted: `t` type, `e` enum, `f` format (only
|
|
5
|
+
* `binary`), `n` nullable, `d` description, `a` anyOf/oneOf, `p` properties,
|
|
6
|
+
* `r` required, `i` items, `m` additionalProperties, `x` truncated at the
|
|
7
|
+
* generation depth (unvalidated below), `$` a shared subtree.
|
|
8
|
+
*/
|
|
9
|
+
export interface SchemaNode {
|
|
10
|
+
readonly t?: string | readonly string[];
|
|
11
|
+
readonly e?: readonly unknown[];
|
|
12
|
+
readonly f?: string;
|
|
13
|
+
readonly n?: 1;
|
|
14
|
+
readonly d?: string;
|
|
15
|
+
readonly a?: readonly SchemaNode[];
|
|
16
|
+
readonly p?: Readonly<Record<string, SchemaNode>>;
|
|
17
|
+
readonly r?: readonly string[];
|
|
18
|
+
readonly i?: SchemaNode;
|
|
19
|
+
readonly m?: SchemaNode;
|
|
20
|
+
readonly x?: 1;
|
|
21
|
+
readonly $?: number;
|
|
22
|
+
}
|
|
23
|
+
/** `[method, path template, operationId, summary, tag index, server index?]` */
|
|
24
|
+
type OperationRow = readonly [string, string, string, string, number, number?];
|
|
25
|
+
/** What `scripts/generate-openapi.mjs` writes to `openapi.generated.ts`. */
|
|
26
|
+
export interface OpenApiData {
|
|
27
|
+
/** The pinned document's URL. */
|
|
28
|
+
readonly source: string;
|
|
29
|
+
/** The pin: a tag, commit, or content hash. */
|
|
30
|
+
readonly revision: string;
|
|
31
|
+
/** `sha256:` digest of the pinned document's bytes. */
|
|
32
|
+
readonly digest: string;
|
|
33
|
+
/** The document's `info.version`; Stripe sends it as `Stripe-Version`. */
|
|
34
|
+
readonly version: string;
|
|
35
|
+
/** `[0]` is the default origin; later entries are per-operation servers. */
|
|
36
|
+
readonly servers: readonly string[];
|
|
37
|
+
readonly tags: readonly string[];
|
|
38
|
+
readonly ops: readonly OperationRow[];
|
|
39
|
+
/** JSON: `{ d: SchemaNode[], o: (0 | [params, body])[] }`, aligned with `ops`. */
|
|
40
|
+
readonly details: string;
|
|
41
|
+
}
|
|
42
|
+
export interface Operation {
|
|
43
|
+
readonly method: RestMethod;
|
|
44
|
+
/** The path template, e.g. `/v1/customers/{customer}`. */
|
|
45
|
+
readonly path: string;
|
|
46
|
+
readonly operationId: string;
|
|
47
|
+
readonly summary: string;
|
|
48
|
+
readonly tag: string;
|
|
49
|
+
/** A non-default origin this operation is served from, if any. */
|
|
50
|
+
readonly server: string | undefined;
|
|
51
|
+
/** Position in `OpenApiData.ops`. */
|
|
52
|
+
readonly row: number;
|
|
53
|
+
}
|
|
54
|
+
/** A concrete call matched to its operation. */
|
|
55
|
+
export interface OperationMatch {
|
|
56
|
+
readonly op: Operation;
|
|
57
|
+
/** Path parameter values, exactly as the caller sent them. */
|
|
58
|
+
readonly params: Readonly<Record<string, string>>;
|
|
59
|
+
}
|
|
60
|
+
/** One operation's request contract, expanded to JSON Schema for an agent. */
|
|
61
|
+
export interface OperationContract {
|
|
62
|
+
method: RestMethod;
|
|
63
|
+
path: string;
|
|
64
|
+
operationId: string;
|
|
65
|
+
summary: string;
|
|
66
|
+
server?: string;
|
|
67
|
+
parameters: {
|
|
68
|
+
name: string;
|
|
69
|
+
in: "path" | "query";
|
|
70
|
+
required: boolean;
|
|
71
|
+
description?: string;
|
|
72
|
+
schema: JsonSchema;
|
|
73
|
+
}[];
|
|
74
|
+
body?: {
|
|
75
|
+
contentType: string;
|
|
76
|
+
required?: true;
|
|
77
|
+
schema: JsonSchema;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** The read side of one vendor's pinned index. */
|
|
81
|
+
export declare class OperationIndex {
|
|
82
|
+
#private;
|
|
83
|
+
readonly version: string;
|
|
84
|
+
readonly revision: string;
|
|
85
|
+
readonly ops: readonly Operation[];
|
|
86
|
+
/** The methods any operation uses, in a stable order. */
|
|
87
|
+
readonly methods: readonly RestMethod[];
|
|
88
|
+
/**
|
|
89
|
+
* `vendor` is the tool-name prefix; `title` the display name in refusals.
|
|
90
|
+
* `slashParams` names path parameters whose values may carry an encoded
|
|
91
|
+
* "/" (`%2F`), such as an object key, and only as the path's last segment.
|
|
92
|
+
*/
|
|
93
|
+
constructor(data: OpenApiData, names: {
|
|
94
|
+
vendor: string;
|
|
95
|
+
title: string;
|
|
96
|
+
slashParams?: readonly string[];
|
|
97
|
+
});
|
|
98
|
+
/** The operation with this exact method and path template, if any. */
|
|
99
|
+
operation(method: string, template: string): Operation | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Match a concrete call to its operation. Literal segments outrank
|
|
102
|
+
* parameters, as OpenAPI requires (`/v1/customers/search` before
|
|
103
|
+
* `/v1/customers/{customer}`); the most specific template that fits decides,
|
|
104
|
+
* and a method it does not define is refused rather than retried against a
|
|
105
|
+
* looser template.
|
|
106
|
+
*/
|
|
107
|
+
resolve(method: string, path: string): OperationMatch;
|
|
108
|
+
/** Up to five operations whose paths most resemble a missed one. */
|
|
109
|
+
nearest(method: string, segments: readonly string[]): string[];
|
|
110
|
+
/**
|
|
111
|
+
* Rank operations for a free-text query: every query word must appear in
|
|
112
|
+
* the path, summary, operationId, or tag (plural-insensitive, prefixes of
|
|
113
|
+
* three letters count), and path matches weigh most. A query that starts
|
|
114
|
+
* with "/" also matches the templates that fit it.
|
|
115
|
+
*/
|
|
116
|
+
search(query: string, options: {
|
|
117
|
+
method?: RestMethod;
|
|
118
|
+
limit: number;
|
|
119
|
+
}): Operation[];
|
|
120
|
+
/** Whether the operation takes a body, and in which framing. */
|
|
121
|
+
bodyType(op: Operation): string | undefined;
|
|
122
|
+
/** The operation's request contract as JSON Schema, optionally one parameter only. */
|
|
123
|
+
contract(op: Operation, only?: string): OperationContract;
|
|
124
|
+
/**
|
|
125
|
+
* Check query and body names, required members, enums, and object/array
|
|
126
|
+
* shapes against the operation, to the depth the index carries. Scalars are
|
|
127
|
+
* not type-policed beyond that: vendors coerce form strings, and a wrong
|
|
128
|
+
* number is the vendor's to explain. `null` and `""` pass anywhere, since
|
|
129
|
+
* they are how form APIs unset a field.
|
|
130
|
+
*/
|
|
131
|
+
check(op: Operation, query: Readonly<Record<string, unknown>>, body: unknown): void;
|
|
132
|
+
}
|
|
133
|
+
export {};
|