@zackbart/connecta 0.17.0 → 0.18.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 +78 -0
- package/README.md +4 -0
- 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/stripe.d.ts +23 -22
- package/dist/providers/stripe.js +58 -31
- 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/meta-tools.md +27 -2
- package/documentation/notion.md +17 -0
- package/documentation/operations.md +10 -10
- package/documentation/provider-audit.md +13 -5
- package/documentation/stripe.md +43 -67
- package/documentation/upgrading.md +22 -4
- package/ethos.md +4 -4
- package/package.json +2 -2
- package/templates/node/README.md +7 -0
- package/templates/node/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,84 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.18.0 — 2026-08-13
|
|
6
|
+
|
|
7
|
+
This minor release spends fewer tokens before the first call and answers
|
|
8
|
+
better when a call goes wrong. The always-loaded MCP instructions and tool
|
|
9
|
+
definitions are 44.6% lighter, the `execute_code` description now names the
|
|
10
|
+
deployment's configured connector IDs before the first catalog search, and a
|
|
11
|
+
failed `connecta.describe` entry carries the same typed recovery as a failed
|
|
12
|
+
call. The compatibility change is the floor: Node 22 is now the minimum
|
|
13
|
+
supported release, matching the shipped Docker template, and CI runs one job
|
|
14
|
+
per pull request against it; the Cloudflare Worker target is unchanged. Stripe
|
|
15
|
+
OAuth deployments must drop the connector-wide `mode` — each returned account
|
|
16
|
+
now carries its own. An evaluation of erasable TypeScript syntax in
|
|
17
|
+
`execute_code` programs ended in a recorded refusal, so portable programs
|
|
18
|
+
remain plain JavaScript. A deployment already on Node 22 that lets accounts
|
|
19
|
+
carry their Stripe mode needs no configuration change.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **`execute_code` now names the deployment's connectors up front.** Its
|
|
24
|
+
description carries a connector inventory derived only from the configured
|
|
25
|
+
registry: canonical IDs in registry order, the executable shortcut when
|
|
26
|
+
sanitization changes the ID, bounded to 256 serialized bytes with an exact
|
|
27
|
+
omitted count. A cold model can scope its first catalog search without
|
|
28
|
+
guessing, and rendering the inventory performs no catalog request and no
|
|
29
|
+
credential access (#416).
|
|
30
|
+
|
|
31
|
+
- **Failed `connecta.describe` entries are now as repairable as failed calls.**
|
|
32
|
+
Each failed entry carries `errorDetails` beside the preserved human text: the
|
|
33
|
+
invocation path's stable `code` and `retryable`, a route-aware `nextAction`,
|
|
34
|
+
and up to three deterministic nearby canonical addresses when the connector
|
|
35
|
+
is known. Caller-authored addresses are clamped everywhere they echo, so
|
|
36
|
+
hostile input can no longer displace partial results (#417).
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **The always-loaded surface got 44.6% lighter.** The MCP instructions and the
|
|
41
|
+
seven tool definitions now carry route selection, the fail-closed read-only
|
|
42
|
+
boundary, and the minimum guest syntax — 6,850 bytes instead of 12,358.
|
|
43
|
+
Selection discipline, runtime differences, worked examples, direct-call
|
|
44
|
+
options, `get_result` rules, the `connecta.ui` reads-binding shape, and
|
|
45
|
+
repair guidance moved into the on-demand `usage` skill, which grew to a
|
|
46
|
+
deliberate 9,000-byte cap and stays byte-identical across deployments.
|
|
47
|
+
Clients that never fetch the skill keep correct routing from the compact
|
|
48
|
+
definitions alone (#418).
|
|
49
|
+
|
|
50
|
+
- **Node 22 is now the minimum supported Node release.** CI and the published
|
|
51
|
+
engine range now match the Node 22 runtime used by the shipped deployment
|
|
52
|
+
template. The Worker test project continues to cover the other deployment
|
|
53
|
+
target (#422).
|
|
54
|
+
|
|
55
|
+
- **Stripe OAuth mode now belongs to each returned account.** OAuth-backed
|
|
56
|
+
connectors no longer accept a connector-wide `mode`. Their neutral metadata
|
|
57
|
+
and guide support mixed live and sandbox accounts, require
|
|
58
|
+
`list_available_accounts_or_orgs`, and carry its exact `stripe_context` and
|
|
59
|
+
`livemode` into each account-scoped call. They use the stricter sandbox
|
|
60
|
+
admission ceiling. Header credentials still require one fixed mode, retain
|
|
61
|
+
key-prefix contradiction checks, and keep Stripe Connect behavior (#414).
|
|
62
|
+
|
|
63
|
+
- **The reviewed Notion writes stay narrow.** Newly published workspace-private
|
|
64
|
+
creation, templates, placement, richer media, locking, and irreversible
|
|
65
|
+
content erasure do not join the maintained surface. They are distinct
|
|
66
|
+
ownership, asynchronous, ordering, file, coordination, or deletion workflows,
|
|
67
|
+
not extra fields on `create_page` or `update_page_properties`. The existing
|
|
68
|
+
request subsets remain valid, and `trash_page` stays isolated and reversible
|
|
69
|
+
(#408, #409).
|
|
70
|
+
|
|
71
|
+
### Fixed
|
|
72
|
+
|
|
73
|
+
- **The Node template now declares its one reviewed install script.** npm 11 no
|
|
74
|
+
longer warns that esbuild's install is unreviewed, and npm 12 will not block
|
|
75
|
+
it. The approval is pinned to `esbuild@0.28.2`; the package smoke reads the
|
|
76
|
+
generated lockfile and fails if any resolved dependency has an unapproved
|
|
77
|
+
install script, including a future esbuild version. Local and container
|
|
78
|
+
installs keep the same `tsx` runtime path (#375).
|
|
79
|
+
|
|
80
|
+
- The documentation gate now ignores `.claude`, whose nested agent worktrees
|
|
81
|
+
are separate historical checkouts rather than source in the current tree.
|
|
82
|
+
|
|
5
83
|
## 0.17.0 — 2026-08-13
|
|
6
84
|
|
|
7
85
|
This minor release makes catalog discovery faster and its answers more exact.
|
package/README.md
CHANGED
|
@@ -110,6 +110,10 @@ connector tools are reachable. Unannotated or write-capable calls stay
|
|
|
110
110
|
individual and cross `call_destructive_tool`, where the MCP host can ask the
|
|
111
111
|
operator for approval.
|
|
112
112
|
|
|
113
|
+
The Node template also pins its one approved dependency install script:
|
|
114
|
+
esbuild, which `tsx` needs to run the deployment source. A dependency update
|
|
115
|
+
that adds or changes an install script fails the package smoke until reviewed.
|
|
116
|
+
|
|
113
117
|
There are two deployment shapes and no others:
|
|
114
118
|
|
|
115
119
|
- [Node, local or Docker](./templates/node/) — what `init` copies
|
|
@@ -72,6 +72,11 @@ interface CatalogFailureDetail {
|
|
|
72
72
|
retryable: boolean;
|
|
73
73
|
retryAfterMs?: number;
|
|
74
74
|
}
|
|
75
|
+
interface CatalogDescriptionFailureDetail extends CatalogFailureDetail {
|
|
76
|
+
nextAction?: NonNullable<CallErrorDetails["nextAction"]>;
|
|
77
|
+
/** Nearby canonical addresses, ranked deterministically by tool name. */
|
|
78
|
+
suggestions?: string[];
|
|
79
|
+
}
|
|
75
80
|
export interface CatalogSearchPage {
|
|
76
81
|
entries: CatalogSearchEntry[];
|
|
77
82
|
total: number;
|
|
@@ -109,6 +114,7 @@ export interface CatalogDescription {
|
|
|
109
114
|
outputSchema?: unknown;
|
|
110
115
|
annotations?: ToolDef["annotations"];
|
|
111
116
|
error?: string;
|
|
117
|
+
errorDetails?: CatalogDescriptionFailureDetail;
|
|
112
118
|
}
|
|
113
119
|
export interface ResolvedCatalogTool {
|
|
114
120
|
connector: Connector;
|
package/dist/catalog-service.js
CHANGED
|
@@ -16,6 +16,7 @@ const MAX_QUERY_TERM_LENGTH = 64;
|
|
|
16
16
|
* of the deployment.
|
|
17
17
|
*/
|
|
18
18
|
const MAX_IDENTITY_CONNECTORS = 3;
|
|
19
|
+
const MAX_DESCRIBE_SUGGESTIONS = 3;
|
|
19
20
|
const encoder = new TextEncoder();
|
|
20
21
|
/** Clip one echoed query term without splitting a non-BMP code point. */
|
|
21
22
|
function boundedQueryTerm(term) {
|
|
@@ -80,6 +81,39 @@ function recoveryQuery(address) {
|
|
|
80
81
|
const candidate = separator >= 0 ? address.slice(separator + 1) : address;
|
|
81
82
|
return boundedEchoText(candidate.replaceAll(/[._-]+/g, " ").trim() || address);
|
|
82
83
|
}
|
|
84
|
+
function editDistance(left, right) {
|
|
85
|
+
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
86
|
+
for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
|
|
87
|
+
const current = [leftIndex + 1];
|
|
88
|
+
for (let rightIndex = 0; rightIndex < right.length; rightIndex += 1) {
|
|
89
|
+
current.push(Math.min(previous[rightIndex + 1] + 1, current[rightIndex] + 1, previous[rightIndex] +
|
|
90
|
+
(left[leftIndex] === right[rightIndex] ? 0 : 1)));
|
|
91
|
+
}
|
|
92
|
+
previous = current;
|
|
93
|
+
}
|
|
94
|
+
return previous[right.length];
|
|
95
|
+
}
|
|
96
|
+
/** Nearby names only; descriptions never influence describe-miss recovery. */
|
|
97
|
+
function describeSuggestions(connectorId, attemptedName, tools) {
|
|
98
|
+
const attempted = attemptedName.toLowerCase();
|
|
99
|
+
return tools
|
|
100
|
+
.map((tool, order) => {
|
|
101
|
+
const name = tool.name.toLowerCase();
|
|
102
|
+
return { tool, order, distance: editDistance(attempted, name) };
|
|
103
|
+
})
|
|
104
|
+
.filter(({ tool, distance }) => {
|
|
105
|
+
const longest = Math.max(attempted.length, tool.name.length);
|
|
106
|
+
return (attempted.includes(tool.name.toLowerCase()) ||
|
|
107
|
+
tool.name.toLowerCase().includes(attempted) ||
|
|
108
|
+
distance <= Math.max(2, Math.floor(longest * 0.4)));
|
|
109
|
+
})
|
|
110
|
+
.sort((left, right) => left.distance - right.distance || left.order - right.order)
|
|
111
|
+
.map(({ tool }) => `${connectorId}.${tool.name}`)
|
|
112
|
+
// A clipped address would no longer be canonical. Omit an implausibly
|
|
113
|
+
// large catalog name instead of letting one suggestion erase the page.
|
|
114
|
+
.filter((address) => boundedEchoText(address) === address)
|
|
115
|
+
.slice(0, MAX_DESCRIBE_SUGGESTIONS);
|
|
116
|
+
}
|
|
83
117
|
/** Serialize once and count the exact bytes the MCP adapter would emit. */
|
|
84
118
|
export function boundedDiscoveryText(value, hint) {
|
|
85
119
|
const text = JSON.stringify(value);
|
|
@@ -734,17 +768,48 @@ export class CatalogService {
|
|
|
734
768
|
});
|
|
735
769
|
return resolved.map(({ address, resolved: addressResolution }) => {
|
|
736
770
|
if (!addressResolution) {
|
|
737
|
-
|
|
771
|
+
const message = `Unknown address "${boundedEchoText(address)}"`;
|
|
772
|
+
return {
|
|
773
|
+
address: boundedEchoText(address),
|
|
774
|
+
error: message,
|
|
775
|
+
errorDetails: {
|
|
776
|
+
...framingError("unknown_address", message),
|
|
777
|
+
nextAction: this.searchRecovery({ query: recoveryQuery(address) }, "Find the configured canonical address before retrying."),
|
|
778
|
+
},
|
|
779
|
+
};
|
|
738
780
|
}
|
|
739
781
|
const catalog = catalogs.get(addressResolution.connector.id);
|
|
740
782
|
if (catalog instanceof Error) {
|
|
741
|
-
|
|
783
|
+
const classified = classifyCallError(catalog, "catalog_lookup_failed");
|
|
784
|
+
const message = boundedEchoText(classified.message);
|
|
785
|
+
return {
|
|
786
|
+
address: boundedEchoText(address),
|
|
787
|
+
error: message,
|
|
788
|
+
errorDetails: {
|
|
789
|
+
code: classified.code,
|
|
790
|
+
message,
|
|
791
|
+
retryable: classified.retryable,
|
|
792
|
+
...(classified.retryAfterMs === undefined
|
|
793
|
+
? {}
|
|
794
|
+
: { retryAfterMs: classified.retryAfterMs }),
|
|
795
|
+
},
|
|
796
|
+
};
|
|
742
797
|
}
|
|
743
798
|
const tool = catalog?.find((item) => item.name === addressResolution.toolName);
|
|
744
799
|
if (!tool) {
|
|
800
|
+
const message = `Unknown tool "${boundedEchoText(addressResolution.toolName)}" on connector "${addressResolution.connector.id}"`;
|
|
801
|
+
const suggestions = describeSuggestions(addressResolution.connector.id, addressResolution.toolName, catalog ?? []);
|
|
745
802
|
return {
|
|
746
|
-
address,
|
|
747
|
-
error:
|
|
803
|
+
address: boundedEchoText(address),
|
|
804
|
+
error: message,
|
|
805
|
+
errorDetails: {
|
|
806
|
+
...framingError("unknown_tool", message),
|
|
807
|
+
nextAction: this.searchRecovery({
|
|
808
|
+
query: recoveryQuery(addressResolution.toolName),
|
|
809
|
+
connector: addressResolution.connector.id,
|
|
810
|
+
}, "Find the connector's current canonical tool address."),
|
|
811
|
+
...(suggestions.length > 0 ? { suggestions } : {}),
|
|
812
|
+
},
|
|
748
813
|
};
|
|
749
814
|
}
|
|
750
815
|
const input = tool.inputSchema ?? { type: "object" };
|
package/dist/execute.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { InvocationFailure } from "./invocation.js";
|
|
|
6
6
|
import type { RegistryView } from "./registry.js";
|
|
7
7
|
import type { Executor, ExecutorProvider, Logger } from "./types.js";
|
|
8
8
|
export declare const EXECUTE_MAX_BATCH_CALLS = 10;
|
|
9
|
+
/** Complete entries plus an exact omission count, all inside this byte cap. */
|
|
10
|
+
export declare const CONNECTOR_INVENTORY_MAX_BYTES = 256;
|
|
9
11
|
/**
|
|
10
12
|
* Default budgets for `connecta.emit`. The byte budget is a transport bound,
|
|
11
13
|
* not a context bound — emitted image/audio blocks reach the model as media,
|
package/dist/execute.js
CHANGED
|
@@ -4,7 +4,7 @@ import { boundedDiscoveryText, CatalogService, DiscoveryPolicyError, flatSearchR
|
|
|
4
4
|
import { errorResult, jsonResult } from "./meta-tools.js";
|
|
5
5
|
import { guardExecuteResultValue, MAX_EXECUTE_LOG_CHARS, truncateExecuteText, } from "./executor-result.js";
|
|
6
6
|
import { ExecutorAdmissionError, ExecutorExecutionError, isAdmittingExecutor, } from "./executor-admission.js";
|
|
7
|
-
import { classifyCallError } from "./errors.js";
|
|
7
|
+
import { boundedEchoText, classifyCallError } from "./errors.js";
|
|
8
8
|
import { InvocationFailure, InvocationService, } from "./invocation.js";
|
|
9
9
|
import { hasConnectorGuides } from "./skills.js";
|
|
10
10
|
import { isExplicitlyReadOnly } from "./tool-safety.js";
|
|
@@ -12,6 +12,8 @@ import { isExplicitlyReadOnly } from "./tool-safety.js";
|
|
|
12
12
|
const EXECUTE_MAX_HOST_CALLS = 20;
|
|
13
13
|
export const EXECUTE_MAX_BATCH_CALLS = 10;
|
|
14
14
|
const EXECUTE_HOST_CALL_TIMEOUT_MS = 15_000;
|
|
15
|
+
/** Complete entries plus an exact omission count, all inside this byte cap. */
|
|
16
|
+
export const CONNECTOR_INVENTORY_MAX_BYTES = 256;
|
|
15
17
|
/**
|
|
16
18
|
* Default budgets for `connecta.emit`. The byte budget is a transport bound,
|
|
17
19
|
* not a context bound — emitted image/audio blocks reach the model as media,
|
|
@@ -996,24 +998,47 @@ function discardedEmitsText(emitted) {
|
|
|
996
998
|
];
|
|
997
999
|
return lines.length > 0 ? `\n\n${lines.join("\n")}` : "";
|
|
998
1000
|
}
|
|
999
|
-
|
|
1001
|
+
function connectorInventory(connectors) {
|
|
1002
|
+
const prefix = "Connectors: ";
|
|
1003
|
+
if (connectors.length === 0)
|
|
1004
|
+
return `${prefix}none.`;
|
|
1005
|
+
const entries = connectors.map((connector) => {
|
|
1006
|
+
const shortcut = sanitizeIdentifier(connector.id);
|
|
1007
|
+
return shortcut === connector.id
|
|
1008
|
+
? connector.id
|
|
1009
|
+
: `${connector.id} (shortcut ${shortcut})`;
|
|
1010
|
+
});
|
|
1011
|
+
const shown = [];
|
|
1012
|
+
for (let index = 0; index < entries.length; index++) {
|
|
1013
|
+
const entry = entries[index];
|
|
1014
|
+
if (entry === undefined)
|
|
1015
|
+
break;
|
|
1016
|
+
const candidate = [...shown, entry].join(", ");
|
|
1017
|
+
const omitted = entries.length - index - 1;
|
|
1018
|
+
const suffix = omitted > 0 ? `; +${omitted} more.` : ".";
|
|
1019
|
+
const serialized = prefix + candidate + suffix;
|
|
1020
|
+
if (boundedEchoText(serialized, CONNECTOR_INVENTORY_MAX_BYTES) !== serialized) {
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
shown.push(entry);
|
|
1024
|
+
}
|
|
1025
|
+
const omitted = entries.length - shown.length;
|
|
1026
|
+
if (omitted === 0)
|
|
1027
|
+
return `${prefix}${shown.join(", ")}.`;
|
|
1028
|
+
return `${prefix}${shown.join(", ")}${shown.length > 0 ? "; " : ""}+${omitted} more.`;
|
|
1029
|
+
}
|
|
1030
|
+
const executeDescription = (emitBudgets, connectorGuides, connectors) => `Choose the route before discovery. Exactly one unknown-address read uses top-level search_tools then call_tool. execute_code is the primary surface for everything wider: make exactly one execute_code call that searches, selects, calls, and reduces. A discovery-only program wastes its round trip: finish here, don't return catalog matches for a later call. Only readOnlyHint: true tools are available. Limits: ${EXECUTE_MAX_HOST_CALLS} host calls per run, ${EXECUTE_MAX_BATCH_CALLS} per batch, ${EXECUTE_HOST_CALL_TIMEOUT_MS / 1_000}-second host deadline.
|
|
1031
|
+
|
|
1032
|
+
${connectorInventory(connectors)}
|
|
1000
1033
|
|
|
1001
|
-
Write
|
|
1002
|
-
-
|
|
1003
|
-
- connecta.
|
|
1004
|
-
- top-level search_tools returns { connectors: [{ id, tools }], total, offset, limit, hasMore }; connecta.search returns { tools, total, offset, limit, hasMore }; connecta.describe returns { tools }.
|
|
1005
|
-
- connecta.search(args) loads catalogs and must be followed by selection and calls in this program; set connector to the obvious id to load one, otherwise it loads all. For distinct operations, make separate short searches here. Check address, description, requiredInputKeys, truncation, safety, and outputs; never take the first lexical or merely input-compatible match. Select by fit; do not require it to be the only match. Missing outputKeys means inspect outputSchema, not discard the candidate. Every required key needs task or prior-result data; do not prefer zero required keys. Put every requiredInputKey in call args. For dependencies, match an earlier outputKey to the later requiredInputKey. [] means no required keys, not permission to invent args. Describe only a truncated/insufficient compact shape. Reducers use declared outputKeys, never guessed items/results roots. Describe one address or an addresses array. Use safety: "readOnly" to avoid advertising calls this sandbox cannot execute.${connectorGuides ? " guideRequired: true = stop. Describe clears only schema_truncated; otherwise return its exact guide, fetch with top-level skills, then write the informed call." : ""}
|
|
1034
|
+
Write one plain-JavaScript async arrow function. Use only:
|
|
1035
|
+
- <connectorId>.<toolName>(args) for a sanitized shortcut, or connecta.call(address, args) for a canonical address.
|
|
1036
|
+
- connecta.search(args), connecta.describe(args), and connecta.batch(calls) for discovery and independent read-only calls.
|
|
1006
1037
|
- connecta.emit(block) — { type: "text", text } or { type: "image" | "audio", data (base64), mimeType }. Success-only; ${emitBudgets.maxBlocks} blocks/${emitBudgets.maxBytes} bytes; invalid/over-budget throws.
|
|
1007
|
-
- connecta.ui(html, options?)
|
|
1038
|
+
- connecta.ui(html, options?) for one success-only view; return the same initial summary the HTML renders.
|
|
1008
1039
|
- console.log(...) — captured.
|
|
1009
1040
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
Dependent example (only when the second call requires a value returned by the first): async () => { const { tools } = await connecta.search({ query: "pipeline run job logs", safety: "readOnly", includeSchemas: "compact" }); const pick = (suffix) => { const match = tools.find((t) => t.address.endsWith(suffix)); if (!match) throw new Error("no tool for " + suffix); return match.address; }; const run = await connecta.call(pick(".get_run"), { runId: 42 }); const logs = await connecta.call(pick(".get_job_logs"), { jobId: run.failedJobId }); return [run, logs]; }
|
|
1013
|
-
|
|
1014
|
-
A caught Connecta failure keeps its message and exposes code, retryable, and details; batch uses the same fields. Branch on fields, never prose. Never retry retryable: false, or rate_limited immediately; portable code cannot wait. Return JSON; reduce large results before they truncate.
|
|
1015
|
-
|
|
1016
|
-
Plain JS. Compact schemas are TypeScript-like: write the property names they display; never guess positions or aliases.`;
|
|
1041
|
+
Programs have no portable ambient capabilities. Return JSON and reduce large results before they truncate. Fetch skills({ name: "usage" }) once for selection rules, exact result shapes, repair, examples, guide handling${connectorGuides ? ", connector-guide rules" : ""}, and runtime differences.`;
|
|
1017
1042
|
/** Register the execute_code meta-tool. Only called when an executor is configured. */
|
|
1018
1043
|
export function registerExecuteTool(server, registry, ctx) {
|
|
1019
1044
|
// Resolved once so the description and the collector cannot disagree about
|
|
@@ -1022,6 +1047,7 @@ export function registerExecuteTool(server, registry, ctx) {
|
|
|
1022
1047
|
maxBytes: resolveEmitBudget(ctx.maxEmittedBytes, EXECUTE_MAX_EMITTED_BYTES),
|
|
1023
1048
|
maxBlocks: resolveEmitBudget(ctx.maxEmittedBlocks, EXECUTE_MAX_EMITTED_BLOCKS),
|
|
1024
1049
|
};
|
|
1050
|
+
const connectors = registry.listConnectors();
|
|
1025
1051
|
const handler = createExecuteTool(registry, ctx.baseUrl, ctx.executor, ctx.logger, ctx.activity, {
|
|
1026
1052
|
...(ctx.discoveryConcurrency !== undefined
|
|
1027
1053
|
? { discoveryConcurrency: ctx.discoveryConcurrency }
|
|
@@ -1034,11 +1060,11 @@ export function registerExecuteTool(server, registry, ctx) {
|
|
|
1034
1060
|
...(ctx.defer !== undefined ? { defer: ctx.defer } : {}),
|
|
1035
1061
|
});
|
|
1036
1062
|
server.registerTool("execute_code", {
|
|
1037
|
-
description: executeDescription(emitBudgets, hasConnectorGuides(
|
|
1063
|
+
description: executeDescription(emitBudgets, hasConnectorGuides(connectors), connectors),
|
|
1038
1064
|
inputSchema: z.object({
|
|
1039
1065
|
code: z
|
|
1040
1066
|
.string()
|
|
1041
|
-
.describe("One complete JavaScript async arrow function
|
|
1067
|
+
.describe("One complete JavaScript async arrow function that discovers, calls, and returns the reduced answer."),
|
|
1042
1068
|
diagnostics: z
|
|
1043
1069
|
.boolean()
|
|
1044
1070
|
.optional()
|
package/dist/meta-tools.js
CHANGED
|
@@ -1044,13 +1044,13 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
1044
1044
|
},
|
|
1045
1045
|
};
|
|
1046
1046
|
}
|
|
1047
|
-
const SEARCH_DESC = `Use top-level search
|
|
1048
|
-
const CALL_DESC = '
|
|
1049
|
-
const CALL_DESTRUCTIVE_DESC = "
|
|
1050
|
-
const GET_RESULT_DESC = "Page a truncated result
|
|
1047
|
+
const SEARCH_DESC = `Use top-level search for one unknown-address read before call_tool, or for approval-required work before call_destructive_tool. Use 2–4 action/object terms and includeSchemas="compact"; the default limit is ${DEFAULT_SEARCH_LIMIT}. Set connector when known. safety="readOnly" finds direct or program calls; "approvalRequired" finds the fail-closed complement. These filters grant no authority. For multiple, dependent, or reduced read-only calls, use one execute_code program instead. Empty query browses.`;
|
|
1048
|
+
const CALL_DESC = 'Call one tool explicitly annotated readOnlyHint: true. Use execute_code for multiple, dependent, or reduced read-only calls. Unannotated or write-capable tools fail closed to call_destructive_tool. fields projects JSON dot-paths; use [] through arrays. A truncated result carries a get_result action.';
|
|
1049
|
+
const CALL_DESTRUCTIVE_DESC = "Call any tool not explicitly annotated readOnlyHint: true. Include a short reason for the human reviewer after checking the schema and consequences. The reason grants no authority and is not sent downstream.";
|
|
1050
|
+
const GET_RESULT_DESC = "Page a truncated direct-call result by id and byte offset. A program result is never paged; reduce it inside execute_code. Returns text, offset, nextOffset when more remains, and totalBytes.";
|
|
1051
1051
|
const AUTHORIZE_DESC = "Use after auth_required. Returns an OAuth or operator-credential handoff, or reports required deployment configuration. force=true restarts OAuth only; this tool never accepts credentials.";
|
|
1052
|
-
const SKILLS_DESC = 'List or fetch
|
|
1053
|
-
const SEARCH_WITH_DESCRIBE_DESC =
|
|
1052
|
+
const SKILLS_DESC = 'List or fetch on-demand guidance. Fetch usage once per task for program syntax, selection, repair, examples, and runtime details.';
|
|
1053
|
+
const SEARCH_WITH_DESCRIBE_DESC = SEARCH_DESC;
|
|
1054
1054
|
/**
|
|
1055
1055
|
* Sentences appended to a meta-tool description only when this connection
|
|
1056
1056
|
* actually has connector guides. Tool descriptions are always-loaded context,
|
|
@@ -1060,9 +1060,9 @@ const SEARCH_WITH_DESCRIBE_DESC = `${SEARCH_DESC} Expand an ambiguous compact sh
|
|
|
1060
1060
|
* Registration is per connection and reads the configured connector set.
|
|
1061
1061
|
*/
|
|
1062
1062
|
const GUIDE_NOTES = {
|
|
1063
|
-
skills: "
|
|
1064
|
-
search: " A result
|
|
1065
|
-
destructive: "
|
|
1063
|
+
skills: " Also lists this deployment's connector guides by exact name.",
|
|
1064
|
+
search: " A result with guideRequired: true requires its exact named connector guide before the call.",
|
|
1065
|
+
destructive: " Fetch any exact connector guide named by discovery before the call.",
|
|
1066
1066
|
};
|
|
1067
1067
|
/** `base`, plus its guide note when any VISIBLE connector carries a guide. */
|
|
1068
1068
|
function describedFor(registry, base, note) {
|
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
import { type RemoteMcpAuth } from "../connectors/remote-mcp.js";
|
|
2
2
|
import type { Connector } from "../types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Which Stripe environment this instance speaks to. Required, and deliberately
|
|
5
|
-
* undefaulted: there is no safe guess between an account that moves real money
|
|
6
|
-
* and one that does not.
|
|
7
|
-
*/
|
|
3
|
+
/** Which Stripe environment a static credential reaches. */
|
|
8
4
|
export type StripeMode = "production" | "sandbox";
|
|
9
|
-
/** Stripe publishes one hosted MCP endpoint
|
|
5
|
+
/** Stripe publishes one hosted MCP endpoint for every account and mode. */
|
|
10
6
|
export declare const STRIPE_MCP_ENDPOINT = "https://mcp.stripe.com/";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Which Stripe environment this connector reaches. Shapes the title,
|
|
14
|
-
* description, guide, and admission budget, and is checked against a
|
|
15
|
-
* recognizable key prefix in `auth` headers.
|
|
16
|
-
*/
|
|
17
|
-
mode: StripeMode;
|
|
18
|
-
/** Human-readable display name; defaults to "Stripe (<mode>)". */
|
|
7
|
+
interface StripeCommonOptions {
|
|
8
|
+
/** Human-readable display name; defaults to "Stripe" for OAuth. */
|
|
19
9
|
title?: string;
|
|
20
10
|
/** Which business purpose and Stripe context this connector is for. */
|
|
21
11
|
purpose: string;
|
|
22
|
-
/** OAuth by default; static headers support restricted API keys. */
|
|
23
|
-
auth?: RemoteMcpAuth;
|
|
24
|
-
/**
|
|
25
|
-
* Connect platform only: act as this connected account (`acct_...`) by
|
|
26
|
-
* sending Stripe's `Stripe-Account` header. Stripe does not support OAuth on
|
|
27
|
-
* connected-account calls, so this requires `headers` auth.
|
|
28
|
-
*/
|
|
29
|
-
connectedAccount?: string;
|
|
30
12
|
/** Connector-specific conventions appended to the maintained provider guide. */
|
|
31
13
|
instructions?: string;
|
|
32
14
|
/** Connector-specific inline result limit; omit to inherit the deployment. */
|
|
33
15
|
maxResultBytes?: number;
|
|
34
16
|
}
|
|
17
|
+
/** OAuth sessions discover account and mode together from Stripe's live tools. */
|
|
18
|
+
export interface StripeOAuthOptions extends StripeCommonOptions {
|
|
19
|
+
auth?: {
|
|
20
|
+
type: "oauth";
|
|
21
|
+
};
|
|
22
|
+
mode?: never;
|
|
23
|
+
connectedAccount?: never;
|
|
24
|
+
}
|
|
25
|
+
/** Static credentials have one fixed mode, including Stripe Connect calls. */
|
|
26
|
+
export interface StripeHeaderOptions extends StripeCommonOptions {
|
|
27
|
+
auth: Extract<RemoteMcpAuth, {
|
|
28
|
+
type: "headers";
|
|
29
|
+
}>;
|
|
30
|
+
mode: StripeMode;
|
|
31
|
+
/** Act as one Connect account by sending Stripe's `Stripe-Account` header. */
|
|
32
|
+
connectedAccount?: string;
|
|
33
|
+
}
|
|
34
|
+
export type StripeOptions = StripeOAuthOptions | StripeHeaderOptions;
|
|
35
35
|
/**
|
|
36
36
|
* The manifest this release reviewed: both lists in one place, which is what
|
|
37
37
|
* makes the classification the connector applies and the drift check that runs
|
|
@@ -47,3 +47,4 @@ export interface StripeOptions {
|
|
|
47
47
|
export declare const STRIPE_VETTED_CATALOG: import("../catalog-drift.js").VettedCatalog;
|
|
48
48
|
/** A maintained Stripe hosted-MCP connection. */
|
|
49
49
|
export declare function stripe(id: string, options: StripeOptions): Connector;
|
|
50
|
+
export {};
|
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,19 +153,23 @@ 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.
|
|
@@ -158,13 +178,11 @@ ${copy.warning}
|
|
|
158
178
|
- 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
179
|
- 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
180
|
- 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}
|
|
181
|
+
- 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
182
|
- 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
183
|
- 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
184
|
- 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
|
-
: ""}`;
|
|
185
|
+
`;
|
|
168
186
|
}
|
|
169
187
|
/** A maintained Stripe hosted-MCP connection. */
|
|
170
188
|
export function stripe(id, options) {
|
|
@@ -172,31 +190,40 @@ export function stripe(id, options) {
|
|
|
172
190
|
if (!purpose) {
|
|
173
191
|
throw new Error("stripe() requires a non-empty account purpose.");
|
|
174
192
|
}
|
|
175
|
-
const mode = options.mode;
|
|
176
|
-
if (mode !== "production" && mode !== "sandbox") {
|
|
177
|
-
throw new Error(`stripe("${id}") requires mode "production" or "sandbox".`);
|
|
178
|
-
}
|
|
179
193
|
const auth = resolveAuth(id, options);
|
|
180
|
-
|
|
181
|
-
|
|
194
|
+
const mode = "mode" in options ? options.mode : undefined;
|
|
195
|
+
if (auth.type === "oauth" && mode !== undefined) {
|
|
196
|
+
throw new Error(`stripe("${id}") cannot declare a connector-wide mode for OAuth; Stripe returns mode with each account.`);
|
|
197
|
+
}
|
|
198
|
+
if (auth.type === "headers" && mode !== "production" && mode !== "sandbox") {
|
|
199
|
+
throw new Error(`stripe("${id}") with headers auth requires mode "production" or "sandbox".`);
|
|
200
|
+
}
|
|
201
|
+
if (auth.type === "headers") {
|
|
202
|
+
assertModeMatchesKey(id, mode, auth);
|
|
203
|
+
}
|
|
204
|
+
const copy = mode === undefined ? undefined : MODE_COPY[mode];
|
|
182
205
|
const connector = remoteMcp(id, {
|
|
183
206
|
url: STRIPE_MCP_ENDPOINT,
|
|
184
|
-
title: options.title ?? copy
|
|
185
|
-
description:
|
|
207
|
+
title: options.title ?? copy?.title ?? "Stripe",
|
|
208
|
+
description: mode === undefined
|
|
209
|
+
? `Stripe payments (live and sandbox accounts) — ${purpose}`
|
|
210
|
+
: `Stripe payments (${copy?.blurb}) — ${purpose}`,
|
|
186
211
|
auth,
|
|
187
212
|
requireHttps: true,
|
|
188
|
-
callAdmission: STRIPE_ADMISSION[mode],
|
|
213
|
+
callAdmission: mode === undefined ? OAUTH_ADMISSION : STRIPE_ADMISSION[mode],
|
|
189
214
|
usageGuide: {
|
|
190
|
-
content:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
215
|
+
content: mode === undefined
|
|
216
|
+
? oauthUsageGuide(purpose, options.instructions)
|
|
217
|
+
: fixedModeUsageGuide(mode, purpose, options.instructions),
|
|
218
|
+
// Explicit rather than derived: fixed credentials must lead with mode,
|
|
219
|
+
// while OAuth must lead with its account-scoped selector pair.
|
|
220
|
+
summary: mode === undefined
|
|
221
|
+
? "Live and sandbox Stripe accounts. List accounts; carry the returned stripe_context and livemode before acting."
|
|
222
|
+
: mode === "production"
|
|
223
|
+
? "PRODUCTION: real money. This static credential has one fixed live-mode scope."
|
|
224
|
+
: "Sandbox: test data only. This static credential has one fixed sandbox scope.",
|
|
225
|
+
// Not `required`. The four generic tools are the routing decision; a
|
|
226
|
+
// guide forced into every call would pay for the same prose repeatedly.
|
|
200
227
|
},
|
|
201
228
|
...(options.maxResultBytes !== undefined
|
|
202
229
|
? { 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. */
|