@zackbart/connecta 0.28.1 → 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 (319) hide show
  1. package/CHANGELOG.md +788 -5
  2. package/README.md +62 -42
  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 +1584 -1580
  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 +93 -71
  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 +59 -7
  52. package/dist/connectors/api-connector.js +106 -19
  53. package/dist/connectors/api.js +4 -1
  54. package/dist/connectors/guarded-fetch.d.ts +144 -0
  55. package/dist/connectors/guarded-fetch.js +84 -35
  56. package/dist/connectors/negotiation-cache.js +46 -0
  57. package/dist/connectors/option-shapes.js +27 -0
  58. package/dist/connectors/remote-mcp.d.ts +30 -4
  59. package/dist/connectors/remote-mcp.js +1774 -320
  60. package/dist/connectors/resource-uri.js +335 -0
  61. package/dist/credential-contract.d.ts +10 -0
  62. package/dist/credential-rules.js +1 -3
  63. package/dist/credential-url.js +108 -0
  64. package/dist/credentials.d.ts +6 -1
  65. package/dist/credentials.js +27 -18
  66. package/dist/d1.d.ts +130 -0
  67. package/dist/d1.js +290 -0
  68. package/dist/describe-config.d.ts +165 -0
  69. package/dist/describe-config.js +315 -0
  70. package/dist/described.js +106 -0
  71. package/dist/downstream-input-context.js +119 -0
  72. package/dist/downstream-input.js +323 -0
  73. package/dist/errors.d.ts +97 -20
  74. package/dist/errors.js +158 -80
  75. package/dist/execute.js +453 -305
  76. package/dist/executor-admission.d.ts +1 -0
  77. package/dist/executor-admission.js +11 -15
  78. package/dist/executor-contract.d.ts +20 -0
  79. package/dist/executor-contract.js +48 -0
  80. package/dist/executor-result.js +24 -9
  81. package/dist/executors/quickjs-child.js +3 -4
  82. package/dist/executors/quickjs-runtime.js +147 -59
  83. package/dist/executors/quickjs.js +29 -35
  84. package/dist/favicon.js +1 -2
  85. package/dist/fix-prompt.js +1 -3
  86. package/dist/guest-runtime.js +188 -0
  87. package/dist/guest-types.d.ts +176 -0
  88. package/dist/guest-types.js +1 -0
  89. package/dist/html-security.js +24 -0
  90. package/dist/identity.js +1 -3
  91. package/dist/inbound-credential.js +52 -0
  92. package/dist/index.d.ts +24 -256
  93. package/dist/index.js +65 -478
  94. package/dist/invocation-auth.js +69 -0
  95. package/dist/invocation.js +230 -73
  96. package/dist/json-schema.d.ts +1 -1
  97. package/dist/mcp-client-context.js +35 -0
  98. package/dist/mcp-result.js +11 -7
  99. package/dist/meta-output.js +76 -0
  100. package/dist/meta-tool-names.d.ts +2 -0
  101. package/dist/meta-tool-names.js +3 -0
  102. package/dist/meta-tools.js +285 -209
  103. package/dist/module-contracts.d.ts +8 -23
  104. package/dist/node.d.ts +0 -2
  105. package/dist/node.js +2 -2
  106. package/dist/oauth-callback-outcome.js +1 -3
  107. package/dist/oauth-handoff.js +117 -0
  108. package/dist/oauth-sealing.js +20 -0
  109. package/dist/operator-record.d.ts +137 -0
  110. package/dist/operator-record.js +420 -0
  111. package/dist/operator-ui/assets.js +31 -0
  112. package/dist/operator-ui/contract.d.ts +72 -0
  113. package/dist/operator-ui/contract.js +1 -0
  114. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  115. package/dist/operator-ui/generated.js +5 -4
  116. package/dist/operator-ui/model.d.ts +12 -87
  117. package/dist/operator-ui/model.js +2 -9
  118. package/dist/operator-ui/setup-commands.js +1 -3
  119. package/dist/operator-ui/view.d.ts +323 -0
  120. package/dist/operator-ui/view.js +62 -189
  121. package/dist/page-styles.js +2 -3
  122. package/dist/program-source.js +14 -2
  123. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  124. package/dist/provider.d.ts +142 -0
  125. package/dist/provider.js +187 -0
  126. package/dist/providers/_shared/google/result-size.js +57 -0
  127. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  128. package/dist/providers/_shared/google/service-account.js +500 -0
  129. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  130. package/dist/providers/_shared/google/workspace.js +518 -0
  131. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  132. package/dist/providers/basecamp/index.js +997 -0
  133. package/dist/providers/basecamp/skill.generated.js +9 -0
  134. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  135. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  136. package/dist/providers/breeze/skill.generated.js +9 -0
  137. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  138. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  139. package/dist/providers/ccb/skill.generated.js +9 -0
  140. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  141. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  142. package/dist/providers/cloudflare/skill.generated.js +10 -0
  143. package/dist/providers/docs/index.d.ts +54 -0
  144. package/dist/providers/docs/index.js +1237 -0
  145. package/dist/providers/docs/skill.generated.js +9 -0
  146. package/dist/providers/drive/index.d.ts +53 -0
  147. package/dist/providers/drive/index.js +1523 -0
  148. package/dist/providers/drive/skill.generated.js +9 -0
  149. package/dist/providers/forms/index.d.ts +54 -0
  150. package/dist/providers/forms/index.js +1217 -0
  151. package/dist/providers/forms/skill.generated.js +11 -0
  152. package/dist/providers/github/auth.d.ts +32 -0
  153. package/dist/providers/github/auth.js +361 -0
  154. package/dist/providers/github/hosted.js +218 -0
  155. package/dist/providers/github/index.d.ts +13 -0
  156. package/dist/providers/github/index.js +140 -0
  157. package/dist/providers/github/key.js +100 -0
  158. package/dist/providers/github/rest.js +321 -0
  159. package/dist/providers/github/scope.d.ts +29 -0
  160. package/dist/providers/github/scope.js +124 -0
  161. package/dist/providers/github/skill.generated.js +8 -0
  162. package/dist/providers/gmail/index.d.ts +51 -0
  163. package/dist/providers/gmail/index.js +1436 -0
  164. package/dist/providers/gmail/skill.generated.js +9 -0
  165. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  166. package/dist/providers/linear/index.js +352 -0
  167. package/dist/providers/linear/skill.generated.js +8 -0
  168. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  169. package/dist/providers/mixpanel/index.js +416 -0
  170. package/dist/providers/mixpanel/skill.generated.js +9 -0
  171. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  172. package/dist/providers/{notion.js → notion/index.js} +226 -239
  173. package/dist/providers/notion/skill.generated.js +10 -0
  174. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  175. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  176. package/dist/providers/overflow/skill.generated.js +8 -0
  177. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  178. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  179. package/dist/providers/planning-center/skill.generated.js +10 -0
  180. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  181. package/dist/providers/revenuecat/index.js +557 -0
  182. package/dist/providers/revenuecat/skill.generated.js +11 -0
  183. package/dist/providers/sheets/index.d.ts +54 -0
  184. package/dist/providers/sheets/index.js +1237 -0
  185. package/dist/providers/sheets/skill.generated.js +10 -0
  186. package/dist/providers/slides/index.d.ts +53 -0
  187. package/dist/providers/slides/index.js +2645 -0
  188. package/dist/providers/slides/skill.generated.js +9 -0
  189. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  190. package/dist/providers/stripe/index.js +266 -0
  191. package/dist/providers/stripe/skill.generated.js +11 -0
  192. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  193. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  194. package/dist/providers/tithely/skill.generated.js +8 -0
  195. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  196. package/dist/providers/vercel/index.js +1038 -0
  197. package/dist/providers/vercel/mcp-ownership.js +212 -0
  198. package/dist/providers/vercel/skill.generated.js +11 -0
  199. package/dist/registry.d.ts +108 -171
  200. package/dist/registry.js +513 -925
  201. package/dist/request-state.js +21 -0
  202. package/dist/resource-read.js +100 -0
  203. package/dist/result-shapes.js +17 -49
  204. package/dist/retired-toolkits.js +1 -1
  205. package/dist/routes/access-tokens.js +3 -6
  206. package/dist/routes/activity-context.js +21 -0
  207. package/dist/routes/activity-disclosure.js +40 -0
  208. package/dist/routes/activity.js +59 -21
  209. package/dist/routes/actor-labels.js +3 -50
  210. package/dist/routes/connect.js +221 -0
  211. package/dist/routes/credentials.js +15 -27
  212. package/dist/routes/mcp.js +356 -130
  213. package/dist/routes/oauth-client-metadata.js +15 -0
  214. package/dist/routes/oauth-management.js +45 -107
  215. package/dist/routes/oauth.js +110 -57
  216. package/dist/routes/operator.js +3 -3
  217. package/dist/routes/shared.d.ts +25 -64
  218. package/dist/routes/shared.js +160 -80
  219. package/dist/routes/skills.js +30 -0
  220. package/dist/routes/ui-config.js +225 -0
  221. package/dist/routes/ui-data.js +40 -35
  222. package/dist/routes/ui.js +66 -56
  223. package/dist/runtime/admission.js +4 -2
  224. package/dist/runtime/call-admission.js +3 -1
  225. package/dist/runtime/connector-scope.js +1 -4
  226. package/dist/runtime/run.js +10 -39
  227. package/dist/runtime/services.js +12 -117
  228. package/dist/runtime/shared-read.js +1 -3
  229. package/dist/runtime/storage.js +8 -1
  230. package/dist/sent-secrets.d.ts +52 -0
  231. package/dist/sent-secrets.js +474 -0
  232. package/dist/server.js +28 -71
  233. package/dist/skills.js +406 -174
  234. package/dist/sqlite.d.ts +48 -0
  235. package/dist/sqlite.js +166 -0
  236. package/dist/storage/keys.js +428 -0
  237. package/dist/storage/memory.js +7 -0
  238. package/dist/storage/sql.d.ts +66 -0
  239. package/dist/storage/sql.js +534 -0
  240. package/dist/timeout.js +4 -2
  241. package/dist/tool-name.js +17 -0
  242. package/dist/tool-safety.d.ts +9 -35
  243. package/dist/tool-safety.js +20 -40
  244. package/dist/types.d.ts +320 -54
  245. package/dist/types.js +0 -1
  246. package/dist/ui.d.ts +7 -19
  247. package/dist/ui.js +76 -90
  248. package/dist/url-safety.js +3 -6
  249. package/dist/usage-guide.js +137 -0
  250. package/dist/validate.d.ts +7 -0
  251. package/dist/validate.js +309 -47
  252. package/dist/version.d.ts +1 -7
  253. package/dist/version.js +3 -7
  254. package/dist/worker.d.ts +11 -0
  255. package/dist/worker.js +434 -0
  256. package/documentation/architecture.md +664 -231
  257. package/documentation/auth.md +936 -546
  258. package/documentation/code-mode.md +501 -312
  259. package/documentation/meta-tools.md +449 -200
  260. package/documentation/operator-tests.md +93 -0
  261. package/documentation/provider-migration-0.29.md +60 -0
  262. package/examples/worker/AGENTS.md +34 -12
  263. package/examples/worker/README.md +286 -255
  264. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  265. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  266. package/examples/worker/src/connecta.config.ts +151 -0
  267. package/examples/worker/src/index.ts +14 -197
  268. package/examples/worker/wrangler.jsonc +15 -35
  269. package/package.json +120 -54
  270. package/templates/node/.dockerignore +1 -2
  271. package/templates/node/.env.example +20 -21
  272. package/templates/node/AGENTS.md +21 -17
  273. package/templates/node/Dockerfile +3 -5
  274. package/templates/node/README.md +133 -58
  275. package/templates/node/docker-compose.yml +11 -12
  276. package/templates/node/package.json +4 -2
  277. package/templates/node/src/connecta.config.ts +119 -0
  278. package/templates/node/src/index.ts +9 -154
  279. package/templates/node/src/provision-token.ts +17 -0
  280. package/AGENTS.md +0 -170
  281. package/dist/artifacts/connector.d.ts +0 -58
  282. package/dist/artifacts/connector.js +0 -730
  283. package/dist/artifacts/document.js +0 -86
  284. package/dist/artifacts/guide.js +0 -140
  285. package/dist/artifacts/html-scan.js +0 -329
  286. package/dist/artifacts/json.js +0 -78
  287. package/dist/artifacts/kv-store.d.ts +0 -20
  288. package/dist/artifacts/kv-store.js +0 -140
  289. package/dist/artifacts/markdown.js +0 -478
  290. package/dist/artifacts/module.d.ts +0 -37
  291. package/dist/artifacts/module.js +0 -66
  292. package/dist/artifacts/operations.d.ts +0 -197
  293. package/dist/artifacts/operations.js +0 -1002
  294. package/dist/artifacts/refresh.d.ts +0 -55
  295. package/dist/artifacts/refresh.js +0 -236
  296. package/dist/artifacts/routes.js +0 -206
  297. package/dist/artifacts/types.d.ts +0 -214
  298. package/dist/artifacts/types.js +0 -22
  299. package/dist/artifacts/validate.d.ts +0 -62
  300. package/dist/artifacts/validate.js +0 -651
  301. package/dist/artifacts.d.ts +0 -6
  302. package/dist/artifacts.js +0 -9
  303. package/dist/auth/bearer.d.ts +0 -11
  304. package/dist/auth/bearer.js +0 -50
  305. package/dist/providers/basecamp.js +0 -445
  306. package/dist/providers/linear.js +0 -217
  307. package/dist/providers/mixpanel.js +0 -247
  308. package/dist/providers/revenuecat.js +0 -317
  309. package/dist/providers/stripe.js +0 -253
  310. package/dist/providers/vercel.js +0 -1253
  311. package/dist/storage/file.d.ts +0 -15
  312. package/dist/storage/file.js +0 -455
  313. package/ethos.md +0 -93
  314. package/examples/worker/src/cloudflare-kv.ts +0 -43
  315. package/examples/worker/src/d1-activity-row.ts +0 -155
  316. package/examples/worker/src/d1-activity.ts +0 -147
  317. package/examples/worker/src/d1-storage.ts +0 -138
  318. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  319. 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 {};