@zackbart/connecta 0.28.0 → 0.29.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.
Files changed (320) hide show
  1. package/CHANGELOG.md +961 -5
  2. package/README.md +62 -39
  3. package/bin/connecta.mjs +110 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  12. package/dist/auth/clerk-sdk/client.js +5 -0
  13. package/dist/auth/clerk-sdk/generated.js +1845 -0
  14. package/dist/auth/clerk-transport.js +19 -0
  15. package/dist/auth/clerk.d.ts +17 -7
  16. package/dist/auth/clerk.js +189 -107
  17. package/dist/auth/cloudflare-access.js +25 -26
  18. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  19. package/dist/auth/downstream-client-metadata.js +133 -0
  20. package/dist/auth/downstream-oauth.js +1632 -1509
  21. package/dist/auth/downstream-revocation.js +106 -0
  22. package/dist/auth/oauth-token-response.js +231 -0
  23. package/dist/auth/oauth-v2-migration.js +116 -0
  24. package/dist/auth/static-oauth.js +526 -0
  25. package/dist/auth-elicitation.js +283 -0
  26. package/dist/branding.d.ts +8 -16
  27. package/dist/branding.js +26 -59
  28. package/dist/byte-read-response.js +39 -0
  29. package/dist/call-admission.js +12 -16
  30. package/dist/catalog-cache.js +739 -0
  31. package/dist/catalog-drift.d.ts +50 -15
  32. package/dist/catalog-drift.js +365 -128
  33. package/dist/catalog-fingerprint.js +2 -4
  34. package/dist/catalog-freshness.js +13 -0
  35. package/dist/catalog-limits.js +2 -2
  36. package/dist/catalog-service.js +368 -224
  37. package/dist/catalog.js +43 -84
  38. package/dist/concurrency.js +3 -4
  39. package/dist/config-defaults.d.ts +39 -0
  40. package/dist/config-defaults.js +46 -0
  41. package/dist/config-schema.d.ts +181 -0
  42. package/dist/config-schema.js +435 -0
  43. package/dist/config-value-sources.js +102 -0
  44. package/dist/config.d.ts +631 -0
  45. package/dist/config.js +503 -0
  46. package/dist/connector-access.d.ts +1 -0
  47. package/dist/connector-access.js +13 -15
  48. package/dist/connector-caller.d.ts +8 -0
  49. package/dist/connector-caller.js +15 -6
  50. package/dist/connector-scope.js +1 -1
  51. package/dist/connectors/api-connector.d.ts +220 -0
  52. package/dist/connectors/api-connector.js +214 -0
  53. package/dist/connectors/api.d.ts +8 -71
  54. package/dist/connectors/api.js +13 -99
  55. package/dist/connectors/guarded-fetch.d.ts +144 -0
  56. package/dist/connectors/guarded-fetch.js +108 -25
  57. package/dist/connectors/negotiation-cache.js +46 -0
  58. package/dist/connectors/option-shapes.js +27 -0
  59. package/dist/connectors/remote-mcp.d.ts +30 -4
  60. package/dist/connectors/remote-mcp.js +1780 -351
  61. package/dist/connectors/resource-uri.js +335 -0
  62. package/dist/credential-contract.d.ts +10 -0
  63. package/dist/credential-rules.js +1 -3
  64. package/dist/credential-url.js +108 -0
  65. package/dist/credentials.d.ts +6 -1
  66. package/dist/credentials.js +27 -18
  67. package/dist/d1.d.ts +130 -0
  68. package/dist/d1.js +290 -0
  69. package/dist/describe-config.d.ts +165 -0
  70. package/dist/describe-config.js +315 -0
  71. package/dist/described.js +106 -0
  72. package/dist/downstream-input-context.js +119 -0
  73. package/dist/downstream-input.js +323 -0
  74. package/dist/errors.d.ts +97 -20
  75. package/dist/errors.js +158 -80
  76. package/dist/execute.js +453 -305
  77. package/dist/executor-admission.d.ts +1 -0
  78. package/dist/executor-admission.js +11 -15
  79. package/dist/executor-contract.d.ts +20 -0
  80. package/dist/executor-contract.js +48 -0
  81. package/dist/executor-result.js +24 -9
  82. package/dist/executors/quickjs-child.js +3 -4
  83. package/dist/executors/quickjs-runtime.js +147 -59
  84. package/dist/executors/quickjs.js +29 -35
  85. package/dist/favicon.js +1 -2
  86. package/dist/fix-prompt.js +1 -3
  87. package/dist/guest-runtime.js +188 -0
  88. package/dist/guest-types.d.ts +176 -0
  89. package/dist/guest-types.js +1 -0
  90. package/dist/html-security.js +24 -0
  91. package/dist/identity.js +1 -3
  92. package/dist/inbound-credential.js +52 -0
  93. package/dist/index.d.ts +25 -257
  94. package/dist/index.js +65 -478
  95. package/dist/invocation-auth.js +69 -0
  96. package/dist/invocation.js +230 -73
  97. package/dist/json-schema.d.ts +1 -1
  98. package/dist/mcp-client-context.js +35 -0
  99. package/dist/mcp-result.js +11 -7
  100. package/dist/meta-output.js +76 -0
  101. package/dist/meta-tool-names.d.ts +2 -0
  102. package/dist/meta-tool-names.js +3 -0
  103. package/dist/meta-tools.js +285 -209
  104. package/dist/module-contracts.d.ts +8 -23
  105. package/dist/node.d.ts +0 -2
  106. package/dist/node.js +2 -2
  107. package/dist/oauth-callback-outcome.js +1 -3
  108. package/dist/oauth-handoff.js +117 -0
  109. package/dist/oauth-partition.js +17 -0
  110. package/dist/oauth-sealing.js +20 -0
  111. package/dist/operator-record.d.ts +137 -0
  112. package/dist/operator-record.js +420 -0
  113. package/dist/operator-ui/assets.js +31 -0
  114. package/dist/operator-ui/contract.d.ts +72 -0
  115. package/dist/operator-ui/contract.js +1 -0
  116. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  117. package/dist/operator-ui/generated.js +5 -4
  118. package/dist/operator-ui/model.d.ts +12 -87
  119. package/dist/operator-ui/model.js +2 -9
  120. package/dist/operator-ui/setup-commands.js +1 -3
  121. package/dist/operator-ui/view.d.ts +323 -0
  122. package/dist/operator-ui/view.js +62 -189
  123. package/dist/page-styles.js +2 -3
  124. package/dist/program-source.js +14 -2
  125. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  126. package/dist/provider.d.ts +142 -0
  127. package/dist/provider.js +187 -0
  128. package/dist/providers/_shared/google/result-size.js +57 -0
  129. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  130. package/dist/providers/_shared/google/service-account.js +500 -0
  131. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  132. package/dist/providers/_shared/google/workspace.js +518 -0
  133. package/dist/providers/basecamp/index.d.ts +43 -0
  134. package/dist/providers/basecamp/index.js +997 -0
  135. package/dist/providers/basecamp/skill.generated.js +9 -0
  136. package/dist/providers/breeze/index.d.ts +34 -0
  137. package/dist/providers/breeze/index.js +1446 -0
  138. package/dist/providers/breeze/skill.generated.js +9 -0
  139. package/dist/providers/ccb/index.d.ts +103 -0
  140. package/dist/providers/ccb/index.js +1725 -0
  141. package/dist/providers/ccb/skill.generated.js +9 -0
  142. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  143. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  144. package/dist/providers/cloudflare/skill.generated.js +10 -0
  145. package/dist/providers/docs/index.d.ts +54 -0
  146. package/dist/providers/docs/index.js +1237 -0
  147. package/dist/providers/docs/skill.generated.js +9 -0
  148. package/dist/providers/drive/index.d.ts +53 -0
  149. package/dist/providers/drive/index.js +1523 -0
  150. package/dist/providers/drive/skill.generated.js +9 -0
  151. package/dist/providers/forms/index.d.ts +54 -0
  152. package/dist/providers/forms/index.js +1217 -0
  153. package/dist/providers/forms/skill.generated.js +11 -0
  154. package/dist/providers/github/auth.d.ts +32 -0
  155. package/dist/providers/github/auth.js +361 -0
  156. package/dist/providers/github/hosted.js +218 -0
  157. package/dist/providers/github/index.d.ts +13 -0
  158. package/dist/providers/github/index.js +140 -0
  159. package/dist/providers/github/key.js +100 -0
  160. package/dist/providers/github/rest.js +321 -0
  161. package/dist/providers/github/scope.d.ts +29 -0
  162. package/dist/providers/github/scope.js +124 -0
  163. package/dist/providers/github/skill.generated.js +8 -0
  164. package/dist/providers/gmail/index.d.ts +51 -0
  165. package/dist/providers/gmail/index.js +1436 -0
  166. package/dist/providers/gmail/skill.generated.js +9 -0
  167. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  168. package/dist/providers/linear/index.js +352 -0
  169. package/dist/providers/linear/skill.generated.js +8 -0
  170. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  171. package/dist/providers/mixpanel/index.js +416 -0
  172. package/dist/providers/mixpanel/skill.generated.js +9 -0
  173. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  174. package/dist/providers/{notion.js → notion/index.js} +226 -239
  175. package/dist/providers/notion/skill.generated.js +10 -0
  176. package/dist/providers/overflow/index.d.ts +60 -0
  177. package/dist/providers/overflow/index.js +1535 -0
  178. package/dist/providers/overflow/skill.generated.js +8 -0
  179. package/dist/providers/planning-center/index.d.ts +59 -0
  180. package/dist/providers/planning-center/index.js +2199 -0
  181. package/dist/providers/planning-center/skill.generated.js +10 -0
  182. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  183. package/dist/providers/revenuecat/index.js +557 -0
  184. package/dist/providers/revenuecat/skill.generated.js +11 -0
  185. package/dist/providers/sheets/index.d.ts +54 -0
  186. package/dist/providers/sheets/index.js +1237 -0
  187. package/dist/providers/sheets/skill.generated.js +10 -0
  188. package/dist/providers/slides/index.d.ts +53 -0
  189. package/dist/providers/slides/index.js +2645 -0
  190. package/dist/providers/slides/skill.generated.js +9 -0
  191. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  192. package/dist/providers/stripe/index.js +266 -0
  193. package/dist/providers/stripe/skill.generated.js +11 -0
  194. package/dist/providers/tithely/index.d.ts +49 -0
  195. package/dist/providers/tithely/index.js +1003 -0
  196. package/dist/providers/tithely/skill.generated.js +8 -0
  197. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  198. package/dist/providers/vercel/index.js +1038 -0
  199. package/dist/providers/vercel/mcp-ownership.js +212 -0
  200. package/dist/providers/vercel/skill.generated.js +11 -0
  201. package/dist/registry.d.ts +108 -171
  202. package/dist/registry.js +513 -925
  203. package/dist/request-state.js +21 -0
  204. package/dist/resource-read.js +100 -0
  205. package/dist/result-shapes.js +17 -49
  206. package/dist/retired-toolkits.js +1 -1
  207. package/dist/routes/access-tokens.js +3 -6
  208. package/dist/routes/activity-context.js +21 -0
  209. package/dist/routes/activity-disclosure.js +40 -0
  210. package/dist/routes/activity.js +59 -21
  211. package/dist/routes/actor-labels.js +3 -50
  212. package/dist/routes/connect.js +221 -0
  213. package/dist/routes/credentials.js +15 -27
  214. package/dist/routes/mcp.js +356 -130
  215. package/dist/routes/oauth-client-metadata.js +15 -0
  216. package/dist/routes/oauth-management.js +45 -107
  217. package/dist/routes/oauth.js +136 -57
  218. package/dist/routes/operator.js +3 -3
  219. package/dist/routes/shared.d.ts +25 -64
  220. package/dist/routes/shared.js +160 -80
  221. package/dist/routes/skills.js +30 -0
  222. package/dist/routes/ui-config.js +225 -0
  223. package/dist/routes/ui-data.js +40 -35
  224. package/dist/routes/ui.js +66 -56
  225. package/dist/runtime/admission.js +4 -2
  226. package/dist/runtime/call-admission.js +3 -1
  227. package/dist/runtime/connector-scope.js +1 -4
  228. package/dist/runtime/run.js +10 -39
  229. package/dist/runtime/services.js +12 -117
  230. package/dist/runtime/shared-read.js +1 -3
  231. package/dist/runtime/storage.js +8 -1
  232. package/dist/sent-secrets.d.ts +52 -0
  233. package/dist/sent-secrets.js +474 -0
  234. package/dist/server.js +28 -71
  235. package/dist/skills.js +406 -174
  236. package/dist/sqlite.d.ts +48 -0
  237. package/dist/sqlite.js +166 -0
  238. package/dist/storage/keys.js +428 -0
  239. package/dist/storage/memory.js +7 -0
  240. package/dist/storage/sql.d.ts +66 -0
  241. package/dist/storage/sql.js +534 -0
  242. package/dist/timeout.js +4 -2
  243. package/dist/tool-name.js +17 -0
  244. package/dist/tool-safety.d.ts +9 -35
  245. package/dist/tool-safety.js +20 -40
  246. package/dist/types.d.ts +320 -54
  247. package/dist/types.js +0 -1
  248. package/dist/ui.d.ts +7 -19
  249. package/dist/ui.js +76 -90
  250. package/dist/url-safety.js +3 -6
  251. package/dist/usage-guide.js +137 -0
  252. package/dist/validate.d.ts +7 -0
  253. package/dist/validate.js +309 -47
  254. package/dist/version.d.ts +1 -7
  255. package/dist/version.js +3 -7
  256. package/dist/worker.d.ts +11 -0
  257. package/dist/worker.js +434 -0
  258. package/documentation/architecture.md +665 -230
  259. package/documentation/auth.md +962 -458
  260. package/documentation/code-mode.md +501 -312
  261. package/documentation/meta-tools.md +449 -200
  262. package/documentation/operator-tests.md +93 -0
  263. package/documentation/provider-migration-0.29.md +60 -0
  264. package/examples/worker/AGENTS.md +34 -12
  265. package/examples/worker/README.md +286 -255
  266. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  267. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  268. package/examples/worker/src/connecta.config.ts +151 -0
  269. package/examples/worker/src/index.ts +14 -197
  270. package/examples/worker/wrangler.jsonc +15 -35
  271. package/package.json +125 -35
  272. package/templates/node/.dockerignore +1 -2
  273. package/templates/node/.env.example +20 -21
  274. package/templates/node/AGENTS.md +21 -17
  275. package/templates/node/Dockerfile +3 -5
  276. package/templates/node/README.md +133 -58
  277. package/templates/node/docker-compose.yml +11 -12
  278. package/templates/node/package.json +4 -2
  279. package/templates/node/src/connecta.config.ts +119 -0
  280. package/templates/node/src/index.ts +9 -154
  281. package/templates/node/src/provision-token.ts +17 -0
  282. package/AGENTS.md +0 -170
  283. package/dist/artifacts/connector.d.ts +0 -58
  284. package/dist/artifacts/connector.js +0 -730
  285. package/dist/artifacts/document.js +0 -86
  286. package/dist/artifacts/guide.js +0 -140
  287. package/dist/artifacts/html-scan.js +0 -329
  288. package/dist/artifacts/json.js +0 -78
  289. package/dist/artifacts/kv-store.d.ts +0 -20
  290. package/dist/artifacts/kv-store.js +0 -140
  291. package/dist/artifacts/markdown.js +0 -478
  292. package/dist/artifacts/module.d.ts +0 -37
  293. package/dist/artifacts/module.js +0 -66
  294. package/dist/artifacts/operations.d.ts +0 -197
  295. package/dist/artifacts/operations.js +0 -1002
  296. package/dist/artifacts/refresh.d.ts +0 -55
  297. package/dist/artifacts/refresh.js +0 -236
  298. package/dist/artifacts/routes.js +0 -206
  299. package/dist/artifacts/types.d.ts +0 -214
  300. package/dist/artifacts/types.js +0 -22
  301. package/dist/artifacts/validate.d.ts +0 -62
  302. package/dist/artifacts/validate.js +0 -651
  303. package/dist/artifacts.d.ts +0 -6
  304. package/dist/artifacts.js +0 -9
  305. package/dist/auth/bearer.d.ts +0 -11
  306. package/dist/auth/bearer.js +0 -50
  307. package/dist/providers/linear.js +0 -217
  308. package/dist/providers/mixpanel.js +0 -247
  309. package/dist/providers/revenuecat.js +0 -317
  310. package/dist/providers/stripe.js +0 -253
  311. package/dist/providers/vercel.js +0 -1253
  312. package/dist/storage/file.d.ts +0 -15
  313. package/dist/storage/file.js +0 -455
  314. package/ethos.md +0 -93
  315. package/examples/worker/src/cloudflare-kv.ts +0 -43
  316. package/examples/worker/src/d1-activity-row.ts +0 -155
  317. package/examples/worker/src/d1-activity.ts +0 -147
  318. package/examples/worker/src/d1-storage.ts +0 -138
  319. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  320. package/templates/node/src/file-activity.ts +0 -182
@@ -0,0 +1,323 @@
1
+ import { inputRequired, specTypeSchemas, ProtocolError, INVALID_PARAMS, CLIENT_CAPABILITIES_META_KEY, PROTOCOL_VERSION_META_KEY, } from "@modelcontextprotocol/server";
2
+ import { ConnectorCallError, echoedCallArgs } from "./errors.js";
3
+ import { redactAgentOutput, sentSecretsForRequest } from "./sent-secrets.js";
4
+ import { inputRetryKeys } from "./storage/keys.js";
5
+ import { REQUEST_STATE_TTL_MS, MAX_INPUT_ROUNDS, invalidRequestState, requestDigest, stateObject, } from "./request-state.js";
6
+ import { bindDownstreamCapabilities, downstreamInputCapabilities, bindDownstreamContinuation, clearDownstreamContinuation, assertNoPrivateStateEchoes, assertDownstreamOutputSafe, } from "./downstream-input-context.js";
7
+ const MAX_PAYLOAD_BYTES = 64 * 1024;
8
+ export const MAX_RELAY_STATE_CHARS = 128 * 1024;
9
+ const MAX_REQUESTS = 16;
10
+ const PURPOSE = "connecta:downstream-input:v1";
11
+ const pendingInputs = new WeakMap();
12
+ const modernRequests = new WeakSet();
13
+ function withinBudget(value) {
14
+ try {
15
+ return new TextEncoder().encode(JSON.stringify(value)).byteLength <= MAX_PAYLOAD_BYTES;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ function namespace(connector, index) {
22
+ return `downstream/${connector}/${index}`;
23
+ }
24
+ function invalidInput() {
25
+ throw new ConnectorCallError("input_required_invalid", "The downstream returned malformed or unsafe input requests.");
26
+ }
27
+ /** Capture opaque state before any result redaction or unwrapping can alter it. */
28
+ export async function captureDownstreamInput(scope, connector, address, raw, secrets) {
29
+ if (!withinBudget(raw))
30
+ throw new ConnectorCallError("input_required_limit", "The downstream input payload exceeds 64 KiB.");
31
+ if (!stateObject(raw) ||
32
+ (raw.requestState !== undefined && typeof raw.requestState !== "string") ||
33
+ (raw.inputRequests !== undefined && !stateObject(raw.inputRequests)) ||
34
+ (raw.requestState === undefined && raw.inputRequests === undefined))
35
+ return invalidInput();
36
+ const entries = Object.entries(raw.inputRequests ?? {});
37
+ if (!entries.length && raw.requestState === undefined)
38
+ return invalidInput();
39
+ if (entries.length > MAX_REQUESTS)
40
+ throw new ConnectorCallError("input_required_limit", "The downstream requested more than 16 inputs.");
41
+ const inputRequests = Object.create(null);
42
+ const inputs = Object.create(null);
43
+ const elicitation = downstreamInputCapabilities(scope).elicitation;
44
+ for (const [index, [key, request]] of entries.entries()) {
45
+ if (!key || key.length > 256 || !stateObject(request))
46
+ return invalidInput();
47
+ if (request.method !== "elicitation/create") {
48
+ throw new ConnectorCallError("input_required_unsupported", "Connecta relays downstream form and URL elicitation only.");
49
+ }
50
+ if (!stateObject(request.params))
51
+ return invalidInput();
52
+ const params = request.params;
53
+ const mode = params.mode ?? "form";
54
+ if (mode !== "form" && mode !== "url")
55
+ return invalidInput();
56
+ if (!elicitation?.[mode])
57
+ throw new ConnectorCallError("input_required_unsupported", "The client did not declare support for the downstream elicitation kind.");
58
+ if (mode === "url") {
59
+ if (typeof params.message !== "string" || typeof params.url !== "string")
60
+ return invalidInput();
61
+ try {
62
+ const url = new URL(params.url);
63
+ // An opaque downstream nonce is retained. Credentials are never put
64
+ // in browser URLs; refusal preserves semantics instead of rewriting it.
65
+ if (url.protocol !== "https:" ||
66
+ url.username ||
67
+ url.password ||
68
+ [...params.url].some((char) => char.charCodeAt(0) <= 0x20 || char.charCodeAt(0) === 0x7f) ||
69
+ secrets.containsUrl(params.url) ||
70
+ redactAgentOutput(secrets, params.url) !== params.url)
71
+ return invalidInput();
72
+ }
73
+ catch {
74
+ return invalidInput();
75
+ }
76
+ inputRequests[namespace(connector, index)] = inputRequired.elicitUrl({
77
+ message: `Downstream ${connector}: ${params.message}`,
78
+ url: params.url,
79
+ });
80
+ }
81
+ else {
82
+ const parsed = await specTypeSchemas.ElicitRequestFormParams["~standard"].validate(params);
83
+ if (parsed.issues)
84
+ return invalidInput();
85
+ // A schema controls the accepted answer. Refuse redaction changes rather
86
+ // than asking the host to submit different field names or enum values.
87
+ if (redactAgentOutput(secrets, parsed.value.requestedSchema) !== parsed.value.requestedSchema)
88
+ return invalidInput();
89
+ inputRequests[namespace(connector, index)] = inputRequired.elicit({
90
+ message: `Downstream ${connector}: ${parsed.value.message}`,
91
+ requestedSchema: parsed.value.requestedSchema,
92
+ });
93
+ }
94
+ inputs[namespace(connector, index)] = { key, mode };
95
+ }
96
+ // Opaque state is private even when a downstream echoes it in a prompt,
97
+ // URL, or schema. Keep the continuation intact and refuse the public prompt.
98
+ assertDownstreamOutputSafe(scope, inputRequests);
99
+ assertNoPrivateStateEchoes(inputRequests, raw.requestState !== undefined ? [raw.requestState] : []);
100
+ if (pendingInputs.has(scope))
101
+ return invalidInput();
102
+ pendingInputs.set(scope, {
103
+ connector,
104
+ address,
105
+ ...(raw.requestState !== undefined ? { requestState: raw.requestState } : {}),
106
+ inputRequests,
107
+ inputs,
108
+ });
109
+ }
110
+ function failure(code, message) {
111
+ const structuredContent = { ok: false, error: { code, message, retryable: false } };
112
+ return { content: [{ type: "text", text: JSON.stringify(structuredContent) }], structuredContent, isError: true };
113
+ }
114
+ /** MCP-only continuation coordination. No downstream prompt enters operator records. */
115
+ export class DownstreamElicitation {
116
+ options;
117
+ constructor(options) {
118
+ this.options = options;
119
+ }
120
+ bind(context) {
121
+ const envelope = context.mcpReq.envelope;
122
+ const declared = envelope?.[CLIENT_CAPABILITIES_META_KEY];
123
+ const elicitation = stateObject(declared) ? declared.elicitation : undefined;
124
+ const modern = envelope?.[PROTOCOL_VERSION_META_KEY] === "2026-07-28";
125
+ if (modern)
126
+ modernRequests.add(this.options.requestScope);
127
+ else
128
+ modernRequests.delete(this.options.requestScope);
129
+ bindDownstreamCapabilities(this.options.requestScope, modern && stateObject(elicitation)
130
+ ? {
131
+ elicitation: {
132
+ ...(stateObject(elicitation.form) || Object.keys(elicitation).length === 0 ? { form: {} } : {}),
133
+ ...(stateObject(elicitation.url) ? { url: {} } : {}),
134
+ },
135
+ }
136
+ : {});
137
+ }
138
+ async verify(wrapper, context) {
139
+ const { vault, registry, principal, endpoint } = this.options;
140
+ if (!vault?.open ||
141
+ !principal ||
142
+ typeof wrapper.connector !== "string" ||
143
+ !registry.getConnector(wrapper.connector) ||
144
+ typeof wrapper.sealed !== "string" ||
145
+ wrapper.sealed.length > MAX_RELAY_STATE_CHARS)
146
+ return invalidRequestState();
147
+ let state;
148
+ try {
149
+ state = JSON.parse(await vault.open(wrapper.connector, PURPOSE, wrapper.sealed));
150
+ }
151
+ catch {
152
+ return invalidRequestState();
153
+ }
154
+ if (!stateObject(state) ||
155
+ state.version !== 2 ||
156
+ state.kind !== "downstream" ||
157
+ state.principal !== principal ||
158
+ state.endpoint !== endpoint ||
159
+ state.connector !== wrapper.connector ||
160
+ typeof state.address !== "string" ||
161
+ typeof state.target !== "string" ||
162
+ !state.target.startsWith(`${state.connector}.`) ||
163
+ !["call_tool", "call_destructive_tool"].includes(String(state.tool)) ||
164
+ typeof state.digest !== "string" ||
165
+ !/^[a-f0-9]{64}$/.test(state.digest) ||
166
+ !Number.isSafeInteger(state.expiresAt) ||
167
+ state.expiresAt <= Date.now() ||
168
+ state.expiresAt > Date.now() + REQUEST_STATE_TTL_MS ||
169
+ !Number.isInteger(state.round) ||
170
+ state.round < 1 ||
171
+ state.round > MAX_INPUT_ROUNDS ||
172
+ typeof state.nonce !== "string" ||
173
+ !/^[a-f0-9-]{36}$/.test(state.nonce) ||
174
+ (state.requestState !== undefined && typeof state.requestState !== "string") ||
175
+ !Array.isArray(state.previousStates) ||
176
+ state.previousStates.length >= state.round ||
177
+ !state.previousStates.every((value) => typeof value === "string") ||
178
+ !withinBudget([...state.previousStates, ...(state.requestState !== undefined ? [state.requestState] : [])]) ||
179
+ !stateObject(state.inputs) ||
180
+ Object.keys(state.inputs).length > MAX_REQUESTS ||
181
+ !Object.entries(state.inputs).every(([wireKey, binding], index) => wireKey === namespace(wrapper.connector, index) &&
182
+ stateObject(binding) &&
183
+ typeof binding.key === "string" &&
184
+ binding.key.length > 0 &&
185
+ binding.key.length <= 256 &&
186
+ (binding.mode === "url" || binding.mode === "form")) ||
187
+ context.mcpReq.method !== "tools/call" ||
188
+ context.http?.req?.headers.get("Mcp-Name") !== state.tool)
189
+ return invalidRequestState();
190
+ return state;
191
+ }
192
+ async resume(state, tool, args, context, operation) {
193
+ if (state.tool !== tool || state.address !== args.address || state.digest !== (await requestDigest(tool, args)))
194
+ return failure("invalid_request_state", "Invalid or expired requestState");
195
+ const responses = Object.create(null);
196
+ const elicitation = downstreamInputCapabilities(this.options.requestScope).elicitation;
197
+ for (const [wireKey, { key, mode }] of Object.entries(state.inputs)) {
198
+ if (!elicitation?.[mode])
199
+ return failure("input_required_unsupported", "The client did not declare support for this continuation's input kind.");
200
+ const response = context.mcpReq.inputResponses?.[wireKey];
201
+ if (response === undefined)
202
+ continue;
203
+ const parsed = await specTypeSchemas.ElicitResult["~standard"].validate(response);
204
+ if (parsed.issues)
205
+ throw new ProtocolError(INVALID_PARAMS, "Invalid downstream input response", {
206
+ reason: "invalid_input_response",
207
+ });
208
+ // URL acknowledgements and decline/cancel never carry form data.
209
+ responses[key] =
210
+ mode === "url" || parsed.value.action !== "accept" ? { action: parsed.value.action } : parsed.value;
211
+ }
212
+ if (!withinBudget(responses))
213
+ throw new ProtocolError(INVALID_PARAMS, "Input responses exceed 64 KiB", { reason: "invalid_input_response" });
214
+ const storage = this.options.registry.contextFor(state.connector, this.options.endpoint, this.options.requestScope).storage;
215
+ // Claim before any downstream dispatch, including decline/cancel. A failed
216
+ // or ambiguous continuation must not leave a reusable write permission.
217
+ if (!(await storage.compareAndSet(inputRetryKeys.used(state.nonce), null, "used", {
218
+ ttlSeconds: REQUEST_STATE_TTL_MS / 1000,
219
+ })))
220
+ return invalidRequestState();
221
+ bindDownstreamContinuation(this.options.requestScope, {
222
+ connector: state.connector,
223
+ address: state.target,
224
+ write: state.tool === "call_destructive_tool",
225
+ privateStates: [...state.previousStates, ...(state.requestState !== undefined ? [state.requestState] : [])],
226
+ input: {
227
+ ...(state.requestState !== undefined ? { requestState: state.requestState } : {}),
228
+ inputResponses: responses,
229
+ },
230
+ });
231
+ try {
232
+ const result = await this.finish(tool, args, context, await operation(), state);
233
+ if (!result.isError || !stateObject(result.structuredContent))
234
+ return result;
235
+ const error = result.structuredContent.error;
236
+ if (!stateObject(error))
237
+ return result;
238
+ // The nonce is spent even when the downstream failure would normally
239
+ // allow a retry. Preserve write reconciliation and auth prerequisites.
240
+ const echoed = echoedCallArgs(args.args);
241
+ const structuredContent = {
242
+ ...result.structuredContent,
243
+ error: {
244
+ ...error,
245
+ retryable: false,
246
+ ...(state.tool === "call_tool" && error.nextAction === undefined && error.retry === undefined
247
+ ? {
248
+ nextAction: {
249
+ tool: state.tool,
250
+ arguments: { address: state.address, ...echoed },
251
+ purpose: "Re-issue the original direct call without requestState or inputResponses to start a fresh input round. Do not resend this continuation." +
252
+ (args.args !== undefined && !("args" in echoed)
253
+ ? " Use the exact arguments you sent; they exceed the echo budget."
254
+ : ""),
255
+ },
256
+ }
257
+ : {}),
258
+ },
259
+ };
260
+ return { ...result, structuredContent, content: [{ type: "text", text: JSON.stringify(structuredContent) }] };
261
+ }
262
+ finally {
263
+ clearDownstreamContinuation(this.options.requestScope);
264
+ }
265
+ }
266
+ async finish(tool, args, context, result, previous) {
267
+ const scope = this.options.requestScope;
268
+ const pending = pendingInputs.get(scope);
269
+ pendingInputs.delete(scope);
270
+ if (!pending)
271
+ return result;
272
+ const { vault, principal, endpoint } = this.options;
273
+ if (!modernRequests.has(scope) ||
274
+ !vault?.requestStateKey ||
275
+ !vault.seal ||
276
+ !vault.open ||
277
+ !principal ||
278
+ !["call_tool", "call_destructive_tool"].includes(tool))
279
+ return failure("input_required_unsupported", "Downstream input requires an authenticated direct call and a sealing vault.");
280
+ if ((previous?.round ?? 0) >= MAX_INPUT_ROUNDS)
281
+ return failure("input_required_round_limit", "The downstream still requires input after three rounds. Start a new direct request.");
282
+ const previousStates = [
283
+ ...new Set([
284
+ ...(previous?.previousStates ?? []),
285
+ ...(previous?.requestState !== undefined ? [previous.requestState] : []),
286
+ ]),
287
+ ].filter((value) => value !== pending.requestState);
288
+ if (!withinBudget([...previousStates, ...(pending.requestState !== undefined ? [pending.requestState] : [])])) {
289
+ return failure("input_required_limit", "The downstream private state history exceeds 64 KiB.");
290
+ }
291
+ const state = {
292
+ version: 2,
293
+ kind: "downstream",
294
+ principal,
295
+ endpoint,
296
+ connector: pending.connector,
297
+ address: String(args.address),
298
+ target: pending.address,
299
+ tool: tool,
300
+ digest: await requestDigest(tool, args),
301
+ round: (previous?.round ?? 0) + 1,
302
+ expiresAt: previous?.expiresAt ?? Date.now() + REQUEST_STATE_TTL_MS,
303
+ nonce: crypto.randomUUID(),
304
+ ...(pending.requestState !== undefined ? { requestState: pending.requestState } : {}),
305
+ previousStates,
306
+ inputs: pending.inputs,
307
+ };
308
+ const requestState = await this.options.mint({
309
+ version: 2,
310
+ kind: "downstream",
311
+ connector: pending.connector,
312
+ sealed: await vault.seal(pending.connector, PURPOSE, JSON.stringify(state)),
313
+ }, context);
314
+ if (requestState.length > MAX_RELAY_STATE_CHARS)
315
+ return failure("input_required_limit", "The sealed downstream state exceeds 128 KiB.");
316
+ // Opaque echoes and mutable schemas/URLs were refused during capture.
317
+ // Prompt messages take the same #736 agent boundary as ordinary outputs.
318
+ return redactAgentOutput(sentSecretsForRequest(scope), inputRequired({
319
+ ...(Object.keys(pending.inputRequests).length ? { inputRequests: pending.inputRequests } : {}),
320
+ requestState,
321
+ }));
322
+ }
323
+ }
package/dist/errors.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { ExecutorAdmissionErrorCode } from "./executor-admission.js";
1
2
  /**
2
3
  * Machine-readable classification of a failed connector tool call.
3
4
  *
@@ -5,7 +6,7 @@
5
6
  * naming a cause — the rule provider conventions call H11, defined in
6
7
  * `test/provider-conventions.test.ts`.
7
8
  */
8
- export type ConnectorCallErrorCode = "timeout" | "auth_required" | "rate_limited" | "unavailable" | "invalid_args"
9
+ export type ConnectorCallErrorCode = "timeout" | "auth_required" | "downstream_oauth_required" | "oauth_revocation_failed" | "provider_permission_denied" | "rate_limited" | "unavailable" | "invalid_args"
9
10
  /**
10
11
  * Provider-owned absence: the caller re-addresses rather than waiting,
11
12
  * re-authorizing, or repairing arguments, and a program looping over ids can
@@ -22,7 +23,18 @@ export type ConnectorCallErrorCode = "timeout" | "auth_required" | "rate_limited
22
23
  * change, and retries with the base `current` reports — repeating the same
23
24
  * call can never succeed, and waiting will not help either.
24
25
  */
25
- | "conflict" | "input_required_unsupported" | "connector_call_failed";
26
+ | "conflict" | "resource_template_ambiguous" | "resource_match_budget_exceeded" | "input_required_unsupported" | "input_required_invalid" | "input_required_limit" | "connector_call_failed";
27
+ /**
28
+ * Every classification code connecta assigns: a connector's typed codes, the
29
+ * executor's admission codes, and the refusals connecta frames itself.
30
+ * Operator records and activity rows carry a code only when it is one of
31
+ * these (INV-6), so a code forwarded from a downstream is never recorded.
32
+ * Adding a member fails typecheck until {@link CLASSIFICATION_CODE_TABLE}
33
+ * lists it.
34
+ */
35
+ export type ClassificationCode = ConnectorCallErrorCode | ExecutorAdmissionErrorCode | "cancelled" | "unknown_address" | "unknown_tool" | "ambiguous_tool_alias" | "catalog_lookup_failed" | "result_too_large" | "destructive_tool_requires_approval" | "auth_replay_refused" | "write_outcome_unknown" | "result_processing_failed" | "budget_exceeded" | "executor_failed" | "program_error";
36
+ /** `value` when it is a code connecta assigns, else undefined. */
37
+ export declare function classificationCode(value: unknown): ClassificationCode | undefined;
26
38
  /** One bounded, payload-free explanation of an input-schema mismatch. */
27
39
  export interface ArgumentValidationIssue {
28
40
  /** JSON Pointer into the submitted arguments; "/" means the root value. */
@@ -37,6 +49,26 @@ export interface ArgumentValidationDetails {
37
49
  /** More findings existed but were omitted from the bounded response. */
38
50
  truncated?: true;
39
51
  }
52
+ /** Agent-only facts from the published schema, never operator record fields. */
53
+ export interface ArgumentRepairDetails {
54
+ acceptedKeys?: string[];
55
+ issues: Array<{
56
+ path: string;
57
+ receivedType: string;
58
+ acceptedKeys?: string[];
59
+ enumValues?: unknown[];
60
+ bounds?: Record<string, number>;
61
+ }>;
62
+ conditionalRequirements?: Array<{
63
+ path: string;
64
+ condition: unknown;
65
+ required: string[];
66
+ }>;
67
+ /** Synthesized from the schema and checked by the same validator. */
68
+ example?: unknown;
69
+ exampleUnavailable?: string;
70
+ truncated?: true;
71
+ }
40
72
  export declare const MAX_ARGUMENT_VALIDATION_ISSUES = 3;
41
73
  /**
42
74
  * Clamp a string to a UTF-8 byte budget, appending `…` when it clipped.
@@ -56,22 +88,61 @@ export declare function echoedCallArgs(args: unknown): {
56
88
  };
57
89
  /** Optional transport diagnostics; never a URL path or raw runtime message. */
58
90
  interface UnavailableDetails {
91
+ /** Guest program diagnostics; returned to the agent, never operator records. */
92
+ name?: string;
93
+ line?: number | null;
94
+ hint?: string;
95
+ /** Host-measured call deadline diagnostics; operation may contain caller text. */
96
+ operation?: string;
97
+ stage?: string;
98
+ elapsedMs?: number;
99
+ deadlineMs?: number;
59
100
  /** HTTP(S) origin only, at most 253 UTF-8 bytes. */
60
101
  host?: string;
61
102
  /** Validated network errno or `timeout`, at most 32 bytes. */
62
103
  code?: string;
63
104
  }
105
+ /** The network errnos `networkErrorCode` reports, and `timeout`. */
106
+ export declare const NETWORK_ERROR_CODES: ReadonlySet<string>;
64
107
  /** Runtime fields only: provider prose cannot supply an errno or a deadline. */
65
108
  export declare function networkErrorCode(error: unknown): string | undefined;
66
- /** Use at a fetch boundary where the destination and transport failure are known. */
67
- export declare function unavailableCallError(cause: unknown, host?: string, message?: string): ConnectorCallError;
68
- /** Agent-visible recovery class attached only to `auth_required` failures. */
109
+ /**
110
+ * Use at a fetch boundary where the destination and transport failure are
111
+ * known. The runtime's error is read for its errno and dropped, never kept as
112
+ * `cause`: its message and cause chain can quote the URL, headers, or body of
113
+ * the request that failed, and a logger that renders an error renders its
114
+ * cause. The origin and errno in `details` are what survives.
115
+ *
116
+ * The one failure kept is the request's own abort reason, when that is what
117
+ * the fetch rejected with: the caller wrote it, not the downstream, and it is
118
+ * how a cancelled request tells its reason from a network failure.
119
+ */
120
+ export declare function unavailableCallError(failure: unknown, host?: string, message?: string, signal?: AbortSignal): ConnectorCallError;
121
+ /**
122
+ * A failure told in connecta's words because the original's text came from a
123
+ * downstream or authorization server, which can echo what the request carried
124
+ * or plant whatever it likes. There is no `cause`: the original is dropped
125
+ * whole, never masked.
126
+ *
127
+ * It is classified exactly as the original would have been. That verdict is
128
+ * read from the original once, here, and kept as two flags, so withholding
129
+ * the text moves no failure between `timeout`, retryable, and the caller's
130
+ * fallback code. Never wrap a `ConnectorCallError`: it already speaks in
131
+ * connecta's words and carries its own code.
132
+ */
133
+ export declare class WithheldTextError extends Error {
134
+ /** The original read as a timeout. */
135
+ readonly timeout: boolean;
136
+ readonly retryable: boolean;
137
+ constructor(message: string, original: unknown);
138
+ }
139
+ /** Agent-visible recovery class for downstream credential failures. */
69
140
  export type AuthRecoveryMode = "oauth" | "operator_config" | "unavailable";
70
141
  /**
71
142
  * Throw from `Connector.callTool` (or anything beneath it) to classify a
72
- * failure exactly. Untyped errors fall back to a message-text heuristic, so a
73
- * connector whose legitimate error text mentions "timeout" is misread as a
74
- * retryable timeout — this class is the escape hatch. `retryable` defaults per
143
+ * failure exactly. Untyped errors are non-retryable unless a runtime errno
144
+ * or abort identifies a transport failure. This class supplies the connector's
145
+ * reviewed verdict. `retryable` defaults per
75
146
  * code (timeout, rate_limited, and unavailable retry; the rest do not) and may
76
147
  * be overridden.
77
148
  *
@@ -93,6 +164,7 @@ export declare class ConnectorCallError extends Error {
93
164
  readonly retryAfterMs: number | undefined;
94
165
  /** Bounded schema findings for `invalid_args`; never submitted values. */
95
166
  readonly validation: ArgumentValidationDetails | undefined;
167
+ readonly repair: ArgumentRepairDetails | undefined;
96
168
  /** Sanitized transport diagnostics for `unavailable` only. */
97
169
  readonly details: UnavailableDetails | undefined;
98
170
  /**
@@ -105,6 +177,7 @@ export declare class ConnectorCallError extends Error {
105
177
  retryAfterMs?: number;
106
178
  cause?: unknown;
107
179
  validation?: ArgumentValidationDetails;
180
+ repair?: ArgumentRepairDetails;
108
181
  details?: UnavailableDetails;
109
182
  current?: Readonly<Record<string, number>>;
110
183
  });
@@ -120,6 +193,7 @@ export interface CallErrorDetails {
120
193
  retryAfterMs?: number;
121
194
  /** Bounded input-schema findings; paths and expectations, never values. */
122
195
  validation?: ArgumentValidationDetails;
196
+ repair?: ArgumentRepairDetails;
123
197
  /** On a `conflict`: where each thing the write touched stands now. */
124
198
  current?: Readonly<Record<string, number>>;
125
199
  /** Connector whose failed operation needs recovery. */
@@ -128,6 +202,8 @@ export interface CallErrorDetails {
128
202
  operation?: string;
129
203
  /** Which safe recovery path `authorize_connector` will return. */
130
204
  recovery?: AuthRecoveryMode;
205
+ /** An auth failure followed a write's downstream send. Reconcile before retry. */
206
+ reconciliationRequired?: true;
131
207
  /** The single model-facing entry point for every credential class. */
132
208
  nextAction?: {
133
209
  tool: "authorize_connector";
@@ -144,7 +220,7 @@ export interface CallErrorDetails {
144
220
  };
145
221
  purpose: string;
146
222
  } | {
147
- tool: "call_destructive_tool";
223
+ tool: "call_tool" | "call_destructive_tool";
148
224
  arguments: {
149
225
  address: string;
150
226
  /**
@@ -175,19 +251,20 @@ export interface CallErrorDetails {
175
251
  addresses: string[];
176
252
  purpose: string;
177
253
  };
254
+ /** Configured connectors in this request's registry view only. */
255
+ configuredConnectors?: string[];
256
+ /** Agent-only uncertain write, never copied to an operator record. */
257
+ uncertainCall?: {
258
+ address: string;
259
+ args?: unknown;
260
+ argsOmitted?: true;
261
+ };
178
262
  /** Explicit retry guidance; recovery never retries or mutates by itself. */
179
263
  retry?: string;
180
264
  }
181
- /**
182
- * Details for a failure connecta itself framed — an address it could not
183
- * resolve, a tool it refuses to run — rather than one a connector threw.
184
- */
185
- export declare function framingError(code: string, message: string): CallErrorDetails;
186
- /**
187
- * Classify a value thrown by a connector call. A `ConnectorCallError` is
188
- * authoritative; anything else falls back to the historical message-text
189
- * heuristic.
190
- */
191
- export declare function classifyCallError(err: unknown, fallbackCode?: string): CallErrorDetails;
265
+ /** Retryability of connecta framing depends only on its code. */
266
+ export declare function framingError(code: ClassificationCode, message: string): CallErrorDetails;
267
+ /** Typed connector verdicts and runtime facts only. Prose never changes a verdict. */
268
+ export declare function classifyCallError(err: unknown, fallbackCode?: ClassificationCode): CallErrorDetails;
192
269
  export declare function msg(err: unknown): string;
193
270
  export {};