@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,2106 +0,0 @@
1
- import { withFileMutationQueue, type AgentToolUpdateCallback, type ExtensionAPI, type ExtensionContext, type ToolInfo } from "@earendil-works/pi-coding-agent";
2
- import { resolve } from "node:path";
3
- import { existsSync } from "node:fs";
4
- import { fileURLToPath } from "node:url";
5
- import type { McpExtensionState } from "./state.ts";
6
- import { isServerDisabled, type DirectToolSpec, type McpAdapterOptions, type McpConfig, type PromptMetadata, type ServerEntry } from "./types.ts";
7
- import type { McpOAuthRuntime } from "./mcp-auth-flow.ts";
8
- import { Type } from "typebox";
9
- import type { TSchema } from "typebox";
10
- import { cloneMcpConfig, discoverConfiguredClaudePluginSkills, getPiGlobalConfigPath, getProjectConfigPath, loadMcpConfig, resolveConfiguredClaudePluginMcp, writeProjectServerDisabledOverride, writeSharedServerEntry } from "./config.ts";
11
- import { buildProxyDescription, getLargeDirectToolsAdvisory, getMissingConfiguredDirectToolServers, prepareDirectToolArguments, resolveDirectTools } from "./direct-tool-surface.ts";
12
- import { isServerInActiveFailureBackoff } from "./failure-backoff.ts";
13
- import { computeServerHash, isServerCacheValid, loadMetadataCache, parseDirectToolSelectors, type MetadataCache } from "./metadata-cache.ts";
14
- import { createPromptCommand, resolveCachedPrompts } from "./prompts.ts";
15
- import { logger } from "./logger.ts";
16
- import { formatMcpFooterStatus, formatTerminalError, getConfigPathFromArgv, normalizeDirectToolInputSchema, truncateAtWord } from "./utils.ts";
17
- import { createMcpDirectToolCallRenderer, createMcpProxyToolCallRenderer, createMcpScriptToolCallRenderer, createMcpToolResultRenderer, resolveMcpToolRenderOptions } from "./tool-result-renderer.ts";
18
- import { toolErrorOverride } from "./error-signal.ts";
19
- import { createMcpRuntimeOwner, createOwnedUi, isAbortError, type McpRuntimeOwner } from "./runtime-owner.ts";
20
- import { publishMcpStatusShutdown } from "./mcp-status.ts";
21
- import { syncNamespaceProxyTools } from "./namespace-tools.ts";
22
- import { restoreSessionApprovalState } from "./session-approvals.ts";
23
- import { createRetryableLoader } from "./lazy-loader.ts";
24
-
25
- export type { McpAdapterOptions } from "./types.ts";
26
- export type { ServerEntry } from "./types.ts";
27
- export {
28
- namespaceProxyName,
29
- parseMcpReference,
30
- resolveMcpToolReferences,
31
- type McpReferenceResolution,
32
- type ParsedMcpReference,
33
- } from "./mcp-references.ts";
34
- export {
35
- MCP_STATUS_EVENT,
36
- MCP_STATUS_SNAPSHOT_VERSION,
37
- MCP_TOOL_APPROVAL_REQUEST_EVENT,
38
- type McpServerRuntimeStatus,
39
- type McpServerStatusSnapshot,
40
- type McpStatusSnapshot,
41
- type McpToolApprovalDecision,
42
- type McpToolApprovalHandler,
43
- type McpToolApprovalOrigin,
44
- type McpToolApprovalRequest,
45
- } from "./types.ts";
46
-
47
- const loadCoreRuntime = createRetryableLoader(() => import("./init.ts"));
48
- const loadOAuthRuntime = createRetryableLoader(() => import("./mcp-auth-flow.ts"));
49
- const loadProxyModes = createRetryableLoader(() => import("./proxy-modes.ts"));
50
- const loadDirectExecution = createRetryableLoader(() => import("./direct-tools.ts"));
51
- const loadCommands = createRetryableLoader(() => import("./commands.ts"));
52
- const loadCodeMode = createRetryableLoader(() => import("./mcp-code.ts"));
53
- const loadInstallParsing = createRetryableLoader(() => import("./mcp-install.ts"));
54
-
55
- const INIT_WAIT_TIMEOUT_MS = 30_000;
56
- const INIT_FAILURE_MESSAGE_MAX_CHARS = 1_000;
57
- const INIT_WAIT_TIMED_OUT: unique symbol = Symbol("init-wait-timed-out");
58
-
59
- function hasEnabledServerWithoutValidMetadata(
60
- config: McpConfig,
61
- cache: MetadataCache | null,
62
- directSpecs: readonly DirectToolSpec[] = [],
63
- ): boolean {
64
- return Object.entries(config.mcpServers).some(([serverName, definition]) => {
65
- if (isServerDisabled(definition) || directSpecs.some((spec) => spec.serverName === serverName)) return false;
66
- const entry = cache?.servers[serverName];
67
- return entry === undefined || !isServerCacheValid(entry, definition);
68
- });
69
- }
70
-
71
- export interface McpServerRegistration {
72
- dispose(): Promise<void>;
73
- }
74
-
75
- export const MCP_RUNTIME_REGISTER_EVENT = "pi-mcp-adapter:runtime-register:v1" as const;
76
- export const MCP_RUNTIME_REGISTER_VERSION = 1 as const;
77
-
78
- export const MCP_RUNTIME_SNAPSHOT_EVENT = "pi-mcp-adapter:runtime-snapshot:v1" as const;
79
- export const MCP_RUNTIME_SNAPSHOT_VERSION = 1 as const;
80
-
81
- export type McpRuntimeRegistrationResult =
82
- | { ok: true; registration: McpServerRegistration }
83
- | { ok: false; error: Error };
84
-
85
- export interface McpRuntimeRegistrationRequest {
86
- version: typeof MCP_RUNTIME_REGISTER_VERSION;
87
- name: string;
88
- definition: ServerEntry;
89
- result?: McpRuntimeRegistrationResult;
90
- }
91
-
92
- export interface McpRuntimeServerSnapshot {
93
- readonly name: string;
94
- readonly definition: ServerEntry;
95
- readonly runtime: true;
96
- readonly persisted: false;
97
- }
98
-
99
- export type McpRuntimeSnapshotResult =
100
- | { ok: true; snapshot: McpRuntimeServerSnapshot }
101
- | { ok: false; error: Error };
102
-
103
- export interface McpRuntimeSnapshotRequest {
104
- version: typeof MCP_RUNTIME_SNAPSHOT_VERSION;
105
- name: string;
106
- result?: McpRuntimeSnapshotResult;
107
- }
108
-
109
- // Fast path for callers that share the adapter's module and ExtensionAPI.
110
- const runtimeRegistrars = new WeakMap<ExtensionAPI, (name: string, definition: ServerEntry) => McpServerRegistration>();
111
- const runtimeSnapshotters = new WeakMap<ExtensionAPI, (name: string) => McpRuntimeServerSnapshot>();
112
-
113
- function resolveProgrammaticClaudePluginPath(path: string, cwd: string): string {
114
- if (path === "~") return resolve(process.env.HOME ?? "", ".");
115
- if (path.startsWith("~/")) return resolve(process.env.HOME ?? "", path.slice(2));
116
- return resolve(cwd, path);
117
- }
118
-
119
- function normalizeProgrammaticConfig(config: McpConfig): McpConfig {
120
- if (!config.claudePlugins) return config;
121
- const cwd = process.cwd();
122
- return {
123
- ...config,
124
- claudePlugins: config.claudePlugins.map(plugin => ({
125
- ...plugin,
126
- path: resolveProgrammaticClaudePluginPath(plugin.path, cwd),
127
- })),
128
- };
129
- }
130
-
131
- async function awaitWithTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T | typeof INIT_WAIT_TIMED_OUT> {
132
- let timer: ReturnType<typeof setTimeout> | undefined;
133
- try {
134
- return await Promise.race([
135
- promise,
136
- new Promise<typeof INIT_WAIT_TIMED_OUT>((resolve) => {
137
- timer = setTimeout(() => resolve(INIT_WAIT_TIMED_OUT), timeoutMs);
138
- timer.unref?.();
139
- }),
140
- ]);
141
- } finally {
142
- clearTimeout(timer);
143
- }
144
- }
145
-
146
- // TypeBox 1.x annotates raw objects passed to Type.Optional with an enumerable
147
- // "~optional" key that survives serialization into provider tool schemas (Gemini
148
- // rejects it with 400 INVALID_ARGUMENT). Prefer a real Type.Number schema; fall
149
- // back to a plain raw schema for host TypeBox shims that omit Type.Number, since
150
- // a property left out of `required` is optional by default.
151
- function optionalNumber(options: { minimum?: number; description: string }): TSchema {
152
- const number = (Type as { Number?: (opts: typeof options) => TSchema }).Number;
153
- return typeof number === "function"
154
- ? Type.Optional(number(options))
155
- : ({ type: "number", ...options } as unknown as TSchema);
156
- }
157
-
158
- function parseEnvDirectToolOverride(raw: string | undefined): string[] | undefined {
159
- return raw?.split(",").map(s => s.trim()).filter(Boolean);
160
- }
161
-
162
- function resolveNamespaceEnvOverride(
163
- raw: string | undefined,
164
- selectors: string[] | undefined,
165
- ): ReturnType<typeof parseDirectToolSelectors> | null {
166
- if (raw === "__none__") return { servers: new Set<string>(), tools: new Map<string, Set<string>>() };
167
- return selectors ? parseDirectToolSelectors(selectors) : null;
168
- }
169
-
170
- function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
171
- const sessionConfig = options.config !== undefined ? cloneMcpConfig(options.config) : undefined;
172
- const programmaticConfig = sessionConfig !== undefined;
173
- let state: McpExtensionState | null = null;
174
- let initPromise: Promise<McpExtensionState> | null = null;
175
- let initStartedPromise: Promise<void> | null = null;
176
- let currentOwner: McpRuntimeOwner | null = null;
177
- let currentOAuthRuntime: McpOAuthRuntime | null = null;
178
- let lifecycleGeneration = 0;
179
- let retainedInitFailure: string | null = null;
180
- let finalizationGuard: (() => void) | null = null;
181
- let finalizationRegistrations: Set<string> | null = null;
182
-
183
- function callReentrant<T>(callback: () => T): T {
184
- finalizationGuard?.();
185
- const result = callback();
186
- finalizationGuard?.();
187
- return result;
188
- }
189
-
190
- function retainInitFailure(error: unknown): string {
191
- const message = truncateAtWord(formatTerminalError(error), INIT_FAILURE_MESSAGE_MAX_CHARS);
192
- retainedInitFailure = message;
193
- return message;
194
- }
195
-
196
- function clearRetainedInitFailure(): void {
197
- retainedInitFailure = null;
198
- }
199
-
200
- function buildInitRetryInstruction(prefix: string, failure: string | null = retainedInitFailure): string {
201
- const retry = "Fix the MCP server configuration or startup failure, then call mcp(...) again to retry initialization.";
202
- return failure ? `${prefix}: ${failure}. ${retry}` : `${prefix}. ${retry}`;
203
- }
204
-
205
- function isOwnerAbortError(error: unknown, owner: McpRuntimeOwner): boolean {
206
- if (!owner.signal.aborted) return isAbortError(error);
207
- const reason = owner.signal.reason;
208
- if (reason instanceof Error && reason.message === "MCP initialization failed" && error !== reason) {
209
- return isAbortError(error);
210
- }
211
- return true;
212
- }
213
-
214
- interface RuntimeGuard {
215
- generation: number;
216
- owner: McpRuntimeOwner | null;
217
- state: McpExtensionState | null;
218
- }
219
-
220
- function captureRuntimeGuard(expectedState: McpExtensionState | null = state): RuntimeGuard {
221
- return { generation: lifecycleGeneration, owner: currentOwner, state: expectedState };
222
- }
223
-
224
- function assertRuntimeGuard(guard: RuntimeGuard): void {
225
- guard.owner?.throwIfInactive();
226
- if (
227
- lifecycleGeneration !== guard.generation
228
- || currentOwner !== guard.owner
229
- || state !== guard.state
230
- ) {
231
- throw guard.owner?.signal.reason ?? new Error("MCP operation belongs to a stale session");
232
- }
233
- }
234
-
235
- function isRuntimeGuardStale(guard: RuntimeGuard): boolean {
236
- return guard.owner?.isActive() === false
237
- || lifecycleGeneration !== guard.generation
238
- || currentOwner !== guard.owner
239
- || state !== guard.state;
240
- }
241
-
242
- async function loadForRuntime<T>(loader: () => Promise<T>, guard: RuntimeGuard): Promise<T> {
243
- const loaded = await loader();
244
- assertRuntimeGuard(guard);
245
- return loaded;
246
- }
247
-
248
- function createCommandContext(ctx: ExtensionContext, owner: McpRuntimeOwner | null): ExtensionContext {
249
- const commandCtx = Object.create(ctx) as ExtensionContext;
250
- const hasUI = ctx.hasUI;
251
- Object.defineProperties(commandCtx, {
252
- hasUI: { value: hasUI, enumerable: true },
253
- ui: {
254
- value: hasUI ? owner ? createOwnedUi(ctx.ui, owner) : ctx.ui : undefined,
255
- enumerable: true,
256
- },
257
- signal: { value: owner?.signal ?? ctx.signal, enumerable: true },
258
- });
259
- return commandCtx;
260
- }
261
-
262
- function startGatewayRetryInitialization(ctx: ExtensionContext): void {
263
- const generation = ++lifecycleGeneration;
264
- const owner = createMcpRuntimeOwner();
265
- currentOwner = owner;
266
- currentOAuthRuntime = null;
267
- state = null;
268
- startInitialization(ctx, owner, generation, "stale_gateway_retry_initialization");
269
- }
270
-
271
- async function shutdownState(currentState: McpExtensionState | null, reason: string, publishStatus = true): Promise<void> {
272
- if (!currentState) {
273
- if (publishStatus) publishMcpStatusShutdown(pi.events);
274
- return;
275
- }
276
-
277
- if (publishStatus) publishMcpStatusShutdown(currentState.statusEvents);
278
-
279
- if (currentState.uiServer) {
280
- currentState.uiServer.close(reason);
281
- currentState.uiServer = null;
282
- }
283
-
284
- let flushError: unknown;
285
- try {
286
- const { flushMetadataCache } = await loadCoreRuntime();
287
- flushMetadataCache(currentState);
288
- } catch (error) {
289
- flushError = error;
290
- }
291
-
292
- try {
293
- if (currentState.owner) {
294
- await currentState.owner.stop(reason);
295
- } else {
296
- await currentState.lifecycle.gracefulShutdown();
297
- }
298
- } catch (error) {
299
- if (flushError) {
300
- console.error(`MCP: graceful shutdown failed after metadata flush error: ${formatTerminalError(error)}`);
301
- } else {
302
- throw error;
303
- }
304
- }
305
-
306
- if (flushError) {
307
- throw flushError;
308
- }
309
- }
310
-
311
- function restoreCurrentSessionApprovals(targetState: McpExtensionState): void {
312
- const sessionManager = targetState.sessionManager;
313
- if (!sessionManager) return;
314
-
315
- let branch: readonly unknown[] = [];
316
- try {
317
- branch = sessionManager.getBranch();
318
- } catch (error) {
319
- const detail = error instanceof Error ? error.message : String(error);
320
- logger.debug(`MCP: could not read the active session branch for approval restore: ${detail}`);
321
- }
322
- restoreSessionApprovalState(targetState, branch);
323
- }
324
-
325
- const earlyConfigPath = programmaticConfig
326
- ? undefined
327
- : options.configPath ?? getConfigPathFromArgv();
328
- const earlyConfig = programmaticConfig
329
- ? resolveConfiguredClaudePluginMcp(cloneMcpConfig(sessionConfig), process.cwd())
330
- : loadMcpConfig(earlyConfigPath);
331
- const earlyCache = loadMetadataCache();
332
- const envRaw = process.env.MCP_DIRECT_TOOLS;
333
- const envDirectToolOverride = parseEnvDirectToolOverride(envRaw);
334
- const namespaceEnvOverride = resolveNamespaceEnvOverride(envRaw, envDirectToolOverride);
335
- const hasStartupServer = Object.values(earlyConfig.mcpServers).some((definition) =>
336
- !isServerDisabled(definition) && (definition.lifecycle === "eager" || definition.lifecycle === "keep-alive"));
337
- const registeredDirectTools = new Map<string, string>();
338
- const registeredDirectToolServers = new Map<string, string>();
339
- const registeredDirectToolVersions = new Map<string, number>();
340
- // Overlapping connect results consume each server's discovery names once.
341
- // Removal clears the record so stale/fallback reactivation is reportable.
342
- const reportedDirectToolNamesByServer = new Map<string, Set<string>>();
343
- const registeredNamespaceProxyTools = new Set<string>();
344
- const fallbackDeactivatedTools = new Set<string>();
345
- // directTools: "search" — registered inactive, activated by mcp({ search }) or a successful mcp({ tool }) call.
346
- const lazyDirectTools = new Set<string>();
347
- const searchActivatedTools = new Set<string>();
348
- const toolRenderOptions = resolveMcpToolRenderOptions(earlyConfig.settings);
349
- const toolRenderShell = toolRenderOptions.resultRendering === "compact" ? "self" : "default";
350
- const renderMcpToolResult = createMcpToolResultRenderer(toolRenderOptions);
351
- let proxyToolRegistered = false;
352
- let proxyToolDescription: string | null = null;
353
- let directToolsFrozen = false;
354
- let largeDirectToolsAdvisoryDelivered = false;
355
- // Session/runtime scoped server registrations from other extensions. They
356
- // survive session restarts within this install and die with the process.
357
- const runtimeServers = new Map<string, { definition: ServerEntry; entry: ServerEntry }>();
358
-
359
- // Mirrors init's per-server lifecycle registration so runtime servers get
360
- // idle cleanup and keep-alive health recovery like configured servers.
361
- function attachRuntimeServerLifecycle(targetState: McpExtensionState, name: string, definition: ServerEntry): void {
362
- const lifecycleMode = definition.lifecycle ?? "lazy";
363
- const persistsAfterFirstSpawn = lifecycleMode === "eager" || lifecycleMode === "lazy-keep-alive";
364
- const idleOverride = definition.idleTimeout ?? (persistsAfterFirstSpawn ? 0 : undefined);
365
- targetState.lifecycle.registerServer(name, definition, idleOverride !== undefined ? { idleTimeout: idleOverride } : undefined);
366
- if (lifecycleMode === "keep-alive") targetState.lifecycle.markKeepAlive(name, definition);
367
- }
368
-
369
- // OMP remaps `typebox` to a host shim that historically lacked Type.Unsafe.
370
- // Prefer Unsafe when present (real TypeBox / fixed OMP shim); otherwise pass
371
- // the normalized JSON Schema through as a plain object so toolWireSchema and
372
- // validateToolArguments still treat it as JSON Schema.
373
- const toToolParameters = (schema: Record<string, unknown>) =>
374
- typeof (Type as { Unsafe?: (value: never) => unknown }).Unsafe === "function"
375
- ? (Type as { Unsafe: (value: never) => unknown }).Unsafe(schema as never)
376
- : schema;
377
-
378
- function directToolFingerprint(spec: DirectToolSpec): string {
379
- return JSON.stringify({
380
- serverName: spec.serverName,
381
- originalName: spec.originalName,
382
- prefixedName: spec.prefixedName,
383
- description: spec.description,
384
- inputSchema: spec.inputSchema,
385
- resourceUri: spec.resourceUri,
386
- uiResourceUri: spec.uiResourceUri,
387
- uiStreamMode: spec.uiStreamMode,
388
- // A mode-only change (search ↔ eager) must re-register, or the tool
389
- // keeps the activation behavior of the mode it was registered under.
390
- lazy: spec.lazy === true,
391
- });
392
- }
393
-
394
- function forgetReportedDirectToolName(serverName: string | undefined, toolName: string): void {
395
- if (!serverName) return;
396
- const reportedNames = reportedDirectToolNamesByServer.get(serverName);
397
- if (!reportedNames) return;
398
- reportedNames.delete(toolName);
399
- if (reportedNames.size === 0) reportedDirectToolNamesByServer.delete(serverName);
400
- }
401
-
402
- function registerDirectTool(spec: DirectToolSpec, config: McpConfig): void {
403
- finalizationRegistrations?.add(spec.prefixedName);
404
- callReentrant(() => (pi.registerTool as (tool: unknown) => unknown)({
405
- name: spec.prefixedName,
406
- label: `MCP: ${spec.originalName}`,
407
- description: spec.description || "(no description)",
408
- promptSnippet: truncateAtWord(spec.description, 100) || `MCP tool from ${spec.serverName}`,
409
- parameters: toToolParameters(normalizeDirectToolInputSchema(spec.inputSchema)),
410
- ...(config.settings?.strictDirectToolArguments === true
411
- ? { prepareArguments: (args: unknown) => prepareDirectToolArguments(spec.inputSchema, args) }
412
- : {}),
413
- async execute(toolCallId: string, params: Record<string, unknown>, signal: AbortSignal | undefined, onUpdate: AgentToolUpdateCallback<Record<string, unknown>> | undefined, ctx: ExtensionContext) {
414
- let executor: ReturnType<(typeof import("./direct-tools.ts"))["createDirectToolExecutor"]>;
415
- let guard: RuntimeGuard | undefined;
416
- try {
417
- const targetState = await ensureSessionRuntime(ctx);
418
- if (!targetState) throw new Error("MCP not initialized");
419
- guard = captureRuntimeGuard(targetState);
420
- const executionGuard = guard;
421
- const { createDirectToolExecutor } = await loadForRuntime(loadDirectExecution, executionGuard);
422
- executor = createDirectToolExecutor(() => executionGuard.state, () => initPromise, spec);
423
- } catch (error) {
424
- if (guard && (isRuntimeGuardStale(guard) || (guard.owner && isOwnerAbortError(error, guard.owner)))) throw error;
425
- const message = error instanceof Error ? error.message : String(error);
426
- return {
427
- content: [{ type: "text" as const, text: `MCP initialization failed for ${spec.serverName}: ${message}` }],
428
- details: { error: "init_failed", server: spec.serverName, message },
429
- };
430
- }
431
- if (!guard) throw new Error("MCP runtime guard unavailable");
432
- assertRuntimeGuard(guard);
433
- return executor(toolCallId, params, signal, onUpdate, ctx);
434
- },
435
- renderShell: toolRenderShell,
436
- renderCall: createMcpDirectToolCallRenderer(spec.prefixedName, toolRenderOptions),
437
- renderResult: renderMcpToolResult,
438
- }));
439
- }
440
-
441
- // Pi registers a tool active. A lazy tool must not stay that way: hold every
442
- // lazy tool that search has not activated out of the active set. Safe to call
443
- // repeatedly; a no-op until Pi's action methods are available.
444
- function holdLazyToolsInactive(): void {
445
- if (lazyDirectTools.size === 0) return;
446
- const activeTools = getActiveToolsIfReady();
447
- if (!activeTools) return;
448
- const next = activeTools.filter((name) => !lazyDirectTools.has(name) || searchActivatedTools.has(name));
449
- if (next.length !== activeTools.length) pi.setActiveTools(next);
450
- }
451
-
452
- /**
453
- * Activate lazy direct tools additively — the ones a search matched, or the
454
- * one a successful proxy call named. This is the one place a search-mode tool
455
- * becomes active; nothing is ever deactivated here. Returns the names that
456
- * actually changed state so the result can report only real additions.
457
- */
458
- function activateSearchMatches(matches: ReadonlyArray<{ server: string; tool: string }>): string[] {
459
- const activeTools = getActiveToolsIfReady();
460
- if (!activeTools) return [];
461
- const activeSet = new Set(activeTools);
462
- // executeSearch reports ToolMetadata.name, which is the prefixed name a
463
- // direct tool is registered under — the same key the lazy set holds.
464
- const added: string[] = [];
465
- for (const match of matches) {
466
- const name = match.tool;
467
- if (!lazyDirectTools.has(name) || registeredDirectToolServers.get(name) !== match.server) continue;
468
- if (activeSet.has(name) || added.includes(name)) continue;
469
- added.push(name);
470
- }
471
- if (added.length > 0) {
472
- pi.setActiveTools([...activeTools, ...added]);
473
- for (const name of added) searchActivatedTools.add(name);
474
- }
475
- return added;
476
- }
477
-
478
- function activeFailureServers(): Set<string> {
479
- const currentState = state;
480
- if (!currentState) return new Set();
481
- return new Set(Object.keys(currentState.config.mcpServers).filter((serverName) => isServerInActiveFailureBackoff(currentState, serverName)));
482
- }
483
-
484
- function resolveCurrentDirectTools(config: McpConfig, cache: MetadataCache | null, reservedNames?: Set<string>): DirectToolSpec[] {
485
- if (envRaw === "__none__") return [];
486
- const prefix = config.settings?.toolPrefix ?? "server";
487
- return resolveDirectTools(config, cache, prefix, envDirectToolOverride, activeFailureServers(), reservedNames);
488
- }
489
-
490
- function getActiveToolsIfReady(): string[] | undefined {
491
- try {
492
- return callReentrant(() => pi.getActiveTools?.());
493
- } catch (error) {
494
- if (error instanceof Error
495
- && error.message.includes("Action methods cannot be called during extension loading")) return undefined;
496
- throw error;
497
- }
498
- }
499
-
500
- function deactivateTools(toolNames: string[]): string[] {
501
- if (toolNames.length === 0) return [];
502
- const unregisterTool = (pi as ExtensionAPI & { unregisterTool?: (name: string) => boolean }).unregisterTool;
503
- const unregistered = toolNames.filter((toolName) => callReentrant(() => unregisterTool?.(toolName)) === true);
504
- const fallbackNames = toolNames.filter((toolName) => !unregistered.includes(toolName));
505
- const activeTools = getActiveToolsIfReady();
506
- if (!activeTools) return unregistered;
507
- const removedFallbackNames = fallbackNames.filter((name) => activeTools.includes(name));
508
- if (removedFallbackNames.length === 0) return unregistered;
509
- const remove = new Set(removedFallbackNames);
510
- const nextActiveTools = activeTools.filter((name) => !remove.has(name));
511
- callReentrant(() => pi.setActiveTools(nextActiveTools));
512
- for (const toolName of removedFallbackNames) fallbackDeactivatedTools.add(toolName);
513
- return unregistered;
514
- }
515
-
516
- function syncDirectTools(config: McpConfig, cache: MetadataCache | null): {
517
- specs: DirectToolSpec[];
518
- reservedDirectNames: Set<string>;
519
- activeDirectNames: Set<string>;
520
- added: string[];
521
- updated: string[];
522
- deactivated: string[];
523
- } {
524
- const reservedDirectNames = new Set<string>();
525
- const specs = resolveCurrentDirectTools(config, cache, reservedDirectNames);
526
- const nextNames = new Set(specs.map((spec) => spec.prefixedName));
527
- const added: string[] = [];
528
- const updated: string[] = [];
529
- const deactivated: string[] = [];
530
-
531
- for (const spec of specs) {
532
- const fingerprint = directToolFingerprint(spec);
533
- const previous = registeredDirectTools.get(spec.prefixedName);
534
- if (previous !== fingerprint) {
535
- const previousServer = registeredDirectToolServers.get(spec.prefixedName);
536
- registerDirectTool(spec, config);
537
- finalizationGuard?.();
538
- registeredDirectTools.set(spec.prefixedName, fingerprint);
539
- registeredDirectToolServers.set(spec.prefixedName, spec.serverName);
540
- registeredDirectToolVersions.set(spec.prefixedName, (registeredDirectToolVersions.get(spec.prefixedName) ?? 0) + 1);
541
- if (previousServer !== spec.serverName) {
542
- forgetReportedDirectToolName(previousServer, spec.prefixedName);
543
- }
544
- if (fallbackDeactivatedTools.delete(spec.prefixedName) && !spec.lazy) {
545
- const activeTools = getActiveToolsIfReady();
546
- if (activeTools && !activeTools.includes(spec.prefixedName)) {
547
- callReentrant(() => pi.setActiveTools([...activeTools, spec.prefixedName]));
548
- }
549
- }
550
- (previous ? updated : added).push(spec.prefixedName);
551
- }
552
- if (spec.lazy) {
553
- // Search mode, whether first registered or flipped from eager (e.g. in
554
- // the panel): held inactive below unless a search already activated it.
555
- lazyDirectTools.add(spec.prefixedName);
556
- } else if (lazyDirectTools.delete(spec.prefixedName)) {
557
- // Search → eager: an eager direct tool is active by definition, so a
558
- // tool that search never activated must be activated now. Pi does not
559
- // re-activate a tool it already knows on re-registration.
560
- searchActivatedTools.delete(spec.prefixedName);
561
- const activeTools = getActiveToolsIfReady();
562
- if (activeTools && !activeTools.includes(spec.prefixedName)) callReentrant(() => pi.setActiveTools([...activeTools, spec.prefixedName]));
563
- }
564
- }
565
-
566
- for (const toolName of [...registeredDirectTools.keys()]) {
567
- if (nextNames.has(toolName)) continue;
568
- const serverName = registeredDirectToolServers.get(toolName);
569
- registeredDirectTools.delete(toolName);
570
- registeredDirectToolServers.delete(toolName);
571
- forgetReportedDirectToolName(serverName, toolName);
572
- if (lazyDirectTools.delete(toolName)) searchActivatedTools.delete(toolName);
573
- deactivated.push(toolName);
574
- }
575
-
576
- deactivateTools(deactivated);
577
- holdLazyToolsInactive();
578
- return { specs, reservedDirectNames, activeDirectNames: nextNames, added, updated, deactivated };
579
- }
580
-
581
- function applyDirectToolConfigChanges(changes: Map<string, true | string[] | false | "search">): void {
582
- if (!state) return;
583
- for (const [serverName, value] of changes) {
584
- const definition = state.config.mcpServers[serverName];
585
- if (!definition) continue;
586
- state.config.mcpServers[serverName] = { ...definition, directTools: value };
587
- }
588
- }
589
-
590
- function loadToolSurfaceCache(config: McpConfig): MetadataCache | null {
591
- const cache = loadMetadataCache();
592
- const currentState = state;
593
- if (!currentState || !cache) return cache;
594
- const servers = { ...cache.servers };
595
- const connections = callReentrant(() => [...currentState.manager.getAllConnections()]);
596
- for (const [serverName, connection] of connections) {
597
- const definition = config.mcpServers[serverName];
598
- const entry = servers[serverName];
599
- if (connection.status !== "connected" || !entry || !definition || isServerDisabled(definition)) continue;
600
- const configHash = computeServerHash(definition);
601
- if (computeServerHash(connection.definition) !== configHash || entry.configHash !== configHash) continue;
602
- const { ttlMs: _liveTtl, ...liveEntry } = entry;
603
- servers[serverName] = liveEntry;
604
- }
605
- return { ...cache, servers };
606
- }
607
-
608
- function syncToolSurface(ctx?: ExtensionContext): void {
609
- const notificationGeneration = lifecycleGeneration;
610
- const notificationOwner = currentOwner;
611
- const config = state?.config ?? earlyConfig;
612
- const cache = loadToolSurfaceCache(config);
613
- const result = syncDirectTools(config, cache);
614
- if (state) {
615
- const directToolCounts = state.directToolCounts ?? new Map<string, number>();
616
- directToolCounts.clear();
617
- for (const spec of result.specs) {
618
- directToolCounts.set(spec.serverName, (directToolCounts.get(spec.serverName) ?? 0) + 1);
619
- }
620
- state.directToolCounts = directToolCounts;
621
- }
622
- syncProxyTool(config, cache, result.specs);
623
- syncNamespaceTools(config, cache, result.reservedDirectNames, result.activeDirectNames);
624
- deliverLargeDirectToolsAdvisory(ctx, config, result.specs);
625
- if (ctx && (notificationGeneration !== lifecycleGeneration
626
- || notificationOwner !== currentOwner
627
- || (notificationOwner && !notificationOwner.isActive()))) {
628
- throw notificationOwner?.signal.reason ?? new Error("Stale MCP session after direct-tools advisory");
629
- }
630
- finalizationGuard?.();
631
- const changed = result.added.length + result.updated.length + result.deactivated.length;
632
- if (changed > 0 && ctx?.hasUI) {
633
- callReentrant(() => ctx.ui.notify(
634
- `MCP: direct tools refreshed (+${result.added.length}, ~${result.updated.length}, -${result.deactivated.length})`,
635
- "info",
636
- ));
637
- }
638
- }
639
-
640
- function deliverLargeDirectToolsAdvisory(
641
- ctx: ExtensionContext | undefined,
642
- config: McpConfig,
643
- specs: readonly DirectToolSpec[],
644
- ): void {
645
- if (!ctx || largeDirectToolsAdvisoryDelivered) return;
646
- const message = getLargeDirectToolsAdvisory(config, specs);
647
- if (!message) return;
648
- largeDirectToolsAdvisoryDelivered = true;
649
- if (ctx.hasUI) {
650
- callReentrant(() => ctx.ui.notify(message, "warning"));
651
- } else {
652
- console.warn(message);
653
- }
654
- }
655
-
656
- function getDeferredSessionSnapshot(cwd: string | undefined): {
657
- config: McpConfig;
658
- cache: MetadataCache | null;
659
- enabledServerCount: number;
660
- } | undefined {
661
- const config = programmaticConfig
662
- ? resolveConfiguredClaudePluginMcp(cloneMcpConfig(sessionConfig), cwd ?? process.cwd())
663
- : loadMcpConfig(earlyConfigPath, cwd);
664
- const cache = loadMetadataCache();
665
- const enabledServers = Object.values(config.mcpServers).filter((definition) => !isServerDisabled(definition));
666
- if (enabledServers.some((definition) => definition.lifecycle === "eager" || definition.lifecycle === "keep-alive")) return undefined;
667
- if (envRaw !== undefined && envRaw !== "__none__"
668
- && getMissingConfiguredDirectToolServers(config, cache, envDirectToolOverride).length > 0) return undefined;
669
- if (config.settings?.deferWithMissingMetadata !== true
670
- && (cache === null || hasEnabledServerWithoutValidMetadata(config, cache))) return undefined;
671
- return { config, cache, enabledServerCount: enabledServers.length };
672
- }
673
-
674
- function syncNamespaceTools(
675
- config: McpConfig,
676
- cache: MetadataCache | null,
677
- reservedDirectNames: Set<string> = new Set(registeredDirectTools.keys()),
678
- activeDirectNames: Set<string> = new Set(registeredDirectTools.keys()),
679
- ): void {
680
- const result = syncNamespaceProxyTools({
681
- config,
682
- cache,
683
- envOverride: namespaceEnvOverride,
684
- existingDirectNames: reservedDirectNames,
685
- activeDirectNames,
686
- existingNamespaceNames: registeredNamespaceProxyTools,
687
- unavailableServers: activeFailureServers(),
688
- pi,
689
- getState: () => state,
690
- getInitPromise: () => initPromise,
691
- ensureRuntime: (ctx) => ensureSessionRuntime(ctx as ExtensionContext),
692
- executeCall: async (...args) => {
693
- const guard = captureRuntimeGuard(args[0]);
694
- return (await loadForRuntime(loadProxyModes, guard)).executeCall(...args);
695
- },
696
- getPiTools: () => pi.getAllTools(),
697
- renderOptions: toolRenderOptions,
698
- renderShell: toolRenderShell,
699
- renderResult: renderMcpToolResult,
700
- guardReentrant: () => finalizationGuard?.(),
701
- onToolRegistered: (name) => finalizationRegistrations?.add(name),
702
- });
703
- finalizationGuard?.();
704
- for (const name of result.added) registeredNamespaceProxyTools.add(name);
705
- for (const name of result.deactivated) registeredNamespaceProxyTools.delete(name);
706
- }
707
-
708
- const registeredPromptCommands = new Set<string>();
709
-
710
- function registerPromptCommands(specs: Iterable<PromptMetadata>): void {
711
- for (const spec of specs) {
712
- if (registeredPromptCommands.has(spec.commandName)) {
713
- logger.debug(`MCP: prompt "${spec.originalName}" on ${spec.serverName} skipped; /${spec.commandName} is already registered`);
714
- continue;
715
- }
716
- callReentrant(() => pi.registerCommand(spec.commandName, createPromptCommand(pi, () => state, spec, {
717
- ensureState: (ctx) => ensureSessionRuntime(ctx as unknown as ExtensionContext),
718
- lazyConnect: async (...args) => {
719
- const guard = captureRuntimeGuard(args[0]);
720
- return (await loadForRuntime(loadCoreRuntime, guard)).lazyConnect(...args);
721
- },
722
- isStateCurrent: (targetState) => state === targetState && currentOwner?.isActive() === true,
723
- })));
724
- registeredPromptCommands.add(spec.commandName);
725
- }
726
- }
727
-
728
- function syncPromptCommands(): void {
729
- registerPromptCommands([...(state?.promptMetadata?.entries() ?? [])]
730
- .filter(([name]) => !state?.provisionalInstalls?.has(name))
731
- .flatMap(([, prompts]) => prompts));
732
- }
733
-
734
- const registerRuntimeServer = (name: string, definition: ServerEntry): McpServerRegistration => {
735
- if (typeof name !== "string" || name.trim() === "") {
736
- throw new Error("MCP server name must be a non-empty string");
737
- }
738
- if (typeof definition !== "object" || definition === null || Array.isArray(definition)) {
739
- throw new Error(`MCP server definition for "${name}" must be an object`);
740
- }
741
- const effective = state?.config ?? earlyConfig;
742
- if (runtimeServers.has(name) || Object.hasOwn(effective.mcpServers, name)) {
743
- throw new Error(`MCP server "${name}" is already registered`);
744
- }
745
- // Runtime-registered servers are proxy-tool-only: direct tools are frozen
746
- // at startup and must not be rebuilt for late registrations.
747
- const snapshotDefinition = structuredClone(definition);
748
- const entry: ServerEntry = { ...structuredClone(snapshotDefinition), directTools: false };
749
- runtimeServers.set(name, { definition: snapshotDefinition, entry });
750
- const registeredState = state;
751
- if (registeredState) {
752
- registeredState.config.mcpServers[name] = entry;
753
- attachRuntimeServerLifecycle(registeredState, name, entry);
754
- syncToolSurface();
755
- const guard = captureRuntimeGuard(registeredState);
756
- void loadForRuntime(loadCoreRuntime, guard)
757
- .then(({ updateStatusBar }) => updateStatusBar(registeredState))
758
- .catch((error) => {
759
- if (!isAbortError(error, guard.owner?.signal)) {
760
- logger.debug(`MCP: could not update status after runtime registration: ${formatTerminalError(error)}`);
761
- }
762
- });
763
- }
764
- let disposed = false;
765
- return {
766
- dispose: async (): Promise<void> => {
767
- if (disposed) return;
768
- disposed = true;
769
- runtimeServers.delete(name);
770
- const currentState = state;
771
- if (!currentState || currentState.config.mcpServers[name] !== entry) return;
772
- delete currentState.config.mcpServers[name];
773
- currentState.lifecycle.unregisterServer(name);
774
- const guard = captureRuntimeGuard(currentState);
775
- await currentState.manager.close(name);
776
- assertRuntimeGuard(guard);
777
- syncToolSurface();
778
- (await loadForRuntime(loadCoreRuntime, guard)).updateStatusBar(currentState);
779
- },
780
- };
781
- };
782
- const getRuntimeServerSnapshot = (name: string): McpRuntimeServerSnapshot => {
783
- if (typeof name !== "string" || name.trim() === "") {
784
- throw new Error("MCP runtime server name must be a non-empty string");
785
- }
786
- const runtimeServer = runtimeServers.get(name);
787
- if (!runtimeServer) {
788
- throw new Error(`MCP runtime server "${name}" is not registered or has been disposed`);
789
- }
790
- const activeState = state;
791
- if (!activeState) {
792
- throw new Error(`MCP runtime server "${name}" is unavailable because the adapter has no active state`);
793
- }
794
- const activeEntry = activeState.config.mcpServers[name];
795
- if (Object.hasOwn(activeState.config.mcpServers, name) && activeEntry !== runtimeServer.entry) {
796
- throw new Error(`MCP runtime server "${name}" is shadowed by a configured server`);
797
- }
798
- if (activeEntry !== runtimeServer.entry) {
799
- throw new Error(`MCP runtime server "${name}" is unavailable in the active adapter state`);
800
- }
801
- return {
802
- name,
803
- definition: structuredClone(runtimeServer.definition),
804
- runtime: true,
805
- persisted: false,
806
- };
807
- };
808
- runtimeRegistrars.set(pi, registerRuntimeServer);
809
- runtimeSnapshotters.set(pi, getRuntimeServerSnapshot);
810
- pi.events.on(MCP_RUNTIME_REGISTER_EVENT, (rawRequest: unknown) => {
811
- if (typeof rawRequest !== "object" || rawRequest === null || Array.isArray(rawRequest)) return;
812
- const request = rawRequest as McpRuntimeRegistrationRequest;
813
- if (request.result !== undefined) return;
814
- if (request.version !== MCP_RUNTIME_REGISTER_VERSION) {
815
- request.result = { ok: false, error: new Error(`Unsupported MCP runtime registration version: ${String(request.version)}`) };
816
- return;
817
- }
818
- try {
819
- request.result = { ok: true, registration: registerRuntimeServer(request.name, request.definition) };
820
- } catch (error) {
821
- request.result = { ok: false, error: error instanceof Error ? error : new Error(String(error)) };
822
- }
823
- });
824
- pi.events.on(MCP_RUNTIME_SNAPSHOT_EVENT, (rawRequest: unknown) => {
825
- if (typeof rawRequest !== "object" || rawRequest === null || Array.isArray(rawRequest)) return;
826
- const request = rawRequest as McpRuntimeSnapshotRequest;
827
- if (request.result !== undefined) return;
828
- if (request.version !== MCP_RUNTIME_SNAPSHOT_VERSION) {
829
- request.result = { ok: false, error: new Error(`Unsupported MCP runtime snapshot version: ${String(request.version)}`) };
830
- return;
831
- }
832
- try {
833
- request.result = { ok: true, snapshot: getRuntimeServerSnapshot(request.name) };
834
- } catch (error) {
835
- request.result = { ok: false, error: error instanceof Error ? error : new Error(String(error)) };
836
- }
837
- });
838
-
839
- const getPiTools = (): ToolInfo[] => pi.getAllTools();
840
-
841
- pi.registerFlag("mcp-config", {
842
- description: "Path to MCP config file",
843
- type: "string",
844
- });
845
-
846
- function startInitialization(ctx: ExtensionContext, owner: McpRuntimeOwner, generation: number, staleReason: string): Promise<void> {
847
- let oauthRuntime: McpOAuthRuntime | null = null;
848
- let markStarted!: () => void;
849
- initStartedPromise = new Promise<void>((resolve) => {
850
- markStarted = resolve;
851
- });
852
- let coreRuntime: Awaited<ReturnType<typeof loadCoreRuntime>> | null = null;
853
- const initializationPromise = (async () => {
854
- const guard = captureRuntimeGuard(null);
855
- const [core, oauth] = await Promise.all([
856
- loadCoreRuntime(),
857
- loadOAuthRuntime(),
858
- ]);
859
- assertRuntimeGuard(guard);
860
- if (generation !== guard.generation || guard.owner !== owner) {
861
- throw owner.signal.reason ?? new Error("Stale MCP initialization");
862
- }
863
- coreRuntime = core;
864
- assertRuntimeGuard(guard);
865
- oauthRuntime = oauth.createOAuthRuntime(owner.signal);
866
- try {
867
- assertRuntimeGuard(guard);
868
- } catch (error) {
869
- await oauth.shutdownOAuth(oauthRuntime);
870
- throw error;
871
- }
872
- currentOAuthRuntime = oauthRuntime;
873
- assertRuntimeGuard(guard);
874
- owner.addCleanup(async () => {
875
- const { cleanupMaterializedBinaryResources } = await import("./tool-registrar.ts");
876
- cleanupMaterializedBinaryResources(owner.signal);
877
- });
878
- assertRuntimeGuard(guard);
879
- const initialization = core.initializeMcp(pi, ctx, owner, {
880
- ...(programmaticConfig || options.configPath !== undefined
881
- ? {
882
- ...(earlyConfigPath !== undefined ? { configPath: earlyConfigPath } : {}),
883
- ...(sessionConfig !== undefined ? { config: sessionConfig } : {}),
884
- }
885
- : {}),
886
- oauthRuntime,
887
- });
888
- assertRuntimeGuard(guard);
889
- markStarted();
890
- return initialization;
891
- })().catch((error) => {
892
- markStarted();
893
- throw error;
894
- }).then((value) => value);
895
- let promise: Promise<McpExtensionState>;
896
- promise = initializationPromise.then(async (nextState) => {
897
- if (!owner.isActive() || generation !== lifecycleGeneration || initPromise !== promise) {
898
- try {
899
- await shutdownState(nextState, staleReason, false);
900
- } catch (error) {
901
- console.error(`MCP: failed to clean stale initialization state: ${formatTerminalError(error)}`);
902
- }
903
- throw owner.signal.reason ?? new Error("Stale MCP initialization completed");
904
- }
905
-
906
- const guard = () => {
907
- owner.throwIfInactive();
908
- if (generation !== lifecycleGeneration || currentOwner !== owner || state !== nextState || initPromise !== promise) {
909
- throw owner.signal.reason ?? new Error("Stale MCP initialization finalized");
910
- }
911
- };
912
- const registeredDuringFinalization = new Set<string>();
913
- let statusPublicationAttempted = false;
914
- state = nextState;
915
- finalizationGuard = guard;
916
- finalizationRegistrations = registeredDuringFinalization;
917
- try {
918
- guard();
919
- // Re-read after asynchronous startup so navigation during initialization
920
- // cannot restore a stale branch.
921
- callReentrant(() => restoreCurrentSessionApprovals(nextState));
922
- for (const [name, { entry }] of runtimeServers) {
923
- guard();
924
- if (Object.hasOwn(nextState.config.mcpServers, name)) {
925
- console.error(`MCP: runtime-registered server "${name}" now collides with a configured server; keeping the configured server`);
926
- continue;
927
- }
928
- nextState.config.mcpServers[name] = entry;
929
- guard();
930
- callReentrant(() => attachRuntimeServerLifecycle(nextState, name, entry));
931
- }
932
- guard();
933
- nextState.onToolMetadataUpdated = (_serverName, _reason) => {
934
- if (state !== nextState || !owner.isActive()) return;
935
- syncPromptCommands();
936
- if (directToolsFrozen) {
937
- logger.debug(`MCP: metadata update for ${_serverName} (${_reason}) skipped — directTools frozen`);
938
- return;
939
- }
940
- syncToolSurface(ctx);
941
- };
942
- guard();
943
- syncPromptCommands();
944
- guard();
945
- syncToolSurface(ctx);
946
- guard();
947
- // A connected snapshot is readiness-like external state. Publish it only
948
- // after Pi's model-facing direct-tool surface reflects live metadata.
949
- nextState.statusEvents = pi.events;
950
- guard();
951
- if (!coreRuntime) throw new Error("MCP core runtime was not loaded");
952
- const loadedCoreRuntime = coreRuntime;
953
- statusPublicationAttempted = true;
954
- callReentrant(() => loadedCoreRuntime.updateStatusBar(nextState));
955
- guard();
956
- clearRetainedInitFailure();
957
- guard();
958
- if (earlyConfig.settings?.freezeDirectTools === true) {
959
- directToolsFrozen = true;
960
- logger.info("MCP: direct tools frozen after initial sync — metadata can refresh without rebuilding the active tool surface");
961
- }
962
- guard();
963
- initPromise = null;
964
- initStartedPromise = null;
965
- return nextState;
966
- } catch (error) {
967
- if (state === nextState) state = null;
968
- finalizationGuard = null;
969
- finalizationRegistrations = null;
970
- if (registeredDuringFinalization.size > 0) {
971
- deactivateTools([...registeredDuringFinalization]);
972
- for (const name of registeredDuringFinalization) {
973
- registeredDirectTools.delete(name);
974
- registeredDirectToolServers.delete(name);
975
- registeredDirectToolVersions.delete(name);
976
- registeredNamespaceProxyTools.delete(name);
977
- }
978
- if (registeredDuringFinalization.has("mcp")) {
979
- proxyToolRegistered = false;
980
- proxyToolDescription = null;
981
- }
982
- }
983
- if (statusPublicationAttempted && generation === lifecycleGeneration && currentOwner === owner) {
984
- publishMcpStatusShutdown(nextState.statusEvents);
985
- }
986
- // A synchronous lifecycle replacement captures published state and owns
987
- // its cleanup. Only clean here when no replacement took ownership.
988
- if (generation === lifecycleGeneration && currentOwner === owner) {
989
- try {
990
- await shutdownState(nextState, staleReason, false);
991
- } catch (cleanupError) {
992
- console.error(`MCP: failed to clean interrupted initialization state: ${formatTerminalError(cleanupError)}`);
993
- }
994
- }
995
- throw error;
996
- } finally {
997
- if (finalizationGuard === guard) finalizationGuard = null;
998
- if (finalizationRegistrations === registeredDuringFinalization) finalizationRegistrations = null;
999
- }
1000
- });
1001
- initPromise = promise;
1002
-
1003
- return promise.then(() => undefined).catch(async err => {
1004
- if (!owner.isActive() || generation !== lifecycleGeneration) {
1005
- return;
1006
- }
1007
- if (initPromise !== promise) return;
1008
- const message = retainInitFailure(err);
1009
- console.error(`MCP initialization failed: ${message}`);
1010
- initPromise = null;
1011
- initStartedPromise = null;
1012
- if (state) return;
1013
-
1014
- try {
1015
- await Promise.all([
1016
- owner.stop("MCP initialization failed"),
1017
- oauthRuntime ? loadOAuthRuntime().then(({ shutdownOAuth }) => shutdownOAuth(oauthRuntime!)) : Promise.resolve(),
1018
- ]);
1019
- } catch (error) {
1020
- console.error(`MCP: failed to clean rejected initialization: ${formatTerminalError(error)}`);
1021
- }
1022
- });
1023
- }
1024
-
1025
- function ensureSessionRuntime(ctx: ExtensionContext): Promise<McpExtensionState | null> {
1026
- if (state) return Promise.resolve(state);
1027
- if (initPromise) return initPromise;
1028
- const owner = currentOwner?.isActive() ? currentOwner : createMcpRuntimeOwner();
1029
- if (owner !== currentOwner) currentOwner = owner;
1030
- const generation = currentOwner === owner && lifecycleGeneration > 0
1031
- ? lifecycleGeneration
1032
- : ++lifecycleGeneration;
1033
- startInitialization(ctx, owner, generation, "stale_first_operation_initialization");
1034
- return initPromise!;
1035
- }
1036
-
1037
- function startLoadTimeInitialization(): void {
1038
- if (!hasStartupServer) return;
1039
- setImmediate(() => {
1040
- if (lifecycleGeneration !== 0 || state || initPromise) return;
1041
- const generation = ++lifecycleGeneration;
1042
- const owner = createMcpRuntimeOwner();
1043
- currentOwner = owner;
1044
- currentOAuthRuntime = null;
1045
- startInitialization({
1046
- mode: "print",
1047
- hasUI: false,
1048
- cwd: process.cwd(),
1049
- model: undefined,
1050
- modelRegistry: undefined,
1051
- signal: undefined,
1052
- } as unknown as ExtensionContext, owner, generation, "stale_load_time_initialization");
1053
- });
1054
- }
1055
-
1056
- pi.on("resources_discover", (event) => {
1057
- const resourceConfig = programmaticConfig
1058
- ? cloneMcpConfig(sessionConfig)
1059
- : loadMcpConfig(earlyConfigPath, event.cwd);
1060
- const skillPaths = discoverConfiguredClaudePluginSkills(resourceConfig, event.cwd);
1061
- if (earlyConfig.settings?.scriptMode !== false) {
1062
- const scriptingSkillPath = fileURLToPath(new URL("./skills/mcp-scripting/SKILL.md", import.meta.url));
1063
- if (existsSync(scriptingSkillPath) && !skillPaths.includes(scriptingSkillPath)) {
1064
- skillPaths.push(scriptingSkillPath);
1065
- }
1066
- }
1067
- return skillPaths.length > 0 ? { skillPaths } : undefined;
1068
- });
1069
-
1070
- pi.on("session_start", async (_event, ctx) => {
1071
- // Reset before any await so replacement sessions cannot inherit activation.
1072
- searchActivatedTools.clear();
1073
- holdLazyToolsInactive();
1074
- const generation = ++lifecycleGeneration;
1075
- largeDirectToolsAdvisoryDelivered = false;
1076
- const previousState = state;
1077
- const previousOwner = currentOwner;
1078
- const previousOAuthRuntime = currentOAuthRuntime;
1079
- const owner = createMcpRuntimeOwner();
1080
- currentOwner = owner;
1081
- currentOAuthRuntime = null;
1082
- state = null;
1083
- initPromise = null;
1084
- initStartedPromise = null;
1085
- clearRetainedInitFailure();
1086
-
1087
- // Abort synchronously before awaiting cleanup so old callbacks and startup
1088
- // work cannot resume into a stale ExtensionContext.
1089
- const stopPrevious = previousOwner?.stop("MCP extension session restarted") ?? Promise.resolve();
1090
- try {
1091
- await Promise.all([
1092
- stopPrevious,
1093
- shutdownState(previousState, "session_restart"),
1094
- previousOAuthRuntime ? loadOAuthRuntime().then(({ shutdownOAuth }) => shutdownOAuth(previousOAuthRuntime)) : Promise.resolve(),
1095
- ]);
1096
- } catch (error) {
1097
- console.error(`MCP: failed to shut down previous session state: ${formatTerminalError(error)}`);
1098
- }
1099
-
1100
- if (generation !== lifecycleGeneration || !owner.isActive()) return;
1101
- if (state) return;
1102
-
1103
- if (!initPromise) {
1104
- const deferredSnapshot = getDeferredSessionSnapshot(ctx.cwd);
1105
- if (deferredSnapshot) {
1106
- const { config, cache, enabledServerCount } = deferredSnapshot;
1107
- const directResult = syncDirectTools(config, cache);
1108
- syncProxyTool(config, cache, directResult.specs);
1109
- syncNamespaceTools(config, cache, directResult.reservedDirectNames, directResult.activeDirectNames);
1110
- // Pi cannot unregister commands. Wait until cwd is authoritative, and
1111
- // under the opt-in wait for live metadata, before exposing prompts.
1112
- if (config.settings?.deferWithMissingMetadata !== true) {
1113
- registerPromptCommands(resolveCachedPrompts(config));
1114
- }
1115
- deliverLargeDirectToolsAdvisory(ctx, config, directResult.specs);
1116
- if (generation !== lifecycleGeneration || !owner.isActive() || currentOwner !== owner) return;
1117
- const serverCount = Object.keys(config.mcpServers).length;
1118
- const formattedStatus = formatMcpFooterStatus(
1119
- config,
1120
- enabledServerCount,
1121
- serverCount - enabledServerCount,
1122
- 0,
1123
- );
1124
- const theme = ctx.ui?.theme;
1125
- const styledStatus = formattedStatus !== undefined && typeof theme?.fg === "function"
1126
- ? theme.fg("accent", formattedStatus)
1127
- : formattedStatus;
1128
- ctx.ui?.setStatus("mcp", styledStatus);
1129
- return;
1130
- }
1131
- startInitialization(ctx, owner, generation, "stale_session_start");
1132
- }
1133
-
1134
- const initialization = initPromise as Promise<McpExtensionState> | null;
1135
- const initializationStarted = initStartedPromise as Promise<void> | null;
1136
- if (envRaw !== undefined && envRaw !== "__none__") {
1137
- const missingEnvDirectTools = getMissingConfiguredDirectToolServers(
1138
- earlyConfig,
1139
- loadMetadataCache(),
1140
- envDirectToolOverride,
1141
- );
1142
- if (missingEnvDirectTools.length > 0) {
1143
- await initialization?.then(() => undefined, () => undefined);
1144
- return;
1145
- }
1146
- }
1147
- await initializationStarted;
1148
- });
1149
-
1150
- // Other extensions can reactivate registered tools after session_start.
1151
- pi.on("before_agent_start", holdLazyToolsInactive);
1152
-
1153
- pi.on("session_tree", (_event, ctx) => {
1154
- const currentState = state;
1155
- const owner = currentOwner;
1156
- if (!currentState || !owner?.isActive() || !currentState.sessionManager) return;
1157
-
1158
- let sessionManager: ExtensionContext["sessionManager"] | undefined;
1159
- try {
1160
- sessionManager = ctx.sessionManager;
1161
- } catch {
1162
- return;
1163
- }
1164
- if (sessionManager !== currentState.sessionManager) return;
1165
-
1166
- restoreCurrentSessionApprovals(currentState);
1167
- });
1168
-
1169
- pi.on("input", async () => {
1170
- const inputOwner = currentOwner;
1171
- if (!inputOwner?.isActive()) return;
1172
-
1173
- if (!state && initPromise) {
1174
- try {
1175
- await awaitWithTimeout(initPromise, INIT_WAIT_TIMEOUT_MS);
1176
- } catch {
1177
- return;
1178
- }
1179
- }
1180
-
1181
- const inputState = state;
1182
- if (!inputState || !inputOwner.isActive()) return;
1183
- try {
1184
- await inputState.lifecycle.ensureConverged(inputOwner.signal);
1185
- } catch (error) {
1186
- if (!isAbortError(error, inputOwner.signal)) {
1187
- logger.debug(`MCP: keep-alive convergence failed before input: ${formatTerminalError(error)}`);
1188
- }
1189
- }
1190
- });
1191
-
1192
- pi.on("session_shutdown", async () => {
1193
- ++lifecycleGeneration;
1194
- const currentState = state;
1195
- const owner = currentOwner;
1196
- const oauthRuntime = currentOAuthRuntime;
1197
- currentOwner = null;
1198
- currentOAuthRuntime = null;
1199
- state = null;
1200
- initPromise = null;
1201
- initStartedPromise = null;
1202
- clearRetainedInitFailure();
1203
-
1204
- // Abort before awaiting cleanup so delayed initialization cannot touch stale
1205
- // Pi context after session shutdown.
1206
- const stopOwner = owner?.stop("MCP extension session shutdown") ?? Promise.resolve();
1207
- try {
1208
- await Promise.all([
1209
- stopOwner,
1210
- shutdownState(currentState, "session_shutdown"),
1211
- oauthRuntime ? loadOAuthRuntime().then(({ shutdownOAuth }) => shutdownOAuth(oauthRuntime)) : Promise.resolve(),
1212
- ]);
1213
- } catch (error) {
1214
- console.error(`MCP: session shutdown cleanup failed: ${formatTerminalError(error)}`);
1215
- }
1216
- });
1217
-
1218
- // Re-flag returned MCP tool failures so pi registers them as errors (see toolErrorOverride).
1219
- pi.on("tool_result", (event) => toolErrorOverride(event.details));
1220
-
1221
- const registerMcpCommand = (commandName: string) => pi.registerCommand(commandName, {
1222
- description: "Show MCP server status",
1223
- getArgumentCompletions: (prefix: string) => {
1224
- const normalized = prefix.trimStart();
1225
- const argumentMatch = normalized.match(/^(\S+)\s+(.*)$/);
1226
- if (!argumentMatch) {
1227
- const subcommands = [
1228
- { value: "reconnect", label: "reconnect — Reconnect servers" },
1229
- { value: "tools", label: "tools — List all tools" },
1230
- { value: "prompts", label: "prompts — List all MCP prompts" },
1231
- { value: "setup", label: "setup — Configure MCP servers" },
1232
- { value: "jev", label: "jev setup — Configure Jev semantic search" },
1233
- { value: "edit", label: "edit — Edit .mcp.json or the global config" },
1234
- { value: "logout", label: "logout — Clear server credentials" },
1235
- { value: "token", label: "token — Manage stored bearer tokens" },
1236
- { value: "disable", label: "disable — Disable a server" },
1237
- { value: "enable", label: "enable — Enable a server" },
1238
- { value: "status", label: "status — Show server status" },
1239
- ].filter(({ value }) => value.startsWith(normalized));
1240
- return subcommands.length > 0 ? subcommands : null;
1241
- }
1242
-
1243
- const [, subcommand, argumentPrefix] = argumentMatch;
1244
- if (subcommand === "jev") {
1245
- return "setup".startsWith((argumentPrefix ?? "").trimStart())
1246
- ? [{ value: "jev setup", label: "setup — Configure Jev semantic search" }]
1247
- : null;
1248
- }
1249
- if (
1250
- (subcommand !== "reconnect" && subcommand !== "logout" && subcommand !== "disable" && subcommand !== "enable" && subcommand !== "token")
1251
- || argumentPrefix === undefined
1252
- ) return null;
1253
-
1254
- const completionConfig = state?.config ?? earlyConfig;
1255
- if (subcommand === "token") {
1256
- const tokenMatch = argumentPrefix.trimStart().match(/^(set|remove|status)\s+(.*)$/);
1257
- if (!tokenMatch) {
1258
- const actions = ["set", "remove", "status"]
1259
- .filter(action => action.startsWith(argumentPrefix.trimStart()))
1260
- .map(action => ({ value: `token ${action} `, label: `${action} — Bearer token ${action}` }));
1261
- return actions.length > 0 ? actions : null;
1262
- }
1263
- const action = tokenMatch[1] ?? "";
1264
- const serverPrefix = tokenMatch[2] ?? "";
1265
- const servers = Object.keys(completionConfig.mcpServers)
1266
- .filter(serverName => serverName.startsWith(serverPrefix.trimStart()))
1267
- .map(serverName => ({ value: `token ${action} ${serverName}`, label: serverName }));
1268
- return servers.length > 0 ? servers : null;
1269
- }
1270
-
1271
- const servers = Object.keys(completionConfig.mcpServers)
1272
- .filter((serverName) => serverName.startsWith(argumentPrefix.trimStart()))
1273
- .map((serverName) => ({ value: `${subcommand} ${serverName}`, label: serverName }));
1274
- return servers.length > 0 ? servers : null;
1275
- },
1276
- handler: async (args, ctx) => {
1277
- let commandOwner = currentOwner;
1278
- const commandReload = typeof ctx.reload === "function" ? ctx.reload.bind(ctx) : async () => {};
1279
- let commandCtx = createCommandContext(ctx as unknown as ExtensionContext, commandOwner);
1280
- if (!state) {
1281
- try {
1282
- state = await ensureSessionRuntime(ctx as unknown as ExtensionContext);
1283
- commandOwner = currentOwner;
1284
- commandCtx = createCommandContext(ctx as unknown as ExtensionContext, commandOwner);
1285
- commandOwner?.throwIfInactive();
1286
- } catch (error) {
1287
- const message = error instanceof Error ? error.message : String(error);
1288
- if (commandCtx.hasUI) commandCtx.ui?.notify(`MCP initialization failed: ${message}`, "error");
1289
- return;
1290
- }
1291
- }
1292
- if (!state) {
1293
- if (commandCtx.hasUI) commandCtx.ui?.notify("MCP not initialized", "error");
1294
- return;
1295
- }
1296
-
1297
- const commandGuard = captureRuntimeGuard(state);
1298
- let commands: Awaited<ReturnType<typeof loadCommands>>;
1299
- try {
1300
- commands = await loadForRuntime(loadCommands, commandGuard);
1301
- } catch (error) {
1302
- if (commandGuard.owner && isOwnerAbortError(error, commandGuard.owner)) return;
1303
- const message = error instanceof Error ? error.message : String(error);
1304
- if (commandCtx.hasUI) commandCtx.ui?.notify(`MCP initialization failed: ${message}`, "error");
1305
- return;
1306
- }
1307
- const parts = args?.trim()?.split(/\s+/) ?? [];
1308
- const subcommand = parts[0] ?? "";
1309
- const targetServer = parts[1];
1310
- const rest = parts.slice(1).join(" ");
1311
-
1312
- switch (subcommand) {
1313
- case "reconnect":
1314
- commandOwner?.throwIfInactive();
1315
- await commands.reconnectServers(state, commandCtx, targetServer);
1316
- break;
1317
- case "tools":
1318
- await commands.showTools(state, commandCtx);
1319
- break;
1320
- case "prompts":
1321
- await commands.showPrompts(state, commandCtx);
1322
- break;
1323
- case "setup": {
1324
- commandOwner?.throwIfInactive();
1325
- if (programmaticConfig) {
1326
- commandCtx.ui?.notify("MCP setup is unavailable when config is supplied by createMcpAdapter().", "info");
1327
- break;
1328
- }
1329
- const result = await commands.openMcpSetup(state, pi, commandCtx, earlyConfigPath, "setup");
1330
- if (result?.configChanged) {
1331
- commandOwner?.throwIfInactive();
1332
- await commandReload();
1333
- return;
1334
- }
1335
- break;
1336
- }
1337
- case "jev": {
1338
- if (parts[1] !== "setup" || parts.length !== 2) {
1339
- commandCtx.ui?.notify("Usage: /mcp jev setup", "error");
1340
- break;
1341
- }
1342
- if (programmaticConfig) {
1343
- commandCtx.ui?.notify("Jev setup is unavailable when config is supplied by createMcpAdapter().", "info");
1344
- break;
1345
- }
1346
- commandOwner?.throwIfInactive();
1347
- if (await commands.setupJevSemanticSearch(state, commandCtx, earlyConfigPath)) {
1348
- commandOwner?.throwIfInactive();
1349
- await commandReload();
1350
- return;
1351
- }
1352
- break;
1353
- }
1354
- case "edit": {
1355
- if (programmaticConfig) {
1356
- commandCtx.ui?.notify("MCP edit is unavailable when config is supplied by createMcpAdapter().", "info");
1357
- break;
1358
- }
1359
- const target = parts[1] ?? "project";
1360
- if (target !== "project" && target !== "global") {
1361
- commandCtx.ui?.notify("Usage: /mcp edit [project|global]", "error");
1362
- return;
1363
- }
1364
- commandOwner?.throwIfInactive();
1365
- if (await commands.editSharedConfig(commandCtx, target)) {
1366
- commandOwner?.throwIfInactive();
1367
- await commandReload();
1368
- return;
1369
- }
1370
- break;
1371
- }
1372
- case "logout": {
1373
- const serverName = rest;
1374
- if (!serverName) {
1375
- if (commandCtx.hasUI) commandCtx.ui?.notify("Usage: /mcp logout <server>", "error");
1376
- return;
1377
- }
1378
- commandOwner?.throwIfInactive();
1379
- await commands.logoutServer(serverName, state, commandCtx);
1380
- break;
1381
- }
1382
- case "token": {
1383
- const action = parts[1];
1384
- const serverName = parts.slice(2).join(" ");
1385
- if (action !== "set" && action !== "remove" && action !== "status") {
1386
- if (commandCtx.hasUI) commandCtx.ui?.notify("Usage: /mcp token set|remove|status <server>", "error");
1387
- return;
1388
- }
1389
- if (!serverName) {
1390
- if (commandCtx.hasUI) commandCtx.ui?.notify("Usage: /mcp token set|remove|status <server>", "error");
1391
- return;
1392
- }
1393
- commandOwner?.throwIfInactive();
1394
- await commands.manageBearerToken(action, serverName, state, commandCtx);
1395
- break;
1396
- }
1397
- case "disable":
1398
- case "enable": {
1399
- const serverName = rest;
1400
- if (programmaticConfig) {
1401
- commandCtx.ui?.notify(`/mcp ${subcommand} is unavailable when config is supplied by createMcpAdapter().`, "info");
1402
- break;
1403
- }
1404
- if (!serverName) {
1405
- commandCtx.ui?.notify(`Usage: /mcp ${subcommand} <server>`, "error");
1406
- break;
1407
- }
1408
- if (!state.config.mcpServers[serverName]) {
1409
- commandCtx.ui?.notify(`Server "${serverName}" not found in effective config`, "error");
1410
- break;
1411
- }
1412
- commandOwner?.throwIfInactive();
1413
- const result = writeProjectServerDisabledOverride(earlyConfigPath, commandCtx.cwd, serverName, subcommand === "disable");
1414
- if (result.changed) {
1415
- commandCtx.ui?.notify(`${subcommand === "disable" ? "Disabled" : "Enabled"} server "${serverName}" in ${result.path} — run /reload to apply`, "info");
1416
- } else {
1417
- commandCtx.ui?.notify(`Server "${serverName}" is already ${subcommand === "disable" ? "disabled" : "enabled"}`, "info");
1418
- }
1419
- break;
1420
- }
1421
- case "status":
1422
- case "":
1423
- default:
1424
- if (commandCtx.hasUI) {
1425
- commandOwner?.throwIfInactive();
1426
- if (programmaticConfig) {
1427
- commandCtx.ui?.notify("MCP status is shown from the in-memory SDK config; configuration discovery is unavailable.", "info");
1428
- await commands.showStatus(state, commandCtx);
1429
- break;
1430
- }
1431
- const result = await commands.openMcpPanel(state, pi, commandCtx, earlyConfigPath, (changes) => {
1432
- applyDirectToolConfigChanges(changes);
1433
- syncToolSurface(commandCtx);
1434
- });
1435
- if (result?.configChanged) {
1436
- commandOwner?.throwIfInactive();
1437
- await commandReload();
1438
- return;
1439
- }
1440
- } else {
1441
- await commands.showStatus(state, commandCtx);
1442
- }
1443
- break;
1444
- }
1445
- },
1446
- });
1447
- registerMcpCommand("mcp");
1448
- registerMcpCommand("pi-mcp");
1449
-
1450
- pi.registerCommand("mcp-auth", {
1451
- description: "Authenticate with an MCP server (OAuth)",
1452
- handler: async (args, ctx) => {
1453
- let commandOwner = currentOwner;
1454
- let commandCtx = createCommandContext(ctx as unknown as ExtensionContext, commandOwner);
1455
- const serverName = args?.trim();
1456
- if (!serverName && !commandCtx.hasUI) {
1457
- return;
1458
- }
1459
-
1460
- if (!state) {
1461
- try {
1462
- state = await ensureSessionRuntime(ctx as unknown as ExtensionContext);
1463
- commandOwner = currentOwner;
1464
- commandCtx = createCommandContext(ctx as unknown as ExtensionContext, commandOwner);
1465
- commandOwner?.throwIfInactive();
1466
- } catch (error) {
1467
- const message = error instanceof Error ? error.message : String(error);
1468
- if (commandCtx.hasUI) commandCtx.ui?.notify(`MCP initialization failed: ${message}`, "error");
1469
- return;
1470
- }
1471
- }
1472
- if (!state) {
1473
- if (commandCtx.hasUI) commandCtx.ui?.notify("MCP not initialized", "error");
1474
- return;
1475
- }
1476
-
1477
- const commandGuard = captureRuntimeGuard(state);
1478
- let commands: Awaited<ReturnType<typeof loadCommands>>;
1479
- try {
1480
- commands = await loadForRuntime(loadCommands, commandGuard);
1481
- } catch (error) {
1482
- if (commandGuard.owner && isOwnerAbortError(error, commandGuard.owner)) return;
1483
- const message = error instanceof Error ? error.message : String(error);
1484
- if (commandCtx.hasUI) commandCtx.ui?.notify(`MCP initialization failed: ${message}`, "error");
1485
- return;
1486
- }
1487
- if (!serverName) {
1488
- if (programmaticConfig) {
1489
- commandCtx.ui?.notify("Use /mcp-auth <server> to authenticate a server from the in-memory SDK config.", "info");
1490
- return;
1491
- }
1492
- await commands.openMcpAuthPanel(state, pi, commandCtx, earlyConfigPath);
1493
- return;
1494
- }
1495
-
1496
- const result = await commands.authenticateServer(serverName, state.config, commandCtx, commandCtx.signal, state.oauthRuntime);
1497
- if (result.ok) {
1498
- commandOwner?.throwIfInactive();
1499
- await commands.reconnectServer(state, commandCtx, serverName);
1500
- }
1501
- },
1502
- });
1503
-
1504
- if (earlyConfig.settings?.scriptMode !== false) {
1505
- (pi.registerTool as (tool: unknown) => unknown)({
1506
- name: "mcpScript",
1507
- label: "MCP Script",
1508
- description: "Run trusted JavaScript that makes multiple MCP tool calls in one request — loop, filter, chain, or fan out between calls. For a single MCP call, search, describe, status check, or auth action, use the mcp tool instead. Discover with await tools.search({ query }) — resolves to { items: [{ path, name, server, description? }], total, hasMore, nextOffset }, not an { ok, data } envelope. Inspect with await tools.describe({ path }) — resolves to the tool descriptor with inputTypeScript, or { path, error: { code, message, suggestions } }. Then call tools.call(path, args) — resolves to { ok: true, data } or { ok: false, error: { code, message } } — or use direct flat calls when the name is already known; use emit(value) for user-visible output.",
1509
- promptSnippet: "Batch multiple MCP tool calls in one JavaScript request (loop, filter, chain)",
1510
- parameters: Type.Object({
1511
- code: Type.String({ description: "Trusted JavaScript MCP script. Use tools.<prefixedToolName>(args) and emit(value)." }),
1512
- timeoutMs: optionalNumber({ minimum: 1, description: "Execution timeout in milliseconds (default: 30000)" }),
1513
- }),
1514
- renderCall: createMcpScriptToolCallRenderer(toolRenderOptions),
1515
- renderResult: renderMcpToolResult,
1516
- async execute(_toolCallId: string, params: { code: string; timeoutMs?: number }, signal: AbortSignal | undefined, _onUpdate: unknown, ctx: ExtensionContext) {
1517
- let executeOwner = currentOwner;
1518
- if (!state) {
1519
- try {
1520
- const initialized = await awaitWithTimeout(ensureSessionRuntime(ctx), INIT_WAIT_TIMEOUT_MS);
1521
- if (initialized === INIT_WAIT_TIMED_OUT) {
1522
- return {
1523
- content: [{ type: "text" as const, text: "MCP initialization is still in progress. Try again shortly." }],
1524
- details: { mode: "script", error: "init_timeout", timeoutMs: INIT_WAIT_TIMEOUT_MS },
1525
- };
1526
- }
1527
- executeOwner = currentOwner;
1528
- executeOwner?.throwIfInactive();
1529
- state = initialized;
1530
- } catch (error) {
1531
- if (executeOwner && isOwnerAbortError(error, executeOwner)) throw error;
1532
- const message = error instanceof Error ? error.message : String(error);
1533
- return {
1534
- content: [{ type: "text" as const, text: `MCP initialization failed: ${message}` }],
1535
- details: { mode: "script", error: "init_failed", message },
1536
- };
1537
- }
1538
- }
1539
- if (!state) {
1540
- return {
1541
- content: [{ type: "text" as const, text: retainedInitFailure
1542
- ? buildInitRetryInstruction("MCP is not initialized after an earlier initialization failure")
1543
- : "MCP not initialized" }],
1544
- details: { mode: "script", error: "not_initialized", ...(retainedInitFailure ? { message: retainedInitFailure } : {}) },
1545
- };
1546
- }
1547
- executeOwner?.throwIfInactive();
1548
- const scriptState = state;
1549
- const scriptGuard = captureRuntimeGuard(scriptState);
1550
- const codeMode = await loadForRuntime(loadCodeMode, scriptGuard);
1551
- return codeMode.runMcpScript(scriptState, params.code, params.timeoutMs, getPiTools, signal);
1552
- },
1553
- });
1554
- }
1555
-
1556
- async function connectAndReport(targetState: McpExtensionState, serverName: string, signal?: AbortSignal, ctx?: ExtensionContext) {
1557
- const guard = captureRuntimeGuard(targetState);
1558
- const directToolsBefore = new Map([...registeredDirectTools.keys()].map((name) => [name, registeredDirectToolVersions.get(name) ?? 0]));
1559
- const proxyModes = await loadForRuntime(loadProxyModes, guard);
1560
- const result = await proxyModes.executeConnect(targetState, serverName, signal);
1561
- assertRuntimeGuard(guard);
1562
- if (!directToolsFrozen) syncToolSurface(ctx);
1563
- const reportedNames = reportedDirectToolNamesByServer.get(serverName) ?? new Set<string>();
1564
- // Attribute only this server's new definitions; search-mode tools load on search, not connect.
1565
- const addedToolNames = [...registeredDirectTools.keys()].filter(
1566
- (name) => (!directToolsBefore.has(name) || (registeredDirectToolVersions.get(name) ?? 0) !== directToolsBefore.get(name))
1567
- && registeredDirectToolServers.get(name) === serverName
1568
- && !reportedNames.has(name)
1569
- && !lazyDirectTools.has(name),
1570
- );
1571
- if (addedToolNames.length === 0) return result;
1572
- for (const name of addedToolNames) reportedNames.add(name);
1573
- reportedDirectToolNamesByServer.set(serverName, reportedNames);
1574
- return { ...result, addedToolNames };
1575
- }
1576
-
1577
- async function executeInstall(
1578
- targetState: McpExtensionState,
1579
- rawUrl: string | undefined,
1580
- requestedName: string | undefined,
1581
- target: string | undefined,
1582
- cwd: string,
1583
- signal?: AbortSignal,
1584
- ) {
1585
- const installOwner = currentOwner;
1586
- signal?.throwIfAborted();
1587
- installOwner?.throwIfInactive();
1588
- if (programmaticConfig) {
1589
- return {
1590
- content: [{ type: "text" as const, text: "MCP install is unavailable when the adapter uses programmatic configuration." }],
1591
- details: { mode: "install", error: "programmatic_config" },
1592
- };
1593
- }
1594
- if (targetState.config.settings?.allowInstall === false) {
1595
- return {
1596
- content: [{ type: "text" as const, text: "MCP install is disabled by configuration." }],
1597
- details: { mode: "install", error: "install_disabled" },
1598
- };
1599
- }
1600
- if (target !== undefined && target !== "global" && target !== "project") {
1601
- return {
1602
- content: [{ type: "text" as const, text: "MCP install target must be 'global' or 'project'." }],
1603
- details: { mode: "install", error: "invalid_target" },
1604
- };
1605
- }
1606
-
1607
- const destination = target === "project" ? getProjectConfigPath(cwd) : getPiGlobalConfigPath(earlyConfigPath);
1608
- if (target === "project" && process.env.PI_MCP_CONFIG_MODE?.trim().toLowerCase() === "exclusive"
1609
- && resolve(destination) !== resolve(getPiGlobalConfigPath(earlyConfigPath))) {
1610
- return {
1611
- content: [{ type: "text" as const, text: "Project installation is unavailable in exclusive config mode; use the global target." }],
1612
- details: { mode: "install", error: "inactive_target" },
1613
- };
1614
- }
1615
-
1616
- const installGuard = captureRuntimeGuard(targetState);
1617
- const installParsing = await loadForRuntime(loadInstallParsing, installGuard);
1618
- let normalized: ReturnType<typeof installParsing.normalizeMcpInstallRequest>;
1619
- try {
1620
- normalized = installParsing.normalizeMcpInstallRequest({
1621
- url: rawUrl ?? "",
1622
- ...(requestedName !== undefined ? { serverName: requestedName } : {}),
1623
- });
1624
- } catch (error) {
1625
- const message = error instanceof Error ? error.message : String(error);
1626
- return {
1627
- content: [{ type: "text" as const, text: `Failed to install MCP server: ${message}` }],
1628
- details: { mode: "install", error: "invalid_request", message },
1629
- };
1630
- }
1631
-
1632
- const matchingUrl = Object.entries(targetState.config.mcpServers).find(([, definition]) =>
1633
- definition.url !== undefined && installParsing.canonicalMcpServerUrl(definition.url) === normalized.url,
1634
- );
1635
- const serverName = requestedName?.trim() || matchingUrl?.[0] || normalized.serverName;
1636
- const existing = targetState.config.mcpServers[serverName];
1637
- if (existing && installParsing.canonicalMcpServerUrl(existing.url ?? "") !== normalized.url) {
1638
- return {
1639
- content: [{ type: "text" as const, text: `MCP server name "${serverName}" is already configured with a different endpoint.` }],
1640
- details: { mode: "install", error: "name_conflict", server: serverName, url: normalized.url },
1641
- };
1642
- }
1643
-
1644
- if (targetState.provisionalInstalls?.has(serverName)) {
1645
- return {
1646
- content: [{ type: "text" as const, text: `MCP server "${serverName}" is still being installed. Retry after that installation finishes.` }],
1647
- details: { mode: "install", error: "install_in_progress", server: serverName },
1648
- };
1649
- }
1650
- if (runtimeServers.has(serverName)) {
1651
- return {
1652
- content: [{ type: "text" as const, text: `Runtime MCP server "${serverName}" cannot be promoted by URL install. Save its full definition manually.` }],
1653
- details: { mode: "install", error: "runtime_promotion_unsupported", server: serverName },
1654
- };
1655
- }
1656
-
1657
- const persistedEntry: ServerEntry = { url: normalized.url };
1658
- const runtimeEntry: ServerEntry = existing ?? { ...persistedEntry, directTools: false };
1659
- const provisional = existing === undefined;
1660
- const restoreMetadata = [targetState.toolMetadata, targetState.promptMetadata, targetState.serverInstructions,
1661
- targetState.resourceCounts, targetState.directToolCounts].map((map) => {
1662
- const previous = map.get(serverName);
1663
- return () => {
1664
- if (previous === undefined) map.delete(serverName);
1665
- else (map as Map<string, unknown>).set(serverName, previous);
1666
- };
1667
- });
1668
- const hadLivePrompts = targetState.promptMetadataLive.has(serverName);
1669
- const rollback = async (): Promise<void> => {
1670
- if (!provisional || targetState.config.mcpServers[serverName] !== runtimeEntry) return;
1671
- delete targetState.config.mcpServers[serverName];
1672
- targetState.lifecycle.unregisterServer(serverName);
1673
- try {
1674
- await targetState.manager.close(serverName);
1675
- } finally {
1676
- targetState.provisionalInstalls?.delete(serverName);
1677
- for (const restore of restoreMetadata) restore();
1678
- if (!hadLivePrompts) targetState.promptMetadataLive.delete(serverName);
1679
- const core = await loadForRuntime(loadCoreRuntime, installGuard);
1680
- core.clearFailure(targetState, serverName, "install-rollback");
1681
- syncToolSurface();
1682
- core.updateStatusBar(targetState);
1683
- }
1684
- };
1685
-
1686
- let connectResult: Awaited<ReturnType<typeof connectAndReport>>;
1687
- try {
1688
- if (provisional) {
1689
- (targetState.provisionalInstalls ??= new Set()).add(serverName);
1690
- targetState.config.mcpServers[serverName] = runtimeEntry;
1691
- attachRuntimeServerLifecycle(targetState, serverName, runtimeEntry);
1692
- syncToolSurface();
1693
- (await loadForRuntime(loadCoreRuntime, installGuard)).updateStatusBar(targetState);
1694
- }
1695
- connectResult = await connectAndReport(targetState, serverName, signal);
1696
- signal?.throwIfAborted();
1697
- installOwner?.throwIfInactive();
1698
- } catch (error) {
1699
- await rollback();
1700
- throw error;
1701
- }
1702
- const connectError = connectResult.details?.error;
1703
- const connectText = connectResult.content.find((content) => content.type === "text")?.text;
1704
- if (connectError && connectError !== "auth_required") {
1705
- await rollback();
1706
- return {
1707
- ...connectResult,
1708
- content: [{ type: "text" as const, text: `MCP installation validation failed for "${serverName}". ${connectText ?? "Connection failed."}` }],
1709
- details: { mode: "install", error: "validation_failed", server: serverName, url: normalized.url },
1710
- };
1711
- }
1712
-
1713
- if (provisional) {
1714
- try {
1715
- await withFileMutationQueue(destination, async () => {
1716
- signal?.throwIfAborted();
1717
- installOwner?.throwIfInactive();
1718
- writeSharedServerEntry(destination, serverName, persistedEntry);
1719
- });
1720
- } catch (error) {
1721
- await rollback();
1722
- const message = error instanceof Error ? error.message : String(error);
1723
- return {
1724
- content: [{ type: "text" as const, text: `MCP server validation succeeded, but configuration could not be saved: ${message}` }],
1725
- details: { mode: "install", error: "persistence_failed", server: serverName, url: normalized.url, message },
1726
- };
1727
- }
1728
- targetState.provisionalInstalls?.delete(serverName);
1729
- try {
1730
- (await loadForRuntime(loadCoreRuntime, installGuard)).updateMetadataCache(targetState, serverName);
1731
- assertRuntimeGuard(installGuard);
1732
- } catch (error) {
1733
- if (isRuntimeGuardStale(installGuard) || isAbortError(error, installGuard.owner?.signal)) throw error;
1734
- logger.warn(`MCP: installed "${serverName}" but could not cache metadata: ${error}`);
1735
- }
1736
- syncPromptCommands();
1737
- }
1738
-
1739
- if (connectError === "auth_required") {
1740
- const proxyModes = await loadForRuntime(loadProxyModes, installGuard);
1741
- const authResult = await proxyModes.executeAuthStart(targetState, serverName, signal);
1742
- const authError = authResult.details?.error;
1743
- const authText = authResult.content.find((content) => content.type === "text")?.text;
1744
- return {
1745
- ...connectResult,
1746
- content: [{
1747
- type: "text" as const,
1748
- text: `${provisional ? "Installed" : "Found"} MCP server "${serverName}" at ${normalized.url}.\n\n${authText ?? "OAuth authorization is required."}`,
1749
- }],
1750
- details: {
1751
- mode: "install",
1752
- status: authError ? "auth_start_failed" : "awaiting_auth",
1753
- server: serverName,
1754
- url: normalized.url,
1755
- ...(provisional ? { path: destination } : {}),
1756
- ...(authError ? { error: authError } : {}),
1757
- },
1758
- };
1759
- }
1760
-
1761
- return {
1762
- ...connectResult,
1763
- content: [{
1764
- type: "text" as const,
1765
- text: `${provisional ? "Installed and connected" : "Already installed; connected"} MCP server "${serverName}" at ${normalized.url}.\n\n${connectText ?? ""}`.trim(),
1766
- }],
1767
- details: {
1768
- mode: "install",
1769
- status: "connected",
1770
- server: serverName,
1771
- url: normalized.url,
1772
- ...(provisional ? { path: destination } : {}),
1773
- },
1774
- };
1775
- }
1776
-
1777
- function registerProxyTool(description: string): void {
1778
- callReentrant(() => (pi.registerTool as (tool: unknown) => unknown)({
1779
- name: "mcp",
1780
- label: "MCP",
1781
- description,
1782
- promptSnippet: "MCP gateway — install by URL, status, search, describe, auth, and single MCP tool calls",
1783
- renderShell: toolRenderShell,
1784
- renderCall: createMcpProxyToolCallRenderer(toolRenderOptions),
1785
- parameters: Type.Object({
1786
- tool: Type.Optional(Type.String({ description: "Tool name to call (e.g., 'xcodebuild_list_sims')" })),
1787
- args: Type.Optional(Type.Union([
1788
- Type.String({ description: "Arguments as a JSON string (e.g., '{\"key\": \"value\"}')" }),
1789
- Type.Object({}, {
1790
- additionalProperties: true,
1791
- description: 'Arguments as a JSON object (e.g., { "key": "value" })',
1792
- }),
1793
- ], { description: "Tool arguments as a JSON object, or as a JSON string encoding one" })),
1794
- connect: Type.Optional(Type.String({ description: "Server name to connect (lazy connect + metadata refresh)" })),
1795
- describe: Type.Optional(Type.String({ description: "Tool name to describe (shows parameters)" })),
1796
- instructions: Type.Optional(Type.String({ description: "Server name to show that server's usage instructions" })),
1797
- search: Type.Optional(Type.String({ description: "Search tools by name/description" })),
1798
- searchMode: Type.Optional(Type.String({ enum: ["lexical", "semantic"], description: "Search backend (default: lexical; semantic is available when a System One key is configured)" })),
1799
- regex: Type.Optional(Type.Boolean({ description: "Treat search as regex (default: substring match)" })),
1800
- includeSchemas: Type.Optional(Type.Boolean({ description: "Include parameter schemas in search results (default: true)" })),
1801
- limit: optionalNumber({ minimum: 1, description: "Maximum search results to return (default: 12)" }),
1802
- offset: optionalNumber({ minimum: 0, description: "Search result offset (default: 0)" }),
1803
- server: Type.Optional(Type.String({ description: "Server name: filters searches, disambiguates calls and describe operations, and optionally names an install" })),
1804
- action: Type.Optional(Type.String({ description: "Action: 'install', 'ui-messages', 'auth-start', or 'auth-complete'" })),
1805
- url: Type.Optional(Type.String({ description: "MCP endpoint URL for action: 'install'" })),
1806
- target: Type.Optional(Type.String({ description: "Install target: 'global' (default) or 'project'" })),
1807
- }),
1808
- renderResult: renderMcpToolResult,
1809
- async execute(_toolCallId: string, params: {
1810
- tool?: string;
1811
- args?: string | Record<string, unknown>;
1812
- connect?: string;
1813
- describe?: string;
1814
- instructions?: string;
1815
- search?: string;
1816
- searchMode?: "lexical" | "semantic";
1817
- regex?: boolean;
1818
- includeSchemas?: boolean;
1819
- limit?: number;
1820
- offset?: number;
1821
- server?: string;
1822
- action?: string;
1823
- url?: string;
1824
- target?: string;
1825
- }, signal: AbortSignal | undefined, _onUpdate: AgentToolUpdateCallback<Record<string, unknown>> | undefined, _ctx: ExtensionContext) {
1826
- let executeOwner = currentOwner;
1827
- const parseArgs = (value: string | Record<string, unknown> | undefined): Record<string, unknown> | undefined => {
1828
- if (value === undefined || value === "") return undefined;
1829
- let args: unknown;
1830
- if (typeof value === "string") {
1831
- try {
1832
- args = JSON.parse(value);
1833
- } catch (error) {
1834
- if (error instanceof SyntaxError) {
1835
- throw new Error(`Invalid args JSON: ${error.message}`, { cause: error });
1836
- }
1837
- throw error;
1838
- }
1839
- } else {
1840
- args = value;
1841
- }
1842
-
1843
- if (typeof args !== "object" || args === null || Array.isArray(args)) {
1844
- const gotType = Array.isArray(args) ? "array" : args === null ? "null" : typeof args;
1845
- throw new Error(`Invalid args: expected a JSON object, got ${gotType}`);
1846
- }
1847
- return args as Record<string, unknown>;
1848
- };
1849
- const parsedArgs = parseArgs(params.args);
1850
- const hasGatewayMode = (value: typeof params): boolean =>
1851
- value.tool !== undefined
1852
- || value.connect !== undefined
1853
- || value.describe !== undefined
1854
- || value.instructions !== undefined
1855
- || value.search !== undefined
1856
- || value.server !== undefined
1857
- || value.action !== undefined
1858
- || value.url !== undefined
1859
- || value.target !== undefined;
1860
- if (!hasGatewayMode(params) && params.args !== undefined) {
1861
- throw new Error("Gateway params were nested inside `args`; pass them top-level (for example, mcp({ search: \"...\" }) or mcp({ tool: \"...\", args: {} })).");
1862
- }
1863
-
1864
- if (!state && !initPromise) {
1865
- if (retainedInitFailure) startGatewayRetryInitialization(_ctx);
1866
- else void ensureSessionRuntime(_ctx);
1867
- executeOwner = currentOwner;
1868
- }
1869
-
1870
- if (!state && initPromise) {
1871
- try {
1872
- const initialized = await awaitWithTimeout(initPromise, INIT_WAIT_TIMEOUT_MS);
1873
- if (initialized === INIT_WAIT_TIMED_OUT) {
1874
- return {
1875
- content: [{ type: "text" as const, text: "MCP initialization is still in progress. Try again shortly." }],
1876
- details: { error: "init_timeout", timeoutMs: INIT_WAIT_TIMEOUT_MS },
1877
- };
1878
- }
1879
- executeOwner?.throwIfInactive();
1880
- state = initialized;
1881
- } catch (error) {
1882
- if (executeOwner && isOwnerAbortError(error, executeOwner)) throw error;
1883
- const message = retainInitFailure(error);
1884
- return {
1885
- content: [{ type: "text" as const, text: buildInitRetryInstruction("MCP initialization failed", message) }],
1886
- details: { error: "init_failed", message },
1887
- };
1888
- }
1889
- }
1890
- if (!state) {
1891
- return {
1892
- content: [{ type: "text" as const, text: retainedInitFailure
1893
- ? buildInitRetryInstruction("MCP is not initialized after an earlier initialization failure")
1894
- : "MCP not initialized" }],
1895
- details: { error: "not_initialized", ...(retainedInitFailure ? { message: retainedInitFailure } : {}) },
1896
- };
1897
- }
1898
- executeOwner?.throwIfInactive();
1899
- const proxyState = state;
1900
- const proxyGuard = captureRuntimeGuard(proxyState);
1901
- const proxyModes = await loadForRuntime(loadProxyModes, proxyGuard);
1902
-
1903
- if (params.action === "install") {
1904
- return executeInstall(proxyState, params.url, params.server, params.target, _ctx.cwd, signal);
1905
- }
1906
- if (params.action === "ui-messages") {
1907
- return proxyModes.executeUiMessages(proxyState);
1908
- }
1909
- if (params.action === "auth-start") {
1910
- if (!params.server) {
1911
- return {
1912
- content: [{ type: "text" as const, text: "auth-start requires `server`. Example: mcp({ action: \"auth-start\", server: \"linear-server\" })" }],
1913
- details: { mode: "auth-start", error: "missing_server" },
1914
- };
1915
- }
1916
- return signal
1917
- ? proxyModes.executeAuthStart(proxyState, params.server, signal)
1918
- : proxyModes.executeAuthStart(proxyState, params.server);
1919
- }
1920
- if (params.action === "auth-complete") {
1921
- if (!params.server) {
1922
- return {
1923
- content: [{ type: "text" as const, text: "auth-complete requires `server`." }],
1924
- details: { mode: "auth-complete", error: "missing_server" },
1925
- };
1926
- }
1927
- const input = parsedArgs?.redirectUrl ?? parsedArgs?.code ?? parsedArgs?.input;
1928
- if (typeof input !== "string" || input.trim().length === 0) {
1929
- return {
1930
- content: [{ type: "text" as const, text: "auth-complete requires args with `redirectUrl`, `code`, or `input`." }],
1931
- details: { mode: "auth-complete", error: "missing_input" },
1932
- };
1933
- }
1934
- return signal
1935
- ? proxyModes.executeAuthComplete(proxyState, params.server, input, signal)
1936
- : proxyModes.executeAuthComplete(proxyState, params.server, input);
1937
- }
1938
- if (params.tool) {
1939
- const result = await proxyModes.executeCall(proxyState, params.tool, parsedArgs, params.server, getPiTools, signal);
1940
- assertRuntimeGuard(proxyGuard);
1941
- if (lazyDirectTools.size === 0) return result;
1942
- // A successful proxy call is as clear a signal as a search hit: the
1943
- // model wants this tool. Activate it the same additive way, keyed on
1944
- // the registered (prefixed) name the call reports as canonicalTool.
1945
- // Failed calls (lookup, approval, tool errors) carry details.error
1946
- // and activate nothing.
1947
- const identity = result.details as { error?: unknown; server?: unknown; canonicalTool?: unknown } | undefined;
1948
- if (!identity || identity.error !== undefined || typeof identity.server !== "string" || typeof identity.canonicalTool !== "string") return result;
1949
- holdLazyToolsInactive();
1950
- const added = activateSearchMatches([{ server: identity.server, tool: identity.canonicalTool }]);
1951
- if (added.length === 0) return result;
1952
- // Content is returned untouched: the call's output is already sized to
1953
- // the output limits and is what a compact view previews. The tool
1954
- // description tells the model a called tool is direct from then on.
1955
- return { ...result, details: { ...(result.details ?? {}), activated: added }, addedToolNames: added };
1956
- }
1957
- if (params.connect) {
1958
- return connectAndReport(proxyState, params.connect, signal, _ctx as ExtensionContext);
1959
- }
1960
- if (params.describe) {
1961
- return proxyModes.executeDescribe(proxyState, params.describe, params.server);
1962
- }
1963
- if (params.instructions) {
1964
- return proxyModes.executeInstructions(proxyState, params.instructions);
1965
- }
1966
- if (params.search !== undefined) {
1967
- const result = await proxyModes.executeSearch(proxyState, params.search, params.regex, params.server, params.includeSchemas, params.limit, params.offset, params.searchMode, signal);
1968
- assertRuntimeGuard(proxyGuard);
1969
- if (lazyDirectTools.size === 0) return result;
1970
- holdLazyToolsInactive();
1971
- const matches = (result.details as { matches?: Array<{ server: string; tool: string }> } | undefined)?.matches ?? [];
1972
- const added = activateSearchMatches(matches);
1973
- if (added.length === 0) return result;
1974
- const text = result.content.map((block) => ("text" in block ? block.text : "")).join("\n");
1975
- return {
1976
- ...result,
1977
- content: [{ type: "text" as const, text: `Activated as direct tools: ${added.join(", ")}.\n\n${text}` }],
1978
- details: { ...(result.details ?? {}), activated: added },
1979
- addedToolNames: added,
1980
- };
1981
- }
1982
- if (params.server) {
1983
- return proxyModes.executeList(proxyState, params.server);
1984
- }
1985
- return proxyModes.executeStatus(proxyState);
1986
- },
1987
- }));
1988
- proxyToolRegistered = true;
1989
- proxyToolDescription = description;
1990
- }
1991
-
1992
- function syncProxyTool(config: McpConfig, cache: MetadataCache | null, directSpecs: DirectToolSpec[]): void {
1993
- const missingConfiguredDirectToolServers = getMissingConfiguredDirectToolServers(
1994
- config,
1995
- cache,
1996
- envRaw === undefined || envRaw === "__none__" ? undefined : envDirectToolOverride,
1997
- );
1998
- // Search-mode tools are registered INACTIVE and the `mcp` gateway (search, or a
1999
- // proxy call) is their only activation entry point, so dropping it strands them: every tool held, nothing
2000
- // able to activate one. Keep it whenever any spec depends on it.
2001
- const hasSearchModeSpecs = directSpecs.some((spec) => spec.lazy === true);
2002
- const shouldRegisterProxyTool =
2003
- config.settings?.disableProxyTool !== true
2004
- || directSpecs.length === 0
2005
- || hasSearchModeSpecs
2006
- || missingConfiguredDirectToolServers.length > 0
2007
- || hasEnabledServerWithoutValidMetadata(config, cache, directSpecs);
2008
-
2009
- if (shouldRegisterProxyTool) {
2010
- const description = buildProxyDescription(config);
2011
- if (!proxyToolRegistered || proxyToolDescription !== description) {
2012
- finalizationRegistrations?.add("mcp");
2013
- registerProxyTool(description);
2014
- finalizationGuard?.();
2015
- }
2016
- const activeTools = getActiveToolsIfReady();
2017
- if (activeTools?.includes("mcp")) {
2018
- // Observed host reactivation ends our fallback ownership. A later
2019
- // host removal must not be mistaken for our own deactivation.
2020
- fallbackDeactivatedTools.delete("mcp");
2021
- } else if (activeTools && fallbackDeactivatedTools.delete("mcp")) {
2022
- // Only undo a fallback deactivation that the adapter still owns.
2023
- callReentrant(() => pi.setActiveTools([...activeTools, "mcp"]));
2024
- }
2025
- return;
2026
- }
2027
-
2028
- if (proxyToolRegistered) {
2029
- const unregistered = deactivateTools(["mcp"]);
2030
- if (unregistered.includes("mcp")) {
2031
- proxyToolRegistered = false;
2032
- proxyToolDescription = null;
2033
- }
2034
- }
2035
- }
2036
-
2037
- const initialDirectResult = syncDirectTools(earlyConfig, earlyCache);
2038
- syncProxyTool(earlyConfig, earlyCache, initialDirectResult.specs);
2039
- // Register namespace-proxy tools eagerly so tool-groups/slow-mode can validate
2040
- // `mcp:<server>` references on the first session_start turn. Without this
2041
- // eager call, the tool-groups expansion runs before MCP initialization
2042
- // completes and emits false `[unknown-tool] mcp__<server>` diagnostics.
2043
- syncNamespaceTools(earlyConfig, earlyCache, initialDirectResult.reservedDirectNames, initialDirectResult.activeDirectNames);
2044
- startLoadTimeInitialization();
2045
- }
2046
-
2047
- export function createMcpAdapter(options: McpAdapterOptions = {}) {
2048
- // Snapshot programmatic plugin roots at the API boundary so early and
2049
- // session-scoped loading cannot resolve the same relative path differently.
2050
- const factoryConfig = options.config !== undefined
2051
- ? normalizeProgrammaticConfig(cloneMcpConfig(options.config))
2052
- : undefined;
2053
- return function mcpAdapter(pi: ExtensionAPI) {
2054
- installMcpAdapter(pi, {
2055
- ...(options.configPath !== undefined ? { configPath: options.configPath } : {}),
2056
- ...(factoryConfig !== undefined ? { config: cloneMcpConfig(factoryConfig) } : {}),
2057
- });
2058
- };
2059
- }
2060
-
2061
- /**
2062
- * Register an MCP server with the adapter installed for this Pi instance.
2063
- * Registrations are session/runtime scoped and never persisted. Duplicate
2064
- * names fail closed. Registered servers are proxy-tool-only; their tools
2065
- * become visible at the next tool sync. To change a definition, dispose the
2066
- * registration and register again.
2067
- */
2068
- export function registerMcpServer(options: { pi: ExtensionAPI; name: string; definition: ServerEntry }): McpServerRegistration {
2069
- const { pi, name, definition } = options;
2070
- const register = runtimeRegistrars.get(pi);
2071
- if (register) return register(name, definition);
2072
- const request: McpRuntimeRegistrationRequest = {
2073
- version: MCP_RUNTIME_REGISTER_VERSION,
2074
- name,
2075
- definition,
2076
- };
2077
- pi.events.emit(MCP_RUNTIME_REGISTER_EVENT, request);
2078
- if (!request.result) {
2079
- throw new Error("pi-mcp-adapter is not installed for this Pi instance");
2080
- }
2081
- if (!request.result.ok) throw request.result.error;
2082
- return request.result.registration;
2083
- }
2084
-
2085
- /**
2086
- * Return a detached, non-persisted snapshot of one runtime-registered MCP
2087
- * server. Configured servers and runtime registrations shadowed by config are
2088
- * never exported through this API.
2089
- */
2090
- export function getRuntimeMcpServerSnapshot(options: { pi: ExtensionAPI; name: string }): McpRuntimeServerSnapshot {
2091
- const { pi, name } = options;
2092
- const getSnapshot = runtimeSnapshotters.get(pi);
2093
- if (getSnapshot) return getSnapshot(name);
2094
- const request: McpRuntimeSnapshotRequest = {
2095
- version: MCP_RUNTIME_SNAPSHOT_VERSION,
2096
- name,
2097
- };
2098
- pi.events.emit(MCP_RUNTIME_SNAPSHOT_EVENT, request);
2099
- if (!request.result) {
2100
- throw new Error("pi-mcp-adapter is not installed for this Pi instance");
2101
- }
2102
- if (!request.result.ok) throw request.result.error;
2103
- return request.result.snapshot;
2104
- }
2105
-
2106
- export default createMcpAdapter();