@zackbart/connecta 0.17.0 → 0.18.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 +133 -0
- package/README.md +79 -114
- package/dist/catalog-service.d.ts +6 -0
- package/dist/catalog-service.js +69 -4
- package/dist/execute.d.ts +2 -0
- package/dist/execute.js +43 -17
- package/dist/meta-tools.js +9 -9
- package/dist/providers/mixpanel.js +4 -1
- package/dist/providers/revenuecat.d.ts +77 -0
- package/dist/providers/revenuecat.js +314 -0
- package/dist/providers/stripe.d.ts +23 -22
- package/dist/providers/stripe.js +63 -32
- package/dist/routes/ui.js +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +53 -16
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +1 -1
- package/documentation/code-mode.md +23 -15
- package/documentation/connector-guides.md +7 -10
- package/documentation/connectors.md +1 -0
- package/documentation/meta-tools.md +27 -2
- package/documentation/notion.md +17 -0
- package/documentation/operations.md +11 -10
- package/documentation/provider-audit.md +45 -7
- package/documentation/provider-conventions.md +24 -15
- package/documentation/revenuecat.md +279 -0
- package/documentation/stripe.md +53 -67
- package/documentation/upgrading.md +32 -4
- package/ethos.md +4 -4
- package/package.json +6 -2
- package/templates/node/README.md +7 -0
- package/templates/node/package.json +5 -2
package/dist/providers/stripe.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { remoteMcp, } from "../connectors/remote-mcp.js";
|
|
2
2
|
import { vettedCatalog, withVettedCatalog } from "../catalog-drift.js";
|
|
3
|
-
/** Stripe publishes one hosted MCP endpoint
|
|
3
|
+
/** Stripe publishes one hosted MCP endpoint for every account and mode. */
|
|
4
4
|
export const STRIPE_MCP_ENDPOINT = "https://mcp.stripe.com/";
|
|
5
5
|
/**
|
|
6
6
|
* Stripe documents no MCP-specific rate limit, so this transcribes the account
|
|
@@ -89,9 +89,9 @@ const TEST_KEY = /\b(?:sk|rk|pk)_test_/;
|
|
|
89
89
|
* Refuse a deployment whose declared mode and supplied key disagree.
|
|
90
90
|
*
|
|
91
91
|
* This is the one half of production/sandbox routing connecta can actually
|
|
92
|
-
* enforce. Nothing here reads or reports key material: an unrecognizable
|
|
93
|
-
*
|
|
94
|
-
*
|
|
92
|
+
* enforce. Nothing here reads or reports key material: an unrecognizable key
|
|
93
|
+
* shape is left alone rather than guessed at, and a mismatch names only the
|
|
94
|
+
* two modes.
|
|
95
95
|
*/
|
|
96
96
|
function assertModeMatchesKey(id, mode, auth) {
|
|
97
97
|
if (auth.type !== "headers")
|
|
@@ -125,6 +125,22 @@ function resolveAuth(id, options) {
|
|
|
125
125
|
headers: { ...auth.headers, "Stripe-Account": connectedAccount },
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
|
+
const OAUTH_ADMISSION = STRIPE_ADMISSION.sandbox;
|
|
129
|
+
function oauthUsageGuide(purpose, instructions) {
|
|
130
|
+
const accountInstructions = instructions?.trim();
|
|
131
|
+
return `# Stripe usage
|
|
132
|
+
|
|
133
|
+
Scope: live and sandbox accounts. Connector purpose: ${purpose}
|
|
134
|
+
|
|
135
|
+
This OAuth session may expose both live and sandbox Stripe accounts. Call \`list_available_accounts_or_orgs\`, then carry its exact \`stripe_context\` and \`livemode\` into every account-scoped call. A live-mode write moves real money; a sandbox write changes test data. Never infer the account or mode from connector metadata.
|
|
136
|
+
|
|
137
|
+
- Call \`list_available_accounts_or_orgs\` before every account-scoped read or write. Select the intended result, then carry its \`stripe_context\` and \`livemode\` unchanged. If the account, mode, or supported selector is ambiguous, stop and ask; never guess.
|
|
138
|
+
- Organization accounts are not Stripe Connect connected accounts. A Connect call requires a separate connector with a deployment-configured restricted key plus Stripe's documented \`Stripe-Account\` header; OAuth does not support that path.
|
|
139
|
+
${sharedUsageGuide("100 requests per second in live mode and 25 in sandbox mode")}
|
|
140
|
+
${accountInstructions
|
|
141
|
+
? `\n## Account instructions\n\n${accountInstructions}\n`
|
|
142
|
+
: ""}`;
|
|
143
|
+
}
|
|
128
144
|
const MODE_COPY = {
|
|
129
145
|
production: {
|
|
130
146
|
title: "Stripe (production)",
|
|
@@ -137,34 +153,40 @@ const MODE_COPY = {
|
|
|
137
153
|
warning: "This is a SANDBOX Stripe connection. Nothing here is real money and none of these objects exist in production, so never answer a question about live revenue, payouts, or a named customer from this connector.",
|
|
138
154
|
},
|
|
139
155
|
};
|
|
140
|
-
function
|
|
156
|
+
function fixedModeUsageGuide(mode, purpose, instructions) {
|
|
141
157
|
const copy = MODE_COPY[mode];
|
|
142
158
|
const accountInstructions = instructions?.trim();
|
|
143
|
-
const rate = mode === "production" ? "100" : "25";
|
|
144
159
|
return `# Stripe usage
|
|
145
160
|
|
|
146
161
|
Mode: ${mode}. Connector purpose: ${purpose}
|
|
147
162
|
|
|
148
163
|
${copy.warning}
|
|
149
164
|
|
|
150
|
-
- One OAuth session may cover more than one account in the same Stripe organization. The connector id, title, and purpose state routing intent; they do not prove which account a call will use.
|
|
151
|
-
- Before every account-scoped read or write, resolve the intended organization account. Inspect the chosen tool's live input schema and carry the exact account or context field it exposes. If the account or its supported selection mechanism is ambiguous, stop and ask; never guess from the connector metadata, invent an MCP argument, or add a request header the live contract does not expose.
|
|
152
165
|
- Organization accounts are not Stripe Connect connected accounts. A Connect call requires a deployment-configured restricted key plus Stripe's documented \`Stripe-Account\` header; OAuth does not support that path. Do not try to turn an organization-account call into a Connect call inside tool arguments.
|
|
166
|
+
${sharedUsageGuide(`${mode === "production" ? "100" : "25"} requests per second`)}
|
|
167
|
+
${accountInstructions
|
|
168
|
+
? `\n## Account instructions\n\n${accountInstructions}\n`
|
|
169
|
+
: ""}`;
|
|
170
|
+
}
|
|
171
|
+
function sharedUsageGuide(rate) {
|
|
172
|
+
return `
|
|
153
173
|
- Four generic tools reach any Stripe API method. Find the method with \`stripe_api_search\`, read its parameters with \`stripe_api_details\`, then call \`stripe_api_read\` (GET) or \`stripe_api_write\` (POST/PATCH/PUT/DELETE). Never guess a path or a parameter name — \`stripe_api_details\` is cheaper than a rejected write.
|
|
154
174
|
- Prefer a dedicated tool when one covers the task: \`get_stripe_account_info\` for account information, \`get_balance_summary\` for balances, \`create_refund\` for refunds, \`stripe_report\` for reports. One call instead of three, and a refund named \`create_refund\` reads far more clearly in the approval a human sees than the same refund buried in \`stripe_api_write\` arguments.
|
|
155
175
|
- \`stripe_api_write\` carries the blast radius of the entire write API — every POST, PATCH, PUT, and DELETE, from a customer edit to a subscription cancellation. State the method and path explicitly; expect approval on every call.
|
|
156
|
-
- Lists are cursor-paginated: \`limit\` defaults to 10 and caps at 100, \`starting_after\` and \`ending_before\` take an object id and are mutually exclusive, and \`has_more\` says whether to continue. Page inside \`execute_code
|
|
176
|
+
- Lists are cursor-paginated: \`limit\` defaults to 10 and caps at 100, \`starting_after\` and \`ending_before\` take an object id and are mutually exclusive, and \`has_more\` says whether to continue. Page inside \`execute_code\`.
|
|
177
|
+
- Any \`stripe_api_read\` list or \`stripe_api_search\` that returns full objects belongs inside \`execute_code\`, projected to the fields the question needs before \`return\`. Neither \`limit\` nor \`expand\` substitutes for that: an unprojected list of customers or invoices truncates long before it answers, and a projected one keeps the customer's name, email, and address out of the transcript.
|
|
178
|
+
- Search filters on a documented per-resource field set, not on arbitrary attributes. Charges search takes \`amount\`, \`created\`, \`currency\`, \`customer\`, \`status\`, \`refunded\`, \`disputed\`, \`metadata\`, \`billing_details.address.postal_code\`, and \`payment_method_details.<source>.*\` card fields — there is no \`payment_intent\` field. When the field you want is not searchable, retrieve the parent object and follow its reference (the PaymentIntent's \`latest_charge\`) instead of retrying the search with another spelling.
|
|
179
|
+
- The account → \`stripe_api_search\` → \`stripe_api_details\` → \`stripe_api_read\` sequence is one program, not four turns: resolve the method once, then call that method as many times as the investigation needs in the same run.
|
|
180
|
+
- Decline outcomes live on the charge — \`outcome\`, \`failure_code\`, \`failure_message\` — reached from the PaymentIntent's \`latest_charge\`, so "why did this payment fail" is a PaymentIntent read followed by one charge read.
|
|
157
181
|
- Resolve ids before acting; never guess one. Stripe ids are typed prefixes — \`cus_\` customer, \`sub_\` subscription, \`ch_\` charge, \`pi_\` payment intent, \`in_\` invoice, \`acct_\` account — and a plausible-looking id belongs to a different object or to nobody. Find the object with \`stripe_api_search\` (or a list endpoint through \`stripe_api_read\`) and carry the \`id\` it returned into the write.
|
|
158
182
|
- This connection's tool list is not a fixed set. Stripe gates parts of its MCP catalog by account, integration, and beta enrollment, so search this connector for what it actually exposes rather than assuming a documented tool is here.
|
|
159
183
|
- Amounts are integers in the currency's minor unit: \`1099\` is 10.99 USD, and zero-decimal currencies like JPY take \`10\` for 10 JPY. Never send a decimal.
|
|
160
184
|
- Send an \`Idempotency-Key\` on every write you might retry, if the tool accepts it, and reuse the same key for the retry. A retry with a fresh key is a second charge, not a second attempt.
|
|
161
|
-
- Stripe answers a rate limit with \`429\` and a \`Stripe-Rate-Limited-Reason\` header; back off on that rather than retrying immediately. Stripe documents an account ceiling of ${rate}
|
|
185
|
+
- Stripe answers a rate limit with \`429\` and a \`Stripe-Rate-Limited-Reason\` header; back off on that rather than retrying immediately. Stripe documents an account ceiling of ${rate}, and any single endpoint is capped at 25 per second regardless of mode, so paging one list is the real constraint.
|
|
162
186
|
- Use \`search_stripe_documentation\` when the shape of an object or a flow is unclear; it is a read and costs nothing but a call.
|
|
163
187
|
- Treat every create, update, delete, refund, and report run as a write. Connecta routes the maintained write catalog through \`call_destructive_tool\`; newly added tools also fail closed until classified.
|
|
164
188
|
- An \`auth_required\` failure means this connector's Stripe authorization is missing or expired: run \`authorize_connector\` for this connector id, then retry the same call unchanged. A rejected argument or a plan restriction comes back in Stripe's own words instead — read it rather than re-authorizing.
|
|
165
|
-
|
|
166
|
-
? `\n## Account instructions\n\n${accountInstructions}\n`
|
|
167
|
-
: ""}`;
|
|
189
|
+
`;
|
|
168
190
|
}
|
|
169
191
|
/** A maintained Stripe hosted-MCP connection. */
|
|
170
192
|
export function stripe(id, options) {
|
|
@@ -172,31 +194,40 @@ export function stripe(id, options) {
|
|
|
172
194
|
if (!purpose) {
|
|
173
195
|
throw new Error("stripe() requires a non-empty account purpose.");
|
|
174
196
|
}
|
|
175
|
-
const mode = options.mode;
|
|
176
|
-
if (mode !== "production" && mode !== "sandbox") {
|
|
177
|
-
throw new Error(`stripe("${id}") requires mode "production" or "sandbox".`);
|
|
178
|
-
}
|
|
179
197
|
const auth = resolveAuth(id, options);
|
|
180
|
-
|
|
181
|
-
|
|
198
|
+
const mode = "mode" in options ? options.mode : undefined;
|
|
199
|
+
if (auth.type === "oauth" && mode !== undefined) {
|
|
200
|
+
throw new Error(`stripe("${id}") cannot declare a connector-wide mode for OAuth; Stripe returns mode with each account.`);
|
|
201
|
+
}
|
|
202
|
+
if (auth.type === "headers" && mode !== "production" && mode !== "sandbox") {
|
|
203
|
+
throw new Error(`stripe("${id}") with headers auth requires mode "production" or "sandbox".`);
|
|
204
|
+
}
|
|
205
|
+
if (auth.type === "headers") {
|
|
206
|
+
assertModeMatchesKey(id, mode, auth);
|
|
207
|
+
}
|
|
208
|
+
const copy = mode === undefined ? undefined : MODE_COPY[mode];
|
|
182
209
|
const connector = remoteMcp(id, {
|
|
183
210
|
url: STRIPE_MCP_ENDPOINT,
|
|
184
|
-
title: options.title ?? copy
|
|
185
|
-
description:
|
|
211
|
+
title: options.title ?? copy?.title ?? "Stripe",
|
|
212
|
+
description: mode === undefined
|
|
213
|
+
? `Stripe payments (live and sandbox accounts) — ${purpose}`
|
|
214
|
+
: `Stripe payments (${copy?.blurb}) — ${purpose}`,
|
|
186
215
|
auth,
|
|
187
216
|
requireHttps: true,
|
|
188
|
-
callAdmission: STRIPE_ADMISSION[mode],
|
|
217
|
+
callAdmission: mode === undefined ? OAUTH_ADMISSION : STRIPE_ADMISSION[mode],
|
|
189
218
|
usageGuide: {
|
|
190
|
-
content:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
219
|
+
content: mode === undefined
|
|
220
|
+
? oauthUsageGuide(purpose, options.instructions)
|
|
221
|
+
: fixedModeUsageGuide(mode, purpose, options.instructions),
|
|
222
|
+
// Explicit rather than derived: fixed credentials must lead with mode,
|
|
223
|
+
// while OAuth must lead with its account-scoped selector pair.
|
|
224
|
+
summary: mode === undefined
|
|
225
|
+
? "Live and sandbox Stripe accounts. List accounts; carry the returned stripe_context and livemode before acting."
|
|
226
|
+
: mode === "production"
|
|
227
|
+
? "PRODUCTION: real money. This static credential has one fixed live-mode scope."
|
|
228
|
+
: "Sandbox: test data only. This static credential has one fixed sandbox scope.",
|
|
229
|
+
// Not `required`. The four generic tools are the routing decision; a
|
|
230
|
+
// guide forced into every call would pay for the same prose repeatedly.
|
|
200
231
|
},
|
|
201
232
|
...(options.maxResultBytes !== undefined
|
|
202
233
|
? { maxResultBytes: options.maxResultBytes }
|
package/dist/routes/ui.js
CHANGED
|
@@ -28,7 +28,7 @@ const INERT_ICON_HEADERS = {
|
|
|
28
28
|
"Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; sandbox",
|
|
29
29
|
"X-Content-Type-Options": "nosniff",
|
|
30
30
|
};
|
|
31
|
-
/** Per-request base64 nonce for an operator shell's scripts (Node
|
|
31
|
+
/** Per-request base64 nonce for an operator shell's scripts (Node 22+ and Workers). */
|
|
32
32
|
function uiScriptNonce() {
|
|
33
33
|
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
|
34
34
|
let binary = "";
|
package/dist/skills.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Connector } from "./types.js";
|
|
2
|
-
export declare const CONNECTA_INSTRUCTIONS = "
|
|
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 fields or 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,33 +1,70 @@
|
|
|
1
|
-
export const CONNECTA_INSTRUCTIONS = '
|
|
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 fields or 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
|
|
5
5
|
|
|
6
6
|
Seven tools: \`execute_code\`, \`search_tools\`, \`call_tool\`, \`call_destructive_tool\`, \`authorize_connector\`, \`get_result\`, \`skills\`. Broad discovery and multi-call work live in a program, not in top-level tools.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
The always-loaded MCP instructions are authoritative for choosing the top-level route. Read this skill at most once per task for the program workflow and recovery details below.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## Inside a program
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
- Anything wider — two or more calls, dependent steps, loops, joins, branching, a whole-catalog browse, or a result to reduce: one \`execute_code\` run.
|
|
14
|
-
- Any unannotated, write-capable, or destructive call: \`call_destructive_tool\`, one at a time, after reviewing its schema and consequences.
|
|
15
|
-
- Truncated result: retry with \`fields\`, else page it with \`get_result\`.
|
|
16
|
-
- \`auth_required\`: \`authorize_connector\`, hand its recovery text to the operator, retry the call.
|
|
12
|
+
Write one plain-JavaScript async arrow function. TypeScript syntax and portable imports do not work. Return JSON-shaped data and reduce large results before returning.
|
|
17
13
|
|
|
18
|
-
|
|
14
|
+
The minimum guest API is:
|
|
15
|
+
|
|
16
|
+
- \`<connectorId>.<toolName>(args)\` calls a sanitized shortcut. Non-identifier characters become \`_\`; leading digits gain \`_\`; reserved words gain a trailing \`_\`.
|
|
17
|
+
- \`connecta.call("connector.tool", args)\` uses the canonical address and returns the unwrapped value.
|
|
18
|
+
- \`connecta.search(args)\` returns \`{ tools, total, offset, limit, hasMore }\`; \`connecta.describe(args)\` returns \`{ tools }\`.
|
|
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, options?)\` produce rich output.
|
|
21
|
+
|
|
22
|
+
## Discover and select
|
|
23
|
+
|
|
24
|
+
Search inside the run and finish the task there. A discovery-only program wastes a round trip. Use 2–4 distinctive action/object terms, not the full request. Use separate short searches for distinct operations.
|
|
25
|
+
|
|
26
|
+
For top-level \`search_tools\`, omit \`limit\` initially (the default is 10), then page with a limit up to 50 if needed. Empty or whitespace-only queries browse all tools. A non-empty query with no ASCII terms returns no matches; mixed input searches with its ASCII terms. \`includeSchemas: "compact"\` adds bounded input and declared output shapes. Plain objects expose \`inputKeys\`, \`requiredInputKeys\`, and \`outputKeys\`; truncation flags mark incomplete shapes; matches also carry declared annotations.
|
|
27
|
+
|
|
28
|
+
- \`connecta.search({})\` loads all catalogs. Pass \`connector: "<id>"\` when the integration is obvious. Use \`safety: "readOnly"\` for program calls. These inputs filter discovery; they grant no authority.
|
|
29
|
+
- Request \`includeSchemas: "compact"\`. Check address, purpose, annotations, required inputs, truncation, safety, and declared outputs. Never select only because a result ranks first or has fewer required inputs.
|
|
30
|
+
- Supply every \`requiredInputKey\` from the task or a prior result. For dependencies, match the earlier \`outputKey\` to the later required key. An empty required-key list does not permit invented arguments. Missing \`outputKeys\` means inspect \`outputSchema\`.
|
|
31
|
+
- Use \`connecta.describe({ address })\` or \`{ addresses }\` when a compact schema is truncated or insufficient. Use \`format: "json"\` only for exact constraints. Write the property names the schema displays; never guess positions or aliases.
|
|
32
|
+
- Reduce through declared output keys. Do not guess collection roots such as \`items\` or \`results\`. If a match or result key is missing, inspect, re-search, or describe inside the same run instead of returning discovery for another call.
|
|
33
|
+
|
|
34
|
+
Only tools explicitly annotated \`readOnlyHint: true\` are reachable. The catalog, credential, admission, and read-only gates run below the sandbox; code cannot widen its authority.
|
|
35
|
+
|
|
36
|
+
## Errors and repair
|
|
37
|
+
|
|
38
|
+
Caught Connecta errors expose \`message\`, \`code\`, \`retryable\`, and \`details\`. Batch failures expose the same classification in \`errorDetails\`. Branch on fields, never prose. Do not retry \`retryable: false\`, and do not retry \`rate_limited\` immediately because portable code has no timer.
|
|
39
|
+
|
|
40
|
+
- \`destructive_tool_requires_approval\`: stop the program and use the returned canonical address with top-level \`call_destructive_tool\`.
|
|
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: retry \`call_tool\` with \`fields\`, or follow its \`get_result\` action. A truncated program result has no page handle; filter, map, or slice inside a new program.
|
|
43
|
+
- Unknown addresses and tools carry scoped search recovery. Use it inside the current run. Do not invent an address.
|
|
44
|
+
|
|
45
|
+
For a direct call, \`fields\` selects JSON dot-paths and \`[]\` traverses arrays, for example \`results[].id\`. Projection misses return \`data\` plus \`$connecta\` feedback. \`resultMode: "value"\` unwraps the result. \`timeoutMs\` sets its deadline. \`maxRetries\` is honored only for safely annotated tools. \`diagnostics: true\` adds timing.
|
|
46
|
+
|
|
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
|
+
|
|
49
|
+
Limits: 20 host calls per run, 10 calls per batch, and a 15-second deadline per host call.
|
|
50
|
+
|
|
51
|
+
## Runtime portability
|
|
52
|
+
|
|
53
|
+
Portable code uses only connector globals, \`connecta\`, and \`console.*\`. QuickJS blocks imports and lacks fetch, process, timers, crypto, and WebSocket. Dynamic Workers must use only \`{ loader }\`; bindings, modules, or globalOutbound grant ambient authority. With loader only, environment maps are empty; node:fs/http/https are absent; outbound fetch, WebSocket, node:net, and node:tls are denied; DNS is unresolved. Runtime builtins remain through \`import()\` and \`process.getBuiltinModule()\`, including node:path and cloudflare:workers; this set can drift. Timers, process, crypto, WebSocket, and data: fetch remain. Avoid every runtime-only capability because QuickJS fails.
|
|
54
|
+
|
|
55
|
+
## Examples
|
|
56
|
+
|
|
57
|
+
One read-only call at a known address:
|
|
58
|
+
|
|
59
|
+
\`async () => await connecta.call("crm.get_account", { id: "acct_42" })\`
|
|
19
60
|
|
|
20
|
-
|
|
61
|
+
Dependent calls, only when the second needs a value from the first:
|
|
21
62
|
|
|
22
|
-
|
|
23
|
-
- Exact schemas: \`connecta.describe({ address: "connector.tool" })\` for one, \`{ addresses: [...] }\` for many; \`format: "json"\` only for exact constraints.
|
|
24
|
-
- Caught Connecta errors have \`message\`, \`code\`, \`retryable\`, and \`details\`; branch on fields. For 2–10 independent calls, \`connecta.batch([...])\` returns success data or an \`errorDetails\` whose code and retryable flag match the throw.
|
|
25
|
-
- Search inside the run; return only the reduction the answer needs, never raw payloads.
|
|
26
|
-
- Only tools annotated \`readOnlyHint: true\` are reachable; the gate, credentials, and admission are enforced below the sandbox — nothing a program does widens its reach.
|
|
63
|
+
\`async () => { const { tools } = await connecta.search({ query: "pipeline run job logs", safety: "readOnly", includeSchemas: "compact" }); const address = (suffix) => { const tool = tools.find((entry) => entry.address.endsWith(suffix)); if (!tool) throw new Error("missing " + suffix); return tool.address; }; const run = await connecta.call(address(".get_run"), { runId: 42 }); const logs = await connecta.call(address(".get_job_logs"), { jobId: run.failedJobId }); return logs.map(({ timestamp, message }) => ({ timestamp, message })); }\`
|
|
27
64
|
|
|
28
65
|
## Rendering a view
|
|
29
66
|
|
|
30
|
-
\`connecta.ui(html)\` renders one success-only display view,
|
|
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. One argument is display-only. Bind read-only refresh or drill-down calls with \`{ reads: { name: { address, fixedArgs?, viewArgs? } } }\`; page markup calls \`connecta.read(name, args)\`. Admission and one shared budget apply 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 initial summary because the model reads the return value, not the view. A second, invalid, or over-budget UI call throws catchably.
|
|
31
68
|
|
|
32
69
|
`;
|
|
33
70
|
/** Deployment-scoped guide routing appended to the shared usage guide. */
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -185,7 +185,7 @@ src/
|
|
|
185
185
|
| The core imports no `node:` builtin and reaches no Node-only module | `test/purity.test.ts` |
|
|
186
186
|
| The published surface matches the same boundary | `test/package-surface.test.ts`, `scripts/check-package.mjs` |
|
|
187
187
|
| Route order, per-route auth, and byte-exact refusals | `test/server-route-contracts.test.ts` |
|
|
188
|
-
| `/mcp` end to end, the open routes, exactly seven tools | `test/server.test.ts`, `test/code-first-surface.test.ts` |
|
|
188
|
+
| `/mcp` end to end, the open routes, exactly seven tools, bounded connector orientation | `test/server.test.ts`, `test/code-first-surface.test.ts` |
|
|
189
189
|
| Construction-time refusals and the grouped config boundary | `test/config.test.ts`, `test/registry.test.ts` |
|
|
190
190
|
| Program and top-level calls take the same enforced path | `test/execute.test.ts` |
|
|
191
191
|
| Both deployment shapes still compile and configure the real thing | `test/deployment-shapes.test.ts`, `npm run check:examples` |
|
|
@@ -173,7 +173,10 @@ global whose properties are its tools, so `<connectorId>.<toolName>(args)` works
|
|
|
173
173
|
with both parts sanitized into JavaScript identifiers — characters outside
|
|
174
174
|
`[A-Za-z0-9_$]` become `_`, a leading digit gets `_` prefixed, and a reserved
|
|
175
175
|
word gets `_` appended (`my-service.get.thing` → `my_service.get_thing`). The
|
|
176
|
-
globals are lazy: no catalog is fetched until a program touches one.
|
|
176
|
+
globals are lazy: no catalog is fetched until a program touches one. The
|
|
177
|
+
bounded deployment inventory in the `execute_code` description shows each
|
|
178
|
+
canonical connector id and labels the shortcut only when it differs; the
|
|
179
|
+
[discovery guide](./meta-tools.md#discovery-context) defines that bound.
|
|
177
180
|
|
|
178
181
|
**A3.** A shortcut that resolves to more than one tool fails closed with
|
|
179
182
|
`ambiguous_tool_alias`, naming the colliding tool names and pointing at
|
|
@@ -256,10 +259,13 @@ const { tools } = await connecta.describe({
|
|
|
256
259
|
});
|
|
257
260
|
```
|
|
258
261
|
|
|
259
|
-
**S4.** Returns `{ tools }` in
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
262
|
+
**S4.** Returns `{ tools }` in order, one entry per address. An unknown address
|
|
263
|
+
or failed catalog returns `error` plus typed `errorDetails`: `code`, `message`, and `retryable`. Misses
|
|
264
|
+
carry a route-aware `nextAction`; a close miss may add three canonical `suggestions`.
|
|
265
|
+
Catalog failures add only `retryAfterMs` when known. One bad address never fails the whole call. Each failed entry clamps its
|
|
266
|
+
caller-authored `address` to 512 UTF-8 bytes with an `…` marker. Entry order
|
|
267
|
+
correlates a clipped address with its request; successes keep canonical addresses. More than 100
|
|
268
|
+
addresses is `invalid_args`; the same 256,000-byte ceiling applies.
|
|
263
269
|
|
|
264
270
|
### connecta.call
|
|
265
271
|
|
|
@@ -377,7 +383,7 @@ exactly first, by containment second — so a program that *wraps* a failure's
|
|
|
377
383
|
message in its own text still reports the underlying typed failure. Keeping the
|
|
378
384
|
type beats keeping the prose.
|
|
379
385
|
|
|
380
|
-
**E7.** `retryable` for `unknown_address`, `unknown_tool`, `ambiguous_tool_alias`, and `destructive_tool_requires_approval` is pinned false, never inferred from an address containing `503`, `429`, or `temporar`. The first two carry `nextAction: { function: "connecta.search", arguments: { query, connector?, includeSchemas: "compact" } }` — the same scoped discovery the top-level record names, keyed to the surface the caller actually has. A program cannot call `search_tools`, so it is never told to.
|
|
386
|
+
**E7.** `retryable` for `unknown_address`, `unknown_tool`, `ambiguous_tool_alias`, and `destructive_tool_requires_approval` is pinned false, never inferred from an address containing `503`, `429`, or `temporar`. The first two carry `nextAction: { function: "connecta.search", arguments: { query, connector?, includeSchemas: "compact" } }` — the same scoped discovery the top-level record names, keyed to the surface the caller actually has. A program cannot call `search_tools`, so it is never told to. The message, the derived `query`, and a failed describe entry's `address` clamp caller-authored text to 512 UTF-8 bytes with an `…` marker. Those values land in the text content and `structuredContent`, so an invented 50 KB address would otherwise produce a refusal orders of magnitude past the deployment's result cap. A clipped address still identifies the mistake by its position; a short one — the common case — is exact and untagged.
|
|
381
387
|
|
|
382
388
|
**E8.** A remote MCP tool whose advertised schema rejects the call fails before provider dispatch with `invalid_args`, carrying bounded, value-free `{ path, code, expected }` findings and scoped search recovery keyed `function: "connecta.search"` like every other in-program miss. A declared property reports the schema keyword that failed, never the validator's duplicate `additionalProperties` branch; a truly undeclared property still reports `additionalProperties`. Unsupported schemas pass through; unrecognized provider prose remains `connector_call_failed`.
|
|
383
389
|
|
|
@@ -585,14 +591,14 @@ the same mistake as automatic host-side projection, refused in `ethos.md`
|
|
|
585
591
|
([#282](https://github.com/zackbart/connecta/issues/282)).
|
|
586
592
|
|
|
587
593
|
**U13.** The always-loaded MCP instructions locate `connecta.ui(html)` before an
|
|
588
|
-
agent chooses a route: it
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
therefore rides `initialize`, under a 1,000-character ceiling for the complete
|
|
594
|
+
agent chooses a route: it exists only inside `execute_code`, never in connector
|
|
595
|
+
search, and carries `U12`'s mirrored-return duty. The detailed call, binding,
|
|
596
|
+
budget, and repair rules live in the on-demand `usage` skill. The location
|
|
597
|
+
distinction rides `initialize`, under a 1,000-character ceiling for the complete
|
|
593
598
|
instructions string. This promotes existing contract, not capability: the
|
|
594
599
|
seven-tool surface, guest API, catalog, Apps delivery, and runtime do not change
|
|
595
|
-
([#286](https://github.com/zackbart/connecta/issues/286)
|
|
600
|
+
([#286](https://github.com/zackbart/connecta/issues/286),
|
|
601
|
+
[#418](https://github.com/zackbart/connecta/issues/418)).
|
|
596
602
|
|
|
597
603
|
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)).
|
|
598
604
|
|
|
@@ -645,6 +651,8 @@ because connecta enforces them above the sandbox:
|
|
|
645
651
|
| Deadline per host call | 15 s |
|
|
646
652
|
| Discovery page | ≤ 100 tools, ≤ 256,000 serialized bytes |
|
|
647
653
|
| `describe` addresses | ≤ 100 |
|
|
654
|
+
| `describe` nearby suggestions | ≤ 3 canonical addresses per failed entry |
|
|
655
|
+
| Caller text echoed by `describe` recovery | ≤ 512 UTF-8 bytes per field, plus `…` |
|
|
648
656
|
| Result | 24,000 serialized characters |
|
|
649
657
|
| Logs presented to the model | 4,000 characters |
|
|
650
658
|
|
|
@@ -828,13 +836,13 @@ the upstream `Executor` shape assignable.
|
|
|
828
836
|
| `P2`, `X5` | `test/guest-api-contract.test.ts` (Dynamic globals plus loader-only filesystem, HTTP, environment, egress, DNS, and local `data:` boundaries), `test/guest-api-contract-quickjs.test.ts` (exact absent globals and blocked imports), `test/deployment-shapes.test.ts` (loader-only Worker construction) |
|
|
829
837
|
| `P3`, `X9` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` |
|
|
830
838
|
| `P4` | `test/guest-api-contract.test.ts` (no cross-run leakage), `test/execute.test.ts` (one catalog load per connector per execution) |
|
|
831
|
-
| `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (sanitizing) |
|
|
839
|
+
| `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (sanitizing), `test/server.test.ts` (bounded live connector inventory) |
|
|
832
840
|
| `A3` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (colliding alias) |
|
|
833
841
|
| `A4` | `test/execute.test.ts` (namespace collisions, reserved namespace) |
|
|
834
842
|
| `A5` | verdict; `A1`–`A3` are its enforcement |
|
|
835
843
|
| `S1`, `S2` | `test/guest-api-contract.test.ts` (flat page, connector guides, schema keys, and the unfiltered browse that replaces `list_connectors`), `test/execute.test.ts` (guide pagination/partial/no-match behavior and `$ref`/`allOf`), `test/meta-tools.test.ts` (mixed complete/partial ranking and stable pagination) |
|
|
836
844
|
| `S3` | `test/guest-api-contract.test.ts` (typed uncaught bound), `test/execute.test.ts` (count limits, fan-out bound) |
|
|
837
|
-
| `S4` | `test/
|
|
845
|
+
| `S4` | both guest-contract executors (ordered mixed describe results with unknown-address, unknown-tool suggestion, and catalog-failure details), `test/meta-tools.test.ts` (top-level routing, no-suggestion, catalog-failure, and hostile-input bounds) |
|
|
838
846
|
| `S5` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`unwrapMcpResult`) |
|
|
839
847
|
| `S6` | `test/execute.test.ts` (fail-closed annotations, activity parity) |
|
|
840
848
|
| `S7` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (batch cap) |
|
|
@@ -875,7 +883,7 @@ the upstream `Executor` shape assignable.
|
|
|
875
883
|
| `U7`, `U8` | two arms passing one case table, `test/codemode-compat.test.ts` |
|
|
876
884
|
| `U9` | `test/execute-ui.test.ts` (a `ui` byte aggregate distinct from `emitted`, absent when nothing was accepted) |
|
|
877
885
|
| `U12` | `test/server.test.ts` (the `connecta.ui` bullet carries the return-value clause); a duty on program authors, so the description is the only place it can be enforced |
|
|
878
|
-
| `U13` | `test/code-first-surface.test.ts`, `test/server.test.ts` (served `initialize.instructions` locate UI inside `execute_code`, exclude it from
|
|
886
|
+
| `U13` | `test/code-first-surface.test.ts`, `test/server.test.ts` (served `initialize.instructions` locate UI inside `execute_code`, exclude it from connector search, state the mirrored-return duty, and stay within the complete 1,000-character budget; the usage skill carries detailed call rules) |
|
|
879
887
|
| `X3` | `test/quickjs-executor.test.ts` (cancels a running child) |
|
|
880
888
|
| `X4` | `test/guest-api-contract.test.ts` (string logs only) |
|
|
881
889
|
| `X6` | `test/quickjs-executor.test.ts` (never-settling await) |
|
|
@@ -60,15 +60,12 @@ unknown name, unknown connector, connector with no guide — is an explicit
|
|
|
60
60
|
error. Nothing silently falls back to the generic guide, because a generic
|
|
61
61
|
answer to a specific question is worse than no answer.
|
|
62
62
|
|
|
63
|
-
Discovery text is conditional on the deployment actually having a guide.
|
|
64
|
-
|
|
63
|
+
Discovery text is conditional on the deployment actually having a guide. Short
|
|
64
|
+
pointers in the `skills`, `search_tools`, `call_destructive_tool`, and
|
|
65
65
|
`execute_code` descriptions appear only when at least one visible connector
|
|
66
|
-
declares one
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
exception: it stays byte-identical across every deployment, including its
|
|
70
|
-
per-connector-guides section, so an agent that has read it once in a task never
|
|
71
|
-
needs a deployment-local copy of it.
|
|
66
|
+
declares one. The detailed selection rules live only in the built-in `usage`
|
|
67
|
+
skill. That skill stays byte-identical across deployments, including its
|
|
68
|
+
per-connector-guides section, so an agent reads it at most once per task.
|
|
72
69
|
|
|
73
70
|
## What belongs in a guide
|
|
74
71
|
|
|
@@ -175,5 +172,5 @@ each branch, the `guide` pointer in search output, and `guideRequired`
|
|
|
175
172
|
appearing for connector-required conventions, approval-bound tools, and
|
|
176
173
|
truncated schemas — and being absent from a search that asked for no schemas.
|
|
177
174
|
`test/server.test.ts` owns the conditional half: it compares a guide-free
|
|
178
|
-
deployment's four
|
|
179
|
-
`usage` skill is byte-identical between them.
|
|
175
|
+
deployment's four short pointers against a guided one's, and asserts the
|
|
176
|
+
complete `usage` skill is byte-identical between them.
|
|
@@ -60,6 +60,14 @@ their smallest successful one-tool shapes:
|
|
|
60
60
|
|
|
61
61
|
## Discovery context
|
|
62
62
|
|
|
63
|
+
The deployment-derived `execute_code` description includes a live connector
|
|
64
|
+
inventory before any catalog search. It preserves registry order and uses each
|
|
65
|
+
canonical id, adding `shortcut <name>` only when the program namespace differs.
|
|
66
|
+
The complete inventory line is capped at 256 UTF-8 bytes. Entries stay whole,
|
|
67
|
+
and a truncated line ends with the exact `+N more` count. This reads only the
|
|
68
|
+
configured registry: it loads no catalog, probes no credential, grants no
|
|
69
|
+
capability, and does not replace canonical discovery or addressing.
|
|
70
|
+
|
|
63
71
|
Start an unknown-address lookup with two to four distinctive action/object
|
|
64
72
|
terms, not the full request, and omit `limit` so the default eight-result page
|
|
65
73
|
stays small. When the integration is obvious, set `connector` to its id: a
|
|
@@ -146,8 +154,12 @@ the zero-tool page.
|
|
|
146
154
|
The built-in `usage` skill is byte-identical across deployments and says to
|
|
147
155
|
read it at most once per task. Connector guides remain scoped to the deployment
|
|
148
156
|
that listed them, even when two deployments happen to use identical content.
|
|
149
|
-
|
|
150
|
-
|
|
157
|
+
The always-loaded instructions and seven tool definitions own route selection,
|
|
158
|
+
the fail-closed boundary, and the minimum guest syntax. The usage skill owns
|
|
159
|
+
program selection detail, examples, runtime differences, and repair guidance.
|
|
160
|
+
This split avoids two normative copies while preserving a valid first program
|
|
161
|
+
for clients that never fetch the skill. Deployments without connector guides
|
|
162
|
+
receive none of the short conditional guide pointers in their definitions.
|
|
151
163
|
|
|
152
164
|
## Result representation
|
|
153
165
|
|
|
@@ -316,6 +328,16 @@ a tool. A read path that reaches an unannotated, write-capable, or destructive
|
|
|
316
328
|
tool returns `nextAction` for `call_destructive_tool` with the canonical
|
|
317
329
|
address. Nothing is executed by these records.
|
|
318
330
|
|
|
331
|
+
`connecta.describe` keeps failures inline so one miss cannot discard the other
|
|
332
|
+
schemas. Each failed entry keeps its human `error` and adds `errorDetails` with
|
|
333
|
+
the equivalent invocation `code` and `retryable`. Address and tool misses use
|
|
334
|
+
the same route-aware discovery action above. A close tool-name miss on a known
|
|
335
|
+
connector may also carry `suggestions`: at most three deterministically ranked
|
|
336
|
+
canonical addresses, with no scores or descriptions. An unknown connector
|
|
337
|
+
stays unscoped and has no suggestions. A catalog-load failure carries only
|
|
338
|
+
`code`, bounded `message`, `retryable`, and any `retryAfterMs`; discovery does
|
|
339
|
+
not inherit later additions to the call-failure envelope.
|
|
340
|
+
|
|
319
341
|
That route echoes the caller's own arguments back only while they fit a
|
|
320
342
|
512-byte budget, and then whole — never clipped. An error envelope is not
|
|
321
343
|
size-guarded the way a result is, so an unbounded echo would let a large
|
|
@@ -393,3 +415,6 @@ duplicate `additionalProperties` branches never reach the caller. A schema the l
|
|
|
393
415
|
validator cannot evaluate passes through to the provider. Provider error prose
|
|
394
416
|
is not parsed or guessed, so an unknown format remains
|
|
395
417
|
`connector_call_failed`.
|
|
418
|
+
|
|
419
|
+
Describe's nearby-address list uses the same three-item recovery bound. It
|
|
420
|
+
contains addresses only; it never serializes ranking scores or result prose.
|
package/documentation/notion.md
CHANGED
|
@@ -237,6 +237,23 @@ and all deliberately absent: this is a deliberate tool surface, not a mirror of
|
|
|
237
237
|
the API. Anything missing is reachable through a custom `api()` connector
|
|
238
238
|
beside this one, which remains a first-class path.
|
|
239
239
|
|
|
240
|
+
The 2026-03-11 contract also offers more fields on create and update. They were
|
|
241
|
+
reviewed after the 0.17.0 drift check and remain deliberately absent:
|
|
242
|
+
|
|
243
|
+
- `create_page` does not create workspace-private pages, apply templates,
|
|
244
|
+
choose page placement, or accept expanded icon and cover forms. Those change
|
|
245
|
+
ownership, start asynchronous content work, control ordering, or depend on
|
|
246
|
+
file surfaces. They are not extensions of the maintained page/row authoring
|
|
247
|
+
contract (#408).
|
|
248
|
+
- `update_page_properties` does not lock pages, apply templates, or erase page
|
|
249
|
+
content. Locking is coordination state, templates finish asynchronously, and
|
|
250
|
+
`erase_content` permanently deletes every child block through the API. None
|
|
251
|
+
belongs under an approval named for property replacement (#409).
|
|
252
|
+
|
|
253
|
+
`trash_page` stays separate and reversible. The current `create_page`,
|
|
254
|
+
`update_page_properties`, and `trash_page` request subsets remain valid against
|
|
255
|
+
the expanded published contract.
|
|
256
|
+
|
|
240
257
|
There is also **no guarded raw-REST escape hatch** — no `notion_api_get`, no
|
|
241
258
|
`notion_api_mutate`. The convention that permits one
|
|
242
259
|
([H14](./provider-conventions.md#h14--a-named-tool-must-beat-the-escape-hatch-and-the-escape-hatch-splits-by-safety))
|