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