@zackbart/connecta 0.18.3 → 0.20.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 +131 -4
- package/dist/apps-shell.d.ts +10 -12
- package/dist/apps-shell.js +29 -220
- package/dist/catalog-service.d.ts +16 -13
- package/dist/catalog-service.js +106 -115
- package/dist/catalog.js +29 -46
- package/dist/connector-scope.js +2 -7
- package/dist/connectors/api.d.ts +4 -16
- package/dist/connectors/api.js +19 -46
- package/dist/connectors/guarded-fetch.d.ts +9 -23
- package/dist/connectors/guarded-fetch.js +38 -76
- package/dist/connectors/remote-mcp.js +36 -79
- package/dist/errors.d.ts +6 -27
- package/dist/errors.js +8 -5
- package/dist/execute.d.ts +23 -28
- package/dist/execute.js +101 -257
- package/dist/executor-result.d.ts +1 -0
- package/dist/executor-result.js +4 -11
- package/dist/executors/quickjs-child.js +1 -3
- package/dist/executors/quickjs-runtime.js +1 -3
- package/dist/executors/quickjs.js +1 -3
- package/dist/index.js +134 -123
- package/dist/invocation.d.ts +1 -1
- package/dist/invocation.js +113 -183
- package/dist/meta-tools.d.ts +15 -29
- package/dist/meta-tools.js +41 -582
- package/dist/operator-ui/generated.d.ts +2 -2
- package/dist/providers/cloudflare.d.ts +2 -18
- package/dist/providers/cloudflare.js +1460 -2451
- package/dist/providers/linear.d.ts +4 -41
- package/dist/providers/linear.js +8 -39
- package/dist/providers/mixpanel.d.ts +3 -25
- package/dist/providers/mixpanel.js +7 -22
- package/dist/providers/notion.d.ts +1 -15
- package/dist/providers/notion.js +44 -173
- package/dist/providers/revenuecat.d.ts +4 -57
- package/dist/providers/revenuecat.js +10 -93
- package/dist/providers/stripe.d.ts +1 -12
- package/dist/providers/stripe.js +7 -45
- package/dist/registry.d.ts +9 -34
- package/dist/registry.js +9 -103
- package/dist/routes/mcp.js +1 -1
- package/dist/routes/oauth.js +3 -3
- package/dist/routes/shared.d.ts +15 -15
- package/dist/routes/shared.js +1 -3
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +5 -5
- package/dist/timeout.d.ts +8 -7
- package/dist/timeout.js +47 -38
- package/dist/types.d.ts +3 -3
- package/dist/ui.d.ts +1 -25
- package/dist/ui.js +18 -45
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/call-admission.md +1 -1
- package/documentation/cloudflare.md +1 -1
- package/documentation/code-mode.md +25 -25
- package/documentation/connectors.md +24 -1
- package/documentation/linear.md +1 -1
- package/documentation/meta-tools.md +4 -30
- package/documentation/mixpanel.md +1 -1
- package/documentation/notion.md +1 -1
- package/documentation/operations.md +29 -25
- package/documentation/provider-conventions.md +4 -5
- package/documentation/revenuecat.md +1 -1
- package/documentation/stripe.md +1 -1
- package/documentation/upgrading.md +43 -8
- package/ethos.md +75 -121
- package/package.json +3 -4
- package/templates/node/package.json +1 -1
- package/documentation/code-first-exploration.md +0 -292
- package/documentation/mcp-2026-07-28.md +0 -46
- package/documentation/mcp-ui-design.md +0 -382
- package/documentation/program-ui-read-calls.md +0 -213
- package/documentation/provider-audit.md +0 -198
- package/documentation/rich-output-design.md +0 -211
package/dist/routes/mcp.js
CHANGED
|
@@ -186,7 +186,7 @@ async function serveMcp(request, opts, baseUrl, actor, registry, runtimeContext)
|
|
|
186
186
|
// it — without this line no host reads execute_code's _meta.ui, no host
|
|
187
187
|
// fetches the shell, and the whole design is inert. This is the one
|
|
188
188
|
// extension connecta advertises; the versioned extensions framework
|
|
189
|
-
// stays declined as a general surface (
|
|
189
|
+
// stays declined as a general surface (https://github.com/zackbart/connecta/blob/main/records/mcp-2026-07-28.md).
|
|
190
190
|
capabilities: {
|
|
191
191
|
extensions: {
|
|
192
192
|
[MCP_APPS_EXTENSION]: { mimeTypes: [PROGRAM_UI_MIME_TYPE] },
|
package/dist/routes/oauth.js
CHANGED
|
@@ -174,9 +174,9 @@ function html(body, status = 200, branding) {
|
|
|
174
174
|
* list the flat 400 was meant to withhold. So zero-I/O refusals read the same
|
|
175
175
|
* keys in the same `conn:<id>:` namespace, where an unconfigured id gets misses.
|
|
176
176
|
*
|
|
177
|
-
* This is deliberately *not* a constant-time claim
|
|
178
|
-
*
|
|
179
|
-
*
|
|
177
|
+
* This is deliberately *not* a constant-time claim: a hit and a miss are not
|
|
178
|
+
* identical in a KV store, and a connector shipping its own `verifyState` may
|
|
179
|
+
* do more or less work. What it
|
|
180
180
|
* removes is the order-of-magnitude "no I/O versus a round trip" difference,
|
|
181
181
|
* which is the only part of the signal that makes enumeration cheap.
|
|
182
182
|
*
|
package/dist/routes/shared.d.ts
CHANGED
|
@@ -6,38 +6,39 @@ import type { DeferredWork } from "../connector-scope.js";
|
|
|
6
6
|
import type { AdmissionController } from "../executor-admission.js";
|
|
7
7
|
import type { Registry } from "../registry.js";
|
|
8
8
|
import type { ConnectaBranding, Executor, InboundAuth, Logger } from "../types.js";
|
|
9
|
+
export { msg } from "../errors.js";
|
|
9
10
|
export interface ServerOptions {
|
|
10
11
|
registry: Registry;
|
|
11
12
|
auth: InboundAuth[];
|
|
12
|
-
publicUrl?: string;
|
|
13
|
+
publicUrl?: string | undefined;
|
|
13
14
|
serverInfo: Implementation;
|
|
14
15
|
logger: Logger;
|
|
15
|
-
activity?: ActivityStore;
|
|
16
|
-
activityReadGate?: ActivityReadGate;
|
|
17
|
-
activityDeploymentId?: string;
|
|
18
|
-
deploymentInfo?: Record<string, unknown
|
|
16
|
+
activity?: ActivityStore | undefined;
|
|
17
|
+
activityReadGate?: ActivityReadGate | undefined;
|
|
18
|
+
activityDeploymentId?: string | undefined;
|
|
19
|
+
deploymentInfo?: Record<string, unknown> | undefined;
|
|
19
20
|
/** Deadline for call_tool/call_destructive_tool calls that pass no timeoutMs. Off when unset. */
|
|
20
|
-
defaultToolTimeoutMs?: number;
|
|
21
|
+
defaultToolTimeoutMs?: number | undefined;
|
|
21
22
|
/** Per-connector deadline for the search/describe probe fan-out. Default 30_000. */
|
|
22
|
-
probeTimeoutMs?: number;
|
|
23
|
+
probeTimeoutMs?: number | undefined;
|
|
23
24
|
/** Maximum simultaneous connector discovery operations. Default 4. */
|
|
24
|
-
discoveryConcurrency?: number;
|
|
25
|
+
discoveryConcurrency?: number | undefined;
|
|
25
26
|
/** Aggregate serialized-byte budget for connecta.emit per run. Default 4_000_000. */
|
|
26
|
-
maxEmittedBytes?: number;
|
|
27
|
+
maxEmittedBytes?: number | undefined;
|
|
27
28
|
/** Block-count budget for connecta.emit per run. Default 32. */
|
|
28
|
-
maxEmittedBlocks?: number;
|
|
29
|
+
maxEmittedBlocks?: number | undefined;
|
|
29
30
|
/** Required sandbox backing the execute_code meta-tool. */
|
|
30
31
|
executor: Executor;
|
|
31
32
|
/** Sanitized identity of the configured sandbox, when it has one. */
|
|
32
|
-
executorName?: string;
|
|
33
|
+
executorName?: string | undefined;
|
|
33
34
|
/** Global FIFO boundary for all non-preflight `/mcp` requests. */
|
|
34
35
|
requestAdmission: AdmissionController;
|
|
35
36
|
/** Encrypted connector-credential storage backing the Credentials page. */
|
|
36
|
-
credentialVault?: CredentialVault;
|
|
37
|
+
credentialVault?: CredentialVault | undefined;
|
|
37
38
|
/** Hashed deployment access tokens backing MCP admission and the Tokens page. */
|
|
38
|
-
accessTokens?: AccessTokenManager;
|
|
39
|
+
accessTokens?: AccessTokenManager | undefined;
|
|
39
40
|
/** Optional browser UI and OAuth result-page labels. */
|
|
40
|
-
branding?: ConnectaBranding;
|
|
41
|
+
branding?: ConnectaBranding | undefined;
|
|
41
42
|
}
|
|
42
43
|
export interface RuntimeExecutionContext {
|
|
43
44
|
waitUntil(promise: Promise<unknown>): void;
|
|
@@ -51,7 +52,6 @@ export interface RouteContext {
|
|
|
51
52
|
defer: DeferredWork | undefined;
|
|
52
53
|
runtimeContext: RuntimeExecutionContext | undefined;
|
|
53
54
|
}
|
|
54
|
-
export declare function msg(err: unknown): string;
|
|
55
55
|
export declare function privateJson(body: unknown, init?: ResponseInit): Response;
|
|
56
56
|
/**
|
|
57
57
|
* Bounded, escaped form of a caller-influenced value (an identity id or OAuth
|
package/dist/routes/shared.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { operatorPageForPath } from "../ui.js";
|
|
2
|
-
export
|
|
3
|
-
return err instanceof Error ? err.message : String(err);
|
|
4
|
-
}
|
|
2
|
+
export { msg } from "../errors.js";
|
|
5
3
|
export function privateJson(body, init = {}) {
|
|
6
4
|
const headers = new Headers(init.headers);
|
|
7
5
|
headers.set("Content-Type", "application/json");
|
package/dist/skills.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Connector } from "./types.js";
|
|
2
|
-
export declare const CONNECTA_INSTRUCTIONS = "Choose a route before discovery. For one read at an unknown address, use search_tools then call_tool; a known address needs only call_tool. For read-only reduction, multiple or dependent calls, loops, joins, or branches, use one execute_code program that discovers, calls, and returns the reduced answer. Only readOnlyHint: true tools run there. Keep unannotated, write-capable, or destructive work top level: search_tools then call_destructive_tool. After auth_required use authorize_connector. After a truncated direct result use
|
|
2
|
+
export declare const CONNECTA_INSTRUCTIONS = "Choose a route before discovery. For one read at an unknown address, use search_tools then call_tool; a known address needs only call_tool. For read-only reduction, multiple or dependent calls, loops, joins, or branches, use one execute_code program that discovers, calls, and returns the reduced answer. Only readOnlyHint: true tools run there. Keep unannotated, write-capable, or destructive work top level: search_tools then call_destructive_tool. After auth_required use authorize_connector. After a truncated direct result use get_result. connecta.ui(html) exists only inside execute_code, not in connector search; return the same summary data the HTML renders. Fetch skills({ name: \"usage\" }) once for program syntax, selection, repair, examples, and runtime details.";
|
|
3
3
|
/** Shared Connecta routing guidance, byte-identical across deployments. */
|
|
4
4
|
export declare const USAGE_SKILL: string;
|
|
5
5
|
/** The always-loaded MCP `instructions` string. */
|
package/dist/skills.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const CONNECTA_INSTRUCTIONS = 'Choose a route before discovery. For one read at an unknown address, use search_tools then call_tool; a known address needs only call_tool. For read-only reduction, multiple or dependent calls, loops, joins, or branches, use one execute_code program that discovers, calls, and returns the reduced answer. Only readOnlyHint: true tools run there. Keep unannotated, write-capable, or destructive work top level: search_tools then call_destructive_tool. After auth_required use authorize_connector. After a truncated direct result use
|
|
1
|
+
export const CONNECTA_INSTRUCTIONS = 'Choose a route before discovery. For one read at an unknown address, use search_tools then call_tool; a known address needs only call_tool. For read-only reduction, multiple or dependent calls, loops, joins, or branches, use one execute_code program that discovers, calls, and returns the reduced answer. Only readOnlyHint: true tools run there. Keep unannotated, write-capable, or destructive work top level: search_tools then call_destructive_tool. After auth_required use authorize_connector. After a truncated direct result use get_result. connecta.ui(html) exists only inside execute_code, not in connector search; return the same summary data the HTML renders. Fetch skills({ name: "usage" }) once for program syntax, selection, repair, examples, and runtime details.';
|
|
2
2
|
const USAGE_SKILL_BASE = `# Connecta usage
|
|
3
3
|
|
|
4
4
|
## The surface
|
|
@@ -17,7 +17,7 @@ The minimum guest API is:
|
|
|
17
17
|
- \`connecta.call("connector.tool", args)\` uses the canonical address and returns the unwrapped value.
|
|
18
18
|
- \`connecta.search(args)\` returns \`{ tools, total, offset, limit, hasMore }\`; \`connecta.describe(args)\` returns \`{ tools }\`.
|
|
19
19
|
- \`connecta.batch(calls)\` runs 2–10 independent calls. Each outcome is \`{ address, ok: true, data }\` or \`{ address, ok: false, error, errorDetails }\`.
|
|
20
|
-
- \`console.log(...)\` is captured. \`connecta.emit(block)\` and \`connecta.ui(html
|
|
20
|
+
- \`console.log(...)\` is captured. \`connecta.emit(block)\` and \`connecta.ui(html)\` produce rich output.
|
|
21
21
|
|
|
22
22
|
## Discover and select
|
|
23
23
|
|
|
@@ -39,10 +39,10 @@ Caught Connecta errors expose \`message\`, \`code\`, \`retryable\`, and \`detail
|
|
|
39
39
|
|
|
40
40
|
- \`destructive_tool_requires_approval\`: stop the program and use the returned canonical address with top-level \`call_destructive_tool\`.
|
|
41
41
|
- \`auth_required\`: let the failure reach the model, then use top-level \`authorize_connector\`, give its handoff to the operator, and retry after recovery.
|
|
42
|
-
- A truncated direct-call result:
|
|
42
|
+
- A truncated direct-call result: follow its \`get_result\` action. A truncated program result has no page handle; filter, map, or slice inside a new program.
|
|
43
43
|
- Unknown addresses and tools carry scoped search recovery. Use it inside the current run. Do not invent an address.
|
|
44
44
|
|
|
45
|
-
For a direct call, \`
|
|
45
|
+
For a direct call, \`resultMode: "value"\` unwraps the result. \`timeoutMs\` sets its deadline. \`maxRetries\` is honored only for safely annotated tools. \`diagnostics: true\` adds timing.
|
|
46
46
|
|
|
47
47
|
\`get_result({ id, offset?, maxBytes? })\` returns \`{ text, offset, nextOffset?, totalBytes }\` for a direct-call result. Both sizes are byte counts: \`maxBytes\` must be a whole number at least 1 and defaults to the deployment cap; \`offset\` must be a whole number at least 0 and defaults to 0. An offset inside a multi-byte character moves back to its first byte, and the response reports the served offset. Follow \`nextOffset\` to reassemble pages. An unknown or expired id is an error.
|
|
48
48
|
|
|
@@ -64,7 +64,7 @@ Dependent calls, only when the second needs a value from the first:
|
|
|
64
64
|
|
|
65
65
|
## Rendering a view
|
|
66
66
|
|
|
67
|
-
\`connecta.emit\` accepts text, image, or audio blocks and delivers them only on success. \`connecta.ui(html)\` renders one success-only display view outside model context.
|
|
67
|
+
\`connecta.emit\` accepts text, image, or audio blocks and delivers them only on success. \`connecta.ui(html)\` renders one success-only display view outside model context. The view has local HTML and JavaScript only: no network, connector calls, discovery, conversation messages, or host links. One shared budget applies to the UI and emitted content, not separate budgets. Fetch and check the data shape first. On empty or missing data, return a trimmed first record instead of rendering. Otherwise render returned variables and return the same summary because the model reads the return value, not the view. A second, invalid, or over-budget UI call throws catchably.
|
|
68
68
|
|
|
69
69
|
`;
|
|
70
70
|
/** Deployment-scoped guide routing appended to the shared usage guide. */
|
package/dist/timeout.d.ts
CHANGED
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
export declare const DEFAULT_PROBE_TIMEOUT_MS = 30000;
|
|
6
6
|
/** A finite, positive integer number of milliseconds, or undefined. */
|
|
7
7
|
export declare function normalizeTimeoutMs(value: number | undefined): number | undefined;
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
/** Resolve after `ms`, or false when the caller aborts first. */
|
|
9
|
+
export declare function sleep(ms: number, signal?: AbortSignal): Promise<boolean>;
|
|
10
|
+
export interface DeadlineOptions {
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
timeoutError: Error;
|
|
14
|
+
}
|
|
15
|
+
export declare function withDeadline<T>(operation: (signal: AbortSignal) => Promise<T>, options: DeadlineOptions): Promise<T>;
|
|
15
16
|
/**
|
|
16
17
|
* Give one operation a caller-facing deadline and the matching cancellation
|
|
17
18
|
* signal. The timeout rejects with the stable, labelled error while aborting
|
package/dist/timeout.js
CHANGED
|
@@ -12,24 +12,53 @@ export function normalizeTimeoutMs(value) {
|
|
|
12
12
|
}
|
|
13
13
|
return Math.max(1, Math.trunc(value));
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const timer = setTimeout(() => {
|
|
24
|
-
reject(new Error(`${label} timed out after ${ms}ms`));
|
|
25
|
-
}, ms);
|
|
26
|
-
promise.then((value) => {
|
|
15
|
+
/** Resolve after `ms`, or false when the caller aborts first. */
|
|
16
|
+
export function sleep(ms, signal) {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
let settled = false;
|
|
19
|
+
const finish = (value) => {
|
|
20
|
+
if (settled)
|
|
21
|
+
return;
|
|
22
|
+
settled = true;
|
|
27
23
|
clearTimeout(timer);
|
|
24
|
+
signal?.removeEventListener("abort", cancel);
|
|
28
25
|
resolve(value);
|
|
29
|
-
}
|
|
26
|
+
};
|
|
27
|
+
const timer = setTimeout(() => finish(true), ms);
|
|
28
|
+
const cancel = () => finish(false);
|
|
29
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
30
|
+
if (signal?.aborted)
|
|
31
|
+
cancel();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export function withDeadline(operation, options) {
|
|
35
|
+
const controller = new AbortController();
|
|
36
|
+
const forwardAbort = () => controller.abort(options.signal?.reason);
|
|
37
|
+
options.signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
38
|
+
if (options.signal?.aborted)
|
|
39
|
+
forwardAbort();
|
|
40
|
+
let rejectAbort;
|
|
41
|
+
const aborted = new Promise((_, reject) => { rejectAbort = reject; });
|
|
42
|
+
const onAbort = () => rejectAbort(controller.signal.reason ?? options.timeoutError);
|
|
43
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
44
|
+
if (controller.signal.aborted)
|
|
45
|
+
onAbort();
|
|
46
|
+
const timer = options.timeoutMs === undefined
|
|
47
|
+
? undefined
|
|
48
|
+
: setTimeout(() => controller.abort(options.timeoutError), options.timeoutMs);
|
|
49
|
+
let work;
|
|
50
|
+
try {
|
|
51
|
+
work = operation(controller.signal);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
work = Promise.reject(error);
|
|
55
|
+
}
|
|
56
|
+
return Promise.race([work, aborted]).finally(() => {
|
|
57
|
+
controller.abort();
|
|
58
|
+
if (timer)
|
|
30
59
|
clearTimeout(timer);
|
|
31
|
-
|
|
32
|
-
|
|
60
|
+
controller.signal.removeEventListener("abort", onAbort);
|
|
61
|
+
options.signal?.removeEventListener("abort", forwardAbort);
|
|
33
62
|
});
|
|
34
63
|
}
|
|
35
64
|
/**
|
|
@@ -38,28 +67,8 @@ export function withTimeout(promise, ms, label) {
|
|
|
38
67
|
* any in-flight work that honors the signal.
|
|
39
68
|
*/
|
|
40
69
|
export function withAbortableTimeout(operation, ms, label) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const timer = setTimeout(() => {
|
|
45
|
-
controller.abort(timeoutError);
|
|
46
|
-
reject(timeoutError);
|
|
47
|
-
}, ms);
|
|
48
|
-
let promise;
|
|
49
|
-
try {
|
|
50
|
-
promise = operation(controller.signal);
|
|
51
|
-
}
|
|
52
|
-
catch (err) {
|
|
53
|
-
clearTimeout(timer);
|
|
54
|
-
reject(err);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
promise.then((value) => {
|
|
58
|
-
clearTimeout(timer);
|
|
59
|
-
resolve(value);
|
|
60
|
-
}, (err) => {
|
|
61
|
-
clearTimeout(timer);
|
|
62
|
-
reject(err);
|
|
63
|
-
});
|
|
70
|
+
return withDeadline(operation, {
|
|
71
|
+
timeoutMs: ms,
|
|
72
|
+
timeoutError: new Error(`${label} timed out after ${ms}ms`),
|
|
64
73
|
});
|
|
65
74
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -311,8 +311,8 @@ export interface Connector {
|
|
|
311
311
|
* download link minted by one of the connector's tools. Called only after
|
|
312
312
|
* every built-in route misses, so a connector can never shadow `/mcp`,
|
|
313
313
|
* `/`, `/credentials`, `/activity`, `/health`, or the credential API. The
|
|
314
|
-
* first connector to return a
|
|
315
|
-
*
|
|
314
|
+
* first connector to return a Response wins, in registration order; return
|
|
315
|
+
* null to decline. See documentation/architecture.md route 9.
|
|
316
316
|
*
|
|
317
317
|
* These routes are PUBLIC: connecta applies no auth gate to them. A
|
|
318
318
|
* connector that serves data here MUST authenticate the request itself — for
|
|
@@ -351,7 +351,7 @@ export interface ExecutorProvider {
|
|
|
351
351
|
* Optional trusted sandbox-side setup run after provider globals exist.
|
|
352
352
|
* Connecta uses this to install lazy connector namespace proxies without
|
|
353
353
|
* materializing one host closure per tool. This is host-authored code, never
|
|
354
|
-
* model input.
|
|
354
|
+
* model input. See documentation/code-mode.md#what-an-executor-must-implement.
|
|
355
355
|
*/
|
|
356
356
|
prelude?: string;
|
|
357
357
|
}
|
package/dist/ui.d.ts
CHANGED
|
@@ -26,11 +26,6 @@ export declare function resolveBranding(branding?: ConnectaBranding): ResolvedBr
|
|
|
26
26
|
* `resolveBranding` does — a warning helper must never throw.
|
|
27
27
|
*/
|
|
28
28
|
export declare function droppedBrandingUrls(branding?: ConnectaBranding): string[];
|
|
29
|
-
/**
|
|
30
|
-
* True only for absolute `http:`/`https:` URLs. Downstream connectors control
|
|
31
|
-
* their `authorizationUrl`, so a hostile/misconfigured one could hand back a
|
|
32
|
-
* `javascript:` (or other) scheme; gate it before it can become an href.
|
|
33
|
-
*/
|
|
34
29
|
export declare function isSafeHttpUrl(url: unknown): boolean;
|
|
35
30
|
/**
|
|
36
31
|
* True for values allowed in the page's `<link rel="icon" href>`: an absolute
|
|
@@ -52,26 +47,7 @@ export declare function isSafeHttpUrl(url: unknown): boolean;
|
|
|
52
47
|
* on its own it would accept an authority that happened to equal the probe host.
|
|
53
48
|
*/
|
|
54
49
|
export declare function isSafeIconHref(href: unknown): boolean;
|
|
55
|
-
/**
|
|
56
|
-
* True only for an absolute `https:` URL — the gate every `uiAuth` URL passes:
|
|
57
|
-
* `frontendApiUrl`, which becomes the operator shell's sign-in loader source,
|
|
58
|
-
* and `signInUrl`/`signUpUrl`, which ClerkJS uses as *navigation targets* when
|
|
59
|
-
* the operator signs in. With those three gated, no operator-config value
|
|
60
|
-
* reaches the browser in a URL position — attribute or navigation — without
|
|
61
|
-
* validation, and there is no exception left to remember.
|
|
62
|
-
*
|
|
63
|
-
* Stricter than `isSafeHttpUrl` on purpose: no `http:` carve-out, no loopback
|
|
64
|
-
* carve-out, and no relative form. Nobody types `frontendApiUrl` — the shipped
|
|
65
|
-
* Clerk adapter derives it from the publishable key, and Clerk's Frontend API is
|
|
66
|
-
* always https — and a cleartext script source on an operator page would be a
|
|
67
|
-
* downgrade even where a browser's mixed-content rules had not already blocked
|
|
68
|
-
* it. `signInUrl`/`signUpUrl` *are* typed by the operator, but what belongs
|
|
69
|
-
* there is a hosted Account Portal address (`https://accounts.<domain>` or
|
|
70
|
-
* `https://<slug>.accounts.dev`), which is https as well; `http:` would carry a
|
|
71
|
-
* sign-in over cleartext, and a path relative to this origin is meaningless
|
|
72
|
-
* because this server hosts no sign-in page of its own. So the looser gate would
|
|
73
|
-
* buy nothing real, and the same strictness holds for all three.
|
|
74
|
-
*/
|
|
50
|
+
/** Absolute HTTPS gate for the `UiAuthConfig` URL fields documented in types.ts. */
|
|
75
51
|
export declare function isSafeHttpsUrl(url: unknown): boolean;
|
|
76
52
|
/**
|
|
77
53
|
* Names of the `uiAuth` URLs an inbound-auth provider supplied that failed their
|
package/dist/ui.js
CHANGED
|
@@ -95,17 +95,19 @@ export function droppedBrandingUrls(branding) {
|
|
|
95
95
|
* their `authorizationUrl`, so a hostile/misconfigured one could hand back a
|
|
96
96
|
* `javascript:` (or other) scheme; gate it before it can become an href.
|
|
97
97
|
*/
|
|
98
|
-
|
|
98
|
+
function safeUrl(url, schemes) {
|
|
99
99
|
if (typeof url !== "string")
|
|
100
100
|
return false;
|
|
101
101
|
try {
|
|
102
|
-
|
|
103
|
-
return scheme === "http:" || scheme === "https:";
|
|
102
|
+
return schemes.includes(new URL(url).protocol);
|
|
104
103
|
}
|
|
105
104
|
catch {
|
|
106
105
|
return false;
|
|
107
106
|
}
|
|
108
107
|
}
|
|
108
|
+
export function isSafeHttpUrl(url) {
|
|
109
|
+
return safeUrl(url, ["http:", "https:"]);
|
|
110
|
+
}
|
|
109
111
|
/**
|
|
110
112
|
* Only the second check's base; any origin works because the check is whether
|
|
111
113
|
* the href stays on whatever origin it is resolved against. It is deliberately
|
|
@@ -149,35 +151,9 @@ export function isSafeIconHref(href) {
|
|
|
149
151
|
return false;
|
|
150
152
|
}
|
|
151
153
|
}
|
|
152
|
-
/**
|
|
153
|
-
* True only for an absolute `https:` URL — the gate every `uiAuth` URL passes:
|
|
154
|
-
* `frontendApiUrl`, which becomes the operator shell's sign-in loader source,
|
|
155
|
-
* and `signInUrl`/`signUpUrl`, which ClerkJS uses as *navigation targets* when
|
|
156
|
-
* the operator signs in. With those three gated, no operator-config value
|
|
157
|
-
* reaches the browser in a URL position — attribute or navigation — without
|
|
158
|
-
* validation, and there is no exception left to remember.
|
|
159
|
-
*
|
|
160
|
-
* Stricter than `isSafeHttpUrl` on purpose: no `http:` carve-out, no loopback
|
|
161
|
-
* carve-out, and no relative form. Nobody types `frontendApiUrl` — the shipped
|
|
162
|
-
* Clerk adapter derives it from the publishable key, and Clerk's Frontend API is
|
|
163
|
-
* always https — and a cleartext script source on an operator page would be a
|
|
164
|
-
* downgrade even where a browser's mixed-content rules had not already blocked
|
|
165
|
-
* it. `signInUrl`/`signUpUrl` *are* typed by the operator, but what belongs
|
|
166
|
-
* there is a hosted Account Portal address (`https://accounts.<domain>` or
|
|
167
|
-
* `https://<slug>.accounts.dev`), which is https as well; `http:` would carry a
|
|
168
|
-
* sign-in over cleartext, and a path relative to this origin is meaningless
|
|
169
|
-
* because this server hosts no sign-in page of its own. So the looser gate would
|
|
170
|
-
* buy nothing real, and the same strictness holds for all three.
|
|
171
|
-
*/
|
|
154
|
+
/** Absolute HTTPS gate for the `UiAuthConfig` URL fields documented in types.ts. */
|
|
172
155
|
export function isSafeHttpsUrl(url) {
|
|
173
|
-
|
|
174
|
-
return false;
|
|
175
|
-
try {
|
|
176
|
-
return new URL(url).protocol === "https:";
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
156
|
+
return safeUrl(url, ["https:"]);
|
|
181
157
|
}
|
|
182
158
|
/**
|
|
183
159
|
* Names of the `uiAuth` URLs an inbound-auth provider supplied that failed their
|
|
@@ -303,18 +279,21 @@ export async function buildUiData(registry, baseUrl, serverInfo, credentialVault
|
|
|
303
279
|
: {}),
|
|
304
280
|
};
|
|
305
281
|
});
|
|
282
|
+
const credentialCard = {
|
|
283
|
+
label: c.credential.label,
|
|
284
|
+
...(c.credential.description
|
|
285
|
+
? { description: c.credential.description }
|
|
286
|
+
: {}),
|
|
287
|
+
...(c.credential.placeholder
|
|
288
|
+
? { placeholder: c.credential.placeholder }
|
|
289
|
+
: {}),
|
|
290
|
+
};
|
|
306
291
|
try {
|
|
307
292
|
const metadata = await credentialVault.metadata(c.id);
|
|
308
293
|
const fields = credentialFields(metadata);
|
|
309
294
|
const shape = storedCredentialShape(c.credential, metadata?.fields ?? null);
|
|
310
295
|
credential = {
|
|
311
|
-
|
|
312
|
-
...(c.credential.description
|
|
313
|
-
? { description: c.credential.description }
|
|
314
|
-
: {}),
|
|
315
|
-
...(c.credential.placeholder
|
|
316
|
-
? { placeholder: c.credential.placeholder }
|
|
317
|
-
: {}),
|
|
296
|
+
...credentialCard,
|
|
318
297
|
...(fields?.length ? { fields } : {}),
|
|
319
298
|
configured: shape.state === "valid",
|
|
320
299
|
removable: Boolean(metadata),
|
|
@@ -341,13 +320,7 @@ export async function buildUiData(registry, baseUrl, serverInfo, credentialVault
|
|
|
341
320
|
catch {
|
|
342
321
|
const fields = credentialFields();
|
|
343
322
|
credential = {
|
|
344
|
-
|
|
345
|
-
...(c.credential.description
|
|
346
|
-
? { description: c.credential.description }
|
|
347
|
-
: {}),
|
|
348
|
-
...(c.credential.placeholder
|
|
349
|
-
? { placeholder: c.credential.placeholder }
|
|
350
|
-
: {}),
|
|
323
|
+
...credentialCard,
|
|
351
324
|
...(fields?.length ? { fields } : {}),
|
|
352
325
|
configured: false,
|
|
353
326
|
removable: true,
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -140,4 +140,4 @@ typed error code.
|
|
|
140
140
|
| --- | --- |
|
|
141
141
|
| Independent partitions, exact rolling-window reset and retry, queued cancellation charging no budget, synchronous cancel during partition derivation, validated values snapshotted rather than read from mutable config, bounded partition state and contained `partitionKey` failures, empty and multi-rule policies refused | `test/call-admission.test.ts` (controller) |
|
|
142
142
|
| One base-registry limiter shared by direct and program calls, batch bounds with input order preserved, cancellation threading, no dispatch or retry or health poisoning after cancellation, short proactive windows retried without poisoning health, payload-free `/health` aggregates | `test/call-admission.test.ts` (integration, Node + Workers) |
|
|
143
|
-
| Where provider budgets are allowed to come from at all | [provider conventions P12](./provider-conventions.md#p12--declare-an-admission-budget-only-when-the-provider-documents-a-number), [provider audit](
|
|
143
|
+
| Where provider budgets are allowed to come from at all | [provider conventions P12](./provider-conventions.md#p12--declare-an-admission-budget-only-when-the-provider-documents-a-number), [provider audit](https://github.com/zackbart/connecta/blob/main/records/provider-audit.md) |
|
|
@@ -410,4 +410,4 @@ the window notices.
|
|
|
410
410
|
This connection is audited against
|
|
411
411
|
[the provider conventions](./provider-conventions.md). Its verdict per
|
|
412
412
|
convention, including every recorded exception, is the Cloudflare section of
|
|
413
|
-
[the provider audit](
|
|
413
|
+
[the provider audit](https://github.com/zackbart/connecta/blob/main/records/provider-audit.md).
|
|
@@ -5,14 +5,14 @@ is promised: what it can reach, what it gets back, how failures look, what it
|
|
|
5
5
|
may retry, what bounds it runs under, and what its execution leaves behind in
|
|
6
6
|
the activity surface. It is the interface a model actually programs against, so
|
|
7
7
|
it is specified in prose first and implemented second — the same discipline the
|
|
8
|
-
[MCP spec bump](
|
|
8
|
+
[MCP spec bump](https://github.com/zackbart/connecta/blob/main/records/mcp-2026-07-28.md) followed.
|
|
9
9
|
|
|
10
10
|
Two executors implement this document: QuickJS in a child process on Node, and
|
|
11
11
|
`DynamicWorkerExecutor` from `@cloudflare/codemode` on Workers. Divergence between
|
|
12
12
|
them is a bug unless it appears in [Executor exceptions](#executor-exceptions),
|
|
13
13
|
which names the reason. Anyone can implement a third from this document alone.
|
|
14
14
|
|
|
15
|
-
The [code-first exploration](
|
|
15
|
+
The [code-first exploration](https://github.com/zackbart/connecta/blob/main/records/code-first-exploration.md) is the evidence behind
|
|
16
16
|
the direction; [`ethos.md`](../ethos.md) carries the verdicts. Where its prototype
|
|
17
17
|
and this document disagree, this document wins. Clause identifiers (`A1`, `E3`, …)
|
|
18
18
|
are stable and cited by the tests in [Verification](#verification).
|
|
@@ -173,7 +173,7 @@ word gets `_` appended (`my-service.get.thing` → `my_service.get_thing`). The
|
|
|
173
173
|
globals are lazy: no catalog is fetched until a program touches one. The
|
|
174
174
|
bounded deployment inventory in the `execute_code` description shows each
|
|
175
175
|
canonical connector id and labels the shortcut only when it differs; the
|
|
176
|
-
[discovery guide](./meta-tools.md#discovery-context) defines that bound.
|
|
176
|
+
[discovery guide](./meta-tools.md#discovery-context) defines that bound. The sugar is frozen: every expansion invents a collision class `A1` already solves ([#223](https://github.com/zackbart/connecta/issues/223)).
|
|
177
177
|
|
|
178
178
|
**A3.** A shortcut that resolves to more than one tool fails closed with
|
|
179
179
|
`ambiguous_tool_alias`, naming the colliding tool names and pointing at
|
|
@@ -426,7 +426,7 @@ on data nobody asked for.
|
|
|
426
426
|
|
|
427
427
|
**R5.** `console.log`, `console.warn`, and `console.error` are captured in call order and returned as a single `logs` string, capped at 4,000 characters with a truncation marker. Logs survive failure — they ride along with the error result, which is what makes them worth writing. How a non-string argument renders is not contract (`X4`).
|
|
428
428
|
|
|
429
|
-
**R6.** Nothing else is added to a normal program result. Passing `diagnostics: true` adds one request-local, payload-free `diagnostics` block; a program that emitted adds `emitted: N` and its blocks (`M2`). Omitted, `false`, and emit-free are byte-for-byte the ordinary response path.
|
|
429
|
+
**R6.** Nothing else is added to a normal program result. Passing `diagnostics: true` adds one request-local, payload-free `diagnostics` block; a program that emitted adds `emitted: N` and its blocks (`M2`). Omitted, `false`, and emit-free are byte-for-byte the ordinary response path. Diagnostics exist so catalog, connector, and executor costs are distinguishable without persisting payloads or charging normal responses ([#247](https://github.com/zackbart/connecta/issues/247)).
|
|
430
430
|
|
|
431
431
|
**R7.** Timing separates admission, provider setup, total executor wall time, catalog work, and connector work. Catalog and connector values are cumulative, so parallel work can exceed executor wall time. Each used operation kind (`search`, `describe`, `call`, `batch`) gets one aggregate with count, failures, duration, returned serialized bytes, and catalog/connector time; batch adds only its total child count.
|
|
432
432
|
|
|
@@ -437,7 +437,7 @@ on data nobody asked for.
|
|
|
437
437
|
MCP-native output a return value cannot carry: base64 is not projectable, so a
|
|
438
438
|
block that survives intake uncapped (`S5`) must not die at the `R2` exit
|
|
439
439
|
guard. The argument and the refused alternatives live in the
|
|
440
|
-
[design record](
|
|
440
|
+
[design record](https://github.com/zackbart/connecta/blob/main/records/rich-output-design.md) and `ethos.md`
|
|
441
441
|
([#267](https://github.com/zackbart/connecta/issues/267),
|
|
442
442
|
[#270](https://github.com/zackbart/connecta/issues/270)).
|
|
443
443
|
|
|
@@ -498,14 +498,14 @@ one MCP Apps view per successful run, assembled where composition already
|
|
|
498
498
|
happens. Programs supply HTML content and nothing else — the only `ui://` URI in
|
|
499
499
|
the system is connecta's build-time shell, so nothing a client could dereference
|
|
500
500
|
is derived from anything a program said. The argument, the refused shapes, and
|
|
501
|
-
the security posture live in the [design record](
|
|
501
|
+
the security posture live in the [design record](https://github.com/zackbart/connecta/blob/main/records/mcp-ui-design.md)
|
|
502
502
|
([#266](https://github.com/zackbart/connecta/issues/266),
|
|
503
503
|
[#277](https://github.com/zackbart/connecta/issues/277)); this section is the
|
|
504
504
|
contract, and it wins where the two disagree.
|
|
505
505
|
|
|
506
|
-
**U1.** `connecta.ui(html
|
|
507
|
-
|
|
508
|
-
|
|
506
|
+
**U1.** `connecta.ui(html)` accepts exactly one non-empty HTML string. There is
|
|
507
|
+
no options parameter, read manifest, or sugar form. Every other shape throws
|
|
508
|
+
catchably and accepts nothing.
|
|
509
509
|
|
|
510
510
|
**U2.** At most one payload per run. A second call throws catchably, naming the
|
|
511
511
|
constraint; the first accepted payload stands. One tool result renders one view,
|
|
@@ -513,7 +513,7 @@ and last-wins would silently discard a payload the program deliberately
|
|
|
513
513
|
supplied.
|
|
514
514
|
|
|
515
515
|
**U3.** Delivered on success only, and out of model context: the tool result
|
|
516
|
-
gains `_meta["connecta/ui"] = { html
|
|
516
|
+
gains `_meta["connecta/ui"] = { html }` and the JSON envelope gains `ui: true`,
|
|
517
517
|
so the model learns a view rendered without seeing its bytes. `structuredContent`
|
|
518
518
|
stays the envelope alone. The single-label `connecta/ui` prefix is deliberate —
|
|
519
519
|
connecta has no domain to reverse, and fabricating one to satisfy MCP's
|
|
@@ -526,29 +526,31 @@ with `emittedDiscarded: N` when one failure discards both.
|
|
|
526
526
|
|
|
527
527
|
**U4.** The payload spends the aggregate emit byte budget
|
|
528
528
|
(`ConnectaConfig.execute.maxEmittedBytes`), measured at the call as the
|
|
529
|
-
serialized bytes of `{ html
|
|
529
|
+
serialized bytes of `{ html }` — `M5`'s measurement. Over budget throws
|
|
530
530
|
catchably, naming the budget and the room remaining, with nothing partially
|
|
531
531
|
accepted. It spends no block count (`maxEmittedBlocks`: it is not a block) and no
|
|
532
532
|
host-call budget (`L4`). One transport bound covers everything rich a program
|
|
533
533
|
delivers.
|
|
534
534
|
|
|
535
535
|
**U5.** One static shell: a connecta-authored HTML5 document at
|
|
536
|
-
`ui://connecta/program-ui/
|
|
536
|
+
`ui://connecta/program-ui/v3`, mimeType `text/html;profile=mcp-app`, declared on
|
|
537
537
|
`execute_code` via `_meta.ui.resourceUri` together with an explicit
|
|
538
|
-
`_meta.ui.visibility: ["model"]`.
|
|
539
|
-
|
|
540
|
-
|
|
538
|
+
`_meta.ui.visibility: ["model"]`. The other six tools declare the same
|
|
539
|
+
model-only visibility without a resource URI. Omission defaults to model and
|
|
540
|
+
app visibility, which would let a display-only view call them. A
|
|
541
|
+
`resources/read` handler answers exactly that URI and fails on any other;
|
|
542
|
+
`resources/list` is served and returns an empty list.
|
|
541
543
|
The version segment bumps whenever the shell's bytes change, because hosts cache
|
|
542
544
|
templates by URI.
|
|
543
545
|
|
|
544
546
|
**U6.** The shell renders the payload in a nested iframe
|
|
545
547
|
(`srcdoc`, `sandbox="allow-scripts"`, no `allow-same-origin`) and declares no CSP
|
|
546
548
|
domains, so the host applies its restrictive default and the `about:srcdoc` frame
|
|
547
|
-
inherits `default-src 'none'; connect-src 'none'`.
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
549
|
+
inherits `default-src 'none'; connect-src 'none'`. The shell offers no direct
|
|
550
|
+
network, tool calls, discovery, conversation messages, writes, or links. It
|
|
551
|
+
participates in the Apps lifecycle — initialize, tool-result, size-changed,
|
|
552
|
+
resource-teardown — and forwards no channel whatsoever from the inner frame to
|
|
553
|
+
the host. That isolation makes
|
|
552
554
|
program views fixed-height by construction: with no bridge there is no
|
|
553
555
|
content-height signal, the shell reports only its own box, and content taller
|
|
554
556
|
than that scrolls inside the inner frame rather than growing the view.
|
|
@@ -591,16 +593,14 @@ the same mistake as automatic host-side projection, refused in `ethos.md`
|
|
|
591
593
|
|
|
592
594
|
**U13.** The always-loaded MCP instructions locate `connecta.ui(html)` before an
|
|
593
595
|
agent chooses a route: it exists only inside `execute_code`, never in connector
|
|
594
|
-
search, and carries `U12`'s mirrored-return duty. The detailed call,
|
|
595
|
-
|
|
596
|
+
search, and carries `U12`'s mirrored-return duty. The detailed call, budget,
|
|
597
|
+
and repair rules live in the on-demand `usage` skill. The location
|
|
596
598
|
distinction rides `initialize`, under a 1,000-character ceiling for the complete
|
|
597
599
|
instructions string. This promotes existing contract, not capability: the
|
|
598
600
|
seven-tool surface, guest API, catalog, Apps delivery, and runtime do not change
|
|
599
601
|
([#286](https://github.com/zackbart/connecta/issues/286),
|
|
600
602
|
[#418](https://github.com/zackbart/connecta/issues/418)).
|
|
601
603
|
|
|
602
|
-
Bounded view reads follow normative [`V1`–`V8`](./program-ui-read-calls.md) ([#287](https://github.com/zackbart/connecta/issues/287), [#289](https://github.com/zackbart/connecta/issues/289)).
|
|
603
|
-
|
|
604
604
|
## Retry semantics
|
|
605
605
|
|
|
606
606
|
**Y1.** Connecta retries nothing beneath a program. `call_tool` accepts an
|
|
@@ -878,7 +878,7 @@ the upstream `Executor` shape assignable.
|
|
|
878
878
|
| `U1`, `U2` | `test/guest-api-contract.test.ts` (invalid and repeated calls throw catchably, first payload stands), `test/execute-ui.test.ts` (every rejected shape) |
|
|
879
879
|
| `U3` | `test/guest-api-contract.test.ts` (`_meta` payload and `ui: true`, identical on both executors), `test/execute-ui.test.ts` (`structuredContent`, byte-for-byte no-call path, discard structured and plain, coexistence with `emittedDiscarded`), `test/quickjs-executor.test.ts` (mid-run shutdown) |
|
|
880
880
|
| `U4` | `test/execute-ui.test.ts` (one shared byte aggregate crossed in either order; block count and host-call budget untouched) |
|
|
881
|
-
| `U5`, `U10`, `U11` | `test/server.test.ts` (the shell URI, mimeType, and body; every other URI fails; empty listing;
|
|
881
|
+
| `U5`, `U10`, `U11` | `test/server.test.ts` (the shell URI, mimeType, and body; every other URI fails; empty listing; exact model-only `_meta.ui` on all seven tools; exactly one declared extension) |
|
|
882
882
|
| `U6` | `test/execute-ui.test.ts` (valid HTML5, `srcdoc` and sandbox attributes, no `allow-same-origin`, no path from the inner frame to the host) |
|
|
883
883
|
| `U7`, `U8` | two arms passing one case table, `test/codemode-compat.test.ts` |
|
|
884
884
|
| `U9` | `test/execute-ui.test.ts` (a `ui` byte aggregate distinct from `emitted`, absent when nothing was accepted) |
|