@zackbart/connecta 0.3.0 → 0.4.1
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 +143 -0
- package/README.md +34 -14
- package/SECURITY.md +1 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +62 -0
- package/dist/catalog.js.map +1 -1
- package/dist/connectors/api.d.ts +10 -0
- package/dist/connectors/api.d.ts.map +1 -1
- package/dist/connectors/api.js +16 -39
- package/dist/connectors/api.js.map +1 -1
- package/dist/connectors/remote-mcp.d.ts +14 -1
- package/dist/connectors/remote-mcp.d.ts.map +1 -1
- package/dist/connectors/remote-mcp.js +29 -0
- package/dist/connectors/remote-mcp.js.map +1 -1
- package/dist/credentials.d.ts +2 -1
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +4 -2
- package/dist/credentials.js.map +1 -1
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +39 -1
- package/dist/errors.js.map +1 -1
- package/dist/executors/quickjs.d.ts.map +1 -1
- package/dist/executors/quickjs.js +32 -4
- package/dist/executors/quickjs.js.map +1 -1
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -1
- package/dist/index.js.map +1 -1
- package/dist/json-schema.d.ts +3 -0
- package/dist/json-schema.d.ts.map +1 -0
- package/dist/json-schema.js +6 -0
- package/dist/json-schema.js.map +1 -0
- package/dist/meta-tools.d.ts +34 -2
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +104 -15
- package/dist/meta-tools.js.map +1 -1
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +34 -6
- package/dist/server.js.map +1 -1
- package/dist/storage/file.d.ts.map +1 -1
- package/dist/storage/file.js +19 -3
- package/dist/storage/file.js.map +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +13 -9
- package/dist/ui.js.map +1 -1
- package/dist/validate.d.ts +71 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +96 -0
- package/dist/validate.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -1
- package/src/catalog.ts +61 -0
- package/src/connectors/api.ts +25 -50
- package/src/connectors/remote-mcp.ts +52 -0
- package/src/credentials.ts +6 -3
- package/src/errors.ts +45 -2
- package/src/executors/quickjs.ts +32 -4
- package/src/index.ts +94 -1
- package/src/json-schema.ts +11 -0
- package/src/meta-tools.ts +156 -28
- package/src/server.ts +39 -6
- package/src/storage/file.ts +18 -2
- package/src/ui.ts +13 -8
- package/src/validate.ts +154 -0
- package/src/version.ts +1 -1
package/src/ui.ts
CHANGED
|
@@ -29,15 +29,16 @@ export function resolveBranding(
|
|
|
29
29
|
): ResolvedBranding {
|
|
30
30
|
const productName = branding?.productName?.trim() || "Connecta";
|
|
31
31
|
const ownerName = branding?.ownerName?.trim();
|
|
32
|
+
// Operator branding URLs become masthead/callback hrefs, so a non-http(s)
|
|
33
|
+
// scheme (javascript:, data:) is dropped the same as an unset URL — the
|
|
34
|
+
// callers already render a <span> instead of an <a> when it is absent.
|
|
35
|
+
const productUrl = branding?.productUrl?.trim();
|
|
36
|
+
const ownerUrl = branding?.ownerUrl?.trim();
|
|
32
37
|
return {
|
|
33
38
|
productName,
|
|
34
|
-
...(
|
|
35
|
-
? { productUrl: branding.productUrl.trim() }
|
|
36
|
-
: {}),
|
|
39
|
+
...(productUrl && isSafeHttpUrl(productUrl) ? { productUrl } : {}),
|
|
37
40
|
...(ownerName ? { ownerName } : {}),
|
|
38
|
-
...(
|
|
39
|
-
? { ownerUrl: branding.ownerUrl.trim() }
|
|
40
|
-
: {}),
|
|
41
|
+
...(ownerUrl && isSafeHttpUrl(ownerUrl) ? { ownerUrl } : {}),
|
|
41
42
|
description:
|
|
42
43
|
branding?.description?.trim() ||
|
|
43
44
|
`Manage the services this ${productName} instance makes available to agents.`,
|
|
@@ -302,10 +303,14 @@ export function renderUiHtml(
|
|
|
302
303
|
uiAuth?: UiAuthConfig,
|
|
303
304
|
mcpUrl = "/mcp",
|
|
304
305
|
branding?: ConnectaBranding,
|
|
306
|
+
nonce?: string,
|
|
305
307
|
): string {
|
|
306
308
|
const auth = uiAuth ?? { kind: "bearer" as const };
|
|
307
309
|
const brand = resolveBranding(branding);
|
|
308
310
|
const title = brand.pageTitle;
|
|
311
|
+
// When the /ui response ships a nonce-based CSP, every <script> it emits must
|
|
312
|
+
// carry that nonce to run; without a nonce the markup is unchanged.
|
|
313
|
+
const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
|
|
309
314
|
// Top-left corner. With an owner set it reads "<owner> <product>"; without
|
|
310
315
|
// one the product label stands alone. Either half links out when the
|
|
311
316
|
// matching URL is configured.
|
|
@@ -323,7 +328,7 @@ export function renderUiHtml(
|
|
|
323
328
|
: "";
|
|
324
329
|
const clerkScript =
|
|
325
330
|
uiAuth?.kind === "clerk"
|
|
326
|
-
? `<script defer crossorigin="anonymous" data-clerk-publishable-key="${escapeHtmlAttr(uiAuth.publishableKey)}" src="${escapeHtmlAttr(uiAuth.frontendApiUrl)}/npm/@clerk/clerk-js@6/dist/clerk.browser.js"></script>`
|
|
331
|
+
? `<script${nonceAttr} defer crossorigin="anonymous" data-clerk-publishable-key="${escapeHtmlAttr(uiAuth.publishableKey)}" src="${escapeHtmlAttr(uiAuth.frontendApiUrl)}/npm/@clerk/clerk-js@6/dist/clerk.browser.js"></script>`
|
|
327
332
|
: "";
|
|
328
333
|
|
|
329
334
|
return `<!doctype html>
|
|
@@ -683,7 +688,7 @@ ${clerkScript}
|
|
|
683
688
|
</section>
|
|
684
689
|
</main>
|
|
685
690
|
|
|
686
|
-
<script>
|
|
691
|
+
<script${nonceAttr}>
|
|
687
692
|
const AUTH = ${jsonForInlineScript(auth)};
|
|
688
693
|
const MCP_URL = ${jsonForInlineScript(mcpUrl)};
|
|
689
694
|
const filterUiConnectors = ${filterUiConnectors.toString()};
|
package/src/validate.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Validator } from "@cfworker/json-schema";
|
|
2
|
+
import { ConnectorCallError } from "./errors.js";
|
|
3
|
+
import type { JsonSchema, Logger } from "./types.js";
|
|
4
|
+
|
|
5
|
+
export interface ValidateToolInputOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Tool address used in the error and warning text, conventionally
|
|
8
|
+
* `"connectorId.toolName"`.
|
|
9
|
+
*/
|
|
10
|
+
address: string;
|
|
11
|
+
/**
|
|
12
|
+
* Destination for the one-time warning emitted when a schema turns out to be
|
|
13
|
+
* unusable. Default console.
|
|
14
|
+
*/
|
|
15
|
+
logger?: Logger;
|
|
16
|
+
/**
|
|
17
|
+
* Fail-closed on a schema the validator cannot evaluate (default false =
|
|
18
|
+
* today's fail-open behavior). When true, a schema that cannot be compiled —
|
|
19
|
+
* or that only fails on first use, e.g. an unresolvable `$ref` — yields a
|
|
20
|
+
* non-retryable `invalid_args` error instead of passing the raw arguments
|
|
21
|
+
* through, so unvalidated input is never silently admitted. The happy path
|
|
22
|
+
* (a schema that compiles and validates) is unaffected.
|
|
23
|
+
*/
|
|
24
|
+
failClosed?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface PrecompileValidatorOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Tool address used in the warning text, conventionally
|
|
30
|
+
* `"connectorId.toolName"`.
|
|
31
|
+
*/
|
|
32
|
+
address: string;
|
|
33
|
+
/**
|
|
34
|
+
* Destination for the warning emitted when the schema cannot be compiled.
|
|
35
|
+
* Default console.
|
|
36
|
+
*/
|
|
37
|
+
logger?: Logger;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Lazy validator cache keyed by the schema object itself; null marks a schema
|
|
41
|
+
// the validator rejected (warned once, then passed through rather than
|
|
42
|
+
// breaking a working tool). A WeakMap so schemas belonging to a discarded
|
|
43
|
+
// connector are collectable, the same pattern compactSchema uses.
|
|
44
|
+
const validators = new WeakMap<JsonSchema, Validator | null>();
|
|
45
|
+
|
|
46
|
+
function unevaluableSchema(address: string): ConnectorCallError {
|
|
47
|
+
return new ConnectorCallError(
|
|
48
|
+
"invalid_args",
|
|
49
|
+
`Cannot validate arguments for "${address}": its inputSchema could not be evaluated`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function disableValidation(
|
|
54
|
+
schema: JsonSchema,
|
|
55
|
+
address: string,
|
|
56
|
+
logger: Logger,
|
|
57
|
+
err: unknown,
|
|
58
|
+
): void {
|
|
59
|
+
validators.set(schema, null);
|
|
60
|
+
logger.warn(
|
|
61
|
+
`[connecta] tool "${address}" has an inputSchema the validator cannot use (${
|
|
62
|
+
err instanceof Error ? err.message : String(err)
|
|
63
|
+
}) — arguments are not validated`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Validate call arguments against a tool's JSON Schema.
|
|
69
|
+
*
|
|
70
|
+
* Returns a non-retryable `invalid_args` ConnectorCallError describing the
|
|
71
|
+
* mismatch, or null when the arguments are acceptable. It deliberately returns
|
|
72
|
+
* rather than throws: the caller decides what to do with the failure, which is
|
|
73
|
+
* what lets a connector own its error prose, or strip connector-wide
|
|
74
|
+
* convention arguments (a `confirm` flag on writes, say) that individual tool
|
|
75
|
+
* schemas do not declare before deciding the call is really invalid.
|
|
76
|
+
*
|
|
77
|
+
* A schema the validator cannot compile (or that only fails on first use, e.g.
|
|
78
|
+
* an unresolvable `$ref`) is warned about once and then passed through — a
|
|
79
|
+
* broken schema should not break an otherwise working tool. Pass
|
|
80
|
+
* `failClosed: true` to instead reject such calls with `invalid_args`, for
|
|
81
|
+
* callers that would rather refuse a call than forward unvalidated arguments.
|
|
82
|
+
*
|
|
83
|
+
* The compiled validator is cached by **schema object identity**, so pass a
|
|
84
|
+
* stable object: hold the parsed manifest and hand the same schema back on
|
|
85
|
+
* every call. A schema rebuilt per call is a cache miss every time — it still
|
|
86
|
+
* validates correctly, but recompiles the validator on each call, silently and
|
|
87
|
+
* with nothing to show for it but latency.
|
|
88
|
+
*
|
|
89
|
+
* `api()` uses this internally; it is exported for connectors that implement
|
|
90
|
+
* the `Connector` interface directly.
|
|
91
|
+
*/
|
|
92
|
+
export function validateToolInput(
|
|
93
|
+
schema: JsonSchema,
|
|
94
|
+
args: unknown,
|
|
95
|
+
opts: ValidateToolInputOptions,
|
|
96
|
+
): ConnectorCallError | null {
|
|
97
|
+
const logger = opts.logger ?? console;
|
|
98
|
+
let validator = validators.get(schema);
|
|
99
|
+
if (validator === undefined) {
|
|
100
|
+
try {
|
|
101
|
+
validator = new Validator(schema as never, "2020-12", false);
|
|
102
|
+
validators.set(schema, validator);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
disableValidation(schema, opts.address, logger, err);
|
|
105
|
+
validator = null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// A schema the validator could not compile (or that a prior call disabled):
|
|
109
|
+
// pass through by default, refuse when the caller opted into fail-closed.
|
|
110
|
+
if (validator === null) {
|
|
111
|
+
return opts.failClosed ? unevaluableSchema(opts.address) : null;
|
|
112
|
+
}
|
|
113
|
+
let result;
|
|
114
|
+
try {
|
|
115
|
+
result = validator.validate(args);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
// e.g. an unresolvable $ref — surfaces on first validate, not compile.
|
|
118
|
+
disableValidation(schema, opts.address, logger, err);
|
|
119
|
+
return opts.failClosed ? unevaluableSchema(opts.address) : null;
|
|
120
|
+
}
|
|
121
|
+
if (result && !result.valid) {
|
|
122
|
+
const units = result.errors.filter((u) => u.instanceLocation !== "#");
|
|
123
|
+
const detail = (units.length > 0 ? units : result.errors)
|
|
124
|
+
.slice(0, 3)
|
|
125
|
+
.map((u) => `${u.instanceLocation}: ${u.error}`)
|
|
126
|
+
.join("; ");
|
|
127
|
+
return new ConnectorCallError(
|
|
128
|
+
"invalid_args",
|
|
129
|
+
`Invalid arguments for "${opts.address}": ${detail || "input does not match the tool's inputSchema"}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Eagerly compile and cache a tool's inputSchema so a schema the validator
|
|
137
|
+
* cannot use surfaces once at connector construction rather than silently on
|
|
138
|
+
* the first call. Reuses the same module-level cache `validateToolInput` reads,
|
|
139
|
+
* so the runtime path hits the cache. Warning-only: it never throws and never
|
|
140
|
+
* changes call behavior. A schema that only fails on first `validate()` (e.g.
|
|
141
|
+
* an unresolvable `$ref`) still slips through here and is caught at call time.
|
|
142
|
+
*/
|
|
143
|
+
export function precompileValidator(
|
|
144
|
+
schema: JsonSchema,
|
|
145
|
+
opts: PrecompileValidatorOptions,
|
|
146
|
+
): void {
|
|
147
|
+
if (validators.has(schema)) return;
|
|
148
|
+
const logger = opts.logger ?? console;
|
|
149
|
+
try {
|
|
150
|
+
validators.set(schema, new Validator(schema as never, "2020-12", false));
|
|
151
|
+
} catch (err) {
|
|
152
|
+
disableValidation(schema, opts.address, logger, err);
|
|
153
|
+
}
|
|
154
|
+
}
|
package/src/version.ts
CHANGED