@code-yeongyu/senpi 2026.11.10 → 2026.11.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/dist/bundle/{SKILL-B4R7BVOT.md → SKILL-R7FEIQKG.md} +1 -1
  3. package/dist/bundle/chunks/{SKILL-AZ6ONO52.js → SKILL-5O2UKORG.js} +1 -1
  4. package/dist/bundle/chunks/{SKILL-B4R7BVOT.md → SKILL-R7FEIQKG.md} +1 -1
  5. package/dist/bundle/chunks/{args-77GYOPEO.js → args-HFYB6URH.js} +1 -1
  6. package/dist/bundle/chunks/{azure-openai-responses-YFZ734T3.js → azure-openai-responses-MNN2TINI.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-ZUQCYIRY.js → chunk-2ODGHBUD.js} +1 -1
  8. package/dist/bundle/chunks/chunk-3IYEGHBI.js +2 -0
  9. package/dist/bundle/chunks/{chunk-I5ASHSLZ.js → chunk-4JMGWVRH.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-YKYWL2NO.js → chunk-5F2XC6UC.js} +3 -3
  11. package/dist/bundle/chunks/{chunk-MSDBBOB2.js → chunk-5H43O4MA.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-R3OP7IA4.js → chunk-5PIZWSNG.js} +4 -4
  13. package/dist/bundle/chunks/{chunk-33MZJTQ3.js → chunk-B4WD5XHP.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-Q63YLZMN.js → chunk-BPEA5SWW.js} +2 -2
  15. package/dist/bundle/chunks/{chunk-MOVJQVMC.js → chunk-BUDJLVMF.js} +3 -3
  16. package/dist/bundle/chunks/{chunk-V5FSENW6.js → chunk-COKX65TE.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-2EJ4BGBM.js → chunk-DHORIIVQ.js} +1 -1
  18. package/dist/bundle/chunks/{chunk-NH7SYIGN.js → chunk-DLPV4B3R.js} +1 -1
  19. package/dist/bundle/chunks/{chunk-RCHIMH7N.js → chunk-DQY26AX3.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-Y7Z7XPUF.js → chunk-DZQA5LVN.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-2CNSCMYC.js → chunk-E5O4SP5I.js} +1 -1
  22. package/dist/bundle/chunks/{chunk-3Q5ZENSD.js → chunk-E6ZBTSC6.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-GVPZPVVO.js → chunk-IBDAV2AL.js} +3 -3
  24. package/dist/bundle/chunks/chunk-IREUIMFE.js +11 -0
  25. package/dist/bundle/chunks/{chunk-KU7OCWJF.js → chunk-J4ADZ2OK.js} +1 -1
  26. package/dist/bundle/chunks/{chunk-T2SMQPRE.js → chunk-K3GAGIPX.js} +1 -1
  27. package/dist/bundle/chunks/{chunk-BELNOPAI.js → chunk-L3KVP7VJ.js} +3 -3
  28. package/dist/bundle/chunks/{chunk-IYSJFQUU.js → chunk-L6DF5G6K.js} +1 -1
  29. package/dist/bundle/chunks/{chunk-7MN7YNO5.js → chunk-L7DGTA3L.js} +1 -1
  30. package/dist/bundle/chunks/chunk-LJFQYCFH.js +2 -0
  31. package/dist/bundle/chunks/{chunk-J2NGJKXL.js → chunk-LXINMDMX.js} +1 -1
  32. package/dist/bundle/chunks/{chunk-H33A33M4.js → chunk-M2VJVJMK.js} +1 -1
  33. package/dist/bundle/chunks/{chunk-2U6HXWXS.js → chunk-NNQ34ZDW.js} +1 -1
  34. package/dist/bundle/chunks/chunk-NQKCJQHB.js +2 -0
  35. package/dist/bundle/chunks/{chunk-EJB5E2JK.js → chunk-NRZAJUUO.js} +2 -2
  36. package/dist/bundle/chunks/{chunk-R2Z7CX77.js → chunk-OSNR3IRD.js} +1 -1
  37. package/dist/bundle/chunks/{chunk-O3FG6HML.js → chunk-PJI5GT7U.js} +1 -1
  38. package/dist/bundle/chunks/{chunk-FDNM2DAE.js → chunk-PUZDKFRT.js} +164 -164
  39. package/dist/bundle/chunks/{chunk-BPNSOZ2C.js → chunk-QUVSIW2R.js} +1 -1
  40. package/dist/bundle/chunks/{chunk-WXYMAUFX.js → chunk-QX7WD3IM.js} +1 -1
  41. package/dist/bundle/chunks/{chunk-Y4ONXQMR.js → chunk-RGHVPUUX.js} +1 -1
  42. package/dist/bundle/chunks/{chunk-RYPM5BZQ.js → chunk-S7NAJQB3.js} +1 -1
  43. package/dist/bundle/chunks/{chunk-2BH54YQR.js → chunk-SANWO37B.js} +1 -1
  44. package/dist/bundle/chunks/{chunk-YQZNARTX.js → chunk-SS5VTBAV.js} +1 -1
  45. package/dist/bundle/chunks/{chunk-LT353NSF.js → chunk-T4CUD7RF.js} +1 -1
  46. package/dist/bundle/chunks/{chunk-J7FW7MED.js → chunk-TCZORL3V.js} +1 -1
  47. package/dist/bundle/chunks/{chunk-KU72IKV5.js → chunk-VGSWOLIY.js} +1 -1
  48. package/dist/bundle/chunks/{chunk-DBQBJTMF.js → chunk-VUY4BZDI.js} +1 -1
  49. package/dist/bundle/chunks/chunk-XI6THO4F.js +2 -0
  50. package/dist/bundle/chunks/{chunk-FAFIGBUD.js → chunk-XUDKT4UH.js} +3 -3
  51. package/dist/bundle/chunks/{cli-main-BX5AFWJQ.js → cli-main-7II7GCIR.js} +1 -1
  52. package/dist/bundle/chunks/{credential-accounts-LVVPKFEY.js → credential-accounts-N3B5YJS7.js} +1 -1
  53. package/dist/bundle/chunks/{dispatch-EP35KKJ3.js → dispatch-YUYZQXRP.js} +1 -1
  54. package/dist/bundle/chunks/github-copilot.js +1 -1
  55. package/dist/bundle/chunks/{hand-entry-F6PWKNPJ.js → hand-entry-ONOLRMVB.js} +3 -3
  56. package/dist/bundle/chunks/{headless-host-HKGNJGYT.js → headless-host-53UMJMM6.js} +1 -1
  57. package/dist/bundle/chunks/{health-write-FBF74QJ7.js → health-write-JLSF5EVV.js} +1 -1
  58. package/dist/bundle/chunks/{help-fast-path-5SCPY3OH.js → help-fast-path-552QLMWT.js} +1 -1
  59. package/dist/bundle/chunks/{host-command-47QQKJEY.js → host-command-KWMJLPBS.js} +1 -1
  60. package/dist/bundle/chunks/{host-lifecycle-VSAH4M3R.js → host-lifecycle-4WQDCN3Y.js} +1 -1
  61. package/dist/bundle/chunks/host-lifecycle.js +5 -5
  62. package/dist/bundle/chunks/{host-session-command-SL4AN5WD.js → host-session-command-ZDYEKCUB.js} +1 -1
  63. package/dist/bundle/chunks/{host-socket-request-DMYXC56U.js → host-socket-request-QN4Q2P5E.js} +1 -1
  64. package/dist/bundle/chunks/{interactive-mode-JDMIHJMQ.js → interactive-mode-EPE4M32G.js} +1 -1
  65. package/dist/bundle/chunks/{login-health-EVXVLHB2.js → login-health-TOSQT5RG.js} +1 -1
  66. package/dist/bundle/chunks/{main-4RITUAQF.js → main-OM7BKVZG.js} +1 -1
  67. package/dist/bundle/chunks/{multi-session-host-SKDD4OWM.js → multi-session-host-DB6XMY53.js} +11 -8
  68. package/dist/bundle/chunks/openai-codex-responses-VRGOAFEG.js +10 -0
  69. package/dist/bundle/chunks/{openai-responses-NIVBWE2H.js → openai-responses-I7PXRWCM.js} +2 -2
  70. package/dist/bundle/chunks/{package-manager-cli-B5VA4KFO.js → package-manager-cli-GLU5Q2KV.js} +1 -1
  71. package/dist/bundle/chunks/{role-HQUN4XXK.js → role-AEOLJRTP.js} +1 -1
  72. package/dist/bundle/chunks/{rotation-stream-F3BEMXBE.js → rotation-stream-E3FJFHXJ.js} +1 -1
  73. package/dist/bundle/chunks/rpc-mode-SDJVBT36.js +2 -0
  74. package/dist/bundle/chunks/{rpc-monitor-source-C6VQ3BC4.js → rpc-monitor-source-PGTDSGWP.js} +1 -1
  75. package/dist/bundle/chunks/{run-attached-tui-R3RB5P7G.js → run-attached-tui-4AENRP7P.js} +1 -1
  76. package/dist/bundle/chunks/{schedule-command-FPEQCX33.js → schedule-command-5SQD7M5N.js} +1 -1
  77. package/dist/bundle/chunks/session-control-endpoint-R25A2QGS.js +3 -0
  78. package/dist/bundle/chunks/{session-picker-2FZSNFS7.js → session-picker-GZM5CVQL.js} +1 -1
  79. package/dist/bundle/chunks/{session-slot-CZGQQCOC.js → session-slot-RH7JLO3F.js} +1 -1
  80. package/dist/bundle/chunks/session-worker.js +335 -332
  81. package/dist/bundle/chunks/{state-store-S7QAESIF.js → state-store-IECVTYXC.js} +1 -1
  82. package/dist/bundle/cli.js +2 -2
  83. package/dist/bundle/host-supervisor.js +5 -5
  84. package/dist/bundle/index.js +1 -1
  85. package/dist/bundle/rpc-entry.js +1 -1
  86. package/dist/bundle/runtime-manifest.json +1 -1
  87. package/dist/cli.js +9 -3
  88. package/dist/core/agent-session-runtime.d.ts +2 -0
  89. package/dist/core/agent-session-runtime.js +5 -0
  90. package/dist/core/agent-session.d.ts +15 -3
  91. package/dist/core/agent-session.js +91 -18
  92. package/dist/core/assistant-error-redaction.d.ts +9 -0
  93. package/dist/core/assistant-error-redaction.js +27 -0
  94. package/dist/core/config-reference-redaction.js +90 -10
  95. package/dist/core/context-files.d.ts +10 -0
  96. package/dist/core/context-files.js +58 -12
  97. package/dist/core/credential-accounts.d.ts +2 -0
  98. package/dist/core/credential-accounts.js +10 -1
  99. package/dist/core/dynamic-prompt/intent-gate.js +2 -2
  100. package/dist/core/dynamic-prompt/style.js +2 -2
  101. package/dist/core/dynamic-prompt/verification.js +2 -2
  102. package/dist/core/egress/wire.d.ts +13 -0
  103. package/dist/core/extensions/builtin/anthropic-subscription/accounts.d.ts +3 -0
  104. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +3 -2
  105. package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-account-test.js +3 -1
  106. package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-connection-test.d.ts +3 -1
  107. package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-connection-test.js +47 -16
  108. package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-login-slots.d.ts +1 -1
  109. package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-login-slots.js +4 -2
  110. package/dist/core/extensions/builtin/anthropic-subscription/claude-settings-env.d.ts +20 -0
  111. package/dist/core/extensions/builtin/anthropic-subscription/claude-settings-env.js +39 -0
  112. package/dist/core/extensions/builtin/anthropic-subscription/context-window.d.ts +17 -0
  113. package/dist/core/extensions/builtin/anthropic-subscription/context-window.js +23 -0
  114. package/dist/core/extensions/builtin/anthropic-subscription/errors.d.ts +1 -0
  115. package/dist/core/extensions/builtin/anthropic-subscription/errors.js +14 -5
  116. package/dist/core/extensions/builtin/anthropic-subscription/oauth-login.js +3 -3
  117. package/dist/core/extensions/builtin/anthropic-subscription/prompt-cache-ttl.d.ts +10 -4
  118. package/dist/core/extensions/builtin/anthropic-subscription/prompt-cache-ttl.js +14 -5
  119. package/dist/core/extensions/builtin/anthropic-subscription/query-environment.d.ts +3 -2
  120. package/dist/core/extensions/builtin/anthropic-subscription/query-environment.js +5 -4
  121. package/dist/core/extensions/builtin/anthropic-subscription/sdk-boundary.lazy.d.ts +2 -1
  122. package/dist/core/extensions/builtin/anthropic-subscription/sdk-boundary.lazy.js +1 -0
  123. package/dist/core/extensions/builtin/anthropic-subscription/stream.js +11 -1
  124. package/dist/core/extensions/builtin/config-reload/change-groups.js +6 -2
  125. package/dist/core/extensions/builtin/goal/continuation.d.ts +6 -0
  126. package/dist/core/extensions/builtin/goal/continuation.js +3 -2
  127. package/dist/core/extensions/builtin/goal/index.js +14 -26
  128. package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts +8 -6
  129. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +77 -39
  130. package/dist/core/extensions/builtin/goal/monitor-continuation.js +66 -7
  131. package/dist/core/extensions/builtin/goal/persistence.js +20 -0
  132. package/dist/core/extensions/builtin/goal/provider-retry.d.ts +9 -0
  133. package/dist/core/extensions/builtin/goal/provider-retry.js +16 -0
  134. package/dist/core/extensions/builtin/goal/reload-reengagement.d.ts +3 -2
  135. package/dist/core/extensions/builtin/goal/reload-reengagement.js +4 -4
  136. package/dist/core/extensions/builtin/goal/session-start-continuation.d.ts +28 -0
  137. package/dist/core/extensions/builtin/goal/session-start-continuation.js +75 -0
  138. package/dist/core/extensions/builtin/goal/store.d.ts +3 -2
  139. package/dist/core/extensions/builtin/goal/store.js +9 -2
  140. package/dist/core/extensions/builtin/goal/types.d.ts +9 -0
  141. package/dist/core/extensions/builtin/handoff-hint/index.js +15 -11
  142. package/dist/core/extensions/builtin/handoff-hint/replacement-relay.d.ts +16 -0
  143. package/dist/core/extensions/builtin/handoff-hint/replacement-relay.js +17 -0
  144. package/dist/core/extensions/builtin/html-render/tool.js +13 -3
  145. package/dist/core/extensions/builtin/imagegen/skill/SKILL.md +1 -1
  146. package/dist/core/extensions/builtin/imagegen/tool.js +4 -14
  147. package/dist/core/extensions/builtin/mcp/auth/catalog-identity.js +32 -8
  148. package/dist/core/extensions/builtin/mcp/auth/commands-auth.js +2 -1
  149. package/dist/core/extensions/builtin/mcp/auth/context.js +3 -0
  150. package/dist/core/extensions/builtin/mcp/auth/oauth-grant.d.ts +43 -0
  151. package/dist/core/extensions/builtin/mcp/auth/oauth-grant.js +196 -0
  152. package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.d.ts +7 -1
  153. package/dist/core/extensions/builtin/mcp/auth/oauth-issuer.js +13 -0
  154. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.d.ts +6 -17
  155. package/dist/core/extensions/builtin/mcp/auth/oauth-provider.js +67 -88
  156. package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.d.ts +1 -1
  157. package/dist/core/extensions/builtin/mcp/auth/oauth-refresh.js +25 -4
  158. package/dist/core/extensions/builtin/mcp/auth/oauth.js +10 -7
  159. package/dist/core/extensions/builtin/mcp/auth/registered-redirect.d.ts +19 -0
  160. package/dist/core/extensions/builtin/mcp/auth/registered-redirect.js +55 -0
  161. package/dist/core/extensions/builtin/mcp/auth/token-store.d.ts +23 -1
  162. package/dist/core/extensions/builtin/mcp/auth/token-store.js +3 -0
  163. package/dist/core/extensions/builtin/mcp/catalog-cache.js +7 -6
  164. package/dist/core/extensions/builtin/mcp/catalog.js +3 -2
  165. package/dist/core/extensions/builtin/mcp/invocation.js +6 -0
  166. package/dist/core/extensions/builtin/mcp/service-register.js +16 -2
  167. package/dist/core/extensions/builtin/mcp/service-tools-changed.js +61 -54
  168. package/dist/core/extensions/builtin/mcp/startup-race.d.ts +2 -0
  169. package/dist/core/extensions/builtin/mcp/startup-race.js +25 -7
  170. package/dist/core/extensions/builtin/permission-system/parsers.js +1 -0
  171. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.js +3 -3
  172. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-2.js +9 -8
  173. package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js +3 -3
  174. package/dist/core/extensions/builtin/prompt-preset/claude-haiku-5-5.js +3 -3
  175. package/dist/core/extensions/builtin/prompt-preset/claude-opus-5-5.js +3 -3
  176. package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.js +4 -4
  177. package/dist/core/extensions/builtin/prompt-preset/claude-sonnet-5-5.js +3 -3
  178. package/dist/core/extensions/builtin/prompt-preset/deepseek-v4-1-flash.js +3 -3
  179. package/dist/core/extensions/builtin/prompt-preset/gpt-5.5.js +3 -3
  180. package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +2 -2
  181. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js +3 -3
  182. package/dist/core/extensions/builtin/prompt-preset/grok-4.5.js +1 -1
  183. package/dist/core/extensions/builtin/prompt-preset/grok-4.6.js +4 -4
  184. package/dist/core/extensions/builtin/prompt-preset/grok-4.7.js +5 -5
  185. package/dist/core/extensions/builtin/prompt-preset/kimi-k3-1.js +4 -4
  186. package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js +7 -6
  187. package/dist/core/extensions/loader.js +24 -4
  188. package/dist/core/extensions/runner.d.ts +9 -1
  189. package/dist/core/extensions/runner.js +9 -4
  190. package/dist/core/extensions/types.d.ts +19 -3
  191. package/dist/core/hand/hook-advertise.d.ts +41 -0
  192. package/dist/core/hand/hook-advertise.js +98 -0
  193. package/dist/core/hand/hook-contract.d.ts +66 -0
  194. package/dist/core/hand/hook-contract.js +138 -0
  195. package/dist/core/open-timing.d.ts +2 -0
  196. package/dist/core/open-timing.js +7 -1
  197. package/dist/core/resource-loader.js +21 -3
  198. package/dist/core/retry-fallback/controller-types.d.ts +2 -0
  199. package/dist/core/retry-fallback/controller.js +9 -2
  200. package/dist/core/retry-fallback/cooldown.d.ts +7 -0
  201. package/dist/core/retry-fallback/cooldown.js +13 -1
  202. package/dist/core/retry-fallback/credential-recovery.d.ts +7 -0
  203. package/dist/core/retry-fallback/credential-recovery.js +24 -0
  204. package/dist/core/retry-fallback/log.d.ts +7 -1
  205. package/dist/core/retry-fallback/log.js +82 -5
  206. package/dist/core/retry-fallback/model-config-error.js +2 -1
  207. package/dist/core/retry-fallback/settings.d.ts +8 -5
  208. package/dist/core/retry-fallback/settings.js +9 -6
  209. package/dist/core/session-manager.d.ts +2 -0
  210. package/dist/core/session-manager.js +15 -1
  211. package/dist/core/settings-manager.d.ts +6 -0
  212. package/dist/core/settings-manager.js +8 -0
  213. package/dist/core/turn-origin.d.ts +18 -10
  214. package/dist/core/turn-origin.js +34 -30
  215. package/dist/core/ultrafast-lanes.d.ts +1 -0
  216. package/dist/core/ultrafast-lanes.js +4 -2
  217. package/dist/modes/interactive/components/footer.d.ts +23 -5
  218. package/dist/modes/interactive/components/footer.js +45 -16
  219. package/dist/modes/interactive/interactive-mode.js +9 -1
  220. package/dist/modes/interactive/session-control-rpc-commands.d.ts +1 -1
  221. package/dist/modes/interactive/session-control-rpc-commands.js +5 -0
  222. package/dist/modes/rpc/admit-external-message-command.d.ts +2 -0
  223. package/dist/modes/rpc/admit-external-message-command.js +5 -1
  224. package/dist/modes/rpc/compact-admission-validation.js +4 -1
  225. package/dist/modes/rpc/connection-authority.d.ts +2 -1
  226. package/dist/modes/rpc/connection-authority.js +11 -9
  227. package/dist/modes/rpc/connection-handler.d.ts +2 -0
  228. package/dist/modes/rpc/connection-handler.js +33 -7
  229. package/dist/modes/rpc/connection-roles.d.ts +7 -0
  230. package/dist/modes/rpc/connection-roles.js +15 -0
  231. package/dist/modes/rpc/custom-capability.d.ts +6 -0
  232. package/dist/modes/rpc/custom-capability.js +6 -0
  233. package/dist/modes/rpc/delegated-drive-origin.d.ts +6 -6
  234. package/dist/modes/rpc/delegated-drive-origin.js +7 -8
  235. package/dist/modes/rpc/delegation-declaration.d.ts +2 -0
  236. package/dist/modes/rpc/delegation-declaration.js +7 -3
  237. package/dist/modes/rpc/effect-commands.d.ts +33 -0
  238. package/dist/modes/rpc/host-capabilities.d.ts +1 -0
  239. package/dist/modes/rpc/host-capabilities.js +7 -1
  240. package/dist/modes/rpc/host-gc-evidence.d.ts +1 -1
  241. package/dist/modes/rpc/host-gc-evidence.js +9 -3
  242. package/dist/modes/rpc/host-gc.js +4 -1
  243. package/dist/modes/rpc/host-observe-request.d.ts +2 -0
  244. package/dist/modes/rpc/host-observe-request.js +6 -1
  245. package/dist/modes/rpc/host-park-ledger.d.ts +19 -0
  246. package/dist/modes/rpc/host-park-ledger.js +110 -0
  247. package/dist/modes/rpc/lifecycle-delivery.js +4 -1
  248. package/dist/modes/rpc/loopless-tool-handlers.d.ts +6 -0
  249. package/dist/modes/rpc/loopless-tool-handlers.js +14 -1
  250. package/dist/modes/rpc/media-placeholders.js +1 -0
  251. package/dist/modes/rpc/method-classes-schema-host.d.ts +42 -1
  252. package/dist/modes/rpc/method-classes-schema-host.js +24 -21
  253. package/dist/modes/rpc/method-classes-schema-session.d.ts +20 -0
  254. package/dist/modes/rpc/method-classes-schema-session.js +6 -0
  255. package/dist/modes/rpc/method-classes-schema.d.ts +62 -1
  256. package/dist/modes/rpc/method-classes-schema.js +5 -2
  257. package/dist/modes/rpc/method-classes.d.ts +4 -0
  258. package/dist/modes/rpc/method-classes.js +6 -0
  259. package/dist/modes/rpc/multi-session-host.js +1 -1
  260. package/dist/modes/rpc/project-file-commands.d.ts +32 -0
  261. package/dist/modes/rpc/project-file-commands.js +195 -0
  262. package/dist/modes/rpc/project-file-path.d.ts +34 -0
  263. package/dist/modes/rpc/project-file-path.js +93 -0
  264. package/dist/modes/rpc/rpc-error-types.d.ts +9 -1
  265. package/dist/modes/rpc/rpc-error-types.js +8 -0
  266. package/dist/modes/rpc/rpc-host-command-types.d.ts +14 -1
  267. package/dist/modes/rpc/rpc-host-response-types.d.ts +21 -0
  268. package/dist/modes/rpc/rpc-mutation-response-types.d.ts +7 -0
  269. package/dist/modes/rpc/rpc-read-response-types.d.ts +7 -0
  270. package/dist/modes/rpc/rpc-session-command-types.d.ts +10 -0
  271. package/dist/modes/rpc/rpc-session-state-types.d.ts +2 -0
  272. package/dist/modes/rpc/session-attachment-gate.js +6 -0
  273. package/dist/modes/rpc/session-binding.d.ts +1 -0
  274. package/dist/modes/rpc/session-binding.js +6 -1
  275. package/dist/modes/rpc/session-command-activity.js +1 -0
  276. package/dist/modes/rpc/session-connection-context.d.ts +2 -0
  277. package/dist/modes/rpc/session-connection-context.js +4 -0
  278. package/dist/modes/rpc/session-registry-attach.d.ts +2 -0
  279. package/dist/modes/rpc/session-registry-attach.js +15 -11
  280. package/dist/modes/rpc/session-registry-claims.js +2 -0
  281. package/dist/modes/rpc/session-registry-open.js +13 -7
  282. package/dist/modes/rpc/session-registry-opening.js +2 -1
  283. package/dist/modes/rpc/session-registry-types.d.ts +4 -0
  284. package/dist/modes/rpc/session-registry.d.ts +3 -0
  285. package/dist/modes/rpc/session-registry.js +27 -3
  286. package/dist/modes/rpc/session-router-close.js +1 -0
  287. package/dist/modes/rpc/session-router-context.d.ts +4 -2
  288. package/dist/modes/rpc/session-router-context.js +3 -0
  289. package/dist/modes/rpc/session-router-host-reads.js +1 -0
  290. package/dist/modes/rpc/session-router-occupancy.js +10 -1
  291. package/dist/modes/rpc/session-router-open-result.d.ts +12 -0
  292. package/dist/modes/rpc/session-router-open-result.js +63 -0
  293. package/dist/modes/rpc/session-router-open.d.ts +3 -0
  294. package/dist/modes/rpc/session-router-open.js +15 -63
  295. package/dist/modes/rpc/session-router-park.d.ts +15 -0
  296. package/dist/modes/rpc/session-router-park.js +176 -0
  297. package/dist/modes/rpc/session-router-routing.d.ts +1 -1
  298. package/dist/modes/rpc/session-router-routing.js +7 -0
  299. package/dist/modes/rpc/session-status-source.js +2 -2
  300. package/dist/private-fd-startup-fillers.d.ts +2 -2
  301. package/dist/private-fd-startup-fillers.js +34 -4
  302. package/dist/senpi +9 -3
  303. package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
  304. package/docs/engine-socket/fixtures/rpc-inbound-samples.json +44 -2
  305. package/docs/extensions.md +11 -1
  306. package/docs/rpc.md +106 -2
  307. package/docs/settings.md +5 -3
  308. package/docs/skills.md +1 -1
  309. package/docs/usage.md +1 -1
  310. package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
  311. package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
  312. package/package.json +6 -6
  313. package/dist/bundle/chunks/chunk-436DNN4Z.js +0 -2
  314. package/dist/bundle/chunks/chunk-DLAY7CIU.js +0 -2
  315. package/dist/bundle/chunks/chunk-SJI6UB23.js +0 -2
  316. package/dist/bundle/chunks/chunk-XH2MWTXA.js +0 -11
  317. package/dist/bundle/chunks/chunk-YK3ZZCHI.js +0 -2
  318. package/dist/bundle/chunks/openai-codex-responses-ABB2J43U.js +0 -10
  319. package/dist/bundle/chunks/rpc-mode-ZDUGW2IM.js +0 -2
  320. package/dist/bundle/chunks/session-control-endpoint-MEDV66PS.js +0 -3
@@ -1,15 +1,22 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { readFileSync } from "node:fs";
3
+ import { Type } from "typebox";
4
+ import { Check } from "typebox/value";
3
5
  import { keyedIdentity } from "../config-identity-key.js";
4
6
  import { resolveAuthMode } from "./context.js";
7
+ import { oauthGrantIdentity } from "./oauth-grant.js";
5
8
  import { McpTokenStore } from "./token-store.js";
6
9
  /** Local snapshot only: never migrates credentials, acquires a lock, or refreshes authentication. */
7
10
  export function mcpCredentialIdentity(config, serverName, agentDir, env) {
8
11
  const material = credentialMaterial(config, serverName, agentDir, env);
9
- return material === undefined || material === "none" ? material : keyedIdentity(agentDir, material);
12
+ return material === undefined || material === "none" || resolveAuthMode(config) === "oauth"
13
+ ? material
14
+ : keyedIdentity(agentDir, material);
10
15
  }
11
16
  /** The unkeyed identity earlier builds persisted; read only to adopt a cache entry for the same credential. */
12
17
  export function legacyMcpCredentialIdentity(config, serverName, agentDir, env) {
18
+ if (resolveAuthMode(config) === "oauth")
19
+ return undefined;
13
20
  const material = credentialMaterial(config, serverName, agentDir, env);
14
21
  return material === undefined || material === "none"
15
22
  ? material
@@ -31,14 +38,15 @@ function credentialMaterial(config, serverName, agentDir, env) {
31
38
  try {
32
39
  const store = new McpTokenStore({ serverName, serverUrl: config.url ?? "", agentDir });
33
40
  const value = JSON.parse(readFileSync(store.tokensPath, "utf8"));
34
- if (typeof value !== "object" || value === null || !("accessToken" in value))
35
- return undefined;
36
- if (typeof value.accessToken !== "string" || value.accessToken.length === 0)
37
- return undefined;
38
- const refreshToken = "refreshToken" in value ? value.refreshToken : undefined;
39
- if (refreshToken !== undefined && typeof refreshToken !== "string")
41
+ if (!Check(storedGrantSchema, value))
40
42
  return undefined;
41
- return JSON.stringify(["mcp-catalog-oauth", value.accessToken, refreshToken]);
43
+ return oauthGrantIdentity(value, {
44
+ serverName,
45
+ serverUrl: store.serverUrl,
46
+ agentDir: store.agentDir,
47
+ clientId: config.oauth?.clientId,
48
+ clientMetadataUrl: config.oauth?.clientMetadataUrl,
49
+ });
42
50
  }
43
51
  catch (error) {
44
52
  if (error instanceof Error)
@@ -46,4 +54,20 @@ function credentialMaterial(config, serverName, agentDir, env) {
46
54
  throw error;
47
55
  }
48
56
  }
57
+ // Parse exactly the stored fields the identity reader consumes; authorization is parsed by its proof owner.
58
+ const storedGrantSchema = Type.Object({
59
+ accessToken: Type.String({ minLength: 1 }),
60
+ refreshToken: Type.Optional(Type.String()),
61
+ issuer: Type.Optional(Type.String()),
62
+ resource: Type.Optional(Type.String()),
63
+ scope: Type.Optional(Type.String()),
64
+ refreshScope: Type.Optional(Type.String()),
65
+ clientInfo: Type.Optional(Type.Object({
66
+ client_id: Type.String(),
67
+ client_secret: Type.Optional(Type.String()),
68
+ token_endpoint_auth_method: Type.Optional(Type.String()),
69
+ issuer: Type.Optional(Type.String()),
70
+ })),
71
+ discoveryState: Type.Optional(Type.Object({ authorizationServerUrl: Type.String() })),
72
+ });
49
73
  //# sourceMappingURL=catalog-identity.js.map
@@ -33,7 +33,8 @@ function buildProvider(deps, redirectUrl, onRedirect) {
33
33
  export async function runAuth(deps) {
34
34
  ensureOAuth(deps);
35
35
  if (deps.config.oauth?.flow === "client_credentials") {
36
- const provider = buildProvider(deps, deps.callbackUrl ?? PASTE_REDIRECT);
36
+ // No authorize request, so no redirect: any stored registration may serve the grant (senpi#6327).
37
+ const provider = buildProvider(deps);
37
38
  await clientCredentialsGrant(provider, deps.flow);
38
39
  await deps.onReconnect();
39
40
  deps.notify(`MCP server ${deps.serverName} authenticated (client_credentials)`);
@@ -34,6 +34,9 @@ export function resolveServerAuth(deps) {
34
34
  // flow and raises UnauthorizedError (-> needs_auth) on a token-less connect,
35
35
  // rather than falling through to a client_credentials token request.
36
36
  redirectUrl: deps.redirectUrl ?? BACKGROUND_REDIRECT,
37
+ // Only a caller that names its redirect sends an authorize request; background connects and refresh keep
38
+ // the stored registration so an existing sign-in keeps refreshing (senpi#6327).
39
+ requireRegisteredRedirect: deps.redirectUrl !== undefined,
37
40
  scopes: deps.config.oauth?.scopes,
38
41
  clientId: deps.config.oauth?.clientId,
39
42
  clientMetadataUrl: deps.config.oauth?.clientMetadataUrl,
@@ -0,0 +1,43 @@
1
+ import type { OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
2
+ import type { McpAuthorizationProof, McpClientRegistration, McpStoredAuth } from "./token-store.ts";
3
+ export type McpGrantRecord = Pick<McpStoredAuth, "accessToken" | "refreshToken" | "issuer" | "resource" | "scope" | "refreshScope"> & {
4
+ readonly authorization?: unknown;
5
+ readonly clientInfo?: Pick<McpClientRegistration, "client_id" | "client_secret" | "token_endpoint_auth_method" | "issuer">;
6
+ readonly discoveryState?: {
7
+ readonly authorizationServerUrl?: string | URL;
8
+ };
9
+ };
10
+ export interface McpGrantContext {
11
+ readonly serverName: string;
12
+ readonly serverUrl: string;
13
+ readonly agentDir: string;
14
+ readonly clientId?: string;
15
+ readonly clientMetadataUrl?: string;
16
+ }
17
+ export declare function storedGrantIssuer(record: McpGrantRecord | undefined): string | undefined;
18
+ export declare function freezeGrantAttribution(record: McpStoredAuth | undefined): McpStoredAuth;
19
+ export declare function storedAuthToTokens(record: McpStoredAuth | undefined, now?: number): OAuthTokens | undefined;
20
+ /** Maps one response; the caller decides which preceding fields it is entitled to inherit. */
21
+ export declare function mergeTokensIntoStoredAuth(current: McpStoredAuth, tokens: OAuthTokens): McpStoredAuth;
22
+ export interface McpTokenResponse {
23
+ readonly tokens: OAuthTokens;
24
+ readonly resource: string;
25
+ readonly fresh: boolean;
26
+ readonly presented?: McpStoredAuth;
27
+ readonly clientInfo?: McpClientRegistration;
28
+ }
29
+ /** An SDK response has no locked predecessor; only explicit fresh acquisition can establish its initial proof. */
30
+ export declare function mergeOAuthTokenResponse(current: McpStoredAuth, context: McpGrantContext, response: McpTokenResponse): McpStoredAuth;
31
+ /** Active binding excludes pending registrations and display/redirect metadata. */
32
+ export declare function grantBinding(record: McpGrantRecord, context: McpGrantContext): {
33
+ issuer: string | undefined;
34
+ resource: string | undefined;
35
+ clientIdentity: string | undefined;
36
+ scope: string | undefined;
37
+ refreshScope: string | undefined;
38
+ };
39
+ export declare function validGrantProof(record: McpGrantRecord, context: McpGrantContext): McpAuthorizationProof | undefined;
40
+ /** Only the refresh transaction passes a predecessor, read while holding its existing token-store lock. */
41
+ export declare function withGrantAuthorization(record: McpStoredAuth, context: McpGrantContext, previous?: McpStoredAuth): McpStoredAuth;
42
+ export declare function oauthGrantIdentity(record: McpGrantRecord, context: McpGrantContext): string;
43
+ //# sourceMappingURL=oauth-grant.d.ts.map
@@ -0,0 +1,196 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { Type } from "typebox";
3
+ import { Check } from "typebox/value";
4
+ import { isEphemeralIdentity, keyedIdentity } from "../config-identity-key.js";
5
+ import { canonicalAuthorizationServerIssuer } from "./oauth-issuer.js";
6
+ const authorizationSchema = Type.Object({
7
+ version: Type.Literal(1),
8
+ id: Type.String({ pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }),
9
+ proof: Type.Optional(Type.Object({
10
+ credentialIdentity: Type.String({ pattern: "^[0-9a-f]{64}$" }),
11
+ previousCredentialIdentity: Type.Optional(Type.String({ pattern: "^[0-9a-f]{64}$" })),
12
+ issuer: Type.String({ minLength: 1 }),
13
+ resource: Type.String({ minLength: 1 }),
14
+ clientIdentity: Type.String({ pattern: "^[0-9a-f]{64}$" }),
15
+ scope: Type.String({ minLength: 1 }),
16
+ refreshScope: Type.Optional(Type.String({ minLength: 1 })),
17
+ })),
18
+ });
19
+ const epochSchema = Type.Object({
20
+ version: Type.Literal(1),
21
+ id: authorizationSchema.properties.id,
22
+ });
23
+ function authorizationEpoch(record) {
24
+ const authorization = record.authorization;
25
+ return Check(epochSchema, authorization) ? authorization.id : undefined;
26
+ }
27
+ function normalizeGrantedScope(scope) {
28
+ if (scope === undefined || !/^[\x21\x23-\x5b\x5d-\x7e]+(?: +[\x21\x23-\x5b\x5d-\x7e]+)*$/.test(scope))
29
+ return undefined;
30
+ return [...new Set(scope.split(" "))].filter(Boolean).sort().join(" ");
31
+ }
32
+ export function storedGrantIssuer(record) {
33
+ return record?.issuer ?? record?.discoveryState?.authorizationServerUrl?.toString();
34
+ }
35
+ export function freezeGrantAttribution(record) {
36
+ const next = { ...(record ?? {}) };
37
+ if (next.issuer !== undefined || (next.accessToken === undefined && next.refreshToken === undefined))
38
+ return next;
39
+ const issuer = storedGrantIssuer(next);
40
+ if (issuer !== undefined)
41
+ next.issuer = issuer;
42
+ else
43
+ delete next.refreshToken;
44
+ return next;
45
+ }
46
+ export function storedAuthToTokens(record, now = Date.now()) {
47
+ if (record?.accessToken === undefined || record.accessToken.length === 0)
48
+ return undefined;
49
+ const expiresIn = record.expiresAt === undefined ? undefined : Math.max(0, Math.ceil((record.expiresAt - now) / 1000));
50
+ const issuer = storedGrantIssuer(record);
51
+ return {
52
+ access_token: record.accessToken,
53
+ refresh_token: issuer === undefined ? undefined : record.refreshToken,
54
+ token_type: "Bearer",
55
+ scope: normalizeGrantedScope(record.scope),
56
+ ...(expiresIn === undefined ? {} : { expires_in: expiresIn }),
57
+ ...(issuer === undefined ? {} : { issuer }),
58
+ };
59
+ }
60
+ /** Maps one response; the caller decides which preceding fields it is entitled to inherit. */
61
+ export function mergeTokensIntoStoredAuth(current, tokens) {
62
+ return {
63
+ ...current,
64
+ accessToken: tokens.access_token,
65
+ refreshToken: tokens.refresh_token ?? current.refreshToken,
66
+ scope: normalizeGrantedScope(tokens.scope),
67
+ expiresAt: tokens.expires_in === undefined ? undefined : Date.now() + tokens.expires_in * 1000,
68
+ issuer: tokens.issuer ?? current.issuer,
69
+ };
70
+ }
71
+ /** An SDK response has no locked predecessor; only explicit fresh acquisition can establish its initial proof. */
72
+ export function mergeOAuthTokenResponse(current, context, response) {
73
+ const { tokens, resource, fresh, presented, clientInfo } = response;
74
+ // Attribute a response to the client selected before the request, never to a later store writer.
75
+ const active = {
76
+ ...current,
77
+ clientInfo,
78
+ pendingClientInfo: current.pendingClientInfo?.client_id === clientInfo?.client_id ? undefined : current.pendingClientInfo,
79
+ };
80
+ const binding = grantBinding({ ...active, issuer: tokens.issuer, resource }, context);
81
+ const canInherit = !fresh &&
82
+ presented !== undefined &&
83
+ JSON.stringify(grantBinding(presented, context)) === JSON.stringify(grantBinding(current, context)) &&
84
+ JSON.stringify(grantBinding(presented, context)) === JSON.stringify(binding) &&
85
+ presented.accessToken === current.accessToken &&
86
+ presented.refreshToken === current.refreshToken &&
87
+ presented.authorization?.id === current.authorization?.id;
88
+ const base = {
89
+ ...active,
90
+ issuer: tokens.issuer,
91
+ resource,
92
+ refreshToken: canInherit ? presented.refreshToken : undefined,
93
+ scope: undefined,
94
+ refreshScope: undefined,
95
+ authorization: undefined,
96
+ };
97
+ const next = mergeTokensIntoStoredAuth(base, tokens);
98
+ return fresh
99
+ ? withGrantAuthorization({ ...next, refreshScope: next.scope }, context)
100
+ : { ...next, authorization: { version: 1, id: randomUUID() } };
101
+ }
102
+ /** Active binding excludes pending registrations and display/redirect metadata. */
103
+ export function grantBinding(record, context) {
104
+ const issuer = storedGrantIssuer(record);
105
+ const registration = record.clientInfo;
106
+ const clientId = context.clientId ?? context.clientMetadataUrl ?? registration?.client_id;
107
+ const configured = context.clientId !== undefined || context.clientMetadataUrl !== undefined;
108
+ const clientIssuer = configured ? issuer : (registration?.issuer ?? issuer);
109
+ const clientIdentity = clientId === undefined
110
+ ? undefined
111
+ : keyedIdentity(context.agentDir, JSON.stringify([
112
+ "mcp-oauth-client-v1",
113
+ clientId,
114
+ configured ? null : (registration?.client_secret ?? null),
115
+ configured ? null : (registration?.token_endpoint_auth_method ?? null),
116
+ clientIssuer === undefined ? null : canonicalAuthorizationServerIssuer(clientIssuer),
117
+ ]));
118
+ return {
119
+ issuer: issuer === undefined ? undefined : canonicalAuthorizationServerIssuer(issuer),
120
+ resource: record.resource,
121
+ clientIdentity,
122
+ scope: normalizeGrantedScope(record.scope),
123
+ refreshScope: normalizeGrantedScope(record.refreshScope),
124
+ };
125
+ }
126
+ function credentialCommitment(record, context, id) {
127
+ const binding = grantBinding(record, context);
128
+ return keyedIdentity(context.agentDir, JSON.stringify([
129
+ "mcp-oauth-credential-v1",
130
+ context.serverName,
131
+ context.serverUrl,
132
+ id ?? null,
133
+ record.accessToken ?? null,
134
+ record.refreshToken ?? null,
135
+ binding.issuer ?? null,
136
+ binding.resource ?? null,
137
+ binding.clientIdentity ?? null,
138
+ binding.scope ?? null,
139
+ binding.refreshScope ?? null,
140
+ ]));
141
+ }
142
+ export function validGrantProof(record, context) {
143
+ const authorization = record.authorization;
144
+ if (!Check(authorizationSchema, authorization) || authorization.proof === undefined)
145
+ return undefined;
146
+ const proof = authorization.proof;
147
+ const binding = grantBinding(record, context);
148
+ if (Object.entries(binding).some(([key, value]) => Reflect.get(proof, key) !== value))
149
+ return undefined;
150
+ return proof.credentialIdentity === credentialCommitment(record, context, authorization.id) ? proof : undefined;
151
+ }
152
+ /** Only the refresh transaction passes a predecessor, read while holding its existing token-store lock. */
153
+ export function withGrantAuthorization(record, context, previous) {
154
+ const predecessor = previous === undefined ? undefined : validGrantProof(previous, context);
155
+ const binding = grantBinding(record, context);
156
+ const sameBinding = predecessor !== undefined &&
157
+ Object.entries(binding).every(([key, value]) => Reflect.get(predecessor, key) === value);
158
+ const id = sameBinding && previous?.authorization !== undefined ? previous.authorization.id : randomUUID();
159
+ const credentialIdentity = credentialCommitment(record, context, id);
160
+ const proof = binding.issuer !== undefined &&
161
+ binding.resource !== undefined &&
162
+ binding.clientIdentity !== undefined &&
163
+ binding.scope !== undefined &&
164
+ !isEphemeralIdentity(credentialIdentity)
165
+ ? {
166
+ ...binding,
167
+ issuer: binding.issuer,
168
+ resource: binding.resource,
169
+ clientIdentity: binding.clientIdentity,
170
+ scope: binding.scope,
171
+ credentialIdentity,
172
+ previousCredentialIdentity: previous === undefined
173
+ ? undefined
174
+ : credentialCommitment(previous, context, authorizationEpoch(previous)),
175
+ }
176
+ : undefined;
177
+ return { ...record, authorization: { version: 1, id, proof } };
178
+ }
179
+ export function oauthGrantIdentity(record, context) {
180
+ const epoch = authorizationEpoch(record);
181
+ const proof = validGrantProof(record, context);
182
+ return proof === undefined
183
+ ? keyedIdentity(context.agentDir, JSON.stringify(["mcp-oauth-opaque-v1", credentialCommitment(record, context, epoch)]))
184
+ : keyedIdentity(context.agentDir, JSON.stringify([
185
+ "mcp-oauth-authorization-v1",
186
+ context.serverName,
187
+ context.serverUrl,
188
+ epoch,
189
+ proof.issuer,
190
+ proof.resource,
191
+ proof.clientIdentity,
192
+ proof.scope,
193
+ proof.refreshScope ?? null,
194
+ ]));
195
+ }
196
+ //# sourceMappingURL=oauth-grant.js.map
@@ -1,6 +1,12 @@
1
- import type { OAuthServerInfo } from "@modelcontextprotocol/sdk/client/auth.js";
1
+ import type { OAuthDiscoveryState, OAuthServerInfo } from "@modelcontextprotocol/sdk/client/auth.js";
2
2
  import type { McpStoredAuth } from "./token-store.ts";
3
3
  export declare function canonicalAuthorizationServerIssuer(issuer: string): string;
4
+ /**
5
+ * Whether a stored discovery record carries its own evidence for the authorization server it names (senpi#4924):
6
+ * that server's metadata, the MCP server's protected-resource metadata naming it, or the MCP server's own origin
7
+ * (the SDK's legacy root fallback). A bare URL from anywhere else would send the SDK to an unverified server.
8
+ */
9
+ export declare function discoveryNamesItsServer(state: OAuthDiscoveryState, serverUrl: string): boolean;
4
10
  export declare function validatedAuthorizationServerIssuer(info: OAuthServerInfo, serverName: string): string;
5
11
  export declare function credentialsMatchIssuer(record: McpStoredAuth | undefined, issuer: string): boolean;
6
12
  //# sourceMappingURL=oauth-issuer.d.ts.map
@@ -3,6 +3,19 @@ import { OAuthFlowError } from "./oauth-errors.js";
3
3
  export function canonicalAuthorizationServerIssuer(issuer) {
4
4
  return issuer.replace(/^(https?:\/\/[^/?#]+)\/$/, "$1");
5
5
  }
6
+ /**
7
+ * Whether a stored discovery record carries its own evidence for the authorization server it names (senpi#4924):
8
+ * that server's metadata, the MCP server's protected-resource metadata naming it, or the MCP server's own origin
9
+ * (the SDK's legacy root fallback). A bare URL from anywhere else would send the SDK to an unverified server.
10
+ */
11
+ export function discoveryNamesItsServer(state, serverUrl) {
12
+ if (state.authorizationServerMetadata !== undefined)
13
+ return true;
14
+ const named = canonicalAuthorizationServerIssuer(String(state.authorizationServerUrl));
15
+ if (named === new URL(serverUrl).origin)
16
+ return true;
17
+ return (state.resourceMetadata?.authorization_servers?.some((server) => canonicalAuthorizationServerIssuer(String(server)) === named) ?? false);
18
+ }
6
19
  // RFC 8414 section 3.3: identifiers from metadata are compared verbatim, not as normalized URLs.
7
20
  export function validatedAuthorizationServerIssuer(info, serverName) {
8
21
  // The SDK's legacy fallback discovers root metadata and spells the origin with a trailing slash.
@@ -1,34 +1,21 @@
1
1
  import type { OAuthClientProvider, OAuthDiscoveryState } from "@modelcontextprotocol/sdk/client/auth.js";
2
2
  import type { OAuthClientInformationFull, OAuthClientInformationMixed, OAuthClientMetadata, OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
3
3
  import type { McpLogger } from "../log.ts";
4
- import type { McpStoredAuth, McpTokenStore } from "./token-store.ts";
5
- export declare const REFRESH_LEEWAY_MS: number;
4
+ import { type McpGrantContext } from "./oauth-grant.ts";
5
+ import type { McpTokenStore } from "./token-store.ts";
6
6
  export interface McpOAuthProviderOptions {
7
7
  serverName: string;
8
8
  serverUrl: string;
9
9
  store: McpTokenStore;
10
10
  redirectUrl?: string | URL;
11
+ /** This flow sends an authorize request: reuse only a stored registration naming `redirectUrl` (senpi#6327). */
12
+ requireRegisteredRedirect?: boolean;
11
13
  scopes?: string[];
12
14
  clientId?: string;
13
15
  clientMetadataUrl?: string;
14
16
  logger?: McpLogger;
15
17
  onRedirect?: (url: URL) => void | Promise<void>;
16
18
  }
17
- export declare function tokenExpiresAt(tokens: OAuthTokens, now?: number): number | undefined;
18
- /**
19
- * The authorization server a stored grant belongs to (senpi#2940, GHSA-6qxp-vccf-f47h): its `issuer` stamp, or
20
- * for a record saved before the stamp existed, the authorization server its sign-in discovered. A record with
21
- * neither cannot be attributed, so its refresh token is never presented anywhere.
22
- */
23
- export declare function storedGrantIssuer(record: McpStoredAuth | undefined): string | undefined;
24
- /**
25
- * Fixes who an unstamped grant belongs to before the discovery record it falls back to is replaced (senpi#2940).
26
- * A later discovery reflects what the MCP server advertises now, not where the grant was issued, so it must never
27
- * attribute an existing grant: the grant keeps the server it was attributed to, or loses its refresh token.
28
- */
29
- export declare function freezeGrantAttribution(record: McpStoredAuth | undefined): McpStoredAuth;
30
- export declare function storedAuthToTokens(record: McpStoredAuth | undefined, now?: number): OAuthTokens | undefined;
31
- export declare function mergeTokensIntoStoredAuth(current: McpStoredAuth | undefined, tokens: OAuthTokens, serverUrl: string, issuer?: string): McpStoredAuth;
32
19
  export declare class McpOAuthProvider implements OAuthClientProvider {
33
20
  #private;
34
21
  constructor(options: McpOAuthProviderOptions);
@@ -45,6 +32,7 @@ export declare class McpOAuthProvider implements OAuthClientProvider {
45
32
  saveDiscoveryState(state: OAuthDiscoveryState): Promise<void>;
46
33
  discoveryState(): OAuthDiscoveryState | undefined;
47
34
  tokens(): OAuthTokens | undefined;
35
+ withFreshAuthorization<T>(exchange: () => Promise<T>): Promise<T>;
48
36
  saveTokens(tokens: OAuthTokens): Promise<void>;
49
37
  saveCodeVerifier(codeVerifier: string): Promise<void>;
50
38
  codeVerifier(): string;
@@ -55,5 +43,6 @@ export declare class McpOAuthProvider implements OAuthClientProvider {
55
43
  get serverUrl(): string;
56
44
  get store(): McpTokenStore;
57
45
  get scopes(): string[] | undefined;
46
+ get grantContext(): McpGrantContext;
58
47
  }
59
48
  //# sourceMappingURL=oauth-provider.d.ts.map
@@ -2,84 +2,9 @@ import { randomBytes } from "node:crypto";
2
2
  import { oauthBrandName } from "@earendil-works/pi-ai/utils/oauth-page";
3
3
  import { fingerprintSecret } from "../log.js";
4
4
  import { OAuthFlowError } from "./oauth-errors.js";
5
- import { canonicalAuthorizationServerIssuer, credentialsMatchIssuer, validatedAuthorizationServerIssuer, } from "./oauth-issuer.js";
6
- // Buffer before absolute token expiry at which a refresh is considered due.
7
- export const REFRESH_LEEWAY_MS = 5 * 60 * 1000;
8
- export function tokenExpiresAt(tokens, now = Date.now()) {
9
- if (tokens.expires_in === undefined)
10
- return undefined;
11
- return now + tokens.expires_in * 1000;
12
- }
13
- /**
14
- * The authorization server a stored grant belongs to (senpi#2940, GHSA-6qxp-vccf-f47h): its `issuer` stamp, or
15
- * for a record saved before the stamp existed, the authorization server its sign-in discovered. A record with
16
- * neither cannot be attributed, so its refresh token is never presented anywhere.
17
- */
18
- export function storedGrantIssuer(record) {
19
- return record?.issuer ?? record?.discoveryState?.authorizationServerUrl?.toString();
20
- }
21
- /**
22
- * Fixes who an unstamped grant belongs to before the discovery record it falls back to is replaced (senpi#2940).
23
- * A later discovery reflects what the MCP server advertises now, not where the grant was issued, so it must never
24
- * attribute an existing grant: the grant keeps the server it was attributed to, or loses its refresh token.
25
- */
26
- export function freezeGrantAttribution(record) {
27
- const next = { ...(record ?? {}) };
28
- if (next.issuer !== undefined || (next.accessToken === undefined && next.refreshToken === undefined))
29
- return next;
30
- const issuer = storedGrantIssuer(next);
31
- if (issuer !== undefined)
32
- next.issuer = issuer;
33
- else
34
- delete next.refreshToken;
35
- return next;
36
- }
37
- /**
38
- * Whether a stored discovery record carries its own evidence for the authorization server it names (senpi#4924):
39
- * that server's metadata, the MCP server's protected-resource metadata naming it, or the MCP server's own origin
40
- * (the SDK's legacy root fallback). A bare URL from anywhere else would send the SDK to an unverified server.
41
- */
42
- function discoveryNamesItsServer(state, serverUrl) {
43
- if (state.authorizationServerMetadata !== undefined)
44
- return true;
45
- const named = canonicalAuthorizationServerIssuer(String(state.authorizationServerUrl));
46
- if (named === new URL(serverUrl).origin)
47
- return true;
48
- return (state.resourceMetadata?.authorization_servers?.some((server) => canonicalAuthorizationServerIssuer(String(server)) === named) ?? false);
49
- }
50
- export function storedAuthToTokens(record, now = Date.now()) {
51
- if (record?.accessToken === undefined || record.accessToken.length === 0)
52
- return undefined;
53
- const expiresIn = record.expiresAt === undefined ? undefined : Math.max(0, Math.ceil((record.expiresAt - now) / 1000));
54
- const issuer = storedGrantIssuer(record);
55
- return {
56
- access_token: record.accessToken,
57
- // An unattributed refresh token is withheld: the SDK would post it to whichever server it discovers.
58
- refresh_token: issuer === undefined ? undefined : record.refreshToken,
59
- token_type: "Bearer",
60
- ...(expiresIn === undefined ? {} : { expires_in: expiresIn }),
61
- ...(issuer === undefined ? {} : { issuer }),
62
- };
63
- }
64
- export function mergeTokensIntoStoredAuth(current, tokens, serverUrl, issuer) {
65
- const next = {
66
- ...(current ?? {}),
67
- accessToken: tokens.access_token,
68
- resource: current?.resource ?? serverUrl,
69
- };
70
- const refreshToken = tokens.refresh_token ?? current?.refreshToken;
71
- if (refreshToken !== undefined)
72
- next.refreshToken = refreshToken;
73
- const expiresAt = tokenExpiresAt(tokens);
74
- if (expiresAt !== undefined)
75
- next.expiresAt = expiresAt;
76
- else
77
- delete next.expiresAt;
78
- const stamp = tokens.issuer ?? issuer;
79
- if (stamp !== undefined)
80
- next.issuer = stamp;
81
- return next;
82
- }
5
+ import { freezeGrantAttribution, grantBinding, mergeOAuthTokenResponse, storedAuthToTokens, storedGrantIssuer, } from "./oauth-grant.js";
6
+ import { canonicalAuthorizationServerIssuer, credentialsMatchIssuer, discoveryNamesItsServer, validatedAuthorizationServerIssuer, } from "./oauth-issuer.js";
7
+ import { grantForFlow, registrationForFlow, withPromotedRegistration, withRegistration, } from "./registered-redirect.js";
83
8
  // Implements the SDK OAuthClientProvider backed by the URL-bound token store.
84
9
  // All persistence goes through the store so tokens survive process restarts and
85
10
  // stay lockable across processes.
@@ -87,6 +12,10 @@ export class McpOAuthProvider {
87
12
  #options;
88
13
  #expectedState;
89
14
  #lastAuthorizationUrl;
15
+ #freshAuthorization = false;
16
+ #resource;
17
+ #presentedGrant;
18
+ #selectedClient;
90
19
  constructor(options) {
91
20
  this.#options = options;
92
21
  }
@@ -133,14 +62,23 @@ export class McpOAuthProvider {
133
62
  return { client_id: this.#options.clientId };
134
63
  if (this.#options.clientMetadataUrl !== undefined)
135
64
  return { client_id: this.#options.clientMetadataUrl };
136
- return this.#store.read()?.clientInfo;
65
+ this.#selectedClient = registrationForFlow(this.#store.read(), this.#options);
66
+ return this.#selectedClient;
137
67
  }
138
68
  async saveClientInformation(info) {
139
69
  const discovery = this.discoveryState();
140
70
  const issuer = discovery === undefined
141
71
  ? info.issuer && canonicalAuthorizationServerIssuer(info.issuer)
142
72
  : validatedAuthorizationServerIssuer(discovery, this.serverName);
143
- await this.#store.update((current) => ({ ...(current ?? {}), clientInfo: { ...info, issuer } }));
73
+ const saved = await this.#store.update((current) => {
74
+ const next = withRegistration(current, { ...info, issuer }, this.#options);
75
+ return current !== undefined &&
76
+ JSON.stringify(grantBinding(current, this.grantContext)) !==
77
+ JSON.stringify(grantBinding(next, this.grantContext))
78
+ ? { ...next, scope: undefined, refreshScope: undefined, authorization: undefined }
79
+ : next;
80
+ });
81
+ this.#selectedClient = registrationForFlow(saved, this.#options);
144
82
  }
145
83
  async saveDiscoveryState(state) {
146
84
  const issuer = validatedAuthorizationServerIssuer(state, this.serverName);
@@ -183,14 +121,37 @@ export class McpOAuthProvider {
183
121
  return state;
184
122
  }
185
123
  tokens() {
186
- return storedAuthToTokens(this.#store.read());
124
+ const grant = grantForFlow(this.#store.read(), this.#options);
125
+ this.#presentedGrant = withPromotedRegistration(grant, this.#options);
126
+ return storedAuthToTokens(grant);
127
+ }
128
+ async withFreshAuthorization(exchange) {
129
+ const previous = this.#freshAuthorization;
130
+ this.#freshAuthorization = true;
131
+ try {
132
+ return await exchange();
133
+ }
134
+ finally {
135
+ this.#freshAuthorization = previous;
136
+ }
187
137
  }
188
138
  async saveTokens(tokens) {
189
139
  const discovery = this.discoveryState();
190
- const issuer = discovery === undefined
191
- ? tokens.issuer && canonicalAuthorizationServerIssuer(tokens.issuer)
192
- : validatedAuthorizationServerIssuer(discovery, this.serverName);
193
- await this.#store.update((current) => mergeTokensIntoStoredAuth(current, { ...tokens, issuer }, this.serverUrl));
140
+ // SDK results carry the exchange's issuer. Never restamp a late response from a newer file snapshot.
141
+ const issuer = tokens.issuer !== undefined
142
+ ? canonicalAuthorizationServerIssuer(tokens.issuer)
143
+ : this.#freshAuthorization && discovery !== undefined
144
+ ? validatedAuthorizationServerIssuer(discovery, this.serverName)
145
+ : storedGrantIssuer(this.#presentedGrant);
146
+ // A flow's staged registration replaces the signed-in client in the same write as its tokens (senpi#6327).
147
+ const response = {
148
+ tokens: { ...tokens, issuer },
149
+ resource: this.#resource ?? new URL(this.serverUrl).href,
150
+ fresh: this.#freshAuthorization,
151
+ presented: this.#presentedGrant,
152
+ clientInfo: this.#selectedClient ?? this.#presentedGrant?.clientInfo,
153
+ };
154
+ this.#presentedGrant = await this.#store.update((current) => mergeOAuthTokenResponse(current ?? {}, this.grantContext, response));
194
155
  this.#logFingerprint("saveTokens", tokens.access_token);
195
156
  }
196
157
  async saveCodeVerifier(codeVerifier) {
@@ -211,7 +172,9 @@ export class McpOAuthProvider {
211
172
  }
212
173
  // RFC 8707: bind the token to this MCP resource.
213
174
  validateResourceURL(serverUrl, resource) {
214
- return Promise.resolve(new URL(resource ?? String(serverUrl)));
175
+ const selected = new URL(resource ?? String(serverUrl));
176
+ this.#resource = selected.href;
177
+ return Promise.resolve(selected);
215
178
  }
216
179
  async invalidateCredentials(scope) {
217
180
  if (scope === "all") {
@@ -227,10 +190,17 @@ export class McpOAuthProvider {
227
190
  delete next.refreshToken;
228
191
  delete next.expiresAt;
229
192
  }
193
+ if (scope === "tokens" || scope === "client") {
194
+ return {
195
+ ...next,
196
+ scope: undefined,
197
+ refreshScope: undefined,
198
+ authorization: undefined,
199
+ ...(scope === "client" ? { clientInfo: undefined } : {}),
200
+ };
201
+ }
230
202
  if (scope === "verifier")
231
203
  next.codeVerifier = undefined;
232
- if (scope === "client")
233
- next.clientInfo = undefined;
234
204
  if (scope === "discovery")
235
205
  next.discoveryState = undefined;
236
206
  return next;
@@ -248,6 +218,15 @@ export class McpOAuthProvider {
248
218
  get scopes() {
249
219
  return this.#options.scopes;
250
220
  }
221
+ get grantContext() {
222
+ return {
223
+ serverName: this.serverName,
224
+ serverUrl: this.serverUrl,
225
+ agentDir: this.store.agentDir,
226
+ clientId: this.#options.clientId,
227
+ clientMetadataUrl: this.#options.clientMetadataUrl,
228
+ };
229
+ }
251
230
  get #store() {
252
231
  return this.#options.store;
253
232
  }
@@ -1,7 +1,7 @@
1
1
  import type { OAuthServerInfo } from "@modelcontextprotocol/sdk/client/auth.js";
2
2
  import type { AuthorizationServerMetadata, OAuthTokens } from "@modelcontextprotocol/sdk/shared/auth.js";
3
3
  import type { FetchLike } from "@modelcontextprotocol/sdk/shared/transport.js";
4
- import { type McpOAuthProvider } from "./oauth-provider.ts";
4
+ import type { McpOAuthProvider } from "./oauth-provider.ts";
5
5
  import type { McpStoredAuth } from "./token-store.ts";
6
6
  export interface RefreshManagerOptions {
7
7
  fetchFn?: FetchLike;