@herbertgao/pi-extensions 2026.9.17 → 2026.9.19

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 (307) hide show
  1. package/README.md +31 -33
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/node_modules/@herbertgao/pi-bark/package.json +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +4 -1
  5. package/node_modules/@herbertgao/pi-cc-extensions/README.md +4 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +21 -6
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +15 -4
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +43 -7
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +7 -2
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +1 -1
  11. package/node_modules/@herbertgao/pi-cc-extensions/package.json +75 -75
  12. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  13. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  14. package/node_modules/@herbertgao/resume-from/package.json +4 -2
  15. package/node_modules/@herbertgao/sol-pi/package.json +6 -6
  16. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +99 -99
  17. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  18. package/node_modules/@tifan/pi-handoff/package.json +1 -1
  19. package/node_modules/@tifan/pi-handoff/src/index.ts +56 -26
  20. package/node_modules/@tifan/pi-mermaid-open/package.json +1 -1
  21. package/node_modules/@tifan/pi-preferred-thinking/package.json +1 -1
  22. package/node_modules/@tifan/pi-recap/package.json +1 -1
  23. package/node_modules/@tifan/pi-rename/package.json +1 -1
  24. package/node_modules/pi-antigravity/README.md +29 -6
  25. package/node_modules/pi-antigravity/package.json +14 -9
  26. package/node_modules/pi-antigravity/src/auth/accounts.ts +327 -0
  27. package/node_modules/pi-antigravity/src/auth/index.ts +9 -0
  28. package/node_modules/pi-antigravity/src/client/client.ts +1 -1
  29. package/node_modules/pi-antigravity/src/image/image.ts +4 -4
  30. package/node_modules/pi-antigravity/src/index.ts +203 -13
  31. package/node_modules/pi-antigravity/src/search/index.ts +1 -0
  32. package/node_modules/pi-antigravity/src/search/search.ts +325 -0
  33. package/node_modules/pi-antigravity/src/stream/stream.ts +191 -14
  34. package/node_modules/pi-antigravity/src/types/enums.ts +45 -36
  35. package/node_modules/pi-antigravity/src/types/types.ts +5 -12
  36. package/node_modules/pi-antigravity/src/utils/http.ts +7 -2
  37. package/node_modules/pi-antigravity/src/utils/util.ts +31 -3
  38. package/node_modules/pi-lens/package.json +158 -158
  39. package/node_modules/pi-multi-account/package.json +2 -2
  40. package/node_modules/pi-typesafe/README.md +3 -1
  41. package/node_modules/pi-typesafe/dist/auth.d.ts +9 -5
  42. package/node_modules/pi-typesafe/dist/auth.js +7 -3
  43. package/node_modules/pi-typesafe/dist/backends.d.ts +33 -3
  44. package/node_modules/pi-typesafe/dist/backends.js +95 -1
  45. package/node_modules/pi-typesafe/dist/client.d.ts +5 -5
  46. package/node_modules/pi-typesafe/dist/client.js +12 -9
  47. package/node_modules/pi-typesafe/dist/credentials.d.ts +14 -8
  48. package/node_modules/pi-typesafe/dist/credentials.js +10 -7
  49. package/node_modules/pi-typesafe/dist/errors.d.ts +2 -2
  50. package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
  51. package/node_modules/pi-typesafe/dist/index.js +1 -1
  52. package/node_modules/pi-typesafe/dist/login.d.ts +2 -2
  53. package/node_modules/pi-typesafe/dist/login.js +2 -2
  54. package/node_modules/pi-typesafe/package.json +6 -5
  55. package/node_modules/pi-web-access/CHANGELOG.md +72 -0
  56. package/node_modules/pi-web-access/README.md +34 -13
  57. package/node_modules/pi-web-access/chrome-cookies.ts +79 -25
  58. package/node_modules/pi-web-access/curator-page.ts +5 -1
  59. package/node_modules/pi-web-access/curator-server.ts +2 -0
  60. package/node_modules/pi-web-access/dist/index.js +1337 -1009
  61. package/node_modules/pi-web-access/exa.ts +20 -52
  62. package/node_modules/pi-web-access/extract.ts +41 -4
  63. package/node_modules/pi-web-access/gemini-search.ts +12 -4
  64. package/node_modules/pi-web-access/index.ts +29 -37
  65. package/node_modules/pi-web-access/openai-search.ts +35 -7
  66. package/node_modules/pi-web-access/package.json +8 -7
  67. package/node_modules/pi-web-access/page-query.ts +2 -2
  68. package/node_modules/pi-web-access/query-rewrite.ts +2 -3
  69. package/node_modules/pi-web-access/ssrf-protection.ts +27 -10
  70. package/node_modules/pi-web-access/storage.ts +48 -77
  71. package/node_modules/pi-web-access/summary-model-scope.ts +6 -77
  72. package/node_modules/pi-web-access/summary-review.ts +3 -4
  73. package/node_modules/pi-web-access/tavily.ts +67 -20
  74. package/node_modules/pi-web-access/tool-activation.ts +53 -33
  75. package/node_modules/pi-web-access/utils.ts +1 -1
  76. package/node_modules/pi-web-access/you.ts +164 -0
  77. package/node_modules/pi-web-access/zai.ts +161 -0
  78. package/node_modules/remote-pi/package.json +5 -1
  79. package/package.json +19 -31
  80. package/node_modules/pi-mcp-adapter/CHANGELOG.md +0 -1013
  81. package/node_modules/pi-mcp-adapter/LICENSE +0 -21
  82. package/node_modules/pi-mcp-adapter/OAUTH.md +0 -369
  83. package/node_modules/pi-mcp-adapter/README.md +0 -1021
  84. package/node_modules/pi-mcp-adapter/abort.ts +0 -34
  85. package/node_modules/pi-mcp-adapter/agent-dir.ts +0 -68
  86. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +0 -416
  87. package/node_modules/pi-mcp-adapter/agent-plugin-provenance.ts +0 -36
  88. package/node_modules/pi-mcp-adapter/app-bridge.bundle.js +0 -67
  89. package/node_modules/pi-mcp-adapter/banner.png +0 -0
  90. package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +0 -193
  91. package/node_modules/pi-mcp-adapter/claude-plugin-loader.ts +0 -365
  92. package/node_modules/pi-mcp-adapter/cli.js +0 -454
  93. package/node_modules/pi-mcp-adapter/commands.ts +0 -861
  94. package/node_modules/pi-mcp-adapter/config.ts +0 -1565
  95. package/node_modules/pi-mcp-adapter/consent-manager.ts +0 -95
  96. package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +0 -244
  97. package/node_modules/pi-mcp-adapter/direct-tools.ts +0 -428
  98. package/node_modules/pi-mcp-adapter/dist/abort.d.ts +0 -2
  99. package/node_modules/pi-mcp-adapter/dist/abort.js +0 -36
  100. package/node_modules/pi-mcp-adapter/dist/abort.js.map +0 -1
  101. package/node_modules/pi-mcp-adapter/dist/agent-dir.d.ts +0 -10
  102. package/node_modules/pi-mcp-adapter/dist/agent-dir.js +0 -63
  103. package/node_modules/pi-mcp-adapter/dist/agent-dir.js.map +0 -1
  104. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.d.ts +0 -8
  105. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js +0 -398
  106. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js.map +0 -1
  107. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.d.ts +0 -1
  108. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js +0 -31
  109. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js.map +0 -1
  110. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +0 -6
  111. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +0 -194
  112. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +0 -1
  113. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.d.ts +0 -9
  114. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js +0 -362
  115. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js.map +0 -1
  116. package/node_modules/pi-mcp-adapter/dist/config.d.ts +0 -134
  117. package/node_modules/pi-mcp-adapter/dist/config.js +0 -1361
  118. package/node_modules/pi-mcp-adapter/dist/config.js.map +0 -1
  119. package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +0 -18
  120. package/node_modules/pi-mcp-adapter/dist/consent-manager.js +0 -88
  121. package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +0 -1
  122. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +0 -17
  123. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +0 -316
  124. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +0 -1
  125. package/node_modules/pi-mcp-adapter/dist/errors.d.ts +0 -131
  126. package/node_modules/pi-mcp-adapter/dist/errors.js +0 -278
  127. package/node_modules/pi-mcp-adapter/dist/errors.js.map +0 -1
  128. package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +0 -8
  129. package/node_modules/pi-mcp-adapter/dist/http-ca.js +0 -88
  130. package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +0 -1
  131. package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +0 -14
  132. package/node_modules/pi-mcp-adapter/dist/jev-client.js +0 -367
  133. package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +0 -1
  134. package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +0 -77
  135. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +0 -2
  136. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +0 -1
  137. package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +0 -51
  138. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +0 -205
  139. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +0 -1
  140. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +0 -2
  141. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +0 -68
  142. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +0 -1
  143. package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +0 -56
  144. package/node_modules/pi-mcp-adapter/dist/lifecycle.js +0 -410
  145. package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +0 -1
  146. package/node_modules/pi-mcp-adapter/dist/logger.d.ts +0 -51
  147. package/node_modules/pi-mcp-adapter/dist/logger.js +0 -131
  148. package/node_modules/pi-mcp-adapter/dist/logger.js.map +0 -1
  149. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +0 -26
  150. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +0 -103
  151. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +0 -1
  152. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +0 -118
  153. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +0 -1097
  154. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +0 -1
  155. package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +0 -170
  156. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +0 -1142
  157. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +0 -1
  158. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.d.ts +0 -24
  159. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +0 -203
  160. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +0 -1
  161. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +0 -53
  162. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +0 -441
  163. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +0 -1
  164. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +0 -148
  165. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +0 -689
  166. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +0 -1
  167. package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +0 -6
  168. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +0 -166
  169. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +0 -1
  170. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +0 -102
  171. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +0 -369
  172. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +0 -1
  173. package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +0 -95
  174. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +0 -242
  175. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +0 -1
  176. package/node_modules/pi-mcp-adapter/dist/metadata-cache.d.ts +0 -19
  177. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +0 -301
  178. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +0 -1
  179. package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +0 -6
  180. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +0 -505
  181. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +0 -1
  182. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.d.ts +0 -2
  183. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js +0 -144
  184. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js.map +0 -1
  185. package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +0 -10
  186. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +0 -312
  187. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +0 -1
  188. package/node_modules/pi-mcp-adapter/dist/resource-tools.d.ts +0 -1
  189. package/node_modules/pi-mcp-adapter/dist/resource-tools.js +0 -15
  190. package/node_modules/pi-mcp-adapter/dist/resource-tools.js.map +0 -1
  191. package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +0 -13
  192. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +0 -97
  193. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +0 -1
  194. package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +0 -17
  195. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +0 -203
  196. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +0 -1
  197. package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +0 -11
  198. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +0 -172
  199. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +0 -1
  200. package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +0 -157
  201. package/node_modules/pi-mcp-adapter/dist/server-manager.js +0 -1745
  202. package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +0 -1
  203. package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +0 -34
  204. package/node_modules/pi-mcp-adapter/dist/session-approvals.js +0 -140
  205. package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +0 -1
  206. package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +0 -26
  207. package/node_modules/pi-mcp-adapter/dist/session-recovery.js +0 -142
  208. package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +0 -1
  209. package/node_modules/pi-mcp-adapter/dist/state.d.ts +0 -73
  210. package/node_modules/pi-mcp-adapter/dist/state.js +0 -2
  211. package/node_modules/pi-mcp-adapter/dist/state.js.map +0 -1
  212. package/node_modules/pi-mcp-adapter/dist/types.d.ts +0 -642
  213. package/node_modules/pi-mcp-adapter/dist/types.js +0 -331
  214. package/node_modules/pi-mcp-adapter/dist/types.js.map +0 -1
  215. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.d.ts +0 -6
  216. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js +0 -37
  217. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js.map +0 -1
  218. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +0 -17
  219. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +0 -219
  220. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +0 -1
  221. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.d.ts +0 -104
  222. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js +0 -59
  223. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js.map +0 -1
  224. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.d.ts +0 -4
  225. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js +0 -27
  226. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js.map +0 -1
  227. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +0 -15
  228. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +0 -85
  229. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +0 -1
  230. package/node_modules/pi-mcp-adapter/dist/utils.d.ts +0 -41
  231. package/node_modules/pi-mcp-adapter/dist/utils.js +0 -417
  232. package/node_modules/pi-mcp-adapter/dist/utils.js.map +0 -1
  233. package/node_modules/pi-mcp-adapter/elicitation-handler.ts +0 -358
  234. package/node_modules/pi-mcp-adapter/error-signal.ts +0 -32
  235. package/node_modules/pi-mcp-adapter/errors.ts +0 -364
  236. package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +0 -85
  237. package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +0 -34
  238. package/node_modules/pi-mcp-adapter/failure-backoff.ts +0 -23
  239. package/node_modules/pi-mcp-adapter/glimpse-ui.ts +0 -80
  240. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -515
  241. package/node_modules/pi-mcp-adapter/http-ca.ts +0 -84
  242. package/node_modules/pi-mcp-adapter/index.ts +0 -2106
  243. package/node_modules/pi-mcp-adapter/init.ts +0 -721
  244. package/node_modules/pi-mcp-adapter/jev-client.ts +0 -300
  245. package/node_modules/pi-mcp-adapter/jev-contracts.ts +0 -53
  246. package/node_modules/pi-mcp-adapter/jev-key-store.ts +0 -192
  247. package/node_modules/pi-mcp-adapter/json-schema-validator.ts +0 -83
  248. package/node_modules/pi-mcp-adapter/lazy-loader.ts +0 -7
  249. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -457
  250. package/node_modules/pi-mcp-adapter/logger.ts +0 -169
  251. package/node_modules/pi-mcp-adapter/mcp-auth-fetch.ts +0 -113
  252. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +0 -1296
  253. package/node_modules/pi-mcp-adapter/mcp-auth.ts +0 -1287
  254. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +0 -250
  255. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +0 -534
  256. package/node_modules/pi-mcp-adapter/mcp-code.ts +0 -495
  257. package/node_modules/pi-mcp-adapter/mcp-install.ts +0 -60
  258. package/node_modules/pi-mcp-adapter/mcp-keyring-helper.cjs +0 -89
  259. package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +0 -819
  260. package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +0 -511
  261. package/node_modules/pi-mcp-adapter/mcp-panel-theme.ts +0 -104
  262. package/node_modules/pi-mcp-adapter/mcp-panel.ts +0 -1099
  263. package/node_modules/pi-mcp-adapter/mcp-probe.ts +0 -201
  264. package/node_modules/pi-mcp-adapter/mcp-references.ts +0 -373
  265. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +0 -137
  266. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +0 -747
  267. package/node_modules/pi-mcp-adapter/mcp-status.ts +0 -105
  268. package/node_modules/pi-mcp-adapter/mcp-tasks.ts +0 -467
  269. package/node_modules/pi-mcp-adapter/mcp-trace.ts +0 -306
  270. package/node_modules/pi-mcp-adapter/metadata-cache.ts +0 -362
  271. package/node_modules/pi-mcp-adapter/namespace-tools.ts +0 -334
  272. package/node_modules/pi-mcp-adapter/npx-resolver.ts +0 -543
  273. package/node_modules/pi-mcp-adapter/oauth-handler.ts +0 -30
  274. package/node_modules/pi-mcp-adapter/oauth.ts +0 -52
  275. package/node_modules/pi-mcp-adapter/onboarding-state.ts +0 -76
  276. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +0 -156
  277. package/node_modules/pi-mcp-adapter/package.json +0 -219
  278. package/node_modules/pi-mcp-adapter/panel-keys.ts +0 -53
  279. package/node_modules/pi-mcp-adapter/prompts.ts +0 -385
  280. package/node_modules/pi-mcp-adapter/proxy-modes.ts +0 -1735
  281. package/node_modules/pi-mcp-adapter/request-headers-command.ts +0 -333
  282. package/node_modules/pi-mcp-adapter/resource-tools.ts +0 -17
  283. package/node_modules/pi-mcp-adapter/runtime-owner.ts +0 -106
  284. package/node_modules/pi-mcp-adapter/sampling-handler.ts +0 -261
  285. package/node_modules/pi-mcp-adapter/sandbox-proxy-template.ts +0 -144
  286. package/node_modules/pi-mcp-adapter/search-ranking.ts +0 -309
  287. package/node_modules/pi-mcp-adapter/secure-keyring.ts +0 -170
  288. package/node_modules/pi-mcp-adapter/semantic-search.ts +0 -186
  289. package/node_modules/pi-mcp-adapter/server-manager.ts +0 -2088
  290. package/node_modules/pi-mcp-adapter/session-approvals.ts +0 -201
  291. package/node_modules/pi-mcp-adapter/session-recovery.ts +0 -158
  292. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +0 -49
  293. package/node_modules/pi-mcp-adapter/state.ts +0 -71
  294. package/node_modules/pi-mcp-adapter/tool-approval.ts +0 -205
  295. package/node_modules/pi-mcp-adapter/tool-metadata.ts +0 -302
  296. package/node_modules/pi-mcp-adapter/tool-registrar.ts +0 -255
  297. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +0 -573
  298. package/node_modules/pi-mcp-adapter/ts-shape.ts +0 -149
  299. package/node_modules/pi-mcp-adapter/types.ts +0 -1120
  300. package/node_modules/pi-mcp-adapter/ui-app-bridge-helpers.ts +0 -40
  301. package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +0 -266
  302. package/node_modules/pi-mcp-adapter/ui-server.ts +0 -993
  303. package/node_modules/pi-mcp-adapter/ui-session.ts +0 -624
  304. package/node_modules/pi-mcp-adapter/ui-stream-types.ts +0 -89
  305. package/node_modules/pi-mcp-adapter/ui-tool-visibility.ts +0 -25
  306. package/node_modules/pi-mcp-adapter/unix-socket-transport.ts +0 -85
  307. package/node_modules/pi-mcp-adapter/utils.ts +0 -464
@@ -1,1296 +0,0 @@
1
- /**
2
- * MCP Auth Flow
3
- *
4
- * High-level OAuth flow management using the MCP SDK's built-in auth functions.
5
- */
6
-
7
- import {
8
- auth as runSdkAuth,
9
- extractWWWAuthenticateParams,
10
- LATEST_PROTOCOL_VERSION,
11
- UnauthorizedError,
12
- validateClientMetadataUrl,
13
- type AuthOptions,
14
- } from "@modelcontextprotocol/client"
15
- import open from "open"
16
- import {
17
- getOAuthCallbackPort,
18
- McpOAuthProvider,
19
- type McpOAuthConfig,
20
- } from "./mcp-oauth-provider.ts"
21
- import {
22
- ensureCallbackServer,
23
- waitForCallback,
24
- cancelPendingCallback,
25
- stopCallbackServer,
26
- stopCallbackServerIfIdle,
27
- releaseCallbackServer,
28
- } from "./mcp-callback-server.ts"
29
- import {
30
- getAuthForUrl,
31
- getAuthEntry,
32
- clearAllCredentials,
33
- clearClientInfo,
34
- clearCodeVerifier,
35
- getOAuthState,
36
- clearOAuthState,
37
- getAuthBaseDir,
38
- beginOAuthRevocation,
39
- captureOAuthAuthority,
40
- OAuthCredentialStoreError,
41
- type AuthStorageOptions,
42
- type OAuthAuthority,
43
- type StoredTokens,
44
- } from "./mcp-auth.ts"
45
- import { isServerDisabled, type ServerEntry } from "./types.ts"
46
- import { formatTerminalError, interpolateEnvVars } from "./utils.ts"
47
- import { createOAuthFetch, oauthHeaderResolver, resolveOAuthHeaders } from "./mcp-auth-fetch.ts"
48
- import { isBuiltInAgentPlugin } from "./agent-plugin-provenance.ts"
49
- import { abortable, throwIfAborted } from "./abort.ts"
50
- import { combineAbortSignals, isAbortError } from "./runtime-owner.ts"
51
-
52
- /** Auth status for a server */
53
- export type AuthStatus = "authenticated" | "expired" | "not_authenticated"
54
-
55
- export interface McpOAuthRuntime {
56
- readonly signal: AbortSignal
57
- }
58
-
59
- export interface AuthenticateOptions {
60
- onAuthorizationUrl?: (authorizationUrl: string) => void | Promise<void>
61
- openAuthorizationUrl?: (authorizationUrl: string) => void | Promise<void>
62
- onAuthorizationInput?: (
63
- authorizationUrl: string,
64
- signal: AbortSignal,
65
- ) => Promise<string | undefined>
66
- authStorageOptions?: AuthStorageOptions
67
- signal?: AbortSignal
68
- runtime?: McpOAuthRuntime
69
- skipIssuerMetadataValidation?: boolean
70
- definition?: Pick<ServerEntry, "headers" | "oauth">
71
- }
72
-
73
- type AuthDiscovery = Pick<AuthOptions, "resourceMetadataUrl" | "scope" | "skipIssuerMetadataValidation">
74
-
75
- function pluginAwareOAuthHeaders(definition?: ServerEntry): () => Headers {
76
- return oauthHeaderResolver(definition?.headers, {
77
- literal: definition ? isBuiltInAgentPlugin(definition, "headers") : false,
78
- })
79
- }
80
-
81
- function applyOAuthConfig(discovery: AuthDiscovery, config: McpOAuthConfig): AuthDiscovery {
82
- return {
83
- ...discovery,
84
- ...(config.scope !== undefined ? { scope: config.scope } : {}),
85
- ...(config.skipIssuerMetadataValidation === true ? { skipIssuerMetadataValidation: true } : {}),
86
- }
87
- }
88
-
89
- type PendingAuth = {
90
- serverName: string
91
- authProvider: McpOAuthProvider
92
- serverUrl: string
93
- authorizationUrl: string
94
- manualRedirect: boolean
95
- manualCompletionController?: AbortController
96
- discovery: AuthDiscovery
97
- getHeaders: () => Headers
98
- authStorageOptions: AuthStorageOptions
99
- authority: OAuthAuthority
100
- }
101
-
102
- type PendingAuthentication = { promise: Promise<AuthStatus>; authority: OAuthAuthority }
103
-
104
- type RuntimeState = {
105
- controller: AbortController
106
- generation: number
107
- pendingAuths: Map<string, PendingAuth>
108
- pendingAuthStates: Map<string, string>
109
- pendingAuthCleanupTimers: Map<string, ReturnType<typeof setTimeout>>
110
- pendingAuthentications: Map<string, PendingAuthentication>
111
- }
112
-
113
- const runtimeStates = new WeakMap<McpOAuthRuntime, RuntimeState>()
114
- const activeRuntimes = new Set<McpOAuthRuntime>()
115
-
116
- export function createOAuthRuntime(signal?: AbortSignal): McpOAuthRuntime {
117
- const controller = new AbortController()
118
- const runtime = { signal: combineAbortSignals(signal, controller.signal)! } satisfies McpOAuthRuntime
119
- runtimeStates.set(runtime, {
120
- controller,
121
- generation: 0,
122
- pendingAuths: new Map(),
123
- pendingAuthStates: new Map(),
124
- pendingAuthCleanupTimers: new Map(),
125
- pendingAuthentications: new Map(),
126
- })
127
- activeRuntimes.add(runtime)
128
- return runtime
129
- }
130
-
131
- let legacyRuntime = createOAuthRuntime()
132
- activeRuntimes.delete(legacyRuntime)
133
-
134
- function getRuntime(options?: AuthenticateOptions): McpOAuthRuntime {
135
- if (options?.runtime) {
136
- options.runtime.signal.throwIfAborted()
137
- activeRuntimes.add(options.runtime)
138
- return options.runtime
139
- }
140
- if (legacyRuntime.signal.aborted) legacyRuntime = createOAuthRuntime()
141
- activeRuntimes.add(legacyRuntime)
142
- return legacyRuntime
143
- }
144
-
145
- function getRuntimeState(runtime: McpOAuthRuntime): RuntimeState {
146
- const state = runtimeStates.get(runtime)
147
- if (!state) throw new Error("Unknown OAuth runtime")
148
- return state
149
- }
150
-
151
- function getAuthStorageIdentity(options: AuthStorageOptions): ["encrypted-file"] | ["os", string] {
152
- return options.credentialStore === "encrypted-file"
153
- ? ["encrypted-file"]
154
- : ["os", getAuthBaseDir(options)]
155
- }
156
-
157
- function getPendingAuthKey(serverName: string, options: AuthStorageOptions): string {
158
- return JSON.stringify([serverName, ...getAuthStorageIdentity(options)])
159
- }
160
-
161
- function hasOAuthAuthority(authority: OAuthAuthority): boolean {
162
- try {
163
- authority()
164
- return true
165
- } catch {
166
- return false
167
- }
168
- }
169
-
170
- export function hasPendingAuth(serverName: string, options?: AuthStorageOptions, runtime?: McpOAuthRuntime): boolean {
171
- const state = getRuntimeState(runtime ?? legacyRuntime)
172
- if (options) {
173
- return state.pendingAuths.has(getPendingAuthKey(serverName, options))
174
- }
175
- return Array.from(state.pendingAuths.values()).some(pendingAuth => pendingAuth.serverName === serverName)
176
- }
177
-
178
- /** Timeout for manual auth completion (5 minutes) */
179
- const MANUAL_AUTH_TIMEOUT_MS = 5 * 60 * 1000
180
-
181
- /**
182
- * Generate a cryptographically secure random state parameter.
183
- */
184
- function generateState(): string {
185
- return Array.from(crypto.getRandomValues(new Uint8Array(32)))
186
- .map((b) => b.toString(16).padStart(2, "0"))
187
- .join("")
188
- }
189
-
190
- /**
191
- * Extract OAuth configuration from a ServerEntry.
192
- */
193
- export function extractOAuthConfig(definition: ServerEntry): McpOAuthConfig {
194
- if (definition.oauth === false) {
195
- return {}
196
- }
197
-
198
- const config: McpOAuthConfig = {}
199
- if (definition.oauth?.grantType !== undefined) config.grantType = definition.oauth.grantType
200
- if (definition.oauth?.clientId !== undefined) {
201
- if (typeof definition.oauth.clientId !== "string") throw new Error("OAuth clientId must be a string")
202
- config.clientId = interpolateEnvVars(definition.oauth.clientId)
203
- }
204
- if (definition.oauth?.clientSecret !== undefined) {
205
- if (typeof definition.oauth.clientSecret !== "string") throw new Error("OAuth clientSecret must be a string")
206
- // Preserve command expressions for the provider; interpolation remains eager for ordinary values.
207
- config.clientSecret = definition.oauth.clientSecret.startsWith("!")
208
- ? definition.oauth.clientSecret
209
- : interpolateEnvVars(definition.oauth.clientSecret)
210
- }
211
- if (definition.oauth?.clientMetadataUrl !== undefined) {
212
- if (typeof definition.oauth.clientMetadataUrl !== "string") {
213
- throw new Error("OAuth clientMetadataUrl must be a string")
214
- }
215
- const clientMetadataUrl = interpolateEnvVars(definition.oauth.clientMetadataUrl).trim()
216
- if (!clientMetadataUrl) {
217
- throw new Error("OAuth clientMetadataUrl must not be empty")
218
- }
219
- validateClientMetadataUrl(clientMetadataUrl)
220
- config.clientMetadataUrl = clientMetadataUrl
221
- }
222
- if (config.clientMetadataUrl !== undefined && config.clientSecret !== undefined && !config.clientId) {
223
- throw new Error("OAuth clientSecret requires an explicit clientId when clientMetadataUrl is configured")
224
- }
225
- if (definition.oauth?.scope !== undefined) {
226
- if (typeof definition.oauth.scope !== "string") throw new Error("OAuth scope must be a string")
227
- config.scope = interpolateEnvVars(definition.oauth.scope)
228
- }
229
- if (definition.oauth?.authorizationParams !== undefined) {
230
- const params = definition.oauth.authorizationParams
231
- if (!params || typeof params !== "object" || Array.isArray(params)) {
232
- throw new Error("OAuth authorizationParams must be an object")
233
- }
234
- config.authorizationParams = {}
235
- for (const [key, value] of Object.entries(params)) {
236
- if (!key) throw new Error("OAuth authorizationParams keys must not be empty")
237
- if (typeof value !== "string") throw new Error(`OAuth authorizationParams.${key} must be a string`)
238
- config.authorizationParams[key] = interpolateEnvVars(value)
239
- }
240
- }
241
- if (definition.oauth?.redirectUri !== undefined) {
242
- if (typeof definition.oauth.redirectUri !== "string") {
243
- throw new Error("OAuth redirectUri must be a string")
244
- }
245
- const redirectUri = interpolateEnvVars(definition.oauth.redirectUri).trim()
246
- if (!redirectUri) {
247
- throw new Error("OAuth redirectUri must not be empty")
248
- }
249
- config.redirectUri = redirectUri
250
- }
251
- if (definition.oauth?.clientName !== undefined) {
252
- if (typeof definition.oauth.clientName !== "string") {
253
- throw new Error("OAuth clientName must be a string")
254
- }
255
- const clientName = interpolateEnvVars(definition.oauth.clientName).trim()
256
- if (!clientName) {
257
- throw new Error("OAuth clientName must not be empty")
258
- }
259
- config.clientName = clientName
260
- }
261
- if (definition.oauth?.clientUri !== undefined) {
262
- if (typeof definition.oauth.clientUri !== "string") {
263
- throw new Error("OAuth clientUri must be a string")
264
- }
265
- const clientUri = interpolateEnvVars(definition.oauth.clientUri).trim()
266
- if (!clientUri) {
267
- throw new Error("OAuth clientUri must not be empty")
268
- }
269
- config.clientUri = clientUri
270
- }
271
- if (definition.oauth?.logoUri !== undefined) {
272
- if (typeof definition.oauth.logoUri !== "string") {
273
- throw new Error("OAuth logoUri must be a string")
274
- }
275
- const logoUri = interpolateEnvVars(definition.oauth.logoUri).trim()
276
- if (!logoUri) {
277
- throw new Error("OAuth logoUri must not be empty")
278
- }
279
- // Consent screens fetch this server-side, so a local path silently renders
280
- // nothing. Fail here instead, where the message can say why.
281
- let parsed: URL
282
- try {
283
- parsed = new URL(logoUri)
284
- } catch {
285
- throw new Error("OAuth logoUri must be an absolute http(s) URL")
286
- }
287
- if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
288
- throw new Error("OAuth logoUri must be an absolute http(s) URL")
289
- }
290
- config.logoUri = logoUri
291
- }
292
- if (definition.oauth?.authServerMetadataUrl !== undefined) {
293
- if (typeof definition.oauth.authServerMetadataUrl !== "string") {
294
- throw new Error("OAuth authServerMetadataUrl must be a string")
295
- }
296
- const authServerMetadataUrl = interpolateEnvVars(definition.oauth.authServerMetadataUrl).trim()
297
- if (!authServerMetadataUrl) {
298
- throw new Error("OAuth authServerMetadataUrl must not be empty")
299
- }
300
- let parsed: URL
301
- try {
302
- parsed = new URL(authServerMetadataUrl)
303
- } catch {
304
- throw new Error("OAuth authServerMetadataUrl must be an absolute https:// URL")
305
- }
306
- if (parsed.protocol !== "https:") {
307
- throw new Error("OAuth authServerMetadataUrl must be an absolute https:// URL")
308
- }
309
- config.authServerMetadataUrl = authServerMetadataUrl
310
- }
311
- if (definition.oauth?.skipIssuerMetadataValidation !== undefined) {
312
- if (typeof definition.oauth.skipIssuerMetadataValidation !== "boolean") {
313
- throw new Error("OAuth skipIssuerMetadataValidation must be a boolean")
314
- }
315
- config.skipIssuerMetadataValidation = definition.oauth.skipIssuerMetadataValidation
316
- }
317
- return config
318
- }
319
-
320
- async function probeAuthDiscovery(serverUrl: string, definition?: ServerEntry, signal?: AbortSignal): Promise<AuthDiscovery> {
321
- // The preliminary probe is command-free; real SDK discovery resolves commands.
322
- const serviceHeaders = resolveOAuthHeaders(definition?.headers, {
323
- commands: false,
324
- literal: definition ? isBuiltInAgentPlugin(definition, "headers") : false,
325
- })
326
- const probeFetch = createOAuthFetch(serverUrl, () => serviceHeaders, signal, { timeout: false })
327
- const headers = new Headers({ "content-type": "application/json" })
328
-
329
- const controller = new AbortController()
330
- const discoverySignal = combineAbortSignals(signal, controller.signal)
331
- const timer = setTimeout(() => controller.abort(), 5000)
332
-
333
- try {
334
- headers.set("accept", "application/json, text/event-stream")
335
-
336
- const response = await probeFetch(new URL(serverUrl), {
337
- method: "POST",
338
- headers,
339
- body: JSON.stringify({
340
- jsonrpc: "2.0",
341
- id: 0,
342
- method: "initialize",
343
- params: {
344
- protocolVersion: LATEST_PROTOCOL_VERSION,
345
- capabilities: {},
346
- clientInfo: { name: "pi-mcp-adapter", version: "2.11.0" },
347
- },
348
- }),
349
- ...(discoverySignal ? { signal: discoverySignal } : {}),
350
- })
351
- const { resourceMetadataUrl, scope } = extractWWWAuthenticateParams(response)
352
- await response.body?.cancel().catch(() => {})
353
- return { ...(resourceMetadataUrl ? { resourceMetadataUrl } : {}), ...(scope ? { scope } : {}) }
354
- } catch (error) {
355
- if (signal?.aborted) throwIfAborted(signal)
356
- return {}
357
- } finally {
358
- clearTimeout(timer)
359
- }
360
- }
361
-
362
- type OAuthRedirectTarget =
363
- | {
364
- mode: "local"
365
- port?: number
366
- callbackHost: string
367
- callbackPath: string
368
- dynamicPort: boolean
369
- resolveRedirectUri: (port: number) => string
370
- }
371
- | { mode: "manual" }
372
-
373
- function parseOAuthRedirectUri(redirectUri: string): OAuthRedirectTarget {
374
- const dynamicPortPlaceholder = "{port}"
375
- const placeholderCount = redirectUri.split(dynamicPortPlaceholder).length - 1
376
- if (placeholderCount > 1) {
377
- throw new Error("OAuth redirectUri may contain at most one {port} placeholder")
378
- }
379
-
380
- let parsedRedirectUri = redirectUri
381
- const dynamicPort = placeholderCount === 1
382
- if (dynamicPort) {
383
- const authorityStart = redirectUri.indexOf("://") + 3
384
- const pathStart = redirectUri.slice(authorityStart).search(/[/?#]/)
385
- const authorityEnd = pathStart === -1 ? redirectUri.length : authorityStart + pathStart
386
- const authority = redirectUri.slice(authorityStart, authorityEnd)
387
- if (authorityStart < 3 || !authority.endsWith(`:${dynamicPortPlaceholder}`)) {
388
- throw new Error("OAuth redirectUri {port} placeholder must be the loopback URI port")
389
- }
390
- // Parse with a real port, then replace the placeholder only after the OS
391
- // assigns the callback listener's port.
392
- parsedRedirectUri = redirectUri.replace(dynamicPortPlaceholder, "1")
393
- }
394
-
395
- let url: URL
396
- try {
397
- url = new URL(parsedRedirectUri)
398
- } catch (error) {
399
- throw new Error(`Invalid OAuth redirectUri: ${redirectUri}`, { cause: error })
400
- }
401
-
402
- if (url.username || url.password) {
403
- throw new Error("OAuth redirectUri must not include username or password")
404
- }
405
-
406
- if (url.hash) {
407
- throw new Error("OAuth redirectUri must not include a fragment")
408
- }
409
-
410
- const hostname = url.hostname.toLowerCase()
411
- const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1"
412
- if (dynamicPort && (url.protocol !== "http:" || !isLocalhost)) {
413
- throw new Error("OAuth redirectUri {port} placeholder is allowed only for an http:// localhost or loopback URI")
414
- }
415
- if (url.port) {
416
- const parsedPort = Number.parseInt(url.port, 10)
417
- if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) {
418
- throw new Error("OAuth redirectUri port must be a positive numeric port")
419
- }
420
- }
421
- if (url.protocol === "https:" && !isLocalhost) {
422
- return { mode: "manual" }
423
- }
424
- if (url.protocol !== "http:" || !isLocalhost) {
425
- throw new Error("OAuth redirectUri must be an https:// URI or an http:// localhost or loopback URI")
426
- }
427
-
428
- if (!url.port) {
429
- throw new Error("OAuth localhost redirectUri must include an explicit numeric port")
430
- }
431
-
432
- const port = Number.parseInt(url.port, 10)
433
- const callbackHost = hostname === "[::1]" ? "::1" : hostname
434
- return {
435
- mode: "local",
436
- ...(dynamicPort ? {} : { port }),
437
- callbackHost,
438
- callbackPath: url.pathname,
439
- dynamicPort,
440
- resolveRedirectUri: assignedPort => dynamicPort
441
- ? redirectUri.replace(dynamicPortPlaceholder, String(assignedPort))
442
- : redirectUri,
443
- }
444
- }
445
-
446
- /**
447
- * Start OAuth authentication flow for a server.
448
- * Returns the authorization URL when browser authorization is required.
449
- */
450
- export async function startAuth(
451
- serverName: string,
452
- serverUrl: string,
453
- definition?: ServerEntry,
454
- options: AuthenticateOptions = {},
455
- operationAuthority?: OAuthAuthority,
456
- ): Promise<{ authorizationUrl: string }> {
457
- if (isServerDisabled(definition)) throw new Error(`MCP server "${serverName}" is disabled`)
458
- const authority = operationAuthority ?? captureOAuthAuthority(serverName)
459
- authority()
460
- const runtime = getRuntime(options)
461
- const runtimeState = getRuntimeState(runtime)
462
- const config = definition ? extractOAuthConfig(definition) : {}
463
- const authStorageOptions = options.authStorageOptions ?? {}
464
- const signal = combineAbortSignals(runtime.signal, options.signal)
465
- const generation = runtimeState.generation
466
- throwIfAborted(signal)
467
-
468
- if (config.grantType === "client_credentials") {
469
- const storedAuth = await getAuthForUrl(serverName, serverUrl, authStorageOptions)
470
- authority()
471
- if (storedAuth?.clientInfo && !storedAuth.tokens && !config.clientId) {
472
- clearClientInfo(serverName, authStorageOptions)
473
- clearCodeVerifier(serverName, authStorageOptions)
474
- clearOAuthState(serverName, authStorageOptions)
475
- }
476
-
477
- authority()
478
- const authProvider = new McpOAuthProvider(serverName, serverUrl, config, {
479
- onRedirect: async () => {
480
- throw new Error("Browser redirect is not used for client_credentials flow")
481
- },
482
- }, authStorageOptions, runtime.signal, undefined, authority)
483
- try {
484
- const fetchFn = createOAuthFetch(serverUrl, pluginAwareOAuthHeaders(definition), signal)
485
- authProvider.setAuthFetch(fetchFn)
486
- const discovery = applyOAuthConfig(await probeAuthDiscovery(serverUrl, definition, signal), config)
487
- authority()
488
- throwIfAborted(signal)
489
- const result = await abortable(runSdkAuth(authProvider, { serverUrl, ...discovery, fetchFn }), signal)
490
- authority()
491
- throwIfAborted(signal)
492
- if (result !== "AUTHORIZED") {
493
- throw new UnauthorizedError("Failed to authorize")
494
- }
495
- return { authorizationUrl: "" }
496
- } finally {
497
- authProvider.deactivate()
498
- }
499
- }
500
-
501
- const existingPendingAuth = runtimeState.pendingAuths.get(getPendingAuthKey(serverName, authStorageOptions))
502
- if (existingPendingAuth?.serverUrl === serverUrl) {
503
- existingPendingAuth.authority()
504
- return { authorizationUrl: existingPendingAuth.authorizationUrl }
505
- }
506
-
507
- const redirectTarget = config.redirectUri !== undefined ? parseOAuthRedirectUri(config.redirectUri) : undefined
508
- const manualRedirect = redirectTarget?.mode === "manual"
509
- const oauthState = generateState()
510
-
511
- if (!manualRedirect) {
512
- try {
513
- await ensureCallbackServer({
514
- strictPort: redirectTarget?.mode === "local"
515
- ? !redirectTarget.dynamicPort
516
- : Boolean(config.clientId),
517
- oauthState,
518
- reserveState: true,
519
- ...(redirectTarget?.mode === "local"
520
- ? {
521
- ...(redirectTarget.port !== undefined ? { port: redirectTarget.port } : {}),
522
- callbackHost: redirectTarget.callbackHost,
523
- callbackPath: redirectTarget.callbackPath,
524
- }
525
- : {}),
526
- })
527
- authority()
528
- throwIfAborted(signal)
529
- if (redirectTarget?.mode === "local" && redirectTarget.dynamicPort) {
530
- config.redirectUri = redirectTarget.resolveRedirectUri(getOAuthCallbackPort())
531
- }
532
- } catch (error) {
533
- releaseCallbackServer(oauthState)
534
- try {
535
- await cleanupAndReleaseCallbackServerIfIdle(() => {
536
- if (hasOAuthAuthority(authority)) clearOAuthState(serverName, authStorageOptions)
537
- })
538
- } catch (cleanupError) {
539
- throw new AggregateError([error, cleanupError], "OAuth startup cleanup failed")
540
- }
541
- throw error
542
- }
543
- }
544
-
545
- let capturedUrl: URL | undefined
546
- authority()
547
- const authProvider = new McpOAuthProvider(serverName, serverUrl, config, {
548
- onRedirect: async (url) => {
549
- capturedUrl = url
550
- },
551
- }, authStorageOptions, runtime.signal, oauthState, authority)
552
-
553
- try {
554
- const storedAuth = await getAuthForUrl(serverName, serverUrl, authStorageOptions)
555
- authority()
556
- if (storedAuth?.clientInfo && !config.clientId) {
557
- if (!storedAuth.tokens) {
558
- clearClientInfo(serverName, authStorageOptions)
559
- clearCodeVerifier(serverName, authStorageOptions)
560
- clearOAuthState(serverName, authStorageOptions)
561
- } else {
562
- const redirectUris = storedAuth.clientInfo.redirectUris
563
- const redirectUriMatches = Array.isArray(redirectUris)
564
- && redirectUris.includes(authProvider.redirectUrl ?? "")
565
- if (!redirectUriMatches && !storedAuth.tokens.refreshToken) {
566
- // A stale redirect URI only blocks the interactive leg; refresh does
567
- // not send redirect_uri, so keep refresh-capable credentials intact.
568
- clearClientInfo(serverName, authStorageOptions)
569
- clearCodeVerifier(serverName, authStorageOptions)
570
- clearOAuthState(serverName, authStorageOptions)
571
- }
572
- }
573
- }
574
-
575
- throwIfAborted(signal)
576
-
577
- const getHeaders = pluginAwareOAuthHeaders(definition)
578
- const fetchFn = createOAuthFetch(serverUrl, getHeaders, signal)
579
- authProvider.setAuthFetch(fetchFn)
580
- const discovery = applyOAuthConfig(await probeAuthDiscovery(serverUrl, definition, signal), config)
581
- authority()
582
- throwIfAborted(signal)
583
- const result = await abortable(runSdkAuth(authProvider, { serverUrl, ...discovery, fetchFn }), signal)
584
- authority()
585
- throwIfAborted(signal)
586
- if (result === "AUTHORIZED") {
587
- authProvider.deactivate()
588
- releaseCallbackServer(oauthState)
589
- clearOAuthState(serverName, authStorageOptions)
590
- await stopCallbackServerIfIdle()
591
- authority()
592
- return { authorizationUrl: "" }
593
- }
594
- if (!capturedUrl) {
595
- throw new UnauthorizedError("OAuth authorization URL was not provided")
596
- }
597
- await setPendingAuth(runtime, serverName, {
598
- serverName,
599
- authProvider,
600
- serverUrl,
601
- authorizationUrl: capturedUrl.toString(),
602
- manualRedirect,
603
- ...(manualRedirect ? { manualCompletionController: new AbortController() } : {}),
604
- discovery,
605
- getHeaders,
606
- authStorageOptions,
607
- authority,
608
- }, oauthState, signal, generation)
609
- authority()
610
- return { authorizationUrl: capturedUrl.toString() }
611
- } catch (error) {
612
- authProvider.deactivate()
613
- try {
614
- await clearPendingAuthAndReleaseIfIdle(runtime, serverName, oauthState, authStorageOptions)
615
- } catch (cleanupError) {
616
- throw new AggregateError([error, cleanupError], "OAuth startup cleanup failed")
617
- }
618
- throw error
619
- }
620
- }
621
-
622
- async function setPendingAuth(
623
- runtime: McpOAuthRuntime,
624
- serverName: string,
625
- pendingAuth: PendingAuth,
626
- oauthState: string,
627
- signal?: AbortSignal,
628
- generation = getRuntimeState(runtime).generation,
629
- ): Promise<void> {
630
- const state = getRuntimeState(runtime)
631
- const key = getPendingAuthKey(serverName, pendingAuth.authStorageOptions)
632
- pendingAuth.authority()
633
- await clearPendingAuth(runtime, serverName, undefined, pendingAuth.authStorageOptions)
634
- pendingAuth.authority()
635
- throwIfAborted(signal)
636
- if (generation !== state.generation) throw new Error("OAuth runtime stopped")
637
- state.pendingAuths.set(key, pendingAuth)
638
- state.pendingAuthStates.set(key, oauthState)
639
- const cleanupTimer = setTimeout(() => {
640
- void clearPendingAuthAndReleaseIfIdle(
641
- runtime,
642
- serverName,
643
- oauthState,
644
- pendingAuth.authStorageOptions,
645
- new Error("OAuth authorization timeout - authorization took too long"),
646
- ).catch(error => {
647
- console.error(`MCP Auth: Timed-out flow cleanup failed: ${formatTerminalError(error)}`)
648
- })
649
- }, MANUAL_AUTH_TIMEOUT_MS)
650
- cleanupTimer.unref?.()
651
- state.pendingAuthCleanupTimers.set(key, cleanupTimer)
652
- }
653
-
654
- async function clearPendingAuth(
655
- runtime: McpOAuthRuntime,
656
- serverName: string,
657
- oauthState?: string,
658
- fallbackStorageOptions: AuthStorageOptions = {},
659
- reason: Error = new Error("Authorization cancelled"),
660
- ): Promise<void> {
661
- const state = getRuntimeState(runtime)
662
- const key = getPendingAuthKey(serverName, fallbackStorageOptions)
663
- const pendingState = state.pendingAuthStates.get(key)
664
- if (oauthState && pendingState !== oauthState) {
665
- cancelPendingCallback(oauthState)
666
- return
667
- }
668
-
669
- const { pendingAuth } = detachPending(state, key, reason)
670
- const authStorageOptions = pendingAuth?.authStorageOptions ?? fallbackStorageOptions
671
- const stateToRelease = pendingState ?? oauthState
672
- if (stateToRelease) {
673
- if (!pendingState) cancelPendingCallback(stateToRelease)
674
- if (pendingAuth && hasOAuthAuthority(pendingAuth.authority)) {
675
- const storedState = getOAuthState(serverName, authStorageOptions)
676
- if (storedState === stateToRelease) clearOAuthState(serverName, authStorageOptions)
677
- }
678
- }
679
- }
680
-
681
- function detachPending(state: RuntimeState, key: string, reason: Error) {
682
- const pendingAuth = state.pendingAuths.get(key)
683
- const oauthState = state.pendingAuthStates.get(key)
684
- const timer = state.pendingAuthCleanupTimers.get(key)
685
- if (timer) clearTimeout(timer)
686
- state.pendingAuthCleanupTimers.delete(key)
687
- state.pendingAuths.delete(key)
688
- state.pendingAuthStates.delete(key)
689
- pendingAuth?.manualCompletionController?.abort(reason)
690
- pendingAuth?.authProvider.deactivate()
691
- if (oauthState) cancelPendingCallback(oauthState)
692
- return { pendingAuth, oauthState }
693
- }
694
-
695
- async function clearPendingAuthAndReleaseIfIdle(
696
- runtime: McpOAuthRuntime,
697
- serverName: string,
698
- oauthState: string | undefined,
699
- fallbackStorageOptions: AuthStorageOptions = {},
700
- reason?: Error,
701
- ): Promise<void> {
702
- await cleanupAndReleaseCallbackServerIfIdle(
703
- () => clearPendingAuth(runtime, serverName, oauthState, fallbackStorageOptions, reason),
704
- )
705
- }
706
-
707
- async function cleanupAndReleaseCallbackServerIfIdle(cleanup: () => void | Promise<void>): Promise<void> {
708
- let cleanupFailure: { error: unknown } | undefined
709
- try {
710
- await cleanup()
711
- } catch (error) {
712
- cleanupFailure = { error }
713
- }
714
-
715
- try {
716
- await stopCallbackServerIfIdle()
717
- } catch (releaseError) {
718
- if (cleanupFailure) {
719
- throw new AggregateError([cleanupFailure.error, releaseError], "OAuth callback cleanup failed")
720
- }
721
- throw releaseError
722
- }
723
-
724
- if (cleanupFailure) throw cleanupFailure.error
725
- }
726
-
727
- function detachPendingAuthsForServer(serverName: string, reason: Error): void {
728
- for (const runtime of activeRuntimes) {
729
- const state = getRuntimeState(runtime)
730
- for (const [key, pendingAuth] of state.pendingAuths) {
731
- if (pendingAuth.serverName !== serverName) continue
732
- if (state.pendingAuths.get(key) !== pendingAuth) continue
733
- detachPending(state, key, reason)
734
- }
735
- }
736
- }
737
-
738
- function getSearchParamsFromInput(input: string): URLSearchParams | undefined {
739
- try {
740
- const url = new URL(input)
741
- const params = new URLSearchParams(url.search)
742
- if (url.hash) {
743
- const hash = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash
744
- const hashParams = new URLSearchParams(hash)
745
- for (const [key, value] of hashParams) {
746
- if (!params.has(key)) params.set(key, value)
747
- }
748
- }
749
- return params
750
- } catch {
751
- const query = input.includes("?") ? input.slice(input.indexOf("?") + 1) : input
752
- const params = new URLSearchParams(query.startsWith("#") ? query.slice(1) : query)
753
- return params.has("code") || params.has("state") || params.has("error") ? params : undefined
754
- }
755
- }
756
-
757
- /** Authorization code plus the optional RFC 9207 `iss` callback parameter. */
758
- export interface AuthorizationCodeInput {
759
- code: string
760
- iss?: string
761
- }
762
-
763
- /**
764
- * Extract an OAuth authorization code (and the RFC 9207 `iss` parameter, when
765
- * present) from either a raw code, a query string, or the full localhost
766
- * redirect URL copied from the browser address bar.
767
- */
768
- export function parseAuthorizationRedirectInput(input: string, expectedState?: string): AuthorizationCodeInput {
769
- const trimmed = input.trim()
770
- if (!trimmed) {
771
- throw new Error("Authorization code or redirect URL is required")
772
- }
773
-
774
- const params = getSearchParamsFromInput(trimmed)
775
- if (params) {
776
- const error = params.get("error")
777
- if (error) {
778
- const description = params.get("error_description")
779
- throw new Error(description ? `${error}: ${description}` : error)
780
- }
781
-
782
- const state = params.get("state")
783
- if (expectedState && !state) {
784
- throw new Error("OAuth state missing from redirect URL")
785
- }
786
- if (expectedState && state !== expectedState) {
787
- throw new Error("OAuth state mismatch - potential CSRF attack")
788
- }
789
-
790
- const code = params.get("code")
791
- if (code) {
792
- const iss = params.get("iss")
793
- return { code, ...(iss !== null ? { iss } : {}) }
794
- }
795
- }
796
-
797
- if (/^[A-Za-z0-9._~+/=-]+$/.test(trimmed)) {
798
- return { code: trimmed }
799
- }
800
-
801
- throw new Error("Could not find an OAuth authorization code in the provided input")
802
- }
803
-
804
- /**
805
- * Extract an OAuth authorization code from either a raw code, a query string,
806
- * or the full localhost redirect URL copied from the browser address bar.
807
- */
808
- export function parseAuthorizationCodeInput(input: string, expectedState?: string): string {
809
- return parseAuthorizationRedirectInput(input, expectedState).code
810
- }
811
-
812
- type AuthorizationResponse = {
813
- input: AuthorizationCodeInput
814
- source: "callback" | "manual"
815
- }
816
-
817
- function waitForManualCompletionCancellation(signal: AbortSignal): Promise<AuthorizationCodeInput> {
818
- return new Promise((_, reject) => {
819
- const rejectFromSignal = () => {
820
- reject(signal.reason instanceof Error ? signal.reason : new Error("Authorization cancelled"))
821
- }
822
- if (signal.aborted) {
823
- rejectFromSignal()
824
- return
825
- }
826
- signal.addEventListener("abort", rejectFromSignal, { once: true })
827
- })
828
- }
829
-
830
- /**
831
- * Wait for either the localhost callback or a manually pasted redirect URL.
832
- * The manual input prompt is dismissed as soon as either path finishes.
833
- */
834
- export async function waitForAuthorizationResponse(
835
- callbackPromise: Promise<AuthorizationCodeInput>,
836
- authorizationUrl: string,
837
- expectedState: string,
838
- onAuthorizationInput?: AuthenticateOptions["onAuthorizationInput"],
839
- signal?: AbortSignal,
840
- ): Promise<AuthorizationResponse> {
841
- if (!onAuthorizationInput) {
842
- return {
843
- input: await abortable(callbackPromise, signal),
844
- source: "callback",
845
- }
846
- }
847
-
848
- const inputController = new AbortController()
849
- try {
850
- const response = await abortable(Promise.race([
851
- callbackPromise.then((input) => ({ input, source: "callback" as const })),
852
- onAuthorizationInput(authorizationUrl, inputController.signal).then((input) => ({
853
- input,
854
- source: "manual" as const,
855
- })),
856
- ]), signal)
857
-
858
- if (response.source === "callback") return response
859
- if (!response.input?.trim()) throw new Error("OAuth authentication cancelled")
860
- if (!getSearchParamsFromInput(response.input.trim())) {
861
- throw new Error("Paste the full OAuth callback URL, including its code and state parameters")
862
- }
863
- return {
864
- input: parseAuthorizationRedirectInput(response.input, expectedState),
865
- source: "manual",
866
- }
867
- } finally {
868
- inputController.abort()
869
- }
870
- }
871
-
872
- /**
873
- * Complete OAuth authentication from manual user input.
874
- */
875
- export async function completeAuthFromInput(
876
- serverName: string,
877
- input: string,
878
- options: AuthenticateOptions = {},
879
- ): Promise<AuthStatus> {
880
- const runtime = getRuntime(options)
881
- const runtimeState = getRuntimeState(runtime)
882
- const fallbackAuthStorageOptions = options.authStorageOptions ?? {}
883
- const signal = combineAbortSignals(runtime.signal, options.signal)
884
- throwIfAborted(signal)
885
- const key = getPendingAuthKey(serverName, fallbackAuthStorageOptions)
886
- const oauthState = runtimeState.pendingAuthStates.get(key)
887
- const pendingAuth = runtimeState.pendingAuths.get(key)
888
- throwIfAborted(signal)
889
- if (pendingAuth?.manualRedirect && !getSearchParamsFromInput(input.trim())) {
890
- throw new Error("Paste the full OAuth callback URL, including its code and state parameters")
891
- }
892
- const parsed = parseAuthorizationRedirectInput(input, oauthState)
893
- return completeAuth(serverName, parsed, options)
894
- }
895
-
896
- /**
897
- * Complete OAuth authentication with the authorization code.
898
- */
899
- export async function completeAuth(
900
- serverName: string,
901
- authorizationCode: string | AuthorizationCodeInput,
902
- options: AuthenticateOptions = {},
903
- ): Promise<AuthStatus> {
904
- const runtime = getRuntime(options)
905
- const runtimeState = getRuntimeState(runtime)
906
- const { code, iss } = typeof authorizationCode === "string"
907
- ? { code: authorizationCode, iss: undefined }
908
- : authorizationCode
909
- const fallbackAuthStorageOptions = options.authStorageOptions ?? {}
910
- const signal = combineAbortSignals(runtime.signal, options.signal)
911
- throwIfAborted(signal)
912
- const key = getPendingAuthKey(serverName, fallbackAuthStorageOptions)
913
- const pendingAuth = runtimeState.pendingAuths.get(key)
914
- const authStorageOptions = pendingAuth?.authStorageOptions ?? fallbackAuthStorageOptions
915
- if (!pendingAuth) {
916
- throw new Error(`No pending OAuth flow for server: ${serverName}`)
917
- }
918
- pendingAuth.authority()
919
-
920
- const oauthState = runtimeState.pendingAuthStates.get(key)
921
- throwIfAborted(signal)
922
-
923
- let keepPendingForRetry = false
924
- let caughtError: unknown
925
- try {
926
- const fetchFn = createOAuthFetch(pendingAuth.serverUrl, pendingAuth.getHeaders, signal)
927
- pendingAuth.authProvider.setAuthFetch(fetchFn)
928
- const discoveryState = await pendingAuth.authProvider.discoveryState()
929
- pendingAuth.authority()
930
- const metadata = discoveryState?.authorizationServerMetadata
931
- const expectedIssuer = metadata?.issuer ?? discoveryState?.authorizationServerUrl
932
- const requiresIssuer = (metadata as { authorization_response_iss_parameter_supported?: unknown } | undefined)
933
- ?.authorization_response_iss_parameter_supported === true
934
- if (expectedIssuer !== undefined && iss === undefined && requiresIssuer) {
935
- keepPendingForRetry = true
936
- throw new Error(
937
- `The authorization server for ${serverName} requires the RFC 9207 "iss" parameter. ` +
938
- "Paste the full redirect URL from the browser address bar (not just the authorization code).",
939
- )
940
- }
941
- if (expectedIssuer !== undefined && iss !== undefined && iss !== expectedIssuer) {
942
- throw new Error(`The OAuth authorization response issuer does not match the discovered issuer for ${serverName}.`)
943
- }
944
-
945
- const result = await abortable(runSdkAuth(pendingAuth.authProvider, {
946
- serverUrl: pendingAuth.serverUrl,
947
- authorizationCode: code,
948
- ...(iss !== undefined ? { iss } : {}),
949
- ...pendingAuth.discovery,
950
- fetchFn,
951
- }), signal)
952
- pendingAuth.authority()
953
- throwIfAborted(signal)
954
- if (result !== "AUTHORIZED") {
955
- throw new UnauthorizedError("Failed to authorize")
956
- }
957
- return "authenticated"
958
- } catch (error) {
959
- caughtError = error
960
- throw error
961
- } finally {
962
- if (!keepPendingForRetry) {
963
- try {
964
- await clearPendingAuthAndReleaseIfIdle(runtime, serverName, oauthState, authStorageOptions)
965
- } catch (cleanupError) {
966
- if (caughtError !== undefined) {
967
- throw new AggregateError([caughtError, cleanupError], "OAuth completion cleanup failed")
968
- }
969
- throw cleanupError
970
- }
971
- if (caughtError === undefined) pendingAuth.authority()
972
- }
973
- }
974
- }
975
-
976
- /**
977
- * Perform the complete OAuth authentication flow for a server.
978
- *
979
- * @param serverName - The name of the MCP server
980
- * @param serverUrl - The URL of the MCP server
981
- * @param definition - The server definition (optional)
982
- * @returns The final auth status
983
- */
984
- export async function authenticate(
985
- serverName: string,
986
- serverUrl: string,
987
- definition?: ServerEntry,
988
- options: AuthenticateOptions = {},
989
- ): Promise<AuthStatus> {
990
- if (isServerDisabled(definition)) throw new Error(`MCP server "${serverName}" is disabled`)
991
- const authority = captureOAuthAuthority(serverName)
992
- const runtime = getRuntime(options)
993
- const runtimeState = getRuntimeState(runtime)
994
- const authStorageOptions = options.authStorageOptions ?? {}
995
- const signal = combineAbortSignals(runtime.signal, options.signal)
996
- throwIfAborted(signal)
997
- const authKey = JSON.stringify([serverName, serverUrl, ...getAuthStorageIdentity(authStorageOptions)])
998
- const inFlight = runtimeState.pendingAuthentications.get(authKey)
999
- if (inFlight) {
1000
- try {
1001
- inFlight.authority()
1002
- return inFlight.promise
1003
- } catch {
1004
- if (runtimeState.pendingAuthentications.get(authKey) === inFlight) {
1005
- runtimeState.pendingAuthentications.delete(authKey)
1006
- }
1007
- }
1008
- }
1009
-
1010
- const operation = (async (): Promise<AuthStatus> => {
1011
- const { authorizationUrl } = await startAuth(serverName, serverUrl, definition, {
1012
- ...options,
1013
- ...(signal ? { signal } : {}),
1014
- runtime,
1015
- }, authority)
1016
- authority()
1017
-
1018
- if (!authorizationUrl) {
1019
- return "authenticated"
1020
- }
1021
-
1022
- let oauthState: string | undefined
1023
- try {
1024
- // Get the state that was already generated and stored in startAuth().
1025
- // Keep this lookup and its abort check inside the cleanup boundary because
1026
- // startAuth already owns the pending flow at this point.
1027
- oauthState = runtimeState.pendingAuthStates.get(getPendingAuthKey(serverName, authStorageOptions))
1028
- throwIfAborted(signal)
1029
- if (!oauthState) {
1030
- throw new Error("OAuth state not found - this should not happen")
1031
- }
1032
-
1033
- const pendingAuth = runtimeState.pendingAuths.get(getPendingAuthKey(serverName, authStorageOptions))
1034
- if (!pendingAuth) {
1035
- throw new Error(`No pending OAuth flow for server: ${serverName}`)
1036
- }
1037
- if (pendingAuth.manualRedirect && !options.onAuthorizationInput) {
1038
- throw new Error(
1039
- `OAuth for ${serverName} uses a remote redirect URI. Complete it with auth-start/auth-complete or /mcp-auth.`,
1040
- )
1041
- }
1042
-
1043
- // Register the localhost callback before opening the browser. Remote
1044
- // pre-registered callbacks are completed by pasting their full URL.
1045
- const callbackPromise: Promise<AuthorizationCodeInput> = pendingAuth.manualRedirect
1046
- ? waitForManualCompletionCancellation(pendingAuth.manualCompletionController!.signal)
1047
- : waitForCallback(oauthState)
1048
- void callbackPromise.catch(() => {})
1049
-
1050
- // Open browser. Always surface the URL first so remote/headless users can copy it
1051
- // even when the OS browser handoff is unavailable or invisible.
1052
- if (options.onAuthorizationUrl) {
1053
- await abortable(Promise.resolve(options.onAuthorizationUrl(authorizationUrl)), signal)
1054
- } else {
1055
- console.log(`MCP Auth: Open this URL to authenticate ${serverName}:\n${authorizationUrl}`)
1056
- }
1057
- try {
1058
- await abortable(Promise.resolve(
1059
- options.openAuthorizationUrl
1060
- ? options.openAuthorizationUrl(authorizationUrl)
1061
- : open(authorizationUrl),
1062
- ), signal)
1063
- } catch (error) {
1064
- if (isAbortError(error, signal)) throw error
1065
- console.warn(`MCP Auth: Failed to open browser for ${serverName}; waiting for manual callback`, { error })
1066
- }
1067
-
1068
- const authorizationResponse = await waitForAuthorizationResponse(
1069
- callbackPromise,
1070
- authorizationUrl,
1071
- oauthState,
1072
- options.onAuthorizationInput,
1073
- signal,
1074
- )
1075
- if (authorizationResponse.source === "manual") {
1076
- cancelPendingCallback(oauthState)
1077
- }
1078
-
1079
- // The callback server accepted only the flow-local reserved state. Manual
1080
- // input is checked against the same state before token exchange.
1081
- throwIfAborted(signal)
1082
-
1083
- return await completeAuth(serverName, authorizationResponse.input, {
1084
- ...options,
1085
- ...(signal ? { signal } : {}),
1086
- runtime,
1087
- })
1088
- } catch (error) {
1089
- if (oauthState) cancelPendingCallback(oauthState)
1090
- try {
1091
- await clearPendingAuthAndReleaseIfIdle(runtime, serverName, oauthState, authStorageOptions)
1092
- } catch (cleanupError) {
1093
- throw new AggregateError([error, cleanupError], "OAuth cancellation cleanup failed")
1094
- }
1095
- throw error
1096
- }
1097
- })()
1098
-
1099
- const pendingAuthentication = { promise: operation, authority }
1100
- runtimeState.pendingAuthentications.set(authKey, pendingAuthentication)
1101
-
1102
- try {
1103
- const result = await operation
1104
- authority()
1105
- return result
1106
- } finally {
1107
- if (runtimeState.pendingAuthentications.get(authKey) === pendingAuthentication) {
1108
- runtimeState.pendingAuthentications.delete(authKey)
1109
- }
1110
- }
1111
- }
1112
-
1113
- /**
1114
- * Get a valid access token for a server, refreshing if necessary.
1115
- *
1116
- * @param serverName - The name of the MCP server
1117
- * @param serverUrl - The URL of the MCP server
1118
- * @returns The valid tokens or null if not authenticated
1119
- */
1120
- export async function getValidToken(
1121
- serverName: string,
1122
- serverUrl: string,
1123
- options: AuthenticateOptions = {},
1124
- ): Promise<StoredTokens | null> {
1125
- const authority = captureOAuthAuthority(serverName)
1126
- const runtime = getRuntime(options)
1127
- const authStorageOptions = options.authStorageOptions ?? {}
1128
- const signal = combineAbortSignals(runtime.signal, options.signal)
1129
- throwIfAborted(signal)
1130
- const entry = await getAuthForUrl(serverName, serverUrl, authStorageOptions)
1131
- if (!hasOAuthAuthority(authority)) return null
1132
- throwIfAborted(signal)
1133
- if (!entry?.tokens) {
1134
- return null
1135
- }
1136
-
1137
- const expired = entry.tokens.expiresAt
1138
- ? entry.tokens.expiresAt < Date.now() / 1000
1139
- : false
1140
- if (expired === false) {
1141
- authority()
1142
- return entry.tokens
1143
- }
1144
-
1145
- if (expired === true && entry.tokens.refreshToken) {
1146
- console.log(`MCP Auth: Token expired for ${serverName}, attempting refresh`)
1147
-
1148
- try {
1149
- const config = options.definition ? extractOAuthConfig(options.definition) : {}
1150
- const fetchFn = createOAuthFetch(serverUrl, pluginAwareOAuthHeaders(options.definition), signal)
1151
- authority()
1152
- const authProvider = new McpOAuthProvider(serverName, serverUrl, config, {
1153
- onRedirect: async () => {},
1154
- }, authStorageOptions, runtime.signal, undefined, authority)
1155
-
1156
- try {
1157
- authProvider.setAuthFetch(fetchFn)
1158
- const clientInfo = await authProvider.clientInformation()
1159
- authority()
1160
- throwIfAborted(signal)
1161
- if (!clientInfo) {
1162
- console.log(`MCP Auth: No client info for refresh for ${serverName}`)
1163
- return null
1164
- }
1165
-
1166
- const discovery = applyOAuthConfig(await probeAuthDiscovery(serverUrl, options.definition, signal), config)
1167
- authority()
1168
- throwIfAborted(signal)
1169
- const result = await abortable(runSdkAuth(authProvider, {
1170
- serverUrl,
1171
- ...discovery,
1172
- ...(options.skipIssuerMetadataValidation === true ? { skipIssuerMetadataValidation: true } : {}),
1173
- fetchFn,
1174
- }), signal)
1175
- authority()
1176
- throwIfAborted(signal)
1177
- if (result !== "AUTHORIZED") {
1178
- return null
1179
- }
1180
- const refreshed = await getAuthForUrl(serverName, serverUrl, authStorageOptions)
1181
- authority()
1182
- throwIfAborted(signal)
1183
- return refreshed?.tokens ?? null
1184
- } finally {
1185
- authProvider.deactivate()
1186
- }
1187
- } catch (error) {
1188
- if (isAbortError(error, signal) || error instanceof OAuthCredentialStoreError) throw error
1189
- console.error(`MCP Auth: Token refresh failed for ${serverName}`)
1190
- return null
1191
- }
1192
- }
1193
-
1194
- authority()
1195
- return null
1196
- }
1197
-
1198
- /**
1199
- * Check the authentication status for a server.
1200
- *
1201
- * @param serverName - The name of the MCP server
1202
- * @returns The current auth status
1203
- */
1204
- export async function getAuthStatus(serverName: string, options: AuthenticateOptions = {}): Promise<AuthStatus> {
1205
- getRuntime(options)
1206
- const authStorageOptions = options.authStorageOptions ?? {}
1207
- const entry = getAuthEntry(serverName, authStorageOptions)
1208
- if (!entry?.tokens) return "not_authenticated"
1209
- return entry.tokens.expiresAt && entry.tokens.expiresAt < Date.now() / 1000 ? "expired" : "authenticated"
1210
- }
1211
-
1212
- /**
1213
- * Remove all OAuth credentials for a server.
1214
- *
1215
- * @param serverName - The name of the MCP server
1216
- */
1217
- export async function removeAuth(serverName: string, options: AuthenticateOptions = {}): Promise<void> {
1218
- const runtime = getRuntime(options)
1219
- const signal = combineAbortSignals(runtime.signal, options.signal)
1220
- throwIfAborted(signal)
1221
- const authStorageOptions = options.authStorageOptions ?? {}
1222
- const releaseRevocation = beginOAuthRevocation(serverName)
1223
- try {
1224
- detachPendingAuthsForServer(serverName, new Error("Authorization cancelled by logout"))
1225
- const storedOAuthState = getOAuthState(serverName, authStorageOptions)
1226
- if (storedOAuthState) cancelPendingCallback(storedOAuthState)
1227
- await stopCallbackServerIfIdle()
1228
- throwIfAborted(signal)
1229
- clearAllCredentials(serverName, authStorageOptions)
1230
- console.log(`MCP Auth: Removed credentials for ${serverName}`)
1231
- } finally {
1232
- releaseRevocation()
1233
- }
1234
- }
1235
-
1236
- /**
1237
- * Check if OAuth is supported for a server configuration.
1238
- * OAuth is supported for HTTP servers unless explicitly disabled.
1239
- *
1240
- * @param definition - The server definition
1241
- * @returns True if OAuth is supported
1242
- */
1243
- export function supportsOAuth(definition: ServerEntry): boolean {
1244
- // OAuth requires a URL
1245
- if (!definition.url) return false
1246
-
1247
- // Explicitly disabled via auth: false or oauth: false
1248
- if (definition.auth === false) return false
1249
- if (definition.oauth === false) return false
1250
- if (definition.auth === "oauth") return true
1251
-
1252
- // Configured custom headers take precedence over implicit OAuth auto-detection.
1253
- if (definition.headers && Object.keys(definition.headers).length > 0) return false
1254
-
1255
- // OAuth is enabled when auth is not specified (auto-detect)
1256
- return definition.auth === undefined
1257
- }
1258
-
1259
- /**
1260
- * Initialize the OAuth system on startup.
1261
- * OAuth callback binding is lazy and starts from startAuth() only.
1262
- */
1263
- export async function initializeOAuth(
1264
- runtimeOrSignal?: McpOAuthRuntime | AbortSignal,
1265
- ): Promise<McpOAuthRuntime> {
1266
- if (runtimeOrSignal && "signal" in runtimeOrSignal) {
1267
- runtimeOrSignal.signal.throwIfAborted()
1268
- activeRuntimes.add(runtimeOrSignal)
1269
- return runtimeOrSignal
1270
- }
1271
-
1272
- await shutdownOAuth(legacyRuntime)
1273
- legacyRuntime = createOAuthRuntime(runtimeOrSignal as AbortSignal | undefined)
1274
- return legacyRuntime
1275
- }
1276
-
1277
- /**
1278
- * Shutdown one OAuth runtime. The callback server remains process-shared while
1279
- * another runtime has pending/reserved callback state or is still active.
1280
- */
1281
- export async function shutdownOAuth(runtime: McpOAuthRuntime = legacyRuntime): Promise<void> {
1282
- const state = getRuntimeState(runtime)
1283
- if (state.controller.signal.aborted) return
1284
- state.generation += 1
1285
- state.controller.abort(new Error("OAuth runtime stopped"))
1286
- for (const callbackState of Array.from(state.pendingAuthStates.values())) cancelPendingCallback(callbackState)
1287
- for (const pendingAuth of Array.from(state.pendingAuths.values())) {
1288
- await clearPendingAuth(runtime, pendingAuth.serverName, undefined, pendingAuth.authStorageOptions)
1289
- }
1290
- state.pendingAuthentications.clear()
1291
- activeRuntimes.delete(runtime)
1292
-
1293
- if (activeRuntimes.size === 0) {
1294
- await stopCallbackServer()
1295
- }
1296
- }