@zackbart/connecta 0.34.1 → 0.35.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 (429) hide show
  1. package/CHANGELOG.md +363 -0
  2. package/README.md +62 -106
  3. package/SECURITY.md +3 -2
  4. package/bin/connecta.mjs +41 -16
  5. package/bin/version.mjs +1 -1
  6. package/dist/access-tokens.d.ts +2 -1
  7. package/dist/access-tokens.js +66 -30
  8. package/dist/activity.d.ts +1 -1
  9. package/dist/activity.js +2 -0
  10. package/dist/agent-config.d.ts +23 -0
  11. package/dist/agent-config.js +59 -0
  12. package/dist/agent-description.js +16 -0
  13. package/dist/agent-identity.js +4 -4
  14. package/dist/agent-observation.js +39 -0
  15. package/dist/auth/clerk-sdk/client.d.ts +2 -0
  16. package/dist/auth/clerk-sdk/generated.js +554 -563
  17. package/dist/auth/clerk-transport.js +70 -3
  18. package/dist/auth/clerk.d.ts +13 -0
  19. package/dist/auth/clerk.js +653 -246
  20. package/dist/auth/cloudflare-access.js +46 -42
  21. package/dist/auth/downstream-client-metadata.js +2 -2
  22. package/dist/auth/downstream-oauth.js +2051 -1160
  23. package/dist/auth/downstream-revocation.js +88 -88
  24. package/dist/auth/native-call.js +20 -0
  25. package/dist/auth/native.js +184 -0
  26. package/dist/auth/oauth-start-reset.js +11 -5
  27. package/dist/auth/oauth-token-response.js +121 -102
  28. package/dist/auth/oauth-v2-migration.js +97 -78
  29. package/dist/auth/renewable-grant.js +10 -0
  30. package/dist/auth/static-oauth.js +663 -271
  31. package/dist/auth/vercel-agent.js +266 -0
  32. package/dist/auth-admission.js +204 -0
  33. package/dist/auth-elicitation.js +350 -241
  34. package/dist/authorization-handoff.js +164 -90
  35. package/dist/call-admission-context.js +3 -10
  36. package/dist/call-admission.js +2 -0
  37. package/dist/call-classification.js +9 -0
  38. package/dist/call-recovery.js +32 -13
  39. package/dist/canonical-json.js +8 -0
  40. package/dist/catalog-cache.js +369 -213
  41. package/dist/catalog-service.js +659 -530
  42. package/dist/catalog.js +5 -1
  43. package/dist/concurrency.js +0 -25
  44. package/dist/config-defaults.d.ts +1 -0
  45. package/dist/config-defaults.js +1 -0
  46. package/dist/config-schema.d.ts +3 -1
  47. package/dist/config-schema.js +13 -3
  48. package/dist/config.d.ts +40 -6
  49. package/dist/config.js +40 -26
  50. package/dist/connector-caller.d.ts +4 -0
  51. package/dist/connector-caller.js +17 -4
  52. package/dist/connectors/api-connector.d.ts +12 -7
  53. package/dist/connectors/api-connector.js +142 -56
  54. package/dist/connectors/guarded-fetch.d.ts +51 -17
  55. package/dist/connectors/guarded-fetch.js +215 -121
  56. package/dist/connectors/negotiation-cache.js +20 -10
  57. package/dist/connectors/remote-mcp.d.ts +10 -30
  58. package/dist/connectors/remote-mcp.js +955 -563
  59. package/dist/d1.js +10 -243
  60. package/dist/derived-context.js +5 -0
  61. package/dist/describe-config.d.ts +3 -1
  62. package/dist/describe-config.js +2 -0
  63. package/dist/downstream-input.js +347 -260
  64. package/dist/errors.d.ts +87 -6
  65. package/dist/errors.js +114 -4
  66. package/dist/execute.js +87 -50
  67. package/dist/executor-admission.js +3 -0
  68. package/dist/executors/quickjs.js +163 -112
  69. package/dist/guarded-transport.js +50 -0
  70. package/dist/guest-runtime.js +1 -1
  71. package/dist/http/client.js +439 -0
  72. package/dist/http/errors.js +558 -0
  73. package/dist/http/fields.js +49 -0
  74. package/dist/http/hatch-paging.js +318 -0
  75. package/dist/http/hatch.js +1279 -0
  76. package/dist/http/json-schema.js +267 -0
  77. package/dist/http/node-transport.js +241 -0
  78. package/dist/http/oauth.js +162 -0
  79. package/dist/http/paging.js +228 -0
  80. package/dist/http/provider.js +221 -0
  81. package/dist/http/safety.js +82 -0
  82. package/dist/http/services.js +79 -0
  83. package/dist/http/tools.js +383 -0
  84. package/dist/http/vendor-index.js +138 -0
  85. package/dist/http/vendor.js +448 -0
  86. package/dist/http/write-request.js +66 -0
  87. package/dist/http-policy.d.ts +11 -0
  88. package/dist/http-policy.js +45 -0
  89. package/dist/inbound-credential.js +1 -1
  90. package/dist/index.d.ts +2 -2
  91. package/dist/index.js +4 -0
  92. package/dist/invocation-auth.js +30 -19
  93. package/dist/invocation.js +218 -85
  94. package/dist/mcp-result.js +0 -4
  95. package/dist/membership.d.ts +4 -0
  96. package/dist/meta-output.js +1 -1
  97. package/dist/meta-tools.js +531 -406
  98. package/dist/native-recovery.js +52 -0
  99. package/dist/native-tools.js +382 -0
  100. package/dist/node.d.ts +4 -3
  101. package/dist/node.js +45 -8
  102. package/dist/oauth-handoff.js +177 -104
  103. package/dist/oauth-partition.js +6 -20
  104. package/dist/oauth-sealing.js +6 -7
  105. package/dist/operation-vocabulary.js +101 -0
  106. package/dist/operator-record.d.ts +2 -2
  107. package/dist/operator-record.js +17 -4
  108. package/dist/operator-ui/agents.d.ts +5 -4
  109. package/dist/operator-ui/agents.js +17 -7
  110. package/dist/operator-ui/config-summary.js +5 -4
  111. package/dist/operator-ui/connection-map.js +7 -5
  112. package/dist/operator-ui/contract.d.ts +7 -3
  113. package/dist/operator-ui/endpoints.js +4 -13
  114. package/dist/operator-ui/fix-prompts.d.ts +1 -1
  115. package/dist/operator-ui/fix-prompts.js +0 -8
  116. package/dist/operator-ui/generated.js +3 -3
  117. package/dist/operator-ui/model.d.ts +2 -3
  118. package/dist/operator-ui/overview.js +5 -2
  119. package/dist/operator-ui/setup-commands.js +0 -4
  120. package/dist/operator-ui/view.d.ts +2 -25
  121. package/dist/operator-ui/view.js +2 -76
  122. package/dist/program-writes.js +47 -26
  123. package/dist/provider-call-facts.d.ts +115 -0
  124. package/dist/provider-call-facts.js +182 -0
  125. package/dist/provider-call.d.ts +112 -0
  126. package/dist/provider-call.js +271 -0
  127. package/dist/provider.d.ts +65 -31
  128. package/dist/provider.js +116 -39
  129. package/dist/providers/_shared/google/hatch.js +91 -0
  130. package/dist/providers/_shared/google/kit.js +537 -0
  131. package/dist/providers/_shared/google/result-size.js +15 -2
  132. package/dist/providers/_shared/google/service-account.d.ts +2 -1
  133. package/dist/providers/_shared/google/service-account.js +8 -4
  134. package/dist/providers/_shared/google/workspace.d.ts +6 -210
  135. package/dist/providers/_shared/google/workspace.js +6 -425
  136. package/dist/providers/_shared/rest/compact-index.js +86 -0
  137. package/dist/providers/_shared/rest/dispatch.js +3 -23
  138. package/dist/providers/_shared/rest/kit.js +546 -0
  139. package/dist/providers/_shared/rest/operation-index.js +230 -14
  140. package/dist/providers/_shared/rest/response-media.js +11 -0
  141. package/dist/providers/_shared/rest/tools.js +101 -78
  142. package/dist/providers/_shared/rest/value-safety.js +353 -195
  143. package/dist/providers/_shared/sensitive-fields.js +27 -0
  144. package/dist/providers/breeze/api.js +584 -0
  145. package/dist/providers/breeze/index.d.ts +1 -3
  146. package/dist/providers/breeze/index.js +67 -1420
  147. package/dist/providers/breeze/openapi.generated.js +60 -0
  148. package/dist/providers/breeze/skill.generated.js +2 -6
  149. package/dist/providers/breeze/tools.js +696 -0
  150. package/dist/providers/ccb/api.js +496 -0
  151. package/dist/providers/ccb/index.d.ts +15 -37
  152. package/dist/providers/ccb/index.js +77 -1702
  153. package/dist/providers/ccb/lib/constants.d.ts +34 -0
  154. package/dist/providers/ccb/lib/constants.js +74 -0
  155. package/dist/providers/ccb/lib/guide.js +9 -0
  156. package/dist/providers/ccb/lib/verdicts.js +387 -0
  157. package/dist/providers/ccb/openapi.generated.js +397 -0
  158. package/dist/providers/ccb/safety.js +295 -0
  159. package/dist/providers/ccb/skill.generated.js +2 -6
  160. package/dist/providers/ccb/tools.js +853 -0
  161. package/dist/providers/cloudflare/api.js +1066 -0
  162. package/dist/providers/cloudflare/index.d.ts +5 -22
  163. package/dist/providers/cloudflare/index.js +79 -197
  164. package/dist/providers/cloudflare/lib/config.d.ts +16 -0
  165. package/dist/providers/cloudflare/lib/config.js +2 -0
  166. package/dist/providers/cloudflare/lib/discovery.js +29 -0
  167. package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
  168. package/dist/providers/cloudflare/lib/hatch.js +103 -0
  169. package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
  170. package/dist/providers/cloudflare/lib/response-media.js +75 -0
  171. package/dist/providers/cloudflare/openapi.generated.js +4 -3
  172. package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
  173. package/dist/providers/cloudflare/skill.generated.js +2 -10
  174. package/dist/providers/cloudflare/tools.js +360 -0
  175. package/dist/providers/docs/api.js +51 -0
  176. package/dist/providers/docs/index.d.ts +1 -12
  177. package/dist/providers/docs/index.js +53 -1165
  178. package/dist/providers/docs/lib/constants.d.ts +18 -0
  179. package/dist/providers/docs/lib/constants.js +18 -0
  180. package/dist/providers/docs/lib/discovery.js +26 -0
  181. package/dist/providers/docs/lib/requests.js +48 -0
  182. package/dist/providers/docs/lib/schemas.js +168 -0
  183. package/dist/providers/docs/openapi.generated.js +16 -0
  184. package/dist/providers/docs/safety.js +54 -0
  185. package/dist/providers/docs/skill.generated.js +2 -6
  186. package/dist/providers/docs/tools.js +802 -0
  187. package/dist/providers/drive/api.js +177 -0
  188. package/dist/providers/drive/index.d.ts +1 -12
  189. package/dist/providers/drive/index.js +38 -1448
  190. package/dist/providers/drive/lib/constants.d.ts +6 -0
  191. package/dist/providers/drive/lib/constants.js +6 -0
  192. package/dist/providers/drive/lib/contracts.js +532 -0
  193. package/dist/providers/drive/lib/hatch.js +197 -0
  194. package/dist/providers/drive/openapi.generated.js +77 -0
  195. package/dist/providers/drive/safety.js +90 -0
  196. package/dist/providers/drive/skill.generated.js +2 -6
  197. package/dist/providers/drive/tools.js +1290 -0
  198. package/dist/providers/forms/api.js +71 -0
  199. package/dist/providers/forms/index.d.ts +1 -12
  200. package/dist/providers/forms/index.js +49 -1145
  201. package/dist/providers/forms/lib/constants.d.ts +14 -0
  202. package/dist/providers/forms/lib/constants.js +19 -0
  203. package/dist/providers/forms/lib/hatch.js +72 -0
  204. package/dist/providers/forms/openapi.generated.js +23 -0
  205. package/dist/providers/forms/safety.js +34 -0
  206. package/dist/providers/forms/skill.generated.js +2 -8
  207. package/dist/providers/forms/tools.js +1024 -0
  208. package/dist/providers/github/api.js +150 -0
  209. package/dist/providers/github/index.d.ts +4 -5
  210. package/dist/providers/github/index.js +30 -87
  211. package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
  212. package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
  213. package/dist/providers/github/lib/discovery.js +132 -0
  214. package/dist/providers/github/lib/operations.js +58 -0
  215. package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
  216. package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
  217. package/dist/providers/github/openapi.generated.js +1216 -0
  218. package/dist/providers/github/safety.js +216 -0
  219. package/dist/providers/github/skill.generated.js +2 -5
  220. package/dist/providers/github/tools.js +506 -0
  221. package/dist/providers/gmail/api.js +114 -0
  222. package/dist/providers/gmail/index.d.ts +1 -12
  223. package/dist/providers/gmail/index.js +73 -1621
  224. package/dist/providers/gmail/lib/constants.d.ts +13 -0
  225. package/dist/providers/gmail/lib/constants.js +16 -0
  226. package/dist/providers/gmail/lib/index.js +173 -0
  227. package/dist/providers/gmail/lib/schemas.js +176 -0
  228. package/dist/providers/gmail/lib/threading.js +120 -0
  229. package/dist/providers/gmail/openapi.generated.js +92 -0
  230. package/dist/providers/gmail/safety.js +21 -0
  231. package/dist/providers/gmail/skill.generated.js +2 -6
  232. package/dist/providers/gmail/tools.js +1435 -0
  233. package/dist/providers/overflow/api.js +1008 -0
  234. package/dist/providers/overflow/index.d.ts +1 -17
  235. package/dist/providers/overflow/index.js +60 -1488
  236. package/dist/providers/overflow/skill.generated.js +2 -5
  237. package/dist/providers/overflow/tools.js +873 -0
  238. package/dist/providers/planning-center/api.js +467 -0
  239. package/dist/providers/planning-center/index.d.ts +4 -35
  240. package/dist/providers/planning-center/index.js +69 -2445
  241. package/dist/providers/planning-center/lib/constants.d.ts +58 -0
  242. package/dist/providers/planning-center/lib/constants.js +83 -0
  243. package/dist/providers/planning-center/lib/discovery.js +40 -0
  244. package/dist/providers/planning-center/lib/guide.js +39 -0
  245. package/dist/providers/planning-center/lib/json-api.js +83 -0
  246. package/dist/providers/planning-center/lib/verdicts.js +1758 -0
  247. package/dist/providers/planning-center/openapi.generated.js +1764 -0
  248. package/dist/providers/planning-center/safety.js +188 -0
  249. package/dist/providers/planning-center/skill.generated.js +2 -27
  250. package/dist/providers/planning-center/tools.js +1010 -0
  251. package/dist/providers/sheets/api.js +133 -0
  252. package/dist/providers/sheets/index.d.ts +4 -16
  253. package/dist/providers/sheets/index.js +61 -1172
  254. package/dist/providers/sheets/lib/constants.d.ts +14 -0
  255. package/dist/providers/sheets/lib/constants.js +14 -0
  256. package/dist/providers/sheets/lib/hatch.js +163 -0
  257. package/dist/providers/sheets/lib/requests.js +105 -0
  258. package/dist/providers/sheets/lib/schemas.js +138 -0
  259. package/dist/providers/sheets/openapi.generated.js +30 -0
  260. package/dist/providers/sheets/safety.js +27 -0
  261. package/dist/providers/sheets/skill.generated.js +2 -7
  262. package/dist/providers/sheets/tools.js +734 -0
  263. package/dist/providers/slides/api.js +147 -0
  264. package/dist/providers/slides/index.d.ts +2 -10
  265. package/dist/providers/slides/index.js +48 -2555
  266. package/dist/providers/slides/lib/constants.d.ts +11 -0
  267. package/dist/providers/slides/lib/constants.js +11 -0
  268. package/dist/providers/slides/lib/requests.js +60 -0
  269. package/dist/providers/slides/lib/schemas.js +565 -0
  270. package/dist/providers/slides/openapi.generated.js +18 -0
  271. package/dist/providers/slides/skill.generated.js +2 -6
  272. package/dist/providers/slides/tools.js +1968 -0
  273. package/dist/providers/stripe/api.js +401 -0
  274. package/dist/providers/stripe/index.d.ts +5 -27
  275. package/dist/providers/stripe/index.js +74 -210
  276. package/dist/providers/stripe/lib/config.d.ts +4 -0
  277. package/dist/providers/stripe/lib/config.js +2 -0
  278. package/dist/providers/stripe/openapi.generated.js +4 -3
  279. package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
  280. package/dist/providers/stripe/skill.generated.js +2 -11
  281. package/dist/providers/stripe/tools.js +77 -0
  282. package/dist/providers/tithely/api.js +531 -0
  283. package/dist/providers/tithely/index.d.ts +17 -0
  284. package/dist/providers/tithely/index.js +86 -965
  285. package/dist/providers/tithely/skill.generated.js +2 -5
  286. package/dist/providers/tithely/tools.js +426 -0
  287. package/dist/providers/vercel/api.js +551 -0
  288. package/dist/providers/vercel/index.d.ts +9 -25
  289. package/dist/providers/vercel/index.js +66 -260
  290. package/dist/providers/vercel/lib/config.d.ts +2 -0
  291. package/dist/providers/vercel/lib/config.js +2 -0
  292. package/dist/providers/vercel/lib/index.js +117 -0
  293. package/dist/providers/vercel/lib/logs.js +100 -0
  294. package/dist/providers/vercel/lib/pin.js +270 -0
  295. package/dist/providers/vercel/openapi.generated.js +10 -5
  296. package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
  297. package/dist/providers/vercel/skill.generated.js +2 -7
  298. package/dist/providers/vercel/tools.js +642 -0
  299. package/dist/redaction-identity.js +47 -0
  300. package/dist/redaction-marker.js +125 -0
  301. package/dist/registry.d.ts +21 -15
  302. package/dist/registry.js +409 -376
  303. package/dist/routes/access-tokens.js +1 -1
  304. package/dist/routes/actor-labels.js +1 -1
  305. package/dist/routes/connect.js +1 -1
  306. package/dist/routes/dispatch.js +103 -0
  307. package/dist/routes/mcp.js +170 -79
  308. package/dist/routes/oauth.js +3 -1
  309. package/dist/routes/operator.js +2 -2
  310. package/dist/routes/shared.d.ts +5 -4
  311. package/dist/routes/shared.js +244 -177
  312. package/dist/routes/skills.js +10 -8
  313. package/dist/routes/ui-config.js +27 -3
  314. package/dist/routes/ui-data.js +21 -20
  315. package/dist/routes/ui.js +1 -1
  316. package/dist/runtime/call-admission.js +0 -26
  317. package/dist/runtime/call-entry.js +30 -0
  318. package/dist/runtime/call.js +556 -0
  319. package/dist/runtime/connector-scope.js +7 -0
  320. package/dist/runtime/credential-probe.js +34 -0
  321. package/dist/runtime/executor.js +40 -0
  322. package/dist/runtime/native-storage.js +63 -0
  323. package/dist/runtime/registry.js +27 -0
  324. package/dist/runtime/request-scope.js +72 -0
  325. package/dist/runtime/run.js +15 -7
  326. package/dist/runtime/shared-read.js +34 -5
  327. package/dist/runtime/storage.js +34 -21
  328. package/dist/sent-secrets.d.ts +194 -37
  329. package/dist/sent-secrets.js +1743 -340
  330. package/dist/server.js +5 -56
  331. package/dist/skills.js +322 -222
  332. package/dist/sqlite.js +15 -14
  333. package/dist/storage/d1-copy.js +255 -0
  334. package/dist/storage/d1-driver.js +15 -0
  335. package/dist/storage/keys.js +28 -0
  336. package/dist/storage/sql-driver.js +88 -0
  337. package/dist/storage/sql-native.js +544 -0
  338. package/dist/storage/sql-schema.d.ts +6 -0
  339. package/dist/storage/sql-schema.js +15 -0
  340. package/dist/storage/sql.d.ts +14 -20
  341. package/dist/storage/sql.js +29 -535
  342. package/dist/timeout.js +0 -26
  343. package/dist/tool-safety.js +20 -10
  344. package/dist/types.d.ts +7 -109
  345. package/dist/upward.js +3 -0
  346. package/dist/usage-guide.js +3 -1
  347. package/dist/validate.d.ts +4 -2
  348. package/dist/validate.js +86 -9
  349. package/dist/version.d.ts +1 -1
  350. package/dist/version.js +1 -1
  351. package/dist/worker.js +312 -292
  352. package/dist/workspace-subject.js +5 -1
  353. package/dist/write-outcome.js +454 -34
  354. package/documentation/README.md +27 -7
  355. package/documentation/architecture.md +2547 -226
  356. package/documentation/auth.md +821 -98
  357. package/documentation/code-mode.md +49 -16
  358. package/documentation/deploying.md +164 -20
  359. package/documentation/hosted-mcp-migration.md +460 -0
  360. package/documentation/integrating.md +129 -144
  361. package/documentation/meta-tools.md +409 -32
  362. package/documentation/operating.md +27 -3
  363. package/documentation/operator-tests.md +3 -0
  364. package/documentation/operator-ui.md +68 -45
  365. package/documentation/provider-auth-migration.md +16 -63
  366. package/documentation/provider-migration-0.29.md +10 -5
  367. package/documentation/provider-migration-0.35.md +975 -0
  368. package/documentation/providers.md +965 -0
  369. package/examples/worker/AGENTS.md +23 -9
  370. package/examples/worker/README.md +254 -135
  371. package/examples/worker/alchemy.run.ts +116 -0
  372. package/examples/worker/gitignore +10 -0
  373. package/examples/worker/src/connecta.config.ts +23 -21
  374. package/examples/worker/wrangler.jsonc +14 -5
  375. package/guides/connecting-agents.md +157 -0
  376. package/guides/how-it-works.md +156 -0
  377. package/guides/safety-and-trust.md +153 -0
  378. package/package.json +27 -46
  379. package/templates/node/.env.example +3 -0
  380. package/templates/node/README.md +22 -2
  381. package/templates/node/docker-compose.yml +1 -0
  382. package/templates/node/package.json +3 -2
  383. package/templates/node/src/connecta.config.ts +8 -0
  384. package/dist/catalog-drift.d.ts +0 -138
  385. package/dist/catalog-drift.js +0 -578
  386. package/dist/providers/_shared/request-token.js +0 -83
  387. package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
  388. package/dist/providers/_shared/rest/tools.d.ts +0 -141
  389. package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
  390. package/dist/providers/basecamp/index.d.ts +0 -45
  391. package/dist/providers/basecamp/index.js +0 -997
  392. package/dist/providers/basecamp/skill.generated.js +0 -9
  393. package/dist/providers/cloudflare/rest.d.ts +0 -37
  394. package/dist/providers/cloudflare/rest.js +0 -1097
  395. package/dist/providers/github/hosted.js +0 -218
  396. package/dist/providers/github/rest.js +0 -321
  397. package/dist/providers/infisical/index.d.ts +0 -7
  398. package/dist/providers/infisical/index.js +0 -713
  399. package/dist/providers/infisical/skill.generated.js +0 -8
  400. package/dist/providers/intercom/index.d.ts +0 -24
  401. package/dist/providers/intercom/index.js +0 -121
  402. package/dist/providers/intercom/skill.generated.js +0 -8
  403. package/dist/providers/linear/index.d.ts +0 -55
  404. package/dist/providers/linear/index.js +0 -537
  405. package/dist/providers/linear/skill.generated.js +0 -8
  406. package/dist/providers/mixpanel/index.d.ts +0 -59
  407. package/dist/providers/mixpanel/index.js +0 -472
  408. package/dist/providers/mixpanel/operations.js +0 -20
  409. package/dist/providers/mixpanel/rest.js +0 -603
  410. package/dist/providers/mixpanel/skill.generated.js +0 -10
  411. package/dist/providers/mixpanel/value-safety.js +0 -16
  412. package/dist/providers/notion/index.d.ts +0 -67
  413. package/dist/providers/notion/index.js +0 -368
  414. package/dist/providers/notion/openapi.generated.js +0 -75
  415. package/dist/providers/notion/rest.d.ts +0 -24
  416. package/dist/providers/notion/rest.js +0 -1226
  417. package/dist/providers/notion/skill.generated.js +0 -11
  418. package/dist/providers/notion/value-safety.js +0 -56
  419. package/dist/providers/revenuecat/index.d.ts +0 -51
  420. package/dist/providers/revenuecat/index.js +0 -697
  421. package/dist/providers/revenuecat/skill.generated.js +0 -11
  422. package/dist/providers/stripe/rest.d.ts +0 -14
  423. package/dist/providers/stripe/rest.js +0 -402
  424. package/dist/providers/vercel/rest.d.ts +0 -20
  425. package/dist/providers/vercel/rest.js +0 -1020
  426. package/dist/providers/wispr-flow/index.d.ts +0 -14
  427. package/dist/providers/wispr-flow/index.js +0 -117
  428. package/dist/providers/wispr-flow/skill.generated.js +0 -8
  429. /package/dist/providers/github/{key.js → lib/key.js} +0 -0
@@ -1,56 +1,85 @@
1
+ import { authView } from "../auth/native-call.js";
2
+ import { authFlow, authCall, authInvoke, authValue, authMethod, authCatch } from "../auth/native.js";
3
+ import { isOAuthCredentialRequest } from "../auth/oauth-token-response.js";
4
+ import { deriveContext } from "../derived-context.js";
5
+ import { operationContext, openRequest, viewServices, viewSignal } from "../runtime/call.js";
1
6
  import { assertRemoteOAuthClient, declareSelfHostedClient, downstreamClientMetadata, downstreamRedirectUri, remoteClientAuthMethod, selfHostedClientUrl, trackRemoteClientRequest, } from "../auth/downstream-client-metadata.js";
2
7
  import { AuthorizationServerMismatchError, Client, InsecureTokenEndpointError, InsufficientScopeError, IssuerMismatchError, MissingRequiredClientCapabilityError, OAuthClientFlowError, OAuthError, ProtocolError, RegistrationRejectedError, ResourceNotFoundError, SdkError, SdkErrorCode, SdkHttpError, SseError, UnsupportedProtocolVersionError, UrlElicitationRequiredError, isInputRequiredResult, fromJsonSchema, isJSONRPCErrorResponse, isJSONRPCNotification, specTypeSchemas, StreamableHTTPClientTransport, UnauthorizedError, } from "@modelcontextprotocol/client";
3
8
  import { Deferred, Duration, Effect, Exit, Scope } from "effect";
4
- import { assertOAuthScope, authorizingContext, KvOAuthProvider, OAuthRequestNotSentError, refreshCoordinatorsByPartition, } from "../auth/downstream-oauth.js";
9
+ import { assertOAuthScope, authorizingContext, codeExchangeResponse, KvOAuthProvider, OAuthRequestNotSentError, refreshCoordinatorsByPartition, } from "../auth/downstream-oauth.js";
5
10
  import { trackOAuthStartReset } from "../auth/oauth-start-reset.js";
6
11
  import { byteReadResponse } from "../byte-read-response.js";
7
- import { redactCatalog, redactSentSecrets, sentSecretsFor, shortSecretWarning, trackCredentialReads, } from "../sent-secrets.js";
12
+ import { redactCatalog, redactSentSecrets, requestSentSecrets, sentSecretsFor, shortSecretWarning, trackCredentialReads, } from "../sent-secrets.js";
8
13
  import { MAX_CATALOG_TOOLS, MAX_SERIALIZED_CATALOG_BYTES } from "../catalog-limits.js";
9
14
  import { catalogClientOptions, catalogItems, observeCompletedCatalogRefresh, closeCatalogCacheScope, observeCatalogChange, observeCatalogFetch, observeCatalogExpiry, } from "../catalog-cache.js";
10
- import { reviewedClassification } from "../catalog-drift.js";
11
15
  import { connectorScopeCleanupClaimed } from "../connector-scope.js";
12
- import { boundedEchoText, ConnectorCallError, msg, networkErrorCode, unavailableCallError, WithheldTextError, } from "../errors.js";
16
+ import { boundedEchoText, ConnectorCallError, msg, networkErrorCode, resendAdvice, resendAdviceOf, unavailableCallError, WithheldTextError, withResendAdvice, } from "../errors.js";
13
17
  import { attachFailureFacts, carryFailureFacts, errorLabel, failureRecord, failureStatus, labelErrorClass, logFailure, OAUTH_ERROR_CODES, ownStatus, } from "../operator-record.js";
14
18
  import { CONNECTA_VERSION } from "../version.js";
15
19
  import { learnedUrlRefusal } from "../url-safety.js";
16
20
  import { oauthSealerFor } from "../oauth-sealing.js";
17
- import { retainingOAuthPartition } from "../oauth-partition.js";
21
+ import { oauthPartitionFor, retainOAuthPartition } from "../oauth-partition.js";
22
+ import { RequestScope } from "../runtime/request-scope.js";
23
+ import { registerCallEntry } from "../runtime/call-entry.js";
24
+ import { Context } from "effect";
18
25
  import { registerInvocationAuth } from "../invocation-auth.js";
19
26
  import { downstreamInputCapabilities, assertDownstreamOutputSafe, downstreamWriteContinuation, } from "../downstream-input-context.js";
20
27
  import { detach, runEdge } from "../runtime/run.js";
21
- import { assertKnownOptions, keys, optionsOf } from "../config-schema.js";
28
+ import { assertKnownOptions, keys, optionsOf, strings } from "../config-schema.js";
22
29
  import { describedEndpoint, describedUrl } from "../described.js";
23
- import { retryAfterMs } from "./guarded-fetch.js";
30
+ import { retryAfterMs } from "../http-policy.js";
24
31
  import { resourceUriMatchesTemplates } from "./resource-uri.js";
25
32
  import { callerOf } from "../connector-caller.js";
33
+ import { callClassification } from "../call-classification.js";
34
+ import { guardedTransport, sendGuarded, sendCredentialPost } from "../guarded-transport.js";
35
+ import { sourceClassification } from "../tool-safety.js";
36
+ import { dispatchProviderRequest, recordProviderResponse, enterProviderBoundary, knownProviderFailure, providerBoundary, providerRequest, renewedAfterUnauthorized, vendorRefusal, } from "../provider-call.js";
37
+ import { providerCallFailure, unsentAfterUnauthorized } from "../write-outcome.js";
38
+ import { recordRequestField } from "../provider-call-facts.js";
26
39
  import { readNegotiation, storeNegotiation } from "./negotiation-cache.js";
27
40
  import { READ_ONLY, CALL_ADMISSION, REMOTE_MCP_AUTH, USAGE_GUIDE } from "./option-shapes.js";
28
- /**
29
- * Apply a maintained provider's slot copy and header framing to credential
30
- * auth the deployment left bare.
31
- *
32
- * A provider knows what its own key is called and how the endpoint expects it
33
- * framed; a deployment that states either one keeps its answer. Every other
34
- * auth shape passes through untouched, so a provider can hand this its whole
35
- * `auth` option without branching first.
36
- */
37
- export function withCredentialDefaults(auth, defaults) {
38
- if (auth.type !== "credential")
39
- return auth;
40
- return {
41
- ...auth,
42
- credential: auth.credential ?? defaults.credential,
43
- ...(auth.scheme === undefined && defaults.scheme !== undefined ? { scheme: defaults.scheme } : {}),
44
- };
45
- }
46
41
  /** The closed options remoteMcp() accepts; see `assertKnownOptions`. */
47
42
  const REMOTE_MCP_OPTIONS = optionsOf()({
48
- ...keys("url", "title", "description", "authScope", "maxResultBytes", "versionNegotiation", "redirects", "requireHttps", "logger", "_transportFactory", "classify", "skills"),
43
+ ...keys("url", "title", "description", "authScope", "maxResultBytes", "versionNegotiation", "redirects", "requireHttps", "logger", "_transportFactory", "skills"),
49
44
  readOnly: READ_ONLY,
50
45
  callAdmission: CALL_ADMISSION,
51
46
  usageGuide: USAGE_GUIDE,
52
47
  auth: REMOTE_MCP_AUTH,
48
+ headers: strings(),
53
49
  });
50
+ /** Extra headers are immutable code configuration, not another auth channel. */
51
+ function extraResourceHeaders(id, opts) {
52
+ if (opts.headers === undefined)
53
+ return undefined;
54
+ const refuse = () => new Error(`remoteMcp(${JSON.stringify(id)}).headers requires non-secret resource headers without reserved or auth-owned names; use auth for credentials.`);
55
+ if (opts.auth?.type === "headers")
56
+ throw refuse();
57
+ const authNames = new Set([
58
+ "authorization",
59
+ ...(opts.auth?.type === "credential" ? [opts.auth.header?.trim().toLowerCase() || "authorization"] : []),
60
+ ...(opts.auth?.type === "request" ? Object.keys(opts.auth.headers ?? {}).map((name) => name.toLowerCase()) : []),
61
+ ]);
62
+ const normalized = Object.create(null);
63
+ for (const [name, value] of Object.entries(opts.headers)) {
64
+ const lower = name.toLowerCase();
65
+ if (!HEADER_NAME_TOKEN.test(name) ||
66
+ typeof value !== "string" ||
67
+ /[\r\n]/.test(value) ||
68
+ Object.hasOwn(normalized, lower) ||
69
+ authNames.has(lower) ||
70
+ /^(?:accept|cookie|host|origin|connection|keep-alive|proxy-authenticate|proxy-authorization|te|trailer|transfer-encoding|upgrade|last-event-id|dpop|www-authenticate)$/.test(lower) ||
71
+ /^(?:content-|mcp-)/.test(lower) ||
72
+ /(?:api[-_]?key|token|secret|password|credential|authorization|signature|session)/.test(lower))
73
+ throw refuse();
74
+ try {
75
+ normalized[lower] = new Headers({ [lower]: value }).get(lower);
76
+ }
77
+ catch {
78
+ throw refuse();
79
+ }
80
+ }
81
+ return Object.freeze(Object.fromEntries(Object.entries(normalized).sort(([a], [b]) => a.localeCompare(b))));
82
+ }
54
83
  /**
55
84
  * How long a downstream gets to answer the session-termination DELETE before
56
85
  * teardown stops waiting. This is a network round-trip budget, deliberately
@@ -455,10 +484,11 @@ function isCursorShapeError(err) {
455
484
  * passed the MCP and OAuth boundaries, and the origin and errno connecta
456
485
  * derived for it, which a message alone would drop.
457
486
  */
487
+ /** An authorization start's failure for authorize_connector: no write's, so its advice stays. */
458
488
  function startMessage(err) {
459
489
  const details = err instanceof ConnectorCallError ? err.details : undefined;
460
490
  const facts = [details?.host, details?.code].filter(Boolean).join(", ");
461
- return facts ? `${msg(err)} (${facts})` : msg(err);
491
+ return withResendAdvice(facts ? `${msg(err)} (${facts})` : msg(err), resendAdviceOf(err));
462
492
  }
463
493
  /**
464
494
  * End the downstream's session before the connection is torn down.
@@ -499,7 +529,7 @@ function terminateSession(transport, logger, connectorId) {
499
529
  return Effect.raceAllFirst([
500
530
  // The SDK issues no request at all when no `mcp-session-id` was captured,
501
531
  // so a stateless downstream never sees a spurious DELETE.
502
- promised(() => Promise.resolve(terminate.call(transport))).pipe(Effect.catch((error) =>
532
+ promised(authMethod(() => authInvoke(Reflect, "apply", [terminate, transport, []]))).pipe(Effect.catch((error) =>
503
533
  // The SDK's error quotes the downstream's status text, and its `data`
504
534
  // the body, so the record keeps only the status it answered with.
505
535
  warn(() => logFailure(logger, "session termination refused or failed; the downstream session may remain until its provider timeout", failureRecord({ connector: connectorId }, error instanceof SdkHttpError ? attachFailureFacts(error, { httpStatus: error.status }) : error))))),
@@ -520,7 +550,7 @@ function closeLocally(client, transport) {
520
550
  if (!client && !transport)
521
551
  return Effect.void;
522
552
  return Effect.raceAllFirst([
523
- promised(() => Promise.resolve(client ? client.close() : transport?.close())).pipe(Effect.ignore),
553
+ promised(authMethod(() => (client ? authInvoke(client, "close", []) : authValue(() => transport?.close())))).pipe(Effect.ignore),
524
554
  Effect.sleep(Duration.millis(LOCAL_CLOSE_BUDGET_MS)),
525
555
  ]);
526
556
  }
@@ -531,7 +561,7 @@ function closeLocally(client, transport) {
531
561
  * allocates no AbortController.
532
562
  */
533
563
  function promised(evaluate) {
534
- return Effect.tryPromise({ try: evaluate, catch: (error) => error });
564
+ return authCall(evaluate, []);
535
565
  }
536
566
  /** A retained cause chain must not smuggle an SDK payload past the boundary. */
537
567
  function hasSdkPayload(error) {
@@ -545,14 +575,14 @@ function hasSdkPayload(error) {
545
575
  }
546
576
  /** Cancellation can bypass an inner SDK catch and return the signal's reason. */
547
577
  function payloadFree(run) {
548
- return async (...args) => {
578
+ return authMethod((...args) => authFlow(function* () {
549
579
  try {
550
- return await run(...args);
580
+ return (yield authCall(run, [...args]));
551
581
  }
552
582
  catch (error) {
553
583
  throw hasSdkPayload(error) ? carryFailureFacts(error, downstreamCallError(error)) : error;
554
584
  }
555
- };
585
+ }));
556
586
  }
557
587
  /** Both the SDK's auth signal and its classified form invalidate a live client. */
558
588
  function requiresAuthorization(error) {
@@ -652,7 +682,7 @@ async function digestOf(value) {
652
682
  *
653
683
  * A scheme ending in `Basic` names HTTP Basic credentials wherever a provider
654
684
  * nests it, so the `user:secret` it frames is base64-encoded — that is what
655
- * makes plain `Basic` and Mixpanel's `Bearer Basic` one rule instead of two.
685
+ * makes plain `Basic` and nested `Bearer Basic` one rule instead of two.
656
686
  */
657
687
  function credentialHeaderValue(scheme, value) {
658
688
  if (scheme === null)
@@ -726,7 +756,7 @@ function redirectedInit(init, status) {
726
756
  * arbitrary static header names receive the same protection as Authorization.
727
757
  */
728
758
  export function redirectSafeFetch(connectorId, policy = "none", baseFetch = fetch) {
729
- return async (input, initialInit = {}) => {
759
+ return authMethod((input, initialInit = {}) => authFlow(function* () {
730
760
  let current = new URL(input);
731
761
  let init = initialInit;
732
762
  const seen = new Set([current.href]);
@@ -734,22 +764,23 @@ export function redirectSafeFetch(connectorId, policy = "none", baseFetch = fetc
734
764
  while (true) {
735
765
  let response;
736
766
  try {
737
- response = await baseFetch(current, {
738
- ...init,
739
- redirect: "manual",
740
- });
767
+ response = (yield authCall(baseFetch, [
768
+ current,
769
+ {
770
+ ...init,
771
+ redirect: "manual",
772
+ },
773
+ ]));
741
774
  }
742
775
  catch (cause) {
743
776
  if (cause instanceof ConnectorCallError)
744
777
  throw downstreamCallError(cause);
745
778
  throw unavailableCallError(cause, current.href, undefined, hasSdkPayload(cause) ? undefined : (init.signal ?? undefined));
746
779
  }
747
- // Every MCP and OAuth exchange reads its answer through here, and the
748
- // SDK reads bodies with `.text()` and `.json()`: as bytes, never text.
749
780
  if (!REDIRECT_STATUSES.has(response.status))
750
781
  return byteReadResponse(response);
751
782
  const location = response.headers.get("location");
752
- await response.body?.cancel().catch(() => { });
783
+ (yield authCatch(authValue(() => response.body?.cancel()), () => { }));
753
784
  if (!location) {
754
785
  throw new RemoteMcpRedirectError(connectorId, `HTTP ${response.status} carried no Location header`);
755
786
  }
@@ -783,7 +814,7 @@ export function redirectSafeFetch(connectorId, policy = "none", baseFetch = fetc
783
814
  init = redirectedInit(init, response.status);
784
815
  current = next;
785
816
  }
786
- };
817
+ }));
787
818
  }
788
819
  class RemoteMcpDestinationError extends OAuthRequestNotSentError {
789
820
  constructor(connectorId, reason) {
@@ -836,13 +867,13 @@ for (const [ctor, label] of [
836
867
  * failures after an HTTP dispatch keep their permanent ambiguous verdict.
837
868
  */
838
869
  function learnedUrlSafeFetch(connectorId, configured, baseFetch) {
839
- return async (input, init) => {
870
+ return authMethod((input, init) => authFlow(function* () {
840
871
  const reason = learnedUrlRefusal(configured, new URL(input));
841
872
  if (reason !== undefined) {
842
873
  throw new RemoteMcpDestinationError(connectorId, reason);
843
874
  }
844
- return byteReadResponse(await baseFetch(input, init));
845
- };
875
+ return byteReadResponse((yield authCall(baseFetch, [input, init])));
876
+ }));
846
877
  }
847
878
  /** The JSON-RPC error answers each transport delivered, as `code\0message`. */
848
879
  const wireErrors = new WeakMap();
@@ -966,6 +997,7 @@ function withheldAs(message, verdict) {
966
997
  ...(verdict.details ? { details: verdict.details } : {}),
967
998
  ...(verdict.validation ? { validation: verdict.validation } : {}),
968
999
  ...(verdict.current ? { current: verdict.current } : {}),
1000
+ ...(verdict.writeOutcome ? { writeOutcome: verdict.writeOutcome } : {}),
969
1001
  });
970
1002
  }
971
1003
  return new WithheldTextError(message, verdict);
@@ -977,7 +1009,7 @@ function withheldAs(message, verdict) {
977
1009
  * `grant_type`), or the JSON POST of dynamic client registration.
978
1010
  */
979
1011
  function tracedOAuthFetch(trail, endpoint, baseFetch) {
980
- return async (input, init) => {
1012
+ return authMethod((input, init) => authFlow(function* () {
981
1013
  const url = new URL(input);
982
1014
  if (url.href === endpoint.href) {
983
1015
  trail.last = undefined;
@@ -986,7 +1018,8 @@ function tracedOAuthFetch(trail, endpoint, baseFetch) {
986
1018
  const body = init?.body;
987
1019
  const step = (init?.method ?? "GET").toUpperCase() === "GET"
988
1020
  ? "discovery"
989
- : body instanceof URLSearchParams || (typeof body === "string" && new URLSearchParams(body).has("grant_type"))
1021
+ : body instanceof URLSearchParams ||
1022
+ (typeof body === "string" && new URLSearchParams(body).has("grant_type"))
990
1023
  ? "token request"
991
1024
  : "client registration";
992
1025
  trail.last = { step, host: url.origin };
@@ -994,7 +1027,7 @@ function tracedOAuthFetch(trail, endpoint, baseFetch) {
994
1027
  trail.registration = url.origin;
995
1028
  }
996
1029
  const leg = trail.last;
997
- const response = await baseFetch(input, init);
1030
+ const response = (yield authCall(baseFetch, [input, init]));
998
1031
  if (leg) {
999
1032
  leg.httpStatus = response.status;
1000
1033
  const wait = retryAfterMs(response.headers);
@@ -1002,7 +1035,7 @@ function tracedOAuthFetch(trail, endpoint, baseFetch) {
1002
1035
  leg.retryAfterMs = wait;
1003
1036
  }
1004
1037
  return byteReadResponse(response);
1005
- };
1038
+ }));
1006
1039
  }
1007
1040
  function registrationErrorCode(body) {
1008
1041
  try {
@@ -1013,6 +1046,37 @@ function registrationErrorCode(body) {
1013
1046
  return undefined;
1014
1047
  }
1015
1048
  }
1049
+ /**
1050
+ * The key a `tools/call`'s params carry its dispatch under. Object spread
1051
+ * copies a symbol-keyed property, and the pinned SDK only spreads a request's
1052
+ * params (to add a progress token or its `_meta` envelope); `JSON.stringify`
1053
+ * skips symbol keys, so nothing of it reaches the wire.
1054
+ */
1055
+ const DISPATCH = Symbol("connecta.dispatch");
1056
+ function newDispatch(write, sourceWrite) {
1057
+ return { write, sourceWrite, sent: false, unauthorized: false, redirected: false, resendRefused: false };
1058
+ }
1059
+ function withDispatch(params, dispatch) {
1060
+ Object.defineProperty(params, DISPATCH, { value: dispatch, enumerable: true });
1061
+ return params;
1062
+ }
1063
+ function dispatchOf(params) {
1064
+ return params !== null && typeof params === "object"
1065
+ ? params[DISPATCH]
1066
+ : undefined;
1067
+ }
1068
+ /** A further request for a write that already left, stopped before it does. */
1069
+ function resendRefused(dispatch) {
1070
+ dispatch.resendRefused = true;
1071
+ return new ConnectorCallError("connector_call_failed", "This write was already sent once; it was not sent again.", {
1072
+ retryable: false,
1073
+ writeOutcome: "unknown",
1074
+ });
1075
+ }
1076
+ /** Lost tracking cannot prove whether an earlier message already sent a write. */
1077
+ function missingDispatch() {
1078
+ return new ConnectorCallError("connector_call_failed", "The downstream call lost its dispatch record and cannot be proven read-only. It was not sent; whether an earlier write took effect is unknown. Check the target before repeating it.", { retryable: false, writeOutcome: "unknown" });
1079
+ }
1016
1080
  /**
1017
1081
  * Proxy a downstream remote MCP server. SDK clients and transports are scoped
1018
1082
  * to one inbound request: reused by calls within a batch/execute_code run, but
@@ -1029,11 +1093,11 @@ export function remoteMcp(id, opts) {
1029
1093
  READ_ONLY.parse(opts.readOnly, `Connector(${JSON.stringify(id)}).readOnly`);
1030
1094
  if (opts.callAdmission !== undefined)
1031
1095
  CALL_ADMISSION.parse(opts.callAdmission, `remoteMcp(${JSON.stringify(id)}).callAdmission`);
1096
+ const extraHeaders = extraResourceHeaders(id, opts);
1032
1097
  const skillsEnabled = opts.skills;
1033
1098
  if (skillsEnabled !== undefined && typeof skillsEnabled !== "boolean") {
1034
1099
  throw new Error(`[connecta] connector "${id}" skills must be a boolean.`);
1035
1100
  }
1036
- const classification = opts.classify === undefined ? undefined : reviewedClassification(opts.classify, `connector "${id}"`);
1037
1101
  const clientMetadataUrl = opts.auth?.type === "oauth" ? opts.auth.clientMetadataUrl : undefined;
1038
1102
  const oauthScope = opts.auth?.type === "oauth" ? opts.auth.scope : undefined;
1039
1103
  assertOAuthScope(id, oauthScope);
@@ -1186,40 +1250,41 @@ export function remoteMcp(id, opts) {
1186
1250
  * Anything not already in connecta's words is withheld, including a store's
1187
1251
  * error while the flow saves what it discovered, which can quote it.
1188
1252
  */
1189
- const withoutAuthorizationServerText = async (ctx, err, trail, signals) => {
1190
- const safeOrigin = async (host) => {
1253
+ const withoutAuthorizationServerText = authMethod((ctx, err, trail, signals) => authFlow(function* () {
1254
+ const safeOrigin = authMethod((host) => authFlow(function* () {
1191
1255
  if (!host || sentSecretsFor(ctx).contains(host, true))
1192
1256
  return undefined;
1193
1257
  if (host === destination.origin || (staticClient && host === new URL(staticClient.issuer).origin))
1194
1258
  return host;
1195
1259
  try {
1196
- return host === (await newProvider(ctx).validatedIssuerOrigin()) ? host : undefined;
1260
+ return host === (yield authInvoke(newProvider(ctx), "validatedIssuerOrigin", []))
1261
+ ? host
1262
+ : undefined;
1197
1263
  }
1198
1264
  catch {
1199
1265
  return undefined;
1200
1266
  }
1201
- };
1267
+ }));
1202
1268
  if (ownAbortReason(err, signals) && !hasSdkPayload(err))
1203
1269
  return err;
1204
1270
  if (err instanceof RegistrationRejectedError) {
1205
1271
  const code = registrationErrorCode(err.body);
1206
- const origin = await safeOrigin(trail.registration);
1272
+ const origin = (yield authCall(safeOrigin, [trail.registration]));
1207
1273
  const facts = {
1208
1274
  step: "OAuth client registration",
1209
1275
  ...(origin ? { origin } : {}),
1210
1276
  httpStatus: err.status,
1211
1277
  ...(code ? { oauthError: code } : {}),
1212
1278
  };
1213
- return attachFailureFacts(carryFailureFacts(err, withheldAs(`Connector "${id}" could not register an OAuth client with ` +
1279
+ return attachFailureFacts(carryFailureFacts(err, resendAdvice(withheldAs(`Connector "${id}" could not register an OAuth client with ` +
1214
1280
  `${origin ?? "its authorization server"}: the ` +
1215
1281
  `registration endpoint answered HTTP ${err.status}` +
1216
1282
  `${code ? ` with OAuth error ${code}` : ""}. Its response is ` +
1217
1283
  "withheld because it can quote the request or anything the server " +
1218
- "chose to add. Check the server's client registration policy, then " +
1219
- "retry authorization.", downstreamCallError(err, undefined, trail.last?.retryAfterMs))), facts);
1284
+ "chose to add. Check the server's client registration policy.", downstreamCallError(err, undefined, trail.last?.retryAfterMs)), ", then retry authorization")), facts);
1220
1285
  }
1221
1286
  const leg = trail.last;
1222
- const origin = await safeOrigin(leg?.host);
1287
+ const origin = (yield authCall(safeOrigin, [leg?.host]));
1223
1288
  const facts = leg
1224
1289
  ? {
1225
1290
  step: `OAuth ${leg.step}`,
@@ -1235,11 +1300,10 @@ export function remoteMcp(id, opts) {
1235
1300
  (classified instanceof ConnectorCallError && classified.code === "provider_permission_denied")) {
1236
1301
  return attachFailureFacts(carryFailureFacts(err, classified), facts);
1237
1302
  }
1238
- return attachFailureFacts(carryFailureFacts(err, withheldAs(`Connector "${id}" OAuth ${leg ? `${leg.step}${origin ? ` with ${origin}` : ""}` : "flow"} ` +
1303
+ return attachFailureFacts(carryFailureFacts(err, resendAdvice(withheldAs(`Connector "${id}" OAuth ${leg ? `${leg.step}${origin ? ` with ${origin}` : ""}` : "flow"} ` +
1239
1304
  `failed${errorKind(err)}. The error is withheld because its text can ` +
1240
- "quote what the server sent. Check the server's OAuth metadata, then " +
1241
- "retry authorization.", classified)), facts);
1242
- };
1305
+ "quote what the server sent. Check the server's OAuth metadata.", classified), ", then retry authorization")), facts);
1306
+ }));
1243
1307
  const endpointOrigin = new URL(opts.url).origin;
1244
1308
  /**
1245
1309
  * The MCP boundary, where an error leaves the SDK at the handshake, a
@@ -1261,7 +1325,7 @@ export function remoteMcp(id, opts) {
1261
1325
  origin: endpointOrigin,
1262
1326
  ...(httpStatus ? { httpStatus } : {}),
1263
1327
  };
1264
- const classified = downstreamCallError(redactSentSecrets(ctx, err), undefined, undefined, undefined, sentSecretsFor(ctx));
1328
+ const classified = downstreamCallError(redactSentSecrets(ctx, err), undefined, undefined, undefined, requestSentSecrets(ctx));
1265
1329
  const verdict = isOauth && classified instanceof ConnectorCallError && classified.code === "auth_required"
1266
1330
  ? carryFailureFacts(err, authRequiredError())
1267
1331
  : classified;
@@ -1308,7 +1372,7 @@ export function remoteMcp(id, opts) {
1308
1372
  */
1309
1373
  const boundOAuthFlows = (ctx, transport, endpoint,
1310
1374
  /** The request's and connection's signals: only their reasons pass as written. */
1311
- signals) => {
1375
+ signals, renewalSignal) => {
1312
1376
  const internals = transport;
1313
1377
  const adapted = internals._authProvider;
1314
1378
  const onUnauthorized = adapted?.onUnauthorized;
@@ -1320,29 +1384,46 @@ export function remoteMcp(id, opts) {
1320
1384
  "flows cannot be bounded. Pin @modelcontextprotocol/client to the " +
1321
1385
  "version this release ships with.");
1322
1386
  }
1323
- const flow = async (start) => {
1387
+ const flow = authMethod((start, signal) => authFlow(function* () {
1324
1388
  const trail = {};
1325
1389
  try {
1326
- return await start((fetchFn) => tracedOAuthFetch(trail, endpoint, async (input, init) => {
1327
- trackSentRequest(ctx, input, init);
1328
- return await fetchFn(input, init);
1329
- }));
1390
+ return (yield authCall(start, [
1391
+ (fetchFn) => tracedOAuthFetch(trail, endpoint, authMethod((input, init) => authFlow(function* () {
1392
+ trackSentRequest(ctx, input, init);
1393
+ return (yield authCall(fetchFn, [
1394
+ input,
1395
+ signal
1396
+ ? {
1397
+ ...init,
1398
+ signal: init?.signal ? AbortSignal.any([signal, init.signal]) : signal,
1399
+ }
1400
+ : init,
1401
+ ]));
1402
+ }))),
1403
+ ]));
1330
1404
  }
1331
1405
  catch (err) {
1332
- throw await withoutAuthorizationServerText(ctx, err, trail, signals);
1333
- }
1334
- };
1335
- adapted.onUnauthorized = (ctx) => flow((trace) => onUnauthorized.call(adapted, { ...ctx, fetchFn: trace(ctx.fetchFn ?? fetch) }));
1336
- internals._stepUpAuthorize = (challenge, retries) => flow((trace) => {
1337
- const traced = trace(internals._fetchWithInit ?? fetch);
1338
- // The step-up reads its fetch from `this`; everything else it reads
1339
- // and writes stays the transport's own.
1340
- const self = new Proxy(transport, {
1341
- get: (target, key) => (key === "_fetchWithInit" ? traced : Reflect.get(target, key, target)),
1342
- set: (target, key, value) => Reflect.set(target, key, value, target),
1343
- });
1344
- return stepUp.call(self, challenge, retries);
1345
- });
1406
+ throw (yield authCall(withoutAuthorizationServerText, [ctx, err, trail, [...signals, signal]]));
1407
+ }
1408
+ }));
1409
+ adapted.onUnauthorized = authMethod((ctx) => authCall(flow, [
1410
+ authMethod((trace) => authInvoke(Reflect, "apply", [
1411
+ onUnauthorized,
1412
+ adapted,
1413
+ [{ ...ctx, fetchFn: trace(ctx.fetchFn ?? fetch) }],
1414
+ ])),
1415
+ renewalSignal(ctx.response),
1416
+ ]));
1417
+ internals._stepUpAuthorize = authMethod((challenge, retries) => authCall(flow, [
1418
+ authMethod((trace) => authFlow(function* () {
1419
+ const traced = trace(internals._fetchWithInit ?? fetch);
1420
+ const self = new Proxy(transport, {
1421
+ get: (target, key) => key === "_fetchWithInit" ? traced : Reflect.get(target, key, target),
1422
+ set: (target, key, value) => Reflect.set(target, key, value, target),
1423
+ });
1424
+ return (yield authInvoke(Reflect, "apply", [stepUp, self, [challenge, retries]]));
1425
+ })),
1426
+ ]));
1346
1427
  };
1347
1428
  class OperatorDisconnectedError extends ConnectorCallError {
1348
1429
  constructor() {
@@ -1367,11 +1448,11 @@ export function remoteMcp(id, opts) {
1367
1448
  * trusting any cached client, so an operator's replacement is picked up
1368
1449
  * without a redeploy. The value stays in the caller's local scope.
1369
1450
  */
1370
- const readCredential = async (ctx) => {
1451
+ const readCredential = authMethod((ctx) => authFlow(function* () {
1371
1452
  if (requestAuth) {
1372
1453
  let value;
1373
1454
  try {
1374
- value = await requestAuth.token(ctx);
1455
+ value = (yield authInvoke(requestAuth, "token", [ctx]));
1375
1456
  }
1376
1457
  catch (error) {
1377
1458
  if (error instanceof ConnectorCallError)
@@ -1393,19 +1474,12 @@ export function remoteMcp(id, opts) {
1393
1474
  throw new CredentialRequiredError(`Connector "${id}" needs an operator-managed credential, but ` +
1394
1475
  "credential storage is not configured. Configure vault in deployment code and redeploy. Call authorize_connector for recovery options.");
1395
1476
  }
1396
- // A stored-shape mismatch already arrives as a typed auth_required from
1397
- // the registry's accessor; nothing to reclassify here.
1398
- const value = (await ctx.credential.get())?.trim();
1477
+ const value = (yield authInvoke(ctx.credential, "get", []))?.trim();
1399
1478
  if (!value) {
1400
1479
  throw new CredentialRequiredError(`Connector "${id}" has no stored credential — call ` +
1401
1480
  `authorize_connector({ connector: "${id}" }) and follow the ` +
1402
1481
  "operator handoff it returns.");
1403
1482
  }
1404
- // Refuse a value a header cannot carry BEFORE anything frames it. A
1405
- // wrapped newline in a pasted key is the ordinary way this happens, and
1406
- // the runtime that rejects the header quotes the whole value back in its
1407
- // TypeError — a message that travels to status, to the activity log, and
1408
- // to the agent. So the check lives here, and says only what is wrong.
1409
1483
  if (carriesIllegalHeaderChar(value)) {
1410
1484
  throw new CredentialRequiredError(`Connector "${id}"'s stored credential contains a character a header ` +
1411
1485
  "cannot carry (a line break or other control character). Call " +
@@ -1413,7 +1487,7 @@ export function remoteMcp(id, opts) {
1413
1487
  "it in this connection in the operator UI. The value is not shown or logged.");
1414
1488
  }
1415
1489
  return value;
1416
- };
1490
+ }));
1417
1491
  /**
1418
1492
  * Replace, never edit.
1419
1493
  *
@@ -1471,48 +1545,59 @@ export function remoteMcp(id, opts) {
1471
1545
  });
1472
1546
  };
1473
1547
  const listTools = client.listTools.bind(client);
1474
- client.listTools = (params, options) => cache.withListing(listingContext(options), async () => {
1475
- const before = toolPages;
1476
- let result;
1477
- try {
1478
- result = await listTools(params, options);
1479
- }
1480
- catch (error) {
1481
- const bounded = atMcpBoundary(cache.currentContext(), error, "tools/list", client.transport, [
1482
- cache.currentContext().signal,
1483
- ]);
1484
- const transient = (error instanceof SdkHttpError && [502, 503, 504].includes(error.status)) ||
1485
- (bounded instanceof ConnectorCallError && bounded.code === "unavailable");
1486
- const fallback = transient && toolPages === before ? await cache.fallbackTools() : undefined;
1487
- if (!fallback)
1488
- throw error;
1489
- return fallback;
1490
- }
1491
- if (toolPages !== before && params?.cursor === undefined) {
1492
- const refresh = await cache.completeCatalogRefresh(result);
1493
- if (refresh) {
1494
- try {
1495
- await observeCompletedCatalogRefresh(cache.currentContext(), refresh);
1496
- }
1497
- catch (error) {
1498
- logFailure(cache.currentContext().logger, "catalog refresh observation failed", failureRecord({ connector: id }, error));
1548
+ client.listTools = authMethod((params, options) => authInvoke(cache, "withListing", [
1549
+ listingContext(options),
1550
+ authMethod(() => authFlow(function* () {
1551
+ const before = toolPages;
1552
+ let result;
1553
+ try {
1554
+ result = (yield authCall(listTools, [params, options]));
1555
+ }
1556
+ catch (error) {
1557
+ const bounded = atMcpBoundary(cache.currentContext(), error, "tools/list", client.transport, [
1558
+ cache.currentContext().signal,
1559
+ ]);
1560
+ const transient = (error instanceof SdkHttpError && [502, 503, 504].includes(error.status)) ||
1561
+ (bounded instanceof ConnectorCallError && bounded.code === "unavailable");
1562
+ const fallback = transient && toolPages === before
1563
+ ? (yield authInvoke(cache, "fallbackTools", []))
1564
+ : undefined;
1565
+ if (!fallback)
1566
+ throw error;
1567
+ return fallback;
1568
+ }
1569
+ if (toolPages !== before && params?.cursor === undefined) {
1570
+ const refresh = (yield authInvoke(cache, "completeCatalogRefresh", [result]));
1571
+ if (refresh) {
1572
+ try {
1573
+ (yield authCall(observeCompletedCatalogRefresh, [cache.currentContext(), refresh]));
1574
+ }
1575
+ catch (error) {
1576
+ logFailure(cache.currentContext().logger, "catalog refresh observation failed", failureRecord({ connector: id }, error));
1577
+ }
1499
1578
  }
1500
1579
  }
1501
- }
1502
- return result;
1503
- });
1580
+ return result;
1581
+ })),
1582
+ ]));
1504
1583
  const listResources = client.listResources.bind(client);
1505
- client.listResources = (params, options) => cache.withListing(listingContext(options), () => listResources(params, options));
1584
+ client.listResources = authMethod((params, options) => authInvoke(cache, "withListing", [
1585
+ listingContext(options),
1586
+ authMethod(() => authCall(listResources, [params, options])),
1587
+ ]));
1506
1588
  const listResourceTemplates = client.listResourceTemplates.bind(client);
1507
- client.listResourceTemplates = (params, options) => cache.withListing(listingContext(options), () => listResourceTemplates(params, options));
1589
+ client.listResourceTemplates = authMethod((params, options) => authInvoke(cache, "withListing", [
1590
+ listingContext(options),
1591
+ authMethod(() => authCall(listResourceTemplates, [params, options])),
1592
+ ]));
1508
1593
  const request = client.request.bind(client);
1509
1594
  const walks = new WeakMap();
1510
- client.request = (async (...args) => {
1595
+ client.request = authMethod((...args) => authFlow(function* () {
1511
1596
  const message = args[0];
1512
1597
  if (message.method !== "tools/list" &&
1513
1598
  message.method !== "resources/list" &&
1514
1599
  message.method !== "resources/templates/list")
1515
- return Reflect.apply(request, client, args);
1600
+ return yield authCall(request, args, client);
1516
1601
  const method = message.method;
1517
1602
  const ctx = cache.currentContext();
1518
1603
  const options = (args.length === 3 ? args[2] : args[1]);
@@ -1528,11 +1613,15 @@ export function remoteMcp(id, opts) {
1528
1613
  }
1529
1614
  let page;
1530
1615
  try {
1531
- page = await request(message, method === "tools/list"
1532
- ? CompatibleListToolsResultSchema
1533
- : method === "resources/list"
1534
- ? specTypeSchemas.ListResourcesResult
1535
- : specTypeSchemas.ListResourceTemplatesResult, options);
1616
+ page = (yield authCall(request, [
1617
+ message,
1618
+ method === "tools/list"
1619
+ ? CompatibleListToolsResultSchema
1620
+ : method === "resources/list"
1621
+ ? specTypeSchemas.ListResourcesResult
1622
+ : specTypeSchemas.ListResourceTemplatesResult,
1623
+ options,
1624
+ ]));
1536
1625
  }
1537
1626
  catch (error) {
1538
1627
  if (isCursorShapeError(error))
@@ -1578,19 +1667,18 @@ export function remoteMcp(id, opts) {
1578
1667
  if (!walk.first)
1579
1668
  walk.first = result;
1580
1669
  else {
1581
- // SDK aggregation retains page-one metadata. A later page may only
1582
- // narrow its reuse grant, never broaden the complete catalog's scope.
1583
1670
  walk.first.ttlMs = Math.min(walk.first.ttlMs, clean.ttlMs);
1584
1671
  if (clean.cacheScope !== "public")
1585
1672
  walk.first.cacheScope = "private";
1586
1673
  }
1587
1674
  observeCatalogExpiry(ctx, method, walk.first.ttlMs);
1588
1675
  return result;
1589
- });
1676
+ }));
1590
1677
  };
1591
1678
  /** This request scope's entry, open or closed, created on first sight. */
1592
1679
  const entryFor = (ctx) => {
1593
- const key = ctx.requestScope ?? ctx;
1680
+ const owner = Context.get(viewServices(ctx), RequestScope);
1681
+ const key = owner;
1594
1682
  let state = states.get(key);
1595
1683
  if (!state) {
1596
1684
  state = {
@@ -1599,6 +1687,7 @@ export function remoteMcp(id, opts) {
1599
1687
  client: null,
1600
1688
  transport: null,
1601
1689
  toolDefinitions: new Map(),
1690
+ dispatches: new Map(),
1602
1691
  connecting: null,
1603
1692
  authRequired: false,
1604
1693
  provider: null,
@@ -1607,6 +1696,13 @@ export function remoteMcp(id, opts) {
1607
1696
  credentialDigest: null,
1608
1697
  };
1609
1698
  states.set(key, state);
1699
+ const owned = state;
1700
+ owner.own(() => {
1701
+ const closing = Scope.closeUnsafe(owned.scope, Exit.void);
1702
+ reset(owned);
1703
+ if (closing)
1704
+ detach(closing);
1705
+ });
1610
1706
  }
1611
1707
  return state;
1612
1708
  };
@@ -1617,7 +1713,7 @@ export function remoteMcp(id, opts) {
1617
1713
  throw scopeEndedError();
1618
1714
  return state;
1619
1715
  };
1620
- const newProvider = (ctx, state, signal = ctx.signal) => {
1716
+ const newProvider = (ctx, state, signal = viewSignal(ctx, true)) => {
1621
1717
  if (state?.provider)
1622
1718
  return state.provider;
1623
1719
  const metadataUrl = staticClient ? undefined : (clientMetadataUrl ?? selfHostedClientUrl(ctx.publicUrl, id));
@@ -1650,76 +1746,250 @@ export function remoteMcp(id, opts) {
1650
1746
  * by the caller so the raw secret is not passed around twice. Never
1651
1747
  * retained past the transport it configures.
1652
1748
  */
1653
- credentialFramed = null, signal = ctx.signal,
1749
+ credentialFramed = null, signal = viewSignal(ctx, true),
1654
1750
  /** The raw value behind `credentialFramed`, under the same terms. */
1655
- credentialValue = null) => {
1751
+ credentialValue = null,
1752
+ /** A code exchange never transfers ownership to the refresh coordinator. */
1753
+ exchangeOnly = false) => {
1754
+ const resourceInit = (input, init) => {
1755
+ if (!extraHeaders)
1756
+ return init;
1757
+ const target = new URL(input instanceof Request ? input.url : String(input));
1758
+ const resource = new URL(opts.url);
1759
+ if (target.origin !== resource.origin || target.pathname !== resource.pathname)
1760
+ return init;
1761
+ const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined));
1762
+ for (const [name, value] of Object.entries(extraHeaders))
1763
+ headers.set(name, value);
1764
+ return { ...init, headers };
1765
+ };
1766
+ const initiatingSignal = ctx.signal;
1767
+ ctx = deriveContext(operationContext(ctx), signal ? { signal } : {});
1656
1768
  if (opts._transportFactory)
1657
1769
  return opts._transportFactory(ctx);
1658
1770
  const url = new URL(opts.url);
1659
- const trackedFetch = async (input, init) => {
1660
- trackSentRequest(ctx, input, init);
1771
+ // Missing records never acquire a fresh write attempt. Only active calls
1772
+ // for this exact tool, all classified read, can prove a recordless message
1773
+ // safe. Catalog hints alone cannot prove the invocation's served verdict.
1774
+ const untrackedRead = (params) => {
1775
+ if (!skillObject(params) || typeof params.name !== "string")
1776
+ return false;
1777
+ let read = false;
1778
+ for (const [dispatch, name] of entryFor(ctx).dispatches) {
1779
+ if (name !== params.name)
1780
+ continue;
1781
+ if (dispatch.write)
1782
+ return false;
1783
+ read = true;
1784
+ }
1785
+ return read;
1786
+ };
1787
+ // This transport's calls on the wire, by JSON-RPC id, while each is sent.
1788
+ const flights = new Map();
1789
+ const trackedFetch = authMethod((input, init) => authFlow(function* () {
1661
1790
  let rpc;
1662
1791
  if (typeof init?.body === "string") {
1663
1792
  try {
1664
1793
  rpc = JSON.parse(init.body);
1665
1794
  }
1666
- catch {
1667
- /* OAuth exchanges can send form-encoded bodies. */
1795
+ catch { }
1796
+ }
1797
+ const toolCall = init?.method === "POST" && skillObject(rpc) && rpc.method === "tools/call" ? rpc : undefined;
1798
+ const writeContinuation = downstreamWriteContinuation(ctx.requestScope ?? ctx) && toolCall !== undefined;
1799
+ const flight = toolCall && (typeof toolCall.id === "string" || typeof toolCall.id === "number")
1800
+ ? flights.get(toolCall.id)
1801
+ : undefined;
1802
+ if (toolCall && !flight && !untrackedRead(toolCall.params))
1803
+ throw missingDispatch();
1804
+ const credentialPost = isOauth && (init?.method ?? "GET").toUpperCase() === "POST" && (!skillObject(rpc) || rpc.jsonrpc !== "2.0");
1805
+ const sender = flight?.write ? guardedTransport(true) : undefined;
1806
+ if (flight?.write) {
1807
+ if (flight.sent) {
1808
+ if (flight.request && flight.authorization)
1809
+ renewedAfterUnauthorized(flight.request.entry, flight.authorization, new Headers(init?.headers).get("authorization") ?? flight.authorization);
1810
+ throw resendRefused(flight);
1668
1811
  }
1812
+ flight.authorization = new Headers(init?.headers).get("authorization");
1813
+ }
1814
+ const request = flight?.request;
1815
+ if (request?.entry.dispatched)
1816
+ request.entry = providerRequest(request.ctx, !flight?.write, request.entry.provider, {
1817
+ method: init?.method ?? "GET",
1818
+ });
1819
+ const entry = request?.entry;
1820
+ const dispatch = () => {
1821
+ if (flight?.write) {
1822
+ if (flight.sent)
1823
+ throw resendRefused(flight);
1824
+ flight.sent = true;
1825
+ }
1826
+ if (request && entry) {
1827
+ if (flight?.sourceWrite)
1828
+ recordRequestField(entry, "sourceWrite", true);
1829
+ dispatchProviderRequest(request.ctx, entry, init?.headers, init?.method ?? "GET");
1830
+ }
1831
+ };
1832
+ trackSentRequest(ctx, input, init);
1833
+ // Attach retained settlement facts at the physical Promise leaf.
1834
+ // Cancellation may end the native waiter before it resumes; neither
1835
+ // the status nor a 401 verdict may disappear with that live authority.
1836
+ const response = (yield authValue(() => {
1837
+ const pending = credentialPost
1838
+ ? sendCredentialPost(input, init ?? {}, dispatch)
1839
+ : sender
1840
+ ? sendGuarded(sender, new URL(input instanceof Request ? input.url : String(input)), { ...init, redirect: "manual" }, dispatch)
1841
+ : (() => {
1842
+ dispatch();
1843
+ return writeContinuation ? fetch(input, { ...init, redirect: "manual" }) : fetch(input, init);
1844
+ })();
1845
+ return pending.then((response) => {
1846
+ if (entry)
1847
+ recordProviderResponse(entry, response.status);
1848
+ if (flight?.write) {
1849
+ if (response.status === 401)
1850
+ flight.unauthorized = true;
1851
+ else if (REDIRECT_STATUSES.has(response.status))
1852
+ flight.redirected = true;
1853
+ }
1854
+ if (init?.signal?.aborted || request?.ctx.signal?.aborted)
1855
+ void response.body?.cancel().catch(() => { });
1856
+ return response;
1857
+ });
1858
+ }));
1859
+ if (credentialPost && !isOAuthCredentialRequest(init) && REDIRECT_STATUSES.has(response.status)) {
1860
+ (yield authCatch(authValue(() => response.body?.cancel()), () => { }));
1861
+ throw new ConnectorCallError("connector_call_failed", "OAuth registration endpoint redirected; the request was not repeated.", { retryable: false });
1669
1862
  }
1670
- const writeContinuation = downstreamWriteContinuation(ctx.requestScope ?? ctx) &&
1671
- init?.method === "POST" &&
1672
- skillObject(rpc) &&
1673
- rpc.method === "tools/call";
1674
- const response = writeContinuation
1675
- ? await fetch(input, { ...init, redirect: "manual" })
1676
- : await fetch(input, init);
1677
1863
  if (writeContinuation && (response.status === 401 || (response.status >= 300 && response.status < 400))) {
1678
- await response.body?.cancel().catch(() => { });
1679
- // Stop before the SDK can refresh auth or follow a redirect and resend
1680
- // this write. The host's one-use continuation nonce remains spent.
1864
+ (yield authCatch(authValue(() => response.body?.cancel()), () => { }));
1681
1865
  if (response.status === 401)
1682
1866
  throw authRequiredError();
1683
- throw new ConnectorCallError("connector_call_failed", "The downstream redirected a write continuation; it was not repeated.", { retryable: false });
1867
+ throw new ConnectorCallError("connector_call_failed", "The downstream redirected a write continuation; it was not repeated.", { retryable: false, writeOutcome: "unknown" });
1868
+ }
1869
+ if (flight?.redirected) {
1870
+ (yield authCatch(authValue(() => response.body?.cancel()), () => { }));
1871
+ throw new ConnectorCallError("connector_call_failed", "The downstream answered this write with a redirect; it was not sent again.", { retryable: false, writeOutcome: "unknown" });
1684
1872
  }
1685
1873
  if (skillsEnabled && skillObject(rpc) && (rpc.method === "skills/list" || rpc.method === "resources/read")) {
1686
- return boundedSkillResponse(response, rpc.method === "skills/list" ? MAX_SERIALIZED_CATALOG_BYTES : MAX_SKILL_READ_RPC_BYTES, rpc.id, init?.signal);
1874
+ return (yield authCall(boundedSkillResponse, [
1875
+ response,
1876
+ rpc.method === "skills/list" ? MAX_SERIALIZED_CATALOG_BYTES : MAX_SKILL_READ_RPC_BYTES,
1877
+ rpc.id,
1878
+ init?.signal,
1879
+ ]));
1687
1880
  }
1688
1881
  return response;
1689
- };
1882
+ }));
1690
1883
  // A runtime refusing the assembled header quotes it, and the transport
1691
1884
  // error below keeps none of what the runtime said. So whether the
1692
1885
  // rejection quoted the credential is decided here, first, and survives
1693
1886
  // as the class of the error rather than as its text.
1694
1887
  const guardedFetch = redirectSafeFetch(id, opts.redirects, credentialFramed === null
1695
1888
  ? trackedFetch
1696
- : async (input, init) => {
1889
+ : authMethod((input, init) => authFlow(function* () {
1697
1890
  try {
1698
- return await trackedFetch(input, init);
1891
+ return (yield authCall(trackedFetch, [input, init]));
1699
1892
  }
1700
1893
  catch (err) {
1701
1894
  throw withoutCredential(err, credentialValue, credentialFramed);
1702
1895
  }
1703
- });
1896
+ })));
1704
1897
  if (opts.auth?.type === "oauth") {
1705
1898
  const oauthProvider = provider ?? newProvider(ctx, undefined, signal);
1899
+ // The session/provider belong to the request. A renewal belongs to the
1900
+ // exact call whose HTTP response challenged it, until claim submission.
1901
+ // Carry that ownership on the response the SDK passes to onUnauthorized;
1902
+ // a shared mutable "current call" would borrow a concurrent call's lease.
1903
+ const renewalSignals = new WeakMap();
1904
+ const exchangeFetch = (send) => exchangeOnly
1905
+ ? authMethod((input, init) => authView(ctx, authMethod((requestSignal) => authFlow(function* () {
1906
+ const response = (yield authCall(send, [
1907
+ input,
1908
+ {
1909
+ ...init,
1910
+ signal: init?.signal ? AbortSignal.any([requestSignal, init.signal]) : requestSignal,
1911
+ },
1912
+ ]));
1913
+ if (requestSignal.aborted) {
1914
+ void response.body?.cancel().catch(() => { });
1915
+ throw requestSignal.reason;
1916
+ }
1917
+ return codeExchangeResponse(response, requestSignal);
1918
+ })), true))
1919
+ : send;
1920
+ const coordinatedFetch = refreshCoordinatorFor(ctx).coordinatedFetch(oauthProvider, exchangeFetch(learnedUrlSafeFetch(id, url, trackedFetch)), signal, ctx.defer, exchangeFetch(learnedUrlSafeFetch(id, url, authMethod((input, init) => authCall(guardedFetch, [input, resourceInit(input, init)])))), () => {
1921
+ guardedTransport(true);
1922
+ });
1706
1923
  const transport = new StreamableHTTPClientTransport(url, {
1707
1924
  authProvider: oauthProvider,
1708
1925
  onInsufficientScope: "throw",
1709
- fetch: refreshCoordinatorFor(ctx).coordinatedFetch(oauthProvider, learnedUrlSafeFetch(id, url, trackedFetch), signal, ctx.defer, learnedUrlSafeFetch(id, url, guardedFetch)),
1926
+ fetch: authMethod((input, init) => authFlow(function* () {
1927
+ const response = (yield authCall(coordinatedFetch, [input, init]));
1928
+ if (response.status === 401) {
1929
+ let owner;
1930
+ if (typeof init?.body === "string") {
1931
+ try {
1932
+ const rpc = JSON.parse(init.body);
1933
+ if (skillObject(rpc)) {
1934
+ if (rpc.method === "initialize" || rpc.method === "server/discover")
1935
+ owner = initiatingSignal;
1936
+ else if (typeof rpc.id === "string" || typeof rpc.id === "number")
1937
+ owner = flights.get(rpc.id)?.request?.ctx.signal;
1938
+ }
1939
+ }
1940
+ catch { }
1941
+ }
1942
+ const ownedSignal = owner && init?.signal ? AbortSignal.any([owner, init.signal]) : (owner ?? init?.signal);
1943
+ if (ownedSignal)
1944
+ renewalSignals.set(response, ownedSignal);
1945
+ }
1946
+ return response;
1947
+ })),
1710
1948
  });
1711
- boundOAuthFlows(ctx, transport, url, [signal, ctx.signal]);
1712
- return transport;
1949
+ boundOAuthFlows(ctx, transport, url, [signal, ctx.signal], (response) => renewalSignals.get(response));
1950
+ return sendingEachWriteOnce(transport, flights, untrackedRead);
1713
1951
  }
1714
1952
  const headers = opts.auth?.type === "headers"
1715
1953
  ? opts.auth.headers
1716
1954
  : (credentialAuth || requestAuth) && credentialFramed !== null
1717
1955
  ? { ...requestAuth?.headers, [credentialHeader]: credentialFramed }
1718
1956
  : undefined;
1719
- return new StreamableHTTPClientTransport(url, {
1957
+ return sendingEachWriteOnce(new StreamableHTTPClientTransport(url, {
1720
1958
  ...(headers ? { requestInit: { headers } } : {}),
1721
- fetch: guardedFetch,
1722
- });
1959
+ fetch: authMethod((input, init) => authCall(guardedFetch, [input, resourceInit(input, init)])),
1960
+ }), flights, untrackedRead);
1961
+ };
1962
+ /**
1963
+ * Admit a write's call to the wire once, whatever JSON-RPC id a later
1964
+ * message for it carries, and show `trackedFetch` which call each message
1965
+ * belongs to while it is sent. A missing record fails closed unless active
1966
+ * calls prove the tool is read (INV-1, INV-9).
1967
+ */
1968
+ const sendingEachWriteOnce = (transport, flights, untrackedRead) => {
1969
+ const send = transport.send.bind(transport);
1970
+ transport.send = authMethod((message, options) => authFlow(function* () {
1971
+ const request = !Array.isArray(message) && "method" in message && message.method === "tools/call" && "id" in message
1972
+ ? message
1973
+ : undefined;
1974
+ if (!request)
1975
+ return (yield authCall(send, [message, options]));
1976
+ const dispatch = dispatchOf(request.params);
1977
+ if (!dispatch) {
1978
+ if (!untrackedRead(request.params))
1979
+ throw missingDispatch();
1980
+ return (yield authCall(send, [message, options]));
1981
+ }
1982
+ if (dispatch.write && dispatch.sent)
1983
+ throw resendRefused(dispatch);
1984
+ flights.set(request.id, dispatch);
1985
+ try {
1986
+ return (yield authCall(send, [message, options]));
1987
+ }
1988
+ finally {
1989
+ flights.delete(request.id);
1990
+ }
1991
+ }));
1992
+ return transport;
1723
1993
  };
1724
1994
  const reset = (state) => {
1725
1995
  state.lease = null;
@@ -1761,101 +2031,85 @@ export function remoteMcp(id, opts) {
1761
2031
  if (release)
1762
2032
  detach(release);
1763
2033
  };
1764
- const ensureConnected = async (ctx, state) => {
1765
- // A 401 after connect is a verdict for the whole request scope, not merely
1766
- // for the one call that observed it. Do not let the still-cached client make
1767
- // a later status or call in the same scope report healthy.
1768
- if (state.authRequired) {
1769
- throw authRequiredError();
1770
- }
1771
- // Read the OAuth epoch before trusting either a cached client or starting a
1772
- // transport. A disconnected epoch is a durable operator instruction, not
1773
- // merely the absence of credentials: passive status/tool probes must not
1774
- // turn it back into a pending consent flow.
1775
- let oauthGeneration;
1776
- if (isOauth && (state.client || state.connecting)) {
1777
- const provider = newProvider(ctx, state);
1778
- oauthGeneration = await provider.liveEpoch();
1779
- if (provider.isOperatorDisconnectedEpoch(oauthGeneration)) {
1780
- closeHalf(state);
1781
- throw operatorDisconnectedError();
2034
+ const waitForConnection = (attempt, signal) => runEdge(Deferred.await(attempt), signal ? { signal } : {});
2035
+ const ensureConnected = authMethod((ctx, state) => authFlow(function* () {
2036
+ let joined = state.connecting;
2037
+ if (joined)
2038
+ connectingWaiters.set(joined, (connectingWaiters.get(joined) ?? 0) + 1);
2039
+ const leave = () => {
2040
+ if (!joined)
2041
+ return;
2042
+ const remaining = (connectingWaiters.get(joined) ?? 1) - 1;
2043
+ if (remaining > 0)
2044
+ connectingWaiters.set(joined, remaining);
2045
+ else {
2046
+ connectingWaiters.delete(joined);
2047
+ if (ctx.signal?.aborted && state.connecting === joined)
2048
+ closeHalf(state);
1782
2049
  }
1783
- }
1784
- // Cross-isolate force re-auth: another isolate replaced the grant's epoch,
1785
- // and the grant with it. This request's cached client still speaks the old
1786
- // token — drop it so the next connect runs against current state.
1787
- if (state.client && oauthGeneration !== undefined && state.connectedGeneration !== null) {
1788
- if (isClosed(state))
1789
- throw scopeEndedError();
1790
- if (oauthGeneration !== state.connectedGeneration) {
1791
- closeHalf(state);
2050
+ };
2051
+ try {
2052
+ if (state.authRequired) {
2053
+ throw authRequiredError();
1792
2054
  }
1793
- }
1794
- // The static-credential counterpart of the epoch read above, and
1795
- // deliberately beside it: the vault is read before any cached client is
1796
- // trusted, so an operator's rotation in the UI takes effect on the next
1797
- // call rather than the next deploy. Compared by digest — the
1798
- // plaintext lives in this function's scope and never reaches `state`.
1799
- let credentialValue = null;
1800
- let credentialFramed = null;
1801
- let credentialDigest = null;
1802
- if (credentialAuth || requestAuth) {
1803
- credentialValue = await readCredential(ctx);
1804
- credentialFramed = credentialHeaderValue(credentialScheme, credentialValue);
1805
- credentialDigest = await digestOf(credentialValue);
1806
- // Gated on a connect in flight as well as a cached client, exactly like
1807
- // the epoch read above: a rotation that lands while the first caller is
1808
- // still connecting must not let the second one ride the key the vault
1809
- // has already replaced.
1810
- if ((state.client || state.connecting) &&
1811
- state.credentialDigest !== null &&
1812
- state.credentialDigest !== credentialDigest) {
1813
- // A slow vault/token read or digest can resume after a newer caller
1814
- // connected with the replacement. Never let that stale read tear down
1815
- // the newer connection and install the rotated-away credential.
1816
- const observedConnection = state.client ?? state.connecting;
1817
- const currentCredential = await readCredential(ctx);
2055
+ let oauthGeneration;
2056
+ if (isOauth && (state.client || state.connecting)) {
2057
+ const provider = newProvider(ctx, state, openRequest(ctx.requestScope ?? ctx).signal);
2058
+ oauthGeneration = (yield authView(ctx, authMethod(() => authInvoke(provider, "liveEpoch", []))));
2059
+ if (provider.isOperatorDisconnectedEpoch(oauthGeneration)) {
2060
+ closeHalf(state);
2061
+ throw operatorDisconnectedError();
2062
+ }
2063
+ }
2064
+ if (state.client && oauthGeneration !== undefined && state.connectedGeneration !== null) {
1818
2065
  if (isClosed(state))
1819
2066
  throw scopeEndedError();
1820
- if (currentCredential !== credentialValue || (state.client ?? state.connecting) !== observedConnection) {
1821
- throw new ConnectorCallError("connector_call_failed", "The downstream credential changed while connecting; retry the operation.", { retryable: true });
2067
+ if (oauthGeneration !== state.connectedGeneration) {
2068
+ closeHalf(state);
1822
2069
  }
1823
- closeHalf(state);
1824
2070
  }
1825
- }
1826
- if (isClosed(state))
1827
- throw scopeEndedError();
1828
- if (state.client)
1829
- return state.client;
1830
- const attempt = state.connecting ?? startConnect(ctx, state, credentialValue, credentialFramed, credentialDigest);
1831
- // A Promise edge of its own, as architecture.md's "Effect inside" requires
1832
- // of every shared wait: whatever this caller does next with the client is
1833
- // its own I/O, so it resumes in its own continuation rather than inside the
1834
- // call that completed the attempt.
1835
- connectingWaiters.set(attempt, (connectingWaiters.get(attempt) ?? 0) + 1);
1836
- let client;
1837
- try {
1838
- client = await runEdge(Deferred.await(attempt), { signal: ctx.signal });
1839
- }
1840
- finally {
1841
- const remaining = (connectingWaiters.get(attempt) ?? 1) - 1;
1842
- if (remaining > 0)
1843
- connectingWaiters.set(attempt, remaining);
1844
- else {
1845
- connectingWaiters.delete(attempt);
1846
- // A cancelled waiter leaves its siblings' handshake alone. Once every
1847
- // waiter has left, abandon the attempt and its OAuth refresh too.
1848
- if (ctx.signal?.aborted && state.connecting === attempt)
2071
+ let credentialValue = null;
2072
+ let credentialFramed = null;
2073
+ let credentialDigest = null;
2074
+ if (credentialAuth || requestAuth) {
2075
+ credentialValue = (yield authCall(readCredential, [ctx]));
2076
+ credentialFramed = credentialHeaderValue(credentialScheme, credentialValue);
2077
+ credentialDigest = (yield authCall(digestOf, [credentialValue]));
2078
+ if ((state.client || state.connecting) &&
2079
+ state.credentialDigest !== null &&
2080
+ state.credentialDigest !== credentialDigest) {
2081
+ const observedConnection = state.client ?? state.connecting;
2082
+ const currentCredential = (yield authCall(readCredential, [ctx]));
2083
+ if (isClosed(state))
2084
+ throw scopeEndedError();
2085
+ if (currentCredential !== credentialValue || (state.client ?? state.connecting) !== observedConnection) {
2086
+ throw resendAdvice(new ConnectorCallError("connector_call_failed", "The downstream credential changed while connecting.", {
2087
+ retryable: true,
2088
+ }), "; retry the operation");
2089
+ }
1849
2090
  closeHalf(state);
2091
+ }
1850
2092
  }
2093
+ viewSignal(ctx).throwIfAborted();
2094
+ if (isClosed(state))
2095
+ throw scopeEndedError();
2096
+ if (state.client)
2097
+ return state.client;
2098
+ const attempt = state.connecting ?? startConnect(ctx, state, credentialValue, credentialFramed, credentialDigest);
2099
+ if (joined !== attempt) {
2100
+ leave();
2101
+ joined = attempt;
2102
+ connectingWaiters.set(attempt, (connectingWaiters.get(attempt) ?? 0) + 1);
2103
+ }
2104
+ const client = (yield authCall(waitForConnection, [attempt, ctx.signal]));
2105
+ if (isClosed(state))
2106
+ throw scopeEndedError();
2107
+ return client;
1851
2108
  }
1852
- // Teardown can land between the attempt completing and this caller
1853
- // resuming. The client it would hand back is being closed, and this scope
1854
- // is over.
1855
- if (isClosed(state))
1856
- throw scopeEndedError();
1857
- return client;
1858
- };
2109
+ finally {
2110
+ leave();
2111
+ }
2112
+ }));
1859
2113
  /**
1860
2114
  * Start this scope's connect attempt, publishing it before any of it runs.
1861
2115
  * The attempt completes its Deferred itself, and every caller — the one that
@@ -1869,7 +2123,10 @@ export function remoteMcp(id, opts) {
1869
2123
  // while this connect is in flight has to be visible to the next caller,
1870
2124
  // which would otherwise wait on a client bound to the older key.
1871
2125
  state.credentialDigest = credentialDigest;
1872
- detach(connect(ctx, state, attempt, credentialValue, credentialFramed).pipe(Effect.onExit((exit) => Effect.sync(() => {
2126
+ detach(connect(ctx, state, attempt, credentialValue, credentialFramed).pipe(
2127
+ // The connection belongs to this request, without the first waiter's
2128
+ // call/credential/budget authority. Refresh coordinators stay separate.
2129
+ Effect.provideService(RequestScope, Context.get(viewServices(ctx), RequestScope)), Effect.onExit((exit) => Effect.sync(() => {
1873
2130
  // Force reset may have abandoned this attempt and installed a new
1874
2131
  // one in the same request scope. An old completion must not erase
1875
2132
  // the new attempt and allow a third concurrent connect.
@@ -1888,8 +2145,8 @@ export function remoteMcp(id, opts) {
1888
2145
  // The transport and its OAuth provider are shared by every call in this
1889
2146
  // scope. A call's deadline signal is aborted even on success, so retaining
1890
2147
  // the first caller's signal here would abort its siblings and later calls.
1891
- // Keep OAuth work cancellable by the connection's own lifetime instead;
1892
- // requestOptions still passes each call's signal to the SDK separately.
2148
+ // The provider uses the connection's lifetime. Each challenged response
2149
+ // separately binds renewal preparation to the call that initiated it.
1893
2150
  const connectionAbort = new AbortController();
1894
2151
  // Own cancellation before beginFlow's storage reads.
1895
2152
  // The lease first owns just the signal, then the transport it creates.
@@ -1917,7 +2174,9 @@ export function remoteMcp(id, opts) {
1917
2174
  // step-up — happens inside the SDK, bound to the epoch live now: a
1918
2175
  // reset meanwhile fails the attempt rather than handing it the next
1919
2176
  // epoch's grant.
1920
- const genAtStart = provider ? yield* promised(() => provider.beginFlow()) : "";
2177
+ const genAtStart = provider
2178
+ ? yield* promised(authMethod(() => authInvoke(provider, "beginFlow", [])))
2179
+ : "";
1921
2180
  if (!owned())
1922
2181
  return yield* Effect.fail(scopeEndedError());
1923
2182
  if (provider)
@@ -1938,39 +2197,57 @@ export function remoteMcp(id, opts) {
1938
2197
  clientName: ctx.oauthClientName,
1939
2198
  }
1940
2199
  : undefined;
1941
- const negotiationDigest = yield* promised(() => digestOf(JSON.stringify([
1942
- opts.url,
1943
- opts.versionNegotiation ?? "auto",
1944
- opts.auth?.type,
1945
- opts.auth?.type === "headers" ? opts.auth.headers : requestAuth?.headers,
1946
- credentialHeader,
1947
- credentialScheme,
1948
- opts.authScope ?? "shared",
1949
- opts.redirects ?? "none",
1950
- oauthConfig,
1951
- oauthScope,
1952
- state.credentialDigest,
1953
- genAtStart,
1954
- callerOf(ctx),
1955
- skillsEnabled === true,
2200
+ const negotiationDigest = yield* promised(authMethod(() => authCall(digestOf, [
2201
+ JSON.stringify([
2202
+ opts.url,
2203
+ opts.versionNegotiation ?? "auto",
2204
+ opts.auth?.type,
2205
+ opts.auth?.type === "headers" ? opts.auth.headers : requestAuth?.headers,
2206
+ ...(extraHeaders ? [extraHeaders] : []),
2207
+ credentialHeader,
2208
+ credentialScheme,
2209
+ opts.authScope ?? "shared",
2210
+ opts.redirects ?? "none",
2211
+ oauthConfig,
2212
+ oauthScope,
2213
+ state.credentialDigest,
2214
+ genAtStart,
2215
+ callerOf(ctx),
2216
+ skillsEnabled === true,
2217
+ ]),
1956
2218
  ])));
1957
2219
  const prior = opts.versionNegotiation === "legacy"
1958
2220
  ? undefined
1959
- : yield* promised(() => readNegotiation(ctx, negotiationDigest));
2221
+ : yield* promised(authMethod(() => authCall(readNegotiation, [ctx, negotiationDigest])));
1960
2222
  if (!owned())
1961
2223
  return yield* Effect.fail(scopeEndedError());
1962
- const cacheOptions = yield* promised(() => catalogClientOptions(ctx, id, JSON.stringify({
1963
- url: new URL(opts.url).href,
1964
- auth: opts.auth?.type ?? null,
1965
- headers: opts.auth?.type === "headers" ? opts.auth.headers : requestAuth?.headers,
1966
- credentialHeader,
1967
- credentialScheme,
1968
- authScope: opts.authScope ?? "shared",
1969
- redirects: opts.redirects ?? "none",
1970
- versionNegotiation: opts.versionNegotiation ?? "auto",
1971
- oauthConfig,
1972
- oauthScope,
1973
- }), JSON.stringify([genAtStart, state.credentialDigest]), connectionAbort.signal, cacheOwner, requestAuth || opts.authScope === "personal" ? "private" : "shared", provider ? async () => digestOf(JSON.stringify(await provider.tokens())) : undefined));
2224
+ const cacheOptions = yield* promised(authMethod(() => authCall(catalogClientOptions, [
2225
+ ctx,
2226
+ id,
2227
+ JSON.stringify({
2228
+ url: new URL(opts.url).href,
2229
+ auth: opts.auth?.type ?? null,
2230
+ headers: opts.auth?.type === "headers" ? opts.auth.headers : requestAuth?.headers,
2231
+ ...(extraHeaders ? { extraHeaders } : {}),
2232
+ credentialHeader,
2233
+ credentialScheme,
2234
+ authScope: opts.authScope ?? "shared",
2235
+ redirects: opts.redirects ?? "none",
2236
+ versionNegotiation: opts.versionNegotiation ?? "auto",
2237
+ oauthConfig,
2238
+ oauthScope,
2239
+ }),
2240
+ JSON.stringify([genAtStart, state.credentialDigest]),
2241
+ connectionAbort.signal,
2242
+ cacheOwner,
2243
+ requestAuth || opts.authScope === "personal" ? "private" : "shared",
2244
+ provider
2245
+ ? authMethod(() => authFlow(function* () {
2246
+ const tokens = yield authInvoke(provider, "tokens", []);
2247
+ return yield authCall(digestOf, [JSON.stringify(tokens)]);
2248
+ }))
2249
+ : undefined,
2250
+ ])));
1974
2251
  if (!owned())
1975
2252
  return yield* Effect.fail(scopeEndedError());
1976
2253
  const makeClient = () => {
@@ -2000,14 +2277,14 @@ export function remoteMcp(id, opts) {
2000
2277
  state.transport = t;
2001
2278
  held.transport = t;
2002
2279
  recordWireErrors(t, () => observeCatalogChange(ctx, id, connectionAbort.signal, cacheOwner));
2003
- yield* promised(async () => {
2280
+ yield* promised(authMethod(() => authFlow(function* () {
2004
2281
  try {
2005
- await c.connect(t, { signal: handshakeAbort.signal, ...(prior ? { prior } : {}) });
2282
+ (yield authInvoke(c, "connect", [
2283
+ t,
2284
+ { signal: handshakeAbort.signal, ...(prior ? { prior } : {}) },
2285
+ ]));
2006
2286
  }
2007
2287
  catch (error) {
2008
- // Some legacy servers crash on an unknown pre-initialize method.
2009
- // Only an auto probe's typed HTTP 5xx permits a fresh legacy
2010
- // handshake. An auth failure, timeout or initialize failure does not.
2011
2288
  if (prior ||
2012
2289
  opts.versionNegotiation === "legacy" ||
2013
2290
  !(error instanceof SdkHttpError) ||
@@ -2017,15 +2294,16 @@ export function remoteMcp(id, opts) {
2017
2294
  !owned() ||
2018
2295
  handshakeAbort.signal.aborted)
2019
2296
  throw error;
2020
- // The SDK closed the failed probe transport. Its replacement owns
2021
- // a new connection, with the same credential and OAuth generation.
2022
2297
  c = makeClient();
2023
2298
  t = buildTransport(ctx, provider, credentialFramed, connectionAbort.signal, credentialValue);
2024
2299
  state.transport = held.transport = t;
2025
2300
  recordWireErrors(t, () => observeCatalogChange(ctx, id, connectionAbort.signal, cacheOwner));
2026
- await c.connect(t, { signal: handshakeAbort.signal, prior: { kind: "legacy" } });
2301
+ (yield authInvoke(c, "connect", [
2302
+ t,
2303
+ { signal: handshakeAbort.signal, prior: { kind: "legacy" } },
2304
+ ]));
2027
2305
  }
2028
- }).pipe(Effect.mapError((err) => atMcpBoundary(ctx,
2306
+ }))).pipe(Effect.mapError((err) => atMcpBoundary(ctx,
2029
2307
  // First, while the runtime's text can still be read for it.
2030
2308
  withoutCredential(err, credentialValue, credentialFramed), "MCP handshake", t, [ctx.signal, connectionAbort.signal, handshakeAbort.signal])));
2031
2309
  held.client = c;
@@ -2039,7 +2317,7 @@ export function remoteMcp(id, opts) {
2039
2317
  // credentials this client just bound to. Discard it rather than cache
2040
2318
  // a stale-isolate connection.
2041
2319
  if (provider) {
2042
- const generation = yield* promised(() => provider.liveEpoch());
2320
+ const generation = yield* promised(authMethod(() => authInvoke(provider, "liveEpoch", []))).pipe(Effect.mapError((error) => (owned() ? error : scopeEndedError())));
2043
2321
  // closeScope can land while the epoch read is pending, after
2044
2322
  // connect succeeded but before this client is cached. Discard the
2045
2323
  // client on that side of the await too.
@@ -2054,11 +2332,15 @@ export function remoteMcp(id, opts) {
2054
2332
  state.authRequired = false;
2055
2333
  const discover = c.getDiscoverResult();
2056
2334
  if (!prior)
2057
- yield* promised(() => storeNegotiation(ctx, negotiationDigest, discover ? { kind: "modern", discover } : { kind: "legacy" }));
2335
+ yield* promised(authMethod(() => authCall(storeNegotiation, [
2336
+ ctx,
2337
+ negotiationDigest,
2338
+ discover ? { kind: "modern", discover } : { kind: "legacy" },
2339
+ ])));
2058
2340
  if (!owned())
2059
2341
  return yield* Effect.fail(scopeEndedError());
2060
2342
  return c;
2061
- }).pipe(Effect.catch((err) => {
2343
+ }).pipe(Effect.catchDefect(Effect.fail), Effect.catch((err) => {
2062
2344
  // Close the failed connection through its client, without waiting.
2063
2345
  if (owned()) {
2064
2346
  state.lease = null;
@@ -2090,27 +2372,23 @@ export function remoteMcp(id, opts) {
2090
2372
  }));
2091
2373
  });
2092
2374
  };
2093
- const disconnectAuthorization = async (ctx, state, operatorDisconnected = false, preserveClient = false) => {
2375
+ const disconnectAuthorization = authMethod((ctx, state, operatorDisconnected = false, preserveClient = false) => authFlow(function* () {
2094
2376
  const provider = newProvider(ctx, state);
2095
- // Publish the replacement epoch before waiting on or closing any
2096
- // request-local transport. A hung connect therefore cannot delay the
2097
- // fence, and every late OAuth write stays in the older namespace.
2098
2377
  const reset = operatorDisconnected
2099
- ? provider.disconnectAuthorization(learnedUrlSafeFetch(id, new URL(opts.url), async (input, init) => {
2100
- trackSentRequest(ctx, input, init);
2101
- return await fetch(input, init);
2102
- }))
2103
- : provider.resetAuthorization(false, preserveClient);
2378
+ ? authInvoke(provider, "disconnectAuthorization", [
2379
+ learnedUrlSafeFetch(id, new URL(opts.url), authMethod((input, init) => authFlow(function* () {
2380
+ trackSentRequest(ctx, input, init);
2381
+ return (yield authCall(sendCredentialPost, [input, init ?? {}]));
2382
+ }))),
2383
+ ])
2384
+ : authInvoke(provider, "resetAuthorization", [false, preserveClient]);
2104
2385
  try {
2105
- await reset;
2386
+ yield reset;
2106
2387
  }
2107
2388
  finally {
2108
- // Abandon any connect in flight and close whichever half of the
2109
- // client/transport exists. Reset is unconditional because storage may already
2110
- // be fenced behind a newer epoch after a cleanup error.
2111
2389
  closeHalf(state);
2112
2390
  }
2113
- };
2391
+ }));
2114
2392
  // Built once from construction-time values: names, framing, and public
2115
2393
  // URLs only. A static header's value and a stored credential never reach
2116
2394
  // it, and a URL keeps origin and path: a CIMD document's query may carry a
@@ -2134,6 +2412,8 @@ export function remoteMcp(id, opts) {
2134
2412
  ...(describedClientMetadataUrl !== undefined ? { clientMetadataUrl: describedClientMetadataUrl } : {}),
2135
2413
  }
2136
2414
  : { mode: "none" };
2415
+ if (extraHeaders)
2416
+ authDescription.headerNames = [...new Set([...(authDescription.headerNames ?? []), ...Object.keys(extraHeaders)])];
2137
2417
  const endpoint = describedEndpoint(opts.url);
2138
2418
  const optionSources = Object.freeze({
2139
2419
  "source.kind": "default",
@@ -2153,9 +2433,9 @@ export function remoteMcp(id, opts) {
2153
2433
  if (ctx.signal?.aborted)
2154
2434
  throw ctx.signal.reason instanceof Error ? ctx.signal.reason : scopeEndedError();
2155
2435
  };
2156
- const withSkillsClient = async (ctx, method, run) => {
2436
+ const withSkillsClient = authMethod((ctx, method, run) => authFlow(function* () {
2157
2437
  const state = stateFor(ctx);
2158
- const client = await ensureConnected(ctx, state);
2438
+ const client = (yield authCall(ensureConnected, [ctx, state]));
2159
2439
  try {
2160
2440
  skillScopeOpen(ctx, state);
2161
2441
  const capabilities = client.getServerCapabilities();
@@ -2166,7 +2446,7 @@ export function remoteMcp(id, opts) {
2166
2446
  (extension.directoryRead !== undefined && typeof extension.directoryRead !== "boolean")) {
2167
2447
  throw new ConnectorCallError("connector_call_failed", "The downstream did not declare a valid Skills extension and resources capability.");
2168
2448
  }
2169
- const result = await run(client, state);
2449
+ const result = (yield authCall(run, [client, state]));
2170
2450
  skillScopeOpen(ctx, state);
2171
2451
  return result;
2172
2452
  }
@@ -2176,11 +2456,9 @@ export function remoteMcp(id, opts) {
2176
2456
  const classified = atMcpBoundary(ctx, error, method, client.transport, [ctx.signal]);
2177
2457
  if (requiresAuthorization(classified) && state.client === client)
2178
2458
  state.authRequired = true;
2179
- // The SDK and downstream can quote whole manifests or bodies in errors.
2180
- // Keep their typed verdict and operator facts, never the quoted payload.
2181
2459
  throw carryFailureFacts(classified, withheldAs(`Connector "${id}" downstream ${method} failed.`, classified));
2182
2460
  }
2183
- };
2461
+ }));
2184
2462
  const connector = {
2185
2463
  id,
2186
2464
  ...(opts.title !== undefined ? { title: opts.title } : {}),
@@ -2205,47 +2483,55 @@ export function remoteMcp(id, opts) {
2205
2483
  ...(skillsEnabled
2206
2484
  ? {
2207
2485
  downstreamSkills: {
2208
- list: (ctx) => withSkillsClient(ctx, "skills/list", async (client, state) => {
2209
- const listed = [];
2210
- const uris = new Set();
2211
- const cursors = new Set();
2212
- let cursor;
2213
- let bytes = 0;
2214
- for (let page = 0; page < MAX_SKILLS; page++) {
2215
- skillScopeOpen(ctx, state);
2216
- const result = await client.request({ method: "skills/list", ...(cursor === undefined ? {} : { params: { cursor } }) }, SkillPageSchema, requestOptions(ctx));
2217
- bytes += result.bytes;
2218
- if (bytes > MAX_SERIALIZED_CATALOG_BYTES || listed.length + result.skills.length > MAX_SKILLS) {
2219
- throw new ConnectorCallError("connector_call_failed", "Downstream Skills listing exceeds the aggregate limit.");
2220
- }
2221
- for (const skill of result.skills) {
2222
- if (uris.has(skill.uri))
2223
- throw new ConnectorCallError("connector_call_failed", "Downstream Skills listing contains duplicate skill URIs.");
2224
- uris.add(skill.uri);
2225
- listed.push(skill);
2226
- }
2227
- if (result.nextCursor === undefined)
2228
- return listed;
2229
- if (!result.skills.length || cursors.has(result.nextCursor)) {
2230
- throw new ConnectorCallError("connector_call_failed", "Downstream Skills pagination loops or makes no progress.");
2486
+ list: authMethod((ctx) => authCall(withSkillsClient, [
2487
+ ctx,
2488
+ "skills/list",
2489
+ authMethod((client, state) => authFlow(function* () {
2490
+ const listed = [];
2491
+ const uris = new Set();
2492
+ const cursors = new Set();
2493
+ let cursor;
2494
+ let bytes = 0;
2495
+ for (let page = 0; page < MAX_SKILLS; page++) {
2496
+ skillScopeOpen(ctx, state);
2497
+ const result = (yield authInvoke(client, "request", [
2498
+ { method: "skills/list", ...(cursor === undefined ? {} : { params: { cursor } }) },
2499
+ SkillPageSchema,
2500
+ requestOptions(ctx),
2501
+ ]));
2502
+ bytes += result.bytes;
2503
+ if (bytes > MAX_SERIALIZED_CATALOG_BYTES || listed.length + result.skills.length > MAX_SKILLS) {
2504
+ throw new ConnectorCallError("connector_call_failed", "Downstream Skills listing exceeds the aggregate limit.");
2505
+ }
2506
+ for (const skill of result.skills) {
2507
+ if (uris.has(skill.uri))
2508
+ throw new ConnectorCallError("connector_call_failed", "Downstream Skills listing contains duplicate skill URIs.");
2509
+ uris.add(skill.uri);
2510
+ listed.push(skill);
2511
+ }
2512
+ if (result.nextCursor === undefined)
2513
+ return listed;
2514
+ if (!result.skills.length || cursors.has(result.nextCursor)) {
2515
+ throw new ConnectorCallError("connector_call_failed", "Downstream Skills pagination loops or makes no progress.");
2516
+ }
2517
+ cursors.add(result.nextCursor);
2518
+ cursor = result.nextCursor;
2231
2519
  }
2232
- cursors.add(result.nextCursor);
2233
- cursor = result.nextCursor;
2234
- }
2235
- throw new ConnectorCallError("connector_call_failed", "Downstream Skills listing exceeds the page limit.");
2236
- }),
2237
- read: (uri, ctx) => withSkillsClient(ctx, "resources/read", async (client) => {
2238
- // The registry admits only advertised manifest URIs. An explicit
2239
- // request bypasses the SDK readResource response cache entirely and
2240
- // validates the raw result before its resource union drops fields.
2241
- return client.request({ method: "resources/read", params: { uri } }, skillReadSchema(uri), requestOptions(ctx));
2242
- }),
2520
+ throw new ConnectorCallError("connector_call_failed", "Downstream Skills listing exceeds the page limit.");
2521
+ })),
2522
+ ])),
2523
+ read: authMethod((uri, ctx) => authCall(withSkillsClient, [
2524
+ ctx,
2525
+ "resources/read",
2526
+ authMethod((client) => authInvoke(client, "request", [
2527
+ { method: "resources/read", params: { uri } },
2528
+ skillReadSchema(uri),
2529
+ requestOptions(ctx),
2530
+ ])),
2531
+ ])),
2243
2532
  },
2244
2533
  }
2245
2534
  : {}),
2246
- // Data the registry classifies every read with; listTools below returns
2247
- // the downstream's listing unclassified.
2248
- ...(classification !== undefined ? { classification } : {}),
2249
2535
  // Declaring the slot is what makes the rest of the operator surface work:
2250
2536
  // the connection's credential form renders it, the shape check compares
2251
2537
  // against it, and authorize_connector returns the operator handoff rather than
@@ -2259,17 +2545,9 @@ export function remoteMcp(id, opts) {
2259
2545
  * here is connecta's to verify — the credential's scope, project,
2260
2546
  * and mode are the provider's answer, not ours.
2261
2547
  */
2262
- testCredential: async (value, ctx) => {
2548
+ testCredential: authMethod((value, ctx) => authFlow(function* () {
2263
2549
  try {
2264
- // The connect below reads the vault itself — the header is
2265
- // assembled deep inside `ensureConnected`, and handing a
2266
- // candidate down that path would mean threading a second secret
2267
- // through the whole connection state. `/ui/credentials/<id>/test`
2268
- // reads the stored value and passes it here, so today the two are
2269
- // the same string. Check rather than assume: a route that later
2270
- // tested an unsaved candidate would otherwise silently report on
2271
- // the old value, which is the one answer worse than refusing.
2272
- const stored = (await ctx.credential?.get())?.trim();
2550
+ const stored = (yield authCall(ctx.credential?.get, [], ctx.credential))?.trim();
2273
2551
  if (stored !== value.trim()) {
2274
2552
  return {
2275
2553
  ok: false,
@@ -2277,7 +2555,7 @@ export function remoteMcp(id, opts) {
2277
2555
  "saved. Save the value first, then test it.",
2278
2556
  };
2279
2557
  }
2280
- const tools = await connector.listTools(ctx);
2558
+ const tools = (yield authInvoke(connector, "listTools", [ctx]));
2281
2559
  return {
2282
2560
  ok: true,
2283
2561
  message: `Connected — the downstream served ${tools.length} tool${tools.length === 1 ? "" : "s"}.`,
@@ -2287,24 +2565,22 @@ export function remoteMcp(id, opts) {
2287
2565
  return { ok: false, message: msg(err) };
2288
2566
  }
2289
2567
  finally {
2290
- // Standalone tests own their scope; an operator route owns
2291
- // bounded teardown for the context it supplied to this hook.
2292
2568
  if (!connectorScopeCleanupClaimed(ctx, id))
2293
- await connector.closeScope?.(ctx);
2569
+ (yield authCall(connector.closeScope, [ctx], connector));
2294
2570
  }
2295
- },
2571
+ })),
2296
2572
  }
2297
2573
  : {}),
2298
2574
  // The SDK walks the whole chain and writes one complete result. Intake
2299
2575
  // runs on its public request seam before any page can enter that cache.
2300
- async listTools(ctx) {
2576
+ listTools: authMethod((ctx) => authFlow(function* () {
2301
2577
  const state = stateFor(ctx);
2302
- const client = await ensureConnected(ctx, state);
2578
+ const client = (yield authCall(ensureConnected, [ctx, state]));
2303
2579
  let clean;
2304
2580
  const options = requestOptions(ctx) ?? {};
2305
2581
  listingContexts.set(options, ctx);
2306
2582
  try {
2307
- const result = await client.listTools(undefined, options);
2583
+ const result = (yield authInvoke(client, "listTools", [undefined, options]));
2308
2584
  clean = redactCatalog(ctx, result.tools);
2309
2585
  }
2310
2586
  catch (err) {
@@ -2328,15 +2604,10 @@ export function remoteMcp(id, opts) {
2328
2604
  ...(t.title !== undefined ? { title: t.title } : {}),
2329
2605
  ...(t.icons !== undefined
2330
2606
  ? {
2331
- // Inline images can consume the catalog ceiling and storage chunks.
2332
- // The SDK needs no icons for validation or parameter mirroring.
2333
2607
  icons: t.icons.filter((icon) => !/^data:/i.test(icon.src)),
2334
2608
  }
2335
2609
  : {}),
2336
2610
  ...(t.execution !== undefined ? { execution: t.execution } : {}),
2337
- // SEP-2243 declarations live in inputSchema. SDK validation and header
2338
- // mirroring need no _meta keys, so arbitrary downstream metadata stays
2339
- // out of the persisted catalog.
2340
2611
  ...(t.description !== undefined ? { description: t.description } : {}),
2341
2612
  ...(t.inputSchema !== undefined
2342
2613
  ? {
@@ -2354,88 +2625,157 @@ export function remoteMcp(id, opts) {
2354
2625
  }
2355
2626
  : {}),
2356
2627
  }));
2357
- },
2358
- async callTool(name, args, ctx, options) {
2359
- if (classification?.unlisted === "hide" && !Object.hasOwn(classification.tools, name)) {
2360
- throw new ConnectorCallError("invalid_args", "This tool is not in the connector allowlist.");
2361
- }
2362
- const state = stateFor(ctx);
2363
- const client = await ensureConnected(ctx, state);
2628
+ })),
2629
+ callTool: authMethod((name, args, ctx, options) => authFlow(function* () {
2630
+ enterProviderBoundary(ctx, connector);
2631
+ let active;
2364
2632
  try {
2365
- const definition = options?.definition;
2366
- const toolDefinition = definition
2367
- ? {
2368
- ...definition,
2369
- // Older stored catalogs and custom connectors may omit inputSchema.
2370
- inputSchema: (definition.inputSchema ?? { type: "object" }),
2633
+ return (yield authView(ctx, authMethod(() => authFlow(function* () {
2634
+ const state = stateFor(ctx);
2635
+ const client = (yield authCatch(authCall(ensureConnected, [ctx, state]), (error) => {
2636
+ throw knownProviderFailure(error);
2637
+ }));
2638
+ const definition = options?.definition;
2639
+ const facts = definition ?? state.toolDefinitions.get(name) ?? { name };
2640
+ const sourceFacts = definition ?? {
2641
+ name,
2642
+ ...(facts.annotations
2643
+ ? { annotations: facts.annotations }
2644
+ : {}),
2645
+ };
2646
+ const source = sourceClassification(sourceFacts);
2647
+ const serverWrite = source === "write";
2648
+ const write = serverWrite || (callClassification(ctx) ?? definition?.classification ?? source) === "write";
2649
+ const dispatch = newDispatch(write, serverWrite);
2650
+ dispatch.request = {
2651
+ entry: providerRequest(ctx, !write, `Connector "${id}"'s MCP server`, { method: "POST" }),
2652
+ ctx,
2653
+ };
2654
+ active = { state, client, dispatch, write };
2655
+ state.dispatches.set(dispatch, name);
2656
+ const toolDefinition = definition
2657
+ ? {
2658
+ ...definition,
2659
+ inputSchema: (definition.inputSchema ?? { type: "object" }),
2660
+ }
2661
+ : state.toolDefinitions.get(name);
2662
+ if (toolDefinition?.execution?.taskSupport === "required") {
2663
+ throw new ConnectorCallError("connector_call_failed", `Tool "${name}" requires task-based execution, which Connecta does not support.`);
2371
2664
  }
2372
- : state.toolDefinitions.get(name);
2373
- if (toolDefinition?.execution?.taskSupport === "required") {
2374
- throw new ConnectorCallError("connector_call_failed", `Tool "${name}" requires task-based execution, which Connecta does not support.`);
2375
- }
2376
- let output;
2377
- const outputError = (code, message) => atMcpBoundary(ctx, new ProtocolError(code, message), "tools/call", client.transport, [ctx.signal]);
2378
- if (toolDefinition?.outputSchema) {
2379
- try {
2380
- output = fromJsonSchema(toolDefinition.outputSchema);
2665
+ let output;
2666
+ const outputError = (code, message) => atMcpBoundary(ctx, new ProtocolError(code, message), "tools/call", client.transport, [ctx.signal]);
2667
+ if (toolDefinition?.outputSchema) {
2668
+ try {
2669
+ output = fromJsonSchema(toolDefinition.outputSchema);
2670
+ }
2671
+ catch {
2672
+ throw outputError(-32602, `Tool '${name}' has an invalid outputSchema: schema could not be compiled`);
2673
+ }
2381
2674
  }
2382
- catch {
2383
- throw outputError(-32602, `Tool '${name}' has an invalid outputSchema: schema could not be compiled`);
2675
+ const result = (yield authCatch(authInvoke(client, "callTool", [
2676
+ withDispatch({
2677
+ name,
2678
+ arguments: (args ?? {}),
2679
+ ...options?.input,
2680
+ }, dispatch),
2681
+ {
2682
+ ...requestOptions(ctx),
2683
+ allowInputRequired: true,
2684
+ ...(toolDefinition
2685
+ ? { toolDefinition: { ...toolDefinition, outputSchema: undefined } }
2686
+ : write
2687
+ ? { toolDefinition: { name, inputSchema: { type: "object" } } }
2688
+ : {}),
2689
+ },
2690
+ ]), (err) => {
2691
+ assertDownstreamOutputSafe(ctx.requestScope ?? ctx, err);
2692
+ throw atMcpBoundary(ctx, err, "tools/call", client.transport, [ctx.signal]);
2693
+ }));
2694
+ if (isInputRequiredResult(result)) {
2695
+ return result;
2696
+ }
2697
+ assertDownstreamOutputSafe(ctx.requestScope ?? ctx, result);
2698
+ if (output && !result.isError) {
2699
+ if (result.structuredContent === undefined)
2700
+ throw outputError(-32600, `Tool ${name} has an output schema but did not return structured content`);
2701
+ const validation = (yield authInvoke(output["~standard"], "validate", [
2702
+ result.structuredContent,
2703
+ ]));
2704
+ if (validation.issues)
2705
+ throw outputError(-32602, "Structured content does not match the tool's output schema: validation failed");
2384
2706
  }
2385
- }
2386
- const result = await client
2387
- .callTool({
2388
- name,
2389
- arguments: (args ?? {}),
2390
- ...options?.input,
2391
- }, {
2392
- ...requestOptions(ctx),
2393
- allowInputRequired: true,
2394
- // SDK 2.3.1's callTool checks outputSchema even for a suspension.
2395
- // Retain its header mirroring, then validate only final results.
2396
- ...(toolDefinition ? { toolDefinition: { ...toolDefinition, outputSchema: undefined } } : {}),
2397
- })
2398
- .catch((err) => {
2399
- assertDownstreamOutputSafe(ctx.requestScope ?? ctx, err);
2400
- throw atMcpBoundary(ctx, err, "tools/call", client.transport, [ctx.signal]);
2401
- });
2402
- if (isInputRequiredResult(result)) {
2403
2707
  return result;
2404
- }
2405
- assertDownstreamOutputSafe(ctx.requestScope ?? ctx, result);
2406
- if (output && !result.isError) {
2407
- if (result.structuredContent === undefined)
2408
- throw outputError(-32600, `Tool ${name} has an output schema but did not return structured content`);
2409
- const validation = await output["~standard"].validate(result.structuredContent);
2410
- if (validation.issues)
2411
- throw outputError(-32602, "Structured content does not match the tool's output schema: validation failed");
2412
- }
2413
- return result;
2708
+ }))));
2414
2709
  }
2415
2710
  catch (err) {
2416
- // A grant revoked after connect surfaces here, not in ensureConnected.
2417
- if (ownAbortReason(err, [ctx.signal]) && !hasSdkPayload(err))
2711
+ if (!active)
2712
+ throw knownProviderFailure(err);
2713
+ const { state, client, dispatch, write } = active;
2714
+ const direct = write && callClassification(ctx) === undefined;
2715
+ const ownAbort = ownAbortReason(err, [ctx.signal]) && !hasSdkPayload(err);
2716
+ // The SDK can reject its wait synchronously from the abort event,
2717
+ // before the physical response's Promise observer records its facts.
2718
+ // Yield only settlement; no closed caller regains live authority.
2719
+ if (ownAbort)
2720
+ yield Effect.yieldNow;
2721
+ if (ownAbort && !direct)
2418
2722
  throw err;
2419
- if (requiresAuthorization(err)) {
2420
- if (state.client === client)
2421
- state.authRequired = true;
2422
- if (err instanceof UnauthorizedError)
2423
- throw carryFailureFacts(err, authRequiredError());
2424
- }
2425
- throw downstreamCallError(err, undefined, undefined, undefined, sentSecretsFor(ctx));
2723
+ if (requiresAuthorization(err) && state.client === client)
2724
+ state.authRequired = true;
2725
+ const failure = ownAbort
2726
+ ? err
2727
+ : err instanceof UnauthorizedError
2728
+ ? carryFailureFacts(err, authRequiredError())
2729
+ : downstreamCallError(err, undefined, undefined, undefined, requestSentSecrets(ctx));
2730
+ if (!write)
2731
+ throw failure;
2732
+ const renewal = dispatch.request?.entry.renewed
2733
+ ? undefined
2734
+ : dispatch.resendRefused
2735
+ ? authRequiredError()
2736
+ : failure;
2737
+ const unsent = (entry) => {
2738
+ const verdict = unsentAfterUnauthorized(`Connector "${id}"'s MCP server`, renewal, isOauth ? "downstream_oauth_required" : "auth_required");
2739
+ return entry ? vendorRefusal(verdict, entry) : verdict;
2740
+ };
2741
+ const verdict = dispatch.unauthorized
2742
+ ? unsent(dispatch.request?.entry)
2743
+ : dispatch.redirected || dispatch.resendRefused
2744
+ ? new ConnectorCallError("connector_call_failed", `Connector "${id}"'s MCP server ` +
2745
+ (dispatch.redirected
2746
+ ? "answered this write with a redirect, and the write was not sent again, "
2747
+ : "was sent this write, and a second send of it was stopped, ") +
2748
+ "so whether it took effect is unknown: check the target before repeating it.", { retryable: false, writeOutcome: "unknown" })
2749
+ : failure;
2750
+ const vendor = { title: `connector "${id}"` };
2751
+ const status = failureRecord({}, err).httpStatus;
2752
+ const settled = providerCallFailure(ctx, vendor, verdict, {
2753
+ dispatched: dispatch.sent,
2754
+ ...(status === undefined ? {} : { status }),
2755
+ fallback: "write",
2756
+ request: dispatch.request?.entry,
2757
+ });
2758
+ throw settled === failure ? failure : carryFailureFacts(failure, settled);
2426
2759
  }
2427
- },
2428
- async readResource(uri, ctx) {
2760
+ finally {
2761
+ if (active)
2762
+ active.state.dispatches.delete(active.dispatch);
2763
+ }
2764
+ })),
2765
+ readResource: authMethod((uri, ctx) => authFlow(function* () {
2429
2766
  const state = stateFor(ctx);
2430
- const client = await ensureConnected(ctx, state);
2767
+ const client = (yield authCall(ensureConnected, [ctx, state]));
2431
2768
  try {
2432
2769
  const options = requestOptions(ctx) ?? {};
2433
2770
  listingContexts.set(options, ctx);
2434
2771
  try {
2435
2772
  if (!client.getServerCapabilities()?.resources)
2436
2773
  throw unadvertisedResource();
2437
- const resources = await client.listResources(undefined, options);
2438
- const templates = await client.listResourceTemplates(undefined, options);
2774
+ const resources = (yield authInvoke(client, "listResources", [undefined, options]));
2775
+ const templates = (yield authInvoke(client, "listResourceTemplates", [
2776
+ undefined,
2777
+ options,
2778
+ ]));
2439
2779
  const exact = resources.resources.some((resource) => resource.uri === uri);
2440
2780
  if (!exact) {
2441
2781
  const match = resourceUriMatchesTemplates(uri, templates.resourceTemplates, (code) => resourceTemplateRefusals.add(code));
@@ -2454,18 +2794,19 @@ export function remoteMcp(id, opts) {
2454
2794
  finally {
2455
2795
  listingContexts.delete(options);
2456
2796
  }
2457
- const result = await client
2458
- .readResource({ uri }, {
2459
- ...requestOptions(ctx),
2460
- cacheMode: "bypass",
2461
- allowInputRequired: true,
2462
- })
2463
- .catch((err) => {
2797
+ const result = (yield authCatch(authInvoke(client, "readResource", [
2798
+ { uri },
2799
+ {
2800
+ ...requestOptions(ctx),
2801
+ cacheMode: "bypass",
2802
+ allowInputRequired: true,
2803
+ },
2804
+ ]), (err) => {
2464
2805
  if (err instanceof ResourceNotFoundError) {
2465
2806
  throw new ConnectorCallError("not_found", "The downstream resource does not exist.");
2466
2807
  }
2467
2808
  throw atMcpBoundary(ctx, err, "resources/read", client.transport, [ctx.signal]);
2468
- });
2809
+ }));
2469
2810
  if (isInputRequiredResult(result)) {
2470
2811
  throw new ConnectorCallError("input_required_unsupported", "Resource reads inside programs cannot request mid-call input.");
2471
2812
  }
@@ -2480,170 +2821,161 @@ export function remoteMcp(id, opts) {
2480
2821
  if (err instanceof UnauthorizedError)
2481
2822
  throw carryFailureFacts(err, authRequiredError());
2482
2823
  }
2483
- throw downstreamCallError(err, undefined, undefined, undefined, sentSecretsFor(ctx));
2824
+ throw downstreamCallError(err, undefined, undefined, undefined, requestSentSecrets(ctx));
2484
2825
  }
2485
- },
2486
- async closeScope(ctx) {
2487
- // A scope closed before its first use gets an entry too, closed at once,
2488
- // so it cannot spring into existence later.
2826
+ })),
2827
+ closeScope: authMethod((ctx) => authFlow(function* () {
2828
+ // Revocation and all finalizers start before any caller guard. Cleanup
2829
+ // retains its own bounded settlement even after the request has closed.
2489
2830
  const state = entryFor(ctx);
2490
- // Closed before any await, and before the finalizers run: every check
2491
- // from here on sees the scope ended. A duplicate teardown finds it
2492
- // closed and has nothing to run.
2493
2831
  const release = Scope.closeUnsafe(state.scope, Exit.void);
2494
2832
  const cacheClosing = closeCatalogCacheScope(ctx, id, cacheOwner);
2495
- // Storage cannot cancel an already-started write. Bound the join in
2496
- // parallel with transport cleanup so it adds no unbounded teardown tail.
2497
- const cacheClosed = cacheClosing &&
2498
- runEdge(Effect.raceAllFirst([
2499
- Effect.promise(() => cacheClosing),
2500
- Effect.sleep(Duration.millis(LOCAL_CLOSE_BUDGET_MS)),
2501
- ]));
2502
2833
  reset(state);
2503
- // Runs the live connection's lease finalizer, if there is one.
2504
- if (release)
2505
- await runEdge(release);
2506
- await cacheClosed;
2507
- },
2508
- async status(ctx) {
2834
+ const closing = runEdge(Effect.all([
2835
+ release ?? Effect.void,
2836
+ cacheClosing
2837
+ ? Effect.raceAllFirst([
2838
+ Effect.promise(() => cacheClosing),
2839
+ Effect.sleep(Duration.millis(LOCAL_CLOSE_BUDGET_MS)),
2840
+ ])
2841
+ : Effect.void,
2842
+ ], { concurrency: "unbounded" }).pipe(Effect.asVoid));
2843
+ void closing.catch(() => { });
2844
+ if (viewSignal(ctx, true).aborted)
2845
+ return yield authValue(() => closing);
2846
+ return yield authView(ctx, authMethod(() => authValue(() => closing)), true);
2847
+ })),
2848
+ status: authMethod((ctx) => authFlow(function* () {
2509
2849
  const state = stateFor(ctx);
2510
- const report = async (status) => {
2511
- const path = isOauth ? await newProvider(ctx, state).registrationPath() : undefined;
2850
+ const report = authMethod((status) => authFlow(function* () {
2851
+ const path = isOauth
2852
+ ? (yield authInvoke(newProvider(ctx, state), "registrationPath", []))
2853
+ : undefined;
2512
2854
  return ownStatus({
2513
2855
  ...status,
2514
2856
  ...(path ? { registrationPath: path } : {}),
2515
2857
  ...(resourceTemplateRefusals.size ? { resourceTemplateRefusals: [...resourceTemplateRefusals] } : {}),
2516
2858
  });
2517
- };
2859
+ }));
2518
2860
  try {
2519
- await ensureConnected(ctx, state);
2520
- return report({ state: "ok" });
2861
+ (yield authCall(ensureConnected, [ctx, state]));
2862
+ return (yield authCall(report, [{ state: "ok" }]));
2521
2863
  }
2522
2864
  catch (err) {
2523
- // An empty slot, or one with no vault behind it, is reported the way a
2524
- // missing grant is: present, unauthenticated, and repairable — never a
2525
- // boot failure and never a silently absent connector.
2526
2865
  if (err instanceof CredentialRequiredError) {
2527
- return report({ state: "auth_required", message: err.message });
2866
+ return (yield authCall(report, [{ state: "auth_required", message: err.message }]));
2528
2867
  }
2529
2868
  if (err instanceof OperatorDisconnectedError) {
2530
- return report({ state: "auth_required", message: err.message });
2869
+ return (yield authCall(report, [{ state: "auth_required", message: err.message }]));
2531
2870
  }
2532
2871
  if (state.authRequired) {
2533
- // Only an OAuth connector has a pending consent URL to offer. A
2534
- // credential connector's downstream 401 is repaired in the operator
2535
- // UI connection, so do not reach into OAuth storage to look for one.
2536
- return report({
2537
- state: "auth_required",
2538
- message: credentialAuth
2539
- ? "Authorization required — the downstream rejected this connector's stored credential."
2540
- : "Authorization required — open the URL to connect.",
2541
- });
2872
+ return (yield authCall(report, [
2873
+ {
2874
+ state: "auth_required",
2875
+ message: credentialAuth
2876
+ ? "Authorization required \u2014 the downstream rejected this connector's stored credential."
2877
+ : "Authorization required \u2014 open the URL to connect.",
2878
+ },
2879
+ ]));
2542
2880
  }
2543
- // An operator surface: the record, never the error's own text.
2544
2881
  return failureStatus(id, err);
2545
2882
  }
2546
- },
2547
- async finishAuth(code, ctx, callbackParams) {
2883
+ })),
2884
+ finishAuth: authMethod((code, ctx, callbackParams) => authView(ctx, authMethod((signal) => authFlow(function* () {
2548
2885
  const state = stateFor(ctx);
2549
2886
  const provider = newProvider(ctx, state);
2550
- // verifyState ran on this request-scoped provider first and found the
2551
- // consent; a programmatic exchange names it by its callback's state.
2552
2887
  const consent = callbackParams?.get("state") ?? null;
2553
- if (!consent || !(await provider.verifyCallbackState(consent))) {
2888
+ if (!consent || !(yield authInvoke(provider, "verifyCallbackState", [consent]))) {
2554
2889
  throw new ConnectorCallError("connector_call_failed", `Connector "${id}" authorization callback matches no pending consent; nothing was exchanged.`);
2555
2890
  }
2556
- // The exchange reads and writes only the epoch its consent was written
2557
- // in; it decides nothing about the grant there.
2558
2891
  provider.validateCallbackIssuer(callbackParams?.get("iss") ?? null);
2559
- await provider.bindFlow();
2560
- // Always a transport of the exchange's own, over the provider that
2561
- // verified the state: that provider holds the consent's claim, and the
2562
- // fence before the token request is its to cross. A connection this
2563
- // scope opened earlier speaks for its connect attempt, not this callback.
2564
- const t = buildTransport(ctx, provider);
2892
+ (yield authInvoke(provider, "bindFlow", []));
2893
+ signal.throwIfAborted();
2894
+ const t = buildTransport(ctx, provider, null, signal, null, true);
2565
2895
  const trail = {};
2566
2896
  const internals = t;
2567
2897
  const traced = tracedOAuthFetch(trail, new URL(opts.url), internals._fetchWithInit ?? fetch);
2568
2898
  const exchange = new Proxy(t, {
2569
- get: (target, key) => (key === "_fetchWithInit" ? traced : Reflect.get(target, key, target)),
2899
+ get: (target, key) => key === "_fetchWithInit" ? traced : Reflect.get(target, key, target),
2570
2900
  set: (target, key, value) => Reflect.set(target, key, value, target),
2571
2901
  });
2572
2902
  try {
2573
- await exchange.finishAuth(callbackParams);
2574
- // Reset so the next use reconnects with the freshly stored tokens.
2903
+ (yield authInvoke(exchange, "finishAuth", [callbackParams]));
2575
2904
  closeHalf(state);
2576
2905
  }
2577
2906
  catch (err) {
2578
- throw await withoutAuthorizationServerText(ctx, err, trail, [ctx.signal]);
2907
+ throw (yield authCall(withoutAuthorizationServerText, [
2908
+ ctx,
2909
+ err,
2910
+ trail,
2911
+ [signal, ctx.signal],
2912
+ ]));
2579
2913
  }
2580
2914
  finally {
2581
- // This exchange-only transport has no lease in the request scope.
2582
2915
  detach(closeConnection(null, t, ctx.logger));
2583
2916
  }
2584
- },
2917
+ })), true)),
2585
2918
  };
2586
2919
  if (opts.auth?.type === "oauth") {
2587
- connector.verifyState = async (oauthState, ctx) => {
2920
+ connector.verifyState = authMethod((oauthState, ctx) => authFlow(function* () {
2588
2921
  const state = stateFor(ctx);
2589
- return newProvider(ctx, state).verifyState(oauthState);
2590
- };
2591
- connector.verifyCallbackIssuer = async (issuer, ctx) => {
2592
- newProvider(ctx, stateFor(ctx)).validateCallbackIssuer(issuer);
2593
- return true;
2594
- };
2595
- connector.consumeAuthError = async (ctx) => {
2596
- await newProvider(ctx, stateFor(ctx)).consumeAuthError();
2597
- };
2598
- connector.disconnectAuth = async (ctx) => {
2599
- await disconnectAuthorization(ctx, stateFor(ctx), true);
2600
- };
2601
- connector.startAuth = async (ctx, startOpts) => {
2922
+ return (yield authInvoke(newProvider(ctx, state), "verifyState", [oauthState]));
2923
+ }));
2924
+ connector.verifyCallbackIssuer = authMethod((issuer, ctx) => Effect.try({
2925
+ try: () => {
2926
+ newProvider(ctx, stateFor(ctx)).validateCallbackIssuer(issuer);
2927
+ return true;
2928
+ },
2929
+ catch: (error) => error,
2930
+ }));
2931
+ connector.consumeAuthError = authMethod((ctx) => authFlow(function* () {
2932
+ (yield authInvoke(newProvider(ctx, stateFor(ctx)), "consumeAuthError", []));
2933
+ }));
2934
+ connector.disconnectAuth = authMethod((ctx) => authFlow(function* () {
2935
+ (yield authCall(disconnectAuthorization, [ctx, stateFor(ctx), true]));
2936
+ }));
2937
+ connector.startAuth = authMethod((ctx, startOpts) => authFlow(function* () {
2602
2938
  ctx = authorizingContext(ctx);
2603
2939
  const state = stateFor(ctx);
2604
2940
  state.provider = null;
2605
2941
  const p = newProvider(ctx, state);
2606
2942
  if (ctx.signal?.aborted)
2607
2943
  throw ctx.signal.reason;
2608
- const disconnected = startOpts?.force ? false : await p.operatorDisconnected();
2944
+ const disconnected = startOpts?.force
2945
+ ? false
2946
+ : (yield authInvoke(p, "operatorDisconnected", []));
2609
2947
  if (ctx.signal?.aborted)
2610
2948
  throw ctx.signal.reason;
2611
2949
  if (startOpts?.force || disconnected) {
2612
- await disconnectAuthorization(ctx, state, false, startOpts?.force === true);
2950
+ (yield authCall(disconnectAuthorization, [ctx, state, false, startOpts?.force === true]));
2613
2951
  }
2614
2952
  else {
2615
- // A consent URL already outstanding? Re-issue it rather than re-running
2616
- // the SDK flow, which would overwrite the PKCE verifier and invalidate
2617
- // the URL the operator may be mid-consent on. Only a recent one: an
2618
- // old or untimed URL falls through to a fresh flow in this same
2619
- // epoch, which keeps the stored client registration and discovery.
2620
- const pending = await p.reusablePendingAuthorizationUrl();
2953
+ const pending = (yield authInvoke(p, "reusablePendingAuthorizationUrl", []));
2621
2954
  if (pending) {
2622
2955
  return {
2623
2956
  state: "auth_required",
2624
2957
  authorizationUrl: pending,
2625
2958
  authorizationReused: true,
2626
- message: "Authorization required — open the URL to connect.",
2959
+ message: "Authorization required \u2014 open the URL to connect.",
2627
2960
  };
2628
2961
  }
2629
2962
  }
2630
2963
  if (ctx.signal?.aborted)
2631
2964
  throw ctx.signal.reason;
2632
2965
  try {
2633
- await ensureConnected(ctx, state);
2966
+ (yield authCall(ensureConnected, [ctx, state]));
2634
2967
  return {
2635
2968
  state: "ok",
2636
- message: "Already authorized — connection is healthy.",
2969
+ message: "Already authorized \u2014 connection is healthy.",
2637
2970
  };
2638
2971
  }
2639
2972
  catch (err) {
2640
2973
  if (state.authRequired) {
2641
- // The consent this start's attempt stored, while its epoch is live:
2642
- // one a later reset published belongs to another flow, and this
2643
- // start fails rather than hand it out.
2644
2974
  let authorizationUrl;
2645
2975
  try {
2646
- authorizationUrl = await state.attemptProvider?.consentUrl();
2976
+ authorizationUrl = (yield state.attemptProvider
2977
+ ? authInvoke(state.attemptProvider, "consentUrl", [viewSignal(ctx, true)])
2978
+ : Effect.succeed(undefined));
2647
2979
  }
2648
2980
  catch (readErr) {
2649
2981
  return { state: "error", message: startMessage(readErr) };
@@ -2651,20 +2983,31 @@ export function remoteMcp(id, opts) {
2651
2983
  return {
2652
2984
  state: "auth_required",
2653
2985
  ...(authorizationUrl !== undefined ? { authorizationUrl } : {}),
2654
- message: "Authorization required — open the URL to connect.",
2986
+ message: "Authorization required \u2014 open the URL to connect.",
2655
2987
  };
2656
2988
  }
2657
2989
  return { state: "error", message: startMessage(err) };
2658
2990
  }
2659
- };
2991
+ }));
2660
2992
  }
2661
- const withActiveSecrets = async (ctx, run, redactResult = true, preserveInput = false) => {
2993
+ const retainOperation = (operation, contextIndex) => authMethod((...args) => authFlow(function* () {
2994
+ const release = retainOAuthPartition(oauthPartitionFor(args[contextIndex]));
2995
+ try {
2996
+ return yield authCall(operation, args);
2997
+ }
2998
+ finally {
2999
+ release();
3000
+ }
3001
+ }));
3002
+ const withActiveSecrets = authMethod((ctx, run, redactResult = true, preserveInput = false) => authFlow(function* () {
2662
3003
  trackCredentialReads(ctx);
2663
3004
  const secrets = sentSecretsFor(ctx);
2664
3005
  if (opts.auth?.type === "headers") {
2665
3006
  for (const value of Object.values(opts.auth.headers))
2666
3007
  secrets.header(value);
2667
3008
  }
3009
+ for (const value of Object.values(extraHeaders ?? {}))
3010
+ secrets.header(value);
2668
3011
  const state = entryFor(ctx);
2669
3012
  let active = activeContexts.get(state);
2670
3013
  if (!active) {
@@ -2673,9 +3016,7 @@ export function remoteMcp(id, opts) {
2673
3016
  }
2674
3017
  active.add(ctx);
2675
3018
  try {
2676
- const result = await run();
2677
- // Opaque state must reach the sealing handler byte-exact. Invocation
2678
- // intercepts it before ordinary result processing or guest exposure.
3019
+ const result = (yield authCall(run, []));
2679
3020
  if (!preserveInput || !isInputRequiredResult(result))
2680
3021
  assertDownstreamOutputSafe(ctx.requestScope ?? ctx, result);
2681
3022
  return (preserveInput && isInputRequiredResult(result)) || !redactResult
@@ -2684,35 +3025,82 @@ export function remoteMcp(id, opts) {
2684
3025
  }
2685
3026
  catch (error) {
2686
3027
  assertDownstreamOutputSafe(ctx.requestScope ?? ctx, error);
2687
- throw redactSentSecrets(ctx, error);
3028
+ throw requestSentSecrets(ctx).redact(error);
2688
3029
  }
2689
3030
  finally {
2690
3031
  active.delete(ctx);
2691
3032
  }
2692
- };
3033
+ }));
2693
3034
  const callTool = connector.callTool;
2694
- connector.callTool = (name, args, ctx, options) => withActiveSecrets(ctx, () => callTool(name, args, ctx, options), true, true);
3035
+ connector.callTool = authMethod((name, args, ctx, options) => authCall(withActiveSecrets, [
3036
+ ctx,
3037
+ authMethod(() => authCall(callTool, [name, args, ctx, options])),
3038
+ true,
3039
+ true,
3040
+ ]));
2695
3041
  const listTools = connector.listTools;
2696
- connector.listTools = (ctx) => withActiveSecrets(ctx, () => listTools(ctx));
3042
+ connector.listTools = authMethod((ctx) => authCall(withActiveSecrets, [
3043
+ ctx,
3044
+ authMethod(() => authCall(listTools, [ctx])),
3045
+ ]));
2697
3046
  const readResource = connector.readResource;
2698
- connector.readResource = (uri, ctx) => withActiveSecrets(ctx, () => readResource(uri, ctx));
3047
+ connector.readResource = authMethod((uri, ctx) => authCall(withActiveSecrets, [
3048
+ ctx,
3049
+ authMethod(() => authCall(readResource, [uri, ctx])),
3050
+ ]));
2699
3051
  if (connector.downstreamSkills) {
2700
3052
  const { list, read } = connector.downstreamSkills;
2701
- connector.downstreamSkills.list = (ctx) => withActiveSecrets(ctx, () => list(ctx), false);
2702
- connector.downstreamSkills.read = (uri, ctx) => withActiveSecrets(ctx, () => read(uri, ctx), false);
3053
+ connector.downstreamSkills.list = authMethod((ctx) => authCall(withActiveSecrets, [
3054
+ ctx,
3055
+ authMethod(() => authCall(list, [ctx])),
3056
+ false,
3057
+ ]));
3058
+ connector.downstreamSkills.read = authMethod((uri, ctx) => authCall(withActiveSecrets, [
3059
+ ctx,
3060
+ authMethod(() => authCall(read, [uri, ctx])),
3061
+ false,
3062
+ ]));
2703
3063
  }
2704
3064
  if (isOauth && !staticClient && clientMetadataUrl === undefined)
2705
3065
  declareSelfHostedClient(connector, oauthScope);
2706
3066
  if (isOauth) {
2707
3067
  const start = connector.startAuth;
2708
- connector.startAuth = async (...args) => {
2709
- const status = await start(...args);
2710
- const path = await newProvider(args[0], stateFor(args[0])).registrationPath();
3068
+ connector.startAuth = authMethod((...args) => authFlow(function* () {
3069
+ const status = (yield authCall(start, [...args]));
3070
+ const path = (yield authInvoke(newProvider(args[0], stateFor(args[0])), "registrationPath", []));
2711
3071
  return { ...status, ...(path ? { registrationPath: path } : {}) };
2712
- };
3072
+ }));
3073
+ }
3074
+ // Every published Promise operation owns its whole wait, including local
3075
+ // storage, digest, registration-path and result processing seams. callTool
3076
+ // settles inside its write decider; closeScope starts cleanup before racing
3077
+ // the join, and finishAuth already owns its request-scoped exchange boundary.
3078
+ const settlingOperation = (operation, contextIndex, requestOwned = false) => authMethod((...args) => authFlow(function* () {
3079
+ const ctx = args[contextIndex];
3080
+ const state = states.get(Context.get(viewServices(ctx), RequestScope));
3081
+ if (state && isClosed(state))
3082
+ return (yield Effect.fail(scopeEndedError()));
3083
+ return (yield authView(ctx, authMethod(() => authCall(operation, [...args])), requestOwned));
3084
+ }));
3085
+ connector.listTools = settlingOperation(connector.listTools, 0);
3086
+ connector.readResource = settlingOperation(connector.readResource, 1);
3087
+ if (connector.downstreamSkills) {
3088
+ connector.downstreamSkills.list = settlingOperation(connector.downstreamSkills.list, 0);
3089
+ connector.downstreamSkills.read = settlingOperation(connector.downstreamSkills.read, 1);
3090
+ }
3091
+ const status = connector.status;
3092
+ connector.status = authMethod((ctx) => authCatch(authView(ctx, authMethod(() => authCall(status, [ctx]))), (error) => failureStatus(id, error)));
3093
+ if (connector.testCredential)
3094
+ connector.testCredential = settlingOperation(connector.testCredential, 1);
3095
+ if (isOauth) {
3096
+ connector.startAuth = settlingOperation(connector.startAuth, 0);
3097
+ connector.disconnectAuth = settlingOperation(connector.disconnectAuth, 0);
3098
+ connector.verifyState = settlingOperation(connector.verifyState, 1, true);
3099
+ connector.verifyCallbackIssuer = settlingOperation(connector.verifyCallbackIssuer, 1, true);
3100
+ connector.consumeAuthError = settlingOperation(connector.consumeAuthError, 0, true);
2713
3101
  // Pin before the first asynchronous storage/discovery read, not only once
2714
3102
  // a refresh flight exists. These operations do not take call admission.
2715
- const retain = retainingOAuthPartition;
3103
+ const retain = retainOperation;
2716
3104
  connector.listTools = retain(connector.listTools, 0);
2717
3105
  connector.callTool = retain(connector.callTool, 2);
2718
3106
  connector.readResource = retain(connector.readResource, 1);
@@ -2739,15 +3127,19 @@ export function remoteMcp(id, opts) {
2739
3127
  connector.startAuth = payloadFree(connector.startAuth);
2740
3128
  if (connector.finishAuth)
2741
3129
  connector.finishAuth = payloadFree(connector.finishAuth);
3130
+ // This boundary's identity in a logical call: this object with its
3131
+ // published, fully wrapped function, fixed now, never what the property or
3132
+ // a copy holds by then.
3133
+ providerBoundary(connector);
3134
+ const ownCall = connector.callTool;
3135
+ registerCallEntry(connector, (name, args, ctx, options) => authCall(ownCall, [name, args, ctx, options]));
2742
3136
  if (isOauth)
2743
- registerInvocationAuth(connector, retainingOAuthPartition(async (ctx) => {
2744
- // Only inspect local grant state here. Discovery, refresh, and MCP
2745
- // transports remain inside callTool and cannot establish retry safety.
3137
+ registerInvocationAuth(connector, retainOperation(settlingOperation(authMethod((ctx) => authFlow(function* () {
2746
3138
  const provider = newProvider(ctx, stateFor(ctx));
2747
- if (await provider.operatorDisconnected())
3139
+ if ((yield authInvoke(provider, "operatorDisconnected", [])))
2748
3140
  throw operatorDisconnectedError();
2749
- if (!(await provider.tokens()))
3141
+ if (!(yield authInvoke(provider, "tokens", [])))
2750
3142
  throw authRequiredError();
2751
- }, 0));
3143
+ })), 0), 0));
2752
3144
  return connector;
2753
3145
  }