@herbertgao/pi-extensions 2026.9.17 → 2026.9.18

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 (266) hide show
  1. package/README.md +14 -15
  2. package/node_modules/@herbertgao/pi-bark/package.json +2 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +4 -1
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.md +4 -1
  5. package/node_modules/@herbertgao/pi-cc-extensions/package.json +75 -75
  6. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  7. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  8. package/node_modules/@herbertgao/resume-from/package.json +4 -2
  9. package/node_modules/@herbertgao/sol-pi/package.json +6 -6
  10. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +99 -99
  11. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  12. package/node_modules/@tifan/pi-handoff/package.json +1 -1
  13. package/node_modules/@tifan/pi-handoff/src/index.ts +56 -26
  14. package/node_modules/@tifan/pi-mermaid-open/package.json +1 -1
  15. package/node_modules/@tifan/pi-preferred-thinking/package.json +1 -1
  16. package/node_modules/@tifan/pi-recap/package.json +1 -1
  17. package/node_modules/@tifan/pi-rename/package.json +1 -1
  18. package/node_modules/pi-antigravity/package.json +2 -2
  19. package/node_modules/pi-lens/package.json +158 -158
  20. package/node_modules/pi-multi-account/package.json +2 -2
  21. package/node_modules/pi-typesafe/README.md +1 -1
  22. package/node_modules/pi-typesafe/dist/auth.d.ts +9 -5
  23. package/node_modules/pi-typesafe/dist/auth.js +7 -3
  24. package/node_modules/pi-typesafe/dist/backends.d.ts +33 -3
  25. package/node_modules/pi-typesafe/dist/backends.js +95 -1
  26. package/node_modules/pi-typesafe/dist/client.d.ts +5 -5
  27. package/node_modules/pi-typesafe/dist/client.js +12 -9
  28. package/node_modules/pi-typesafe/dist/credentials.d.ts +14 -8
  29. package/node_modules/pi-typesafe/dist/credentials.js +10 -7
  30. package/node_modules/pi-typesafe/dist/errors.d.ts +2 -2
  31. package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
  32. package/node_modules/pi-typesafe/dist/index.js +1 -1
  33. package/node_modules/pi-typesafe/dist/login.d.ts +2 -2
  34. package/node_modules/pi-typesafe/dist/login.js +2 -2
  35. package/node_modules/pi-typesafe/package.json +5 -5
  36. package/node_modules/pi-web-access/package.json +2 -2
  37. package/node_modules/remote-pi/package.json +5 -1
  38. package/package.json +16 -28
  39. package/node_modules/pi-mcp-adapter/CHANGELOG.md +0 -1013
  40. package/node_modules/pi-mcp-adapter/LICENSE +0 -21
  41. package/node_modules/pi-mcp-adapter/OAUTH.md +0 -369
  42. package/node_modules/pi-mcp-adapter/README.md +0 -1021
  43. package/node_modules/pi-mcp-adapter/abort.ts +0 -34
  44. package/node_modules/pi-mcp-adapter/agent-dir.ts +0 -68
  45. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +0 -416
  46. package/node_modules/pi-mcp-adapter/agent-plugin-provenance.ts +0 -36
  47. package/node_modules/pi-mcp-adapter/app-bridge.bundle.js +0 -67
  48. package/node_modules/pi-mcp-adapter/banner.png +0 -0
  49. package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +0 -193
  50. package/node_modules/pi-mcp-adapter/claude-plugin-loader.ts +0 -365
  51. package/node_modules/pi-mcp-adapter/cli.js +0 -454
  52. package/node_modules/pi-mcp-adapter/commands.ts +0 -861
  53. package/node_modules/pi-mcp-adapter/config.ts +0 -1565
  54. package/node_modules/pi-mcp-adapter/consent-manager.ts +0 -95
  55. package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +0 -244
  56. package/node_modules/pi-mcp-adapter/direct-tools.ts +0 -428
  57. package/node_modules/pi-mcp-adapter/dist/abort.d.ts +0 -2
  58. package/node_modules/pi-mcp-adapter/dist/abort.js +0 -36
  59. package/node_modules/pi-mcp-adapter/dist/abort.js.map +0 -1
  60. package/node_modules/pi-mcp-adapter/dist/agent-dir.d.ts +0 -10
  61. package/node_modules/pi-mcp-adapter/dist/agent-dir.js +0 -63
  62. package/node_modules/pi-mcp-adapter/dist/agent-dir.js.map +0 -1
  63. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.d.ts +0 -8
  64. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js +0 -398
  65. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js.map +0 -1
  66. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.d.ts +0 -1
  67. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js +0 -31
  68. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js.map +0 -1
  69. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +0 -6
  70. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +0 -194
  71. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +0 -1
  72. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.d.ts +0 -9
  73. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js +0 -362
  74. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js.map +0 -1
  75. package/node_modules/pi-mcp-adapter/dist/config.d.ts +0 -134
  76. package/node_modules/pi-mcp-adapter/dist/config.js +0 -1361
  77. package/node_modules/pi-mcp-adapter/dist/config.js.map +0 -1
  78. package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +0 -18
  79. package/node_modules/pi-mcp-adapter/dist/consent-manager.js +0 -88
  80. package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +0 -1
  81. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +0 -17
  82. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +0 -316
  83. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +0 -1
  84. package/node_modules/pi-mcp-adapter/dist/errors.d.ts +0 -131
  85. package/node_modules/pi-mcp-adapter/dist/errors.js +0 -278
  86. package/node_modules/pi-mcp-adapter/dist/errors.js.map +0 -1
  87. package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +0 -8
  88. package/node_modules/pi-mcp-adapter/dist/http-ca.js +0 -88
  89. package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +0 -1
  90. package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +0 -14
  91. package/node_modules/pi-mcp-adapter/dist/jev-client.js +0 -367
  92. package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +0 -1
  93. package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +0 -77
  94. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +0 -2
  95. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +0 -1
  96. package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +0 -51
  97. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +0 -205
  98. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +0 -1
  99. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +0 -2
  100. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +0 -68
  101. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +0 -1
  102. package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +0 -56
  103. package/node_modules/pi-mcp-adapter/dist/lifecycle.js +0 -410
  104. package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +0 -1
  105. package/node_modules/pi-mcp-adapter/dist/logger.d.ts +0 -51
  106. package/node_modules/pi-mcp-adapter/dist/logger.js +0 -131
  107. package/node_modules/pi-mcp-adapter/dist/logger.js.map +0 -1
  108. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +0 -26
  109. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +0 -103
  110. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +0 -1
  111. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +0 -118
  112. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +0 -1097
  113. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +0 -1
  114. package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +0 -170
  115. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +0 -1142
  116. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +0 -1
  117. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.d.ts +0 -24
  118. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +0 -203
  119. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +0 -1
  120. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +0 -53
  121. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +0 -441
  122. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +0 -1
  123. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +0 -148
  124. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +0 -689
  125. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +0 -1
  126. package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +0 -6
  127. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +0 -166
  128. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +0 -1
  129. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +0 -102
  130. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +0 -369
  131. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +0 -1
  132. package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +0 -95
  133. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +0 -242
  134. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +0 -1
  135. package/node_modules/pi-mcp-adapter/dist/metadata-cache.d.ts +0 -19
  136. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +0 -301
  137. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +0 -1
  138. package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +0 -6
  139. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +0 -505
  140. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +0 -1
  141. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.d.ts +0 -2
  142. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js +0 -144
  143. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js.map +0 -1
  144. package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +0 -10
  145. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +0 -312
  146. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +0 -1
  147. package/node_modules/pi-mcp-adapter/dist/resource-tools.d.ts +0 -1
  148. package/node_modules/pi-mcp-adapter/dist/resource-tools.js +0 -15
  149. package/node_modules/pi-mcp-adapter/dist/resource-tools.js.map +0 -1
  150. package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +0 -13
  151. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +0 -97
  152. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +0 -1
  153. package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +0 -17
  154. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +0 -203
  155. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +0 -1
  156. package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +0 -11
  157. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +0 -172
  158. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +0 -1
  159. package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +0 -157
  160. package/node_modules/pi-mcp-adapter/dist/server-manager.js +0 -1745
  161. package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +0 -1
  162. package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +0 -34
  163. package/node_modules/pi-mcp-adapter/dist/session-approvals.js +0 -140
  164. package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +0 -1
  165. package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +0 -26
  166. package/node_modules/pi-mcp-adapter/dist/session-recovery.js +0 -142
  167. package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +0 -1
  168. package/node_modules/pi-mcp-adapter/dist/state.d.ts +0 -73
  169. package/node_modules/pi-mcp-adapter/dist/state.js +0 -2
  170. package/node_modules/pi-mcp-adapter/dist/state.js.map +0 -1
  171. package/node_modules/pi-mcp-adapter/dist/types.d.ts +0 -642
  172. package/node_modules/pi-mcp-adapter/dist/types.js +0 -331
  173. package/node_modules/pi-mcp-adapter/dist/types.js.map +0 -1
  174. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.d.ts +0 -6
  175. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js +0 -37
  176. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js.map +0 -1
  177. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +0 -17
  178. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +0 -219
  179. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +0 -1
  180. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.d.ts +0 -104
  181. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js +0 -59
  182. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js.map +0 -1
  183. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.d.ts +0 -4
  184. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js +0 -27
  185. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js.map +0 -1
  186. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +0 -15
  187. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +0 -85
  188. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +0 -1
  189. package/node_modules/pi-mcp-adapter/dist/utils.d.ts +0 -41
  190. package/node_modules/pi-mcp-adapter/dist/utils.js +0 -417
  191. package/node_modules/pi-mcp-adapter/dist/utils.js.map +0 -1
  192. package/node_modules/pi-mcp-adapter/elicitation-handler.ts +0 -358
  193. package/node_modules/pi-mcp-adapter/error-signal.ts +0 -32
  194. package/node_modules/pi-mcp-adapter/errors.ts +0 -364
  195. package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +0 -85
  196. package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +0 -34
  197. package/node_modules/pi-mcp-adapter/failure-backoff.ts +0 -23
  198. package/node_modules/pi-mcp-adapter/glimpse-ui.ts +0 -80
  199. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -515
  200. package/node_modules/pi-mcp-adapter/http-ca.ts +0 -84
  201. package/node_modules/pi-mcp-adapter/index.ts +0 -2106
  202. package/node_modules/pi-mcp-adapter/init.ts +0 -721
  203. package/node_modules/pi-mcp-adapter/jev-client.ts +0 -300
  204. package/node_modules/pi-mcp-adapter/jev-contracts.ts +0 -53
  205. package/node_modules/pi-mcp-adapter/jev-key-store.ts +0 -192
  206. package/node_modules/pi-mcp-adapter/json-schema-validator.ts +0 -83
  207. package/node_modules/pi-mcp-adapter/lazy-loader.ts +0 -7
  208. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -457
  209. package/node_modules/pi-mcp-adapter/logger.ts +0 -169
  210. package/node_modules/pi-mcp-adapter/mcp-auth-fetch.ts +0 -113
  211. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +0 -1296
  212. package/node_modules/pi-mcp-adapter/mcp-auth.ts +0 -1287
  213. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +0 -250
  214. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +0 -534
  215. package/node_modules/pi-mcp-adapter/mcp-code.ts +0 -495
  216. package/node_modules/pi-mcp-adapter/mcp-install.ts +0 -60
  217. package/node_modules/pi-mcp-adapter/mcp-keyring-helper.cjs +0 -89
  218. package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +0 -819
  219. package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +0 -511
  220. package/node_modules/pi-mcp-adapter/mcp-panel-theme.ts +0 -104
  221. package/node_modules/pi-mcp-adapter/mcp-panel.ts +0 -1099
  222. package/node_modules/pi-mcp-adapter/mcp-probe.ts +0 -201
  223. package/node_modules/pi-mcp-adapter/mcp-references.ts +0 -373
  224. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +0 -137
  225. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +0 -747
  226. package/node_modules/pi-mcp-adapter/mcp-status.ts +0 -105
  227. package/node_modules/pi-mcp-adapter/mcp-tasks.ts +0 -467
  228. package/node_modules/pi-mcp-adapter/mcp-trace.ts +0 -306
  229. package/node_modules/pi-mcp-adapter/metadata-cache.ts +0 -362
  230. package/node_modules/pi-mcp-adapter/namespace-tools.ts +0 -334
  231. package/node_modules/pi-mcp-adapter/npx-resolver.ts +0 -543
  232. package/node_modules/pi-mcp-adapter/oauth-handler.ts +0 -30
  233. package/node_modules/pi-mcp-adapter/oauth.ts +0 -52
  234. package/node_modules/pi-mcp-adapter/onboarding-state.ts +0 -76
  235. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +0 -156
  236. package/node_modules/pi-mcp-adapter/package.json +0 -219
  237. package/node_modules/pi-mcp-adapter/panel-keys.ts +0 -53
  238. package/node_modules/pi-mcp-adapter/prompts.ts +0 -385
  239. package/node_modules/pi-mcp-adapter/proxy-modes.ts +0 -1735
  240. package/node_modules/pi-mcp-adapter/request-headers-command.ts +0 -333
  241. package/node_modules/pi-mcp-adapter/resource-tools.ts +0 -17
  242. package/node_modules/pi-mcp-adapter/runtime-owner.ts +0 -106
  243. package/node_modules/pi-mcp-adapter/sampling-handler.ts +0 -261
  244. package/node_modules/pi-mcp-adapter/sandbox-proxy-template.ts +0 -144
  245. package/node_modules/pi-mcp-adapter/search-ranking.ts +0 -309
  246. package/node_modules/pi-mcp-adapter/secure-keyring.ts +0 -170
  247. package/node_modules/pi-mcp-adapter/semantic-search.ts +0 -186
  248. package/node_modules/pi-mcp-adapter/server-manager.ts +0 -2088
  249. package/node_modules/pi-mcp-adapter/session-approvals.ts +0 -201
  250. package/node_modules/pi-mcp-adapter/session-recovery.ts +0 -158
  251. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +0 -49
  252. package/node_modules/pi-mcp-adapter/state.ts +0 -71
  253. package/node_modules/pi-mcp-adapter/tool-approval.ts +0 -205
  254. package/node_modules/pi-mcp-adapter/tool-metadata.ts +0 -302
  255. package/node_modules/pi-mcp-adapter/tool-registrar.ts +0 -255
  256. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +0 -573
  257. package/node_modules/pi-mcp-adapter/ts-shape.ts +0 -149
  258. package/node_modules/pi-mcp-adapter/types.ts +0 -1120
  259. package/node_modules/pi-mcp-adapter/ui-app-bridge-helpers.ts +0 -40
  260. package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +0 -266
  261. package/node_modules/pi-mcp-adapter/ui-server.ts +0 -993
  262. package/node_modules/pi-mcp-adapter/ui-session.ts +0 -624
  263. package/node_modules/pi-mcp-adapter/ui-stream-types.ts +0 -89
  264. package/node_modules/pi-mcp-adapter/ui-tool-visibility.ts +0 -25
  265. package/node_modules/pi-mcp-adapter/unix-socket-transport.ts +0 -85
  266. package/node_modules/pi-mcp-adapter/utils.ts +0 -464
@@ -1,861 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
3
- import type { OverlayHandle } from "@earendil-works/pi-tui";
4
- import type { McpExtensionState } from "./state.ts";
5
- import { isServerDisabled, type McpAuthResult, type McpConfig, type McpPanelCallbacks, type McpPanelResult, type ImportKind } from "./types.ts";
6
- import {
7
- ensureCompatibilityImports,
8
- getSharedConfigPath,
9
- getMcpDiscoverySummary,
10
- getMcpStandardConfigSummary,
11
- type KnownServerPreset,
12
- type SharedConfigTarget,
13
- getServerProvenance,
14
- previewCompatibilityImports,
15
- previewSharedServerEntry,
16
- previewStarterSharedConfig,
17
- writeDirectToolsConfig,
18
- writeJevSemanticSearchConfig,
19
- writeProjectServerDisabledOverride,
20
- writeSharedServerEntry,
21
- writeSharedConfigText,
22
- writeStarterSharedConfig,
23
- } from "./config.ts";
24
- import { markKeepAliveAfterConnect, notifyToolMetadataUpdated, updateMetadataCache, updateStatusBar, getFailureAgeSeconds, getFailureMessage, clearFailure, recordFailure } from "./init.ts";
25
- import { isServerInActiveFailureBackoff } from "./failure-backoff.ts";
26
- import { loadMetadataCache, reconstructPromptMetadata } from "./metadata-cache.ts";
27
- import { buildToolMetadata } from "./tool-metadata.ts";
28
- import { supportsOAuth, authenticate, removeAuth, type McpOAuthRuntime } from "./mcp-auth-flow.ts";
29
- import { getAuthStorageOptions, inspectAuthForUrl } from "./mcp-auth.ts";
30
- import { inspectBearerTokenForUrl, removeBearerToken } from "./mcp-bearer-store.ts";
31
- import { loadOnboardingState, markSetupCompleted as persistSetupCompleted, markSharedConfigHintShown } from "./onboarding-state.ts";
32
- import { formatTerminalError, openPath, resolveServerUrl, sanitizeTerminalText } from "./utils.ts";
33
- import { isAbortError } from "./runtime-owner.ts";
34
- import { resolveJevCredential } from "./jev-key-store.ts";
35
-
36
- function terminalHyperlink(label: string, url: string): string {
37
- return `\u001B]8;;${sanitizeTerminalText(url)}\u001B\\${sanitizeTerminalText(label)}\u001B]8;;\u001B\\`;
38
- }
39
-
40
- /**
41
- * True when this run mode can display a `ctx.ui.custom()` overlay.
42
- *
43
- * `ctx.hasUI` only reports that *some* UI context is bound. In rpc/print mode
44
- * that context is a headless stub whose `custom()` returns immediately without
45
- * ever invoking the factory or the `done` callback, so a panel awaited through
46
- * `new Promise(resolve => ctx.ui.custom(...))` never settles and the command
47
- * hangs. Overlay panels therefore additionally require `ctx.mode === "tui"`.
48
- */
49
- function canRenderPanel(ctx: ExtensionContext): boolean {
50
- return ctx.hasUI && ctx.mode === "tui";
51
- }
52
-
53
- export async function editSharedConfig(ctx: ExtensionContext, target: SharedConfigTarget): Promise<boolean> {
54
- if (!ctx.hasUI) return false;
55
- const path = getSharedConfigPath(target, ctx.cwd);
56
- const before = existsSync(path) ? readFileSync(path, "utf8") : '{\n "mcpServers": {}\n}\n';
57
- const after = await ctx.ui.editor(`Edit ${path} (Ctrl+G opens $EDITOR)`, before);
58
- if (after === undefined || after === before) return false;
59
- try {
60
- writeSharedConfigText(path, after);
61
- } catch (error) {
62
- ctx.ui.notify(`MCP: not saved: ${formatTerminalError(error)}`, "error");
63
- return false;
64
- }
65
- return true;
66
- }
67
-
68
- export async function setupJevSemanticSearch(
69
- state: McpExtensionState,
70
- ctx: ExtensionContext,
71
- configOverridePath?: string,
72
- ): Promise<boolean> {
73
- if (!ctx.hasUI) return false;
74
- const credential = resolveJevCredential();
75
- if (credential.status !== "present") {
76
- const detail = credential.status === "unavailable" ? ` ${credential.message}` : "";
77
- ctx.ui.notify(
78
- `Jev needs a System One API key.${detail}\nRun \`pi-mcp-adapter key set systemone\` in a terminal, then run \`/mcp jev setup\` again.`,
79
- "error",
80
- );
81
- return false;
82
- }
83
-
84
- const servers = Object.keys(state.config.mcpServers)
85
- .filter((name) => !isServerDisabled(state.config.mcpServers[name]))
86
- .sort((a, b) => a.localeCompare(b));
87
- if (servers.length === 0) {
88
- ctx.ui.notify("Enable or add an MCP server before setting up Jev semantic search.", "error");
89
- return false;
90
- }
91
- const configuredJev = state.config.settings?.jev;
92
- const payloadDisclosure = configuredJev && configuredJev.scriptEvaluation
93
- ? " Allowed servers can also be sources for script evaluations, which may send state and MCP results."
94
- : " Semantic search does not send tool results.";
95
-
96
- const choice = await ctx.ui.select("Configure Jev semantic search", [
97
- `Use all ${servers.length} enabled servers (default)`,
98
- "Restrict to selected servers",
99
- "Cancel",
100
- ]);
101
- if (!choice || choice === "Cancel") return false;
102
-
103
- let allowedServers: string[];
104
- if (choice.startsWith("Use all ")) {
105
- const confirmed = await ctx.ui.confirm(
106
- "Share MCP tool metadata with Jev?",
107
- `Semantic searches send the query text, server names, tool paths, tool names, and descriptions from ${servers.length} servers to the configured Jev endpoint.${payloadDisclosure}`,
108
- );
109
- if (!confirmed) return false;
110
- allowedServers = servers;
111
- } else {
112
- allowedServers = [];
113
- for (const server of servers) {
114
- if (await ctx.ui.confirm(
115
- `Allow ${server}?`,
116
- `Semantic searches send the query text, server name, tool paths, tool names, and descriptions to the configured Jev endpoint.${payloadDisclosure}`,
117
- )) allowedServers.push(server);
118
- }
119
- if (allowedServers.length === 0) {
120
- ctx.ui.notify("Jev setup cancelled because no servers were allowed.", "info");
121
- return false;
122
- }
123
- }
124
-
125
- try {
126
- const result = writeJevSemanticSearchConfig(configOverridePath, ctx.cwd, allowedServers, state.config.settings?.jev);
127
- ctx.ui.notify(
128
- result.changed
129
- ? `Jev semantic search configured for ${allowedServers.length} server${allowedServers.length === 1 ? "" : "s"}. Reloading Pi…`
130
- : "Jev semantic search is already configured for those servers.",
131
- "info",
132
- );
133
- return result.changed;
134
- } catch (error) {
135
- ctx.ui.notify(`Jev setup failed: ${formatTerminalError(error)}`, "error");
136
- return false;
137
- }
138
- }
139
-
140
- export async function showStatus(state: McpExtensionState, ctx: ExtensionContext): Promise<void> {
141
- if (!ctx.hasUI) return;
142
-
143
- const lines: string[] = ["MCP Server Status:", ""];
144
- if (!state.programmaticConfig) {
145
- lines.push(
146
- "Shared MCP config: .mcp.json for this project/team or ~/.config/mcp/mcp.json for all projects.",
147
- "Pi-owned files hold compatibility imports and adapter-specific overrides.",
148
- "",
149
- );
150
- }
151
-
152
- for (const name of Object.keys(state.config.mcpServers)) {
153
- const definition = state.config.mcpServers[name];
154
- if (isServerDisabled(definition)) {
155
- lines.push(`⊘ ${name}: disabled (run /mcp enable ${name}, then /reload)`);
156
- continue;
157
- }
158
- const connection = state.manager.getConnection(name);
159
- const metadata = state.toolMetadata.get(name);
160
- const toolCount = metadata?.length ?? 0;
161
- const failedAgo = getFailureAgeSeconds(state, name);
162
- let status = "not listening (disconnected)";
163
- let statusIcon = "○";
164
- let failed = false;
165
-
166
- if (connection?.status === "connected") {
167
- status = connection.listenState === "active"
168
- ? connection.listenCatalogStale
169
- ? "connected; listen active; catalog may be stale"
170
- : "connected; listen active"
171
- : connection.listenState === "dropped"
172
- ? "connected; catalog may be stale — will reconcile on next keep-alive or tool use"
173
- : connection.listenState === "re-establishing"
174
- ? "connected; re-establishing listen"
175
- : connection.listenState === "legacy"
176
- ? "connected; legacy notification path"
177
- : "connected; not listening for catalog updates";
178
- statusIcon = "✓";
179
- } else if (connection?.status === "needs-auth") {
180
- status = "needs auth";
181
- statusIcon = "⚠";
182
- } else if (failedAgo !== null) {
183
- const reason = sanitizeTerminalText(getFailureMessage(state, name) ?? "");
184
- status = reason ? `failed ${failedAgo}s ago — ${reason}` : `failed ${failedAgo}s ago`;
185
- statusIcon = "✗";
186
- failed = true;
187
- } else if (metadata !== undefined) {
188
- status = "cached; not listening (disconnected)";
189
- }
190
-
191
- const toolSuffix = failed ? "" : ` (${toolCount} tools${status.startsWith("cached") ? ", cached" : ""})`;
192
- lines.push(`${statusIcon} ${name}: ${status}${toolSuffix}`);
193
- }
194
-
195
- if (state.config.settings?.freezeDirectTools === true) {
196
- lines.push("", "Direct tools frozen; active registrations may differ from current metadata.");
197
- }
198
-
199
- if (Object.keys(state.config.mcpServers).length === 0) {
200
- lines.push("No MCP servers configured");
201
- lines.push("Run /mcp setup to add a server to .mcp.json or ~/.config/mcp/mcp.json");
202
- }
203
-
204
- ctx.ui.notify(lines.join("\n"), "info");
205
- }
206
-
207
- export async function showPrompts(state: McpExtensionState, ctx: ExtensionContext): Promise<void> {
208
- if (!ctx.hasUI) return;
209
- const allPrompts = [...(state.promptMetadata?.values() ?? [])].flat();
210
- const failedPromptServers = [...(state.manager.getAllConnections?.() ?? [])]
211
- .filter(([, connection]) => connection.status === "connected" && connection.promptDiscoveryFailed)
212
- .map(([serverName]) => serverName)
213
- .sort();
214
- if (allPrompts.length === 0) {
215
- const failureNote = failedPromptServers.length > 0
216
- ? ` Prompt discovery failed for: ${failedPromptServers.join(", ")}.`
217
- : "";
218
- ctx.ui.notify(`No MCP prompts available. Prompts are discovered when servers with the \`prompts\` capability connect.${failureNote}`, "info");
219
- return;
220
- }
221
- const lines = ["MCP Prompts:", ""];
222
- const grouped = new Map<string, typeof allPrompts>();
223
- for (const prompt of allPrompts) {
224
- const list = grouped.get(prompt.serverName) ?? [];
225
- list.push(prompt);
226
- grouped.set(prompt.serverName, list);
227
- }
228
- for (const [serverName, prompts] of [...grouped.entries()].sort(([a], [b]) => a.localeCompare(b))) {
229
- lines.push(`${serverName}:`);
230
- for (const prompt of prompts.sort((a, b) => a.commandName.localeCompare(b.commandName))) {
231
- const args = prompt.arguments.map(argument => argument.required ? `<${argument.name}>` : `[${argument.name}]`).join(" ");
232
- lines.push(` /${prompt.commandName}${args ? ` ${args}` : ""}`);
233
- if (prompt.description) lines.push(` ${prompt.description}`);
234
- }
235
- lines.push("");
236
- }
237
- lines.push(`Total: ${allPrompts.length} prompt${allPrompts.length === 1 ? "" : "s"}`);
238
- if (failedPromptServers.length > 0) {
239
- lines.push(`Prompt discovery failed for: ${failedPromptServers.join(", ")}. Cached prompt metadata may be stale.`);
240
- }
241
- ctx.ui.notify(lines.join("\n"), "info");
242
- }
243
-
244
- export async function showTools(state: McpExtensionState, ctx: ExtensionContext): Promise<void> {
245
- if (!ctx.hasUI) return;
246
-
247
- const allTools = [...state.toolMetadata.entries()]
248
- .filter(([serverName]) => !isServerDisabled(state.config.mcpServers[serverName]))
249
- .filter(([serverName]) => !isServerInActiveFailureBackoff(state, serverName))
250
- .flatMap(([, metadata]) => metadata.map(m => m.name));
251
-
252
- if (allTools.length === 0) {
253
- ctx.ui.notify("No MCP tools available", "info");
254
- return;
255
- }
256
-
257
- const lines = [
258
- "MCP Tools:",
259
- "",
260
- ...allTools.map(t => ` ${t}`),
261
- "",
262
- `Total: ${allTools.length} tools`,
263
- ];
264
-
265
- ctx.ui.notify(lines.join("\n"), "info");
266
- }
267
-
268
- export async function reconnectServer(
269
- state: McpExtensionState,
270
- ctx: ExtensionContext,
271
- name: string,
272
- ): Promise<boolean> {
273
- const definition = state.config.mcpServers[name];
274
- const ui = ctx.hasUI ? ctx.ui : undefined;
275
- const signal = state.owner?.signal;
276
- if (!definition) {
277
- if (ui) {
278
- ui.notify(`Server "${name}" not found in config`, "error");
279
- }
280
- return false;
281
- }
282
- if (isServerDisabled(definition)) {
283
- if (ui) ui.notify(`MCP: ${name} is disabled. Run /mcp enable ${name}, then /reload.`, "warning");
284
- return false;
285
- }
286
-
287
- try {
288
- state.owner?.throwIfInactive();
289
- const current = state.manager.getConnection(name);
290
- const connection = current
291
- ? await state.manager.reconnect(name, definition, current, signal)
292
- : await state.manager.connect(name, definition, signal);
293
- state.owner?.throwIfInactive();
294
- if (connection.status === "needs-auth") {
295
- if (ui) {
296
- ui.notify(`MCP: ${name} requires OAuth. Run /mcp-auth ${name} first.`, "warning");
297
- }
298
- updateStatusBar(state);
299
- return false;
300
- }
301
-
302
- const prefix = state.config.settings?.toolPrefix ?? "server";
303
- const { metadata, failedTools } = buildToolMetadata(connection.tools, connection.resources, definition, name, prefix, state.config.mcpServers, state.toolMetadata);
304
- state.toolMetadata.set(name, metadata);
305
- if (!connection.promptDiscoveryFailed) {
306
- state.promptMetadata?.set(name, reconstructPromptMetadata(name, connection.prompts ?? [], prefix, definition));
307
- state.promptMetadataLive?.add(name);
308
- }
309
- if (connection.instructions) {
310
- state.serverInstructions.set(name, connection.instructions);
311
- } else {
312
- state.serverInstructions.delete(name);
313
- }
314
- updateMetadataCache(state, name);
315
- const restored = clearFailure(state, name, "command-reconnect");
316
- if (!restored) notifyToolMetadataUpdated(state, name, "command-reconnect");
317
- markKeepAliveAfterConnect(state, name);
318
-
319
- if (ui) {
320
- ui.notify(
321
- `MCP: Reconnected to ${name} (${connection.tools.length} tools, ${connection.resources.length} resources)`,
322
- "info"
323
- );
324
- if (failedTools.length > 0) {
325
- ui.notify(`MCP: ${name} - ${failedTools.length} tools skipped`, "warning");
326
- }
327
- }
328
- updateStatusBar(state);
329
- return true;
330
- } catch (error) {
331
- const message = error instanceof Error ? error.message : String(error);
332
- if (isAbortError(error, signal)) throw error;
333
- recordFailure(state, name, message);
334
- if (ui) {
335
- ui.notify(`MCP: Failed to reconnect to ${name}: ${sanitizeTerminalText(message)}`, "error");
336
- }
337
- updateStatusBar(state);
338
- return false;
339
- }
340
- }
341
-
342
- export async function reconnectServers(
343
- state: McpExtensionState,
344
- ctx: ExtensionContext,
345
- targetServer?: string
346
- ): Promise<void> {
347
- if (targetServer && !state.config.mcpServers[targetServer]) {
348
- if (ctx.hasUI) {
349
- ctx.ui.notify(`Server "${targetServer}" not found in config`, "error");
350
- }
351
- return;
352
- }
353
-
354
- const names = targetServer ? [targetServer] : Object.keys(state.config.mcpServers);
355
- for (const name of names) {
356
- await reconnectServer(state, ctx, name);
357
- }
358
-
359
- updateStatusBar(state);
360
- }
361
-
362
- export async function authenticateServer(
363
- serverName: string,
364
- config: McpConfig,
365
- ctx: ExtensionContext,
366
- signal?: AbortSignal,
367
- runtime?: McpOAuthRuntime,
368
- ): Promise<McpAuthResult> {
369
- const ui = ctx.hasUI ? ctx.ui : undefined;
370
- const cwd = ctx.cwd;
371
- signal ??= ctx.signal;
372
- if (!ui) return { ok: false, message: "OAuth authentication requires an interactive session." };
373
-
374
- const definition = config.mcpServers[serverName];
375
- if (!definition) {
376
- const message = `Server "${serverName}" not found in config`;
377
- ui.notify(message, "error");
378
- return { ok: false, message };
379
- }
380
- if (isServerDisabled(definition)) {
381
- const message = `Server "${serverName}" is disabled. Run /mcp enable ${serverName}, then /reload.`;
382
- ui.notify(message, "warning");
383
- return { ok: false, message };
384
- }
385
-
386
- if (!supportsOAuth(definition)) {
387
- const message = `Server "${serverName}" does not use OAuth authentication. Set "auth": "oauth" or omit auth for auto-detection.`;
388
- ui.notify(
389
- `Server "${serverName}" does not use OAuth authentication.\n` +
390
- `Set "auth": "oauth" or omit auth for auto-detection.`,
391
- "error"
392
- );
393
- return { ok: false, message };
394
- }
395
-
396
- try {
397
- const serverUrl = resolveServerUrl(definition);
398
- if (!serverUrl) {
399
- const message = `Server "${serverName}" has no URL configured (OAuth requires HTTP transport)`;
400
- ui.notify(message, "error");
401
- return { ok: false, message };
402
- }
403
-
404
- ui.setStatus("mcp-auth", `Authenticating ${serverName}...`);
405
- const authStorageOptions = getAuthStorageOptions(config.settings?.oauthDir, cwd, config.settings?.oauthCredentialStore);
406
- const status = await authenticate(serverName, serverUrl, definition, {
407
- ...(Object.keys(authStorageOptions).length > 0 ? { authStorageOptions } : {}),
408
- onAuthorizationUrl: () => {},
409
- onAuthorizationInput: async (authorizationUrl, inputSignal) => {
410
- if (inputSignal.aborted) return undefined;
411
- return ui.input(
412
- `Complete ${serverName} OAuth\n\n` +
413
- `${terminalHyperlink("Open authorization page", authorizationUrl)}\n${authorizationUrl}\n\n` +
414
- "Approve access, then paste the full callback URL from the browser address bar below.",
415
- undefined,
416
- { signal: inputSignal },
417
- );
418
- },
419
- ...(signal ? { signal } : {}),
420
- ...(runtime ? { runtime } : {}),
421
- });
422
- if (signal?.aborted) signal.throwIfAborted();
423
-
424
- if (status === "authenticated") {
425
- const message = `OAuth authentication successful for "${serverName}".`;
426
- ui.notify(message, "info");
427
- return { ok: true, message };
428
- }
429
-
430
- const message = `OAuth authentication failed for "${serverName}".`;
431
- ui.notify(message, "error");
432
- return { ok: false, message };
433
- } catch (error) {
434
- if (signal?.aborted) throw error;
435
- const message = error instanceof Error ? error.message : String(error);
436
- ui.notify(`Failed to authenticate "${serverName}": ${message}`, "error");
437
- return { ok: false, message };
438
- } finally {
439
- if (!signal?.aborted) ui.setStatus("mcp-auth", undefined);
440
- }
441
- }
442
-
443
- export async function logoutServer(
444
- serverName: string,
445
- state: McpExtensionState,
446
- ctx: ExtensionContext
447
- ): Promise<{ ok: boolean; message: string }> {
448
- const definition = state.config.mcpServers[serverName];
449
- const ui = ctx.hasUI ? ctx.ui : undefined;
450
- if (!definition) {
451
- const message = `Server "${serverName}" not found in config`;
452
- if (ui) ui.notify(message, "error");
453
- return { ok: false, message };
454
- }
455
-
456
- const signal = state.owner?.signal;
457
- try {
458
- await state.manager.close(serverName);
459
- } catch (error) {
460
- if (isAbortError(error, signal)) throw error;
461
- const message = error instanceof Error ? error.message : String(error);
462
- if (ui) {
463
- ui.notify(`Failed to close OAuth server "${serverName}"; credentials were not cleared: ${sanitizeTerminalText(message)}`, "error");
464
- }
465
- return { ok: false, message };
466
- }
467
-
468
- state.owner?.throwIfInactive();
469
- try {
470
- await removeAuth(serverName, { authStorageOptions: state.authStorageOptions, signal, runtime: state.oauthRuntime });
471
- } catch (error) {
472
- if (isAbortError(error, signal)) throw error;
473
- const message = error instanceof Error ? error.message : String(error);
474
- if (ui) {
475
- ui.notify(`Failed to clear OAuth credentials for "${serverName}": ${sanitizeTerminalText(message)}`, "error");
476
- }
477
- return { ok: false, message };
478
- }
479
-
480
- state.owner?.throwIfInactive();
481
- updateStatusBar(state);
482
-
483
- const message = `OAuth credentials cleared for "${serverName}". Run /mcp-auth ${serverName} to authenticate again.`;
484
- if (ui) ui.notify(message, "info");
485
- return { ok: true, message };
486
- }
487
-
488
- function validateBearerTokenStoreServer(
489
- serverName: string,
490
- state: McpExtensionState,
491
- ): { ok: true; serverUrl: string } | { ok: false; message: string; type: "error" | "warning" } {
492
- const safeName = sanitizeTerminalText(serverName);
493
- const definition = state.config.mcpServers[serverName];
494
- if (!definition) return { ok: false, message: `Server "${safeName}" not found in config`, type: "error" };
495
- if (isServerDisabled(definition)) return { ok: false, message: `Server "${safeName}" is disabled. Run /mcp enable ${safeName}, then /reload.`, type: "warning" };
496
- if (definition.auth !== "bearer" || definition.bearerTokenStore !== true) {
497
- return { ok: false, message: `Server "${safeName}" is not configured for bearerTokenStore.`, type: "error" };
498
- }
499
- // resolveServerUrl embeds the interpolated URL in its exceptions; redact it
500
- // because the URL can carry userinfo or interpolated secrets.
501
- let serverUrl: string | undefined;
502
- try {
503
- serverUrl = resolveServerUrl(definition);
504
- } catch {
505
- return { ok: false, message: `Server "${safeName}" has an invalid or unresolvable URL.`, type: "error" };
506
- }
507
- if (!serverUrl) return { ok: false, message: `Server "${safeName}" has no URL configured.`, type: "error" };
508
- return { ok: true, serverUrl };
509
- }
510
-
511
- export async function manageBearerToken(
512
- action: "set" | "remove" | "status",
513
- serverName: string,
514
- state: McpExtensionState,
515
- ctx: ExtensionContext,
516
- ): Promise<{ ok: boolean; message: string }> {
517
- const ui = ctx.hasUI ? ctx.ui : undefined;
518
- const safeName = sanitizeTerminalText(serverName);
519
- const validation = validateBearerTokenStoreServer(serverName, state);
520
- if (!validation.ok) {
521
- if (ui) ui.notify(validation.message, validation.type);
522
- return { ok: false, message: validation.message };
523
- }
524
-
525
- if (action === "set") {
526
- const message = `Cannot store bearer token here: Pi extension UI has no masked secret input primitive. Run \`pi-mcp-adapter token set ${safeName}\` in a terminal; it reads the token from stdin only.`;
527
- if (ui) ui.notify(message, "error");
528
- return { ok: false, message };
529
- }
530
-
531
- if (action === "status") {
532
- const status = inspectBearerTokenForUrl(serverName, validation.serverUrl);
533
- const message = status.status === "present"
534
- ? `Bearer token is stored for "${safeName}".`
535
- : status.status === "url-mismatch"
536
- ? `Bearer token is stored for "${safeName}", but its URL does not match the current server URL.`
537
- : status.status === "unavailable"
538
- ? status.message
539
- : `No bearer token is stored for "${safeName}".`;
540
- if (ui) ui.notify(message, status.status === "unavailable" ? "error" : "info");
541
- return { ok: status.status !== "unavailable", message };
542
- }
543
-
544
- try {
545
- removeBearerToken(serverName);
546
- } catch (error) {
547
- const message = error instanceof Error ? error.message : String(error);
548
- if (ui) ui.notify(`Failed to remove bearer token for "${safeName}": ${sanitizeTerminalText(message)}`, "error");
549
- return { ok: false, message };
550
- }
551
-
552
- const message = `Bearer token removed for "${safeName}".`;
553
- if (ui) ui.notify(message, "info");
554
- return { ok: true, message };
555
- }
556
-
557
- export interface PanelFlowResult {
558
- configChanged: boolean;
559
- }
560
-
561
- function buildSharedConfigNoticeLines(configOverridePath: string | undefined, cwd: string): { lines: string[]; fingerprint: string | null } {
562
- const discovery = getMcpStandardConfigSummary(configOverridePath, cwd);
563
- const onboardingState = loadOnboardingState();
564
- const sharedSources = discovery.sources.filter((source) =>
565
- (source.id === "shared-project" || source.id === "shared-project-ancestor" || source.id === "shared-global") && source.serverCount > 0,
566
- );
567
- if (sharedSources.length === 0 || onboardingState.sharedConfigHintShown) {
568
- return { lines: [], fingerprint: null };
569
- }
570
-
571
- const sourceList = sharedSources.map((source) => source.path).join(", ");
572
- return {
573
- lines: [
574
- `Using standard MCP config from ${sourceList}.`,
575
- "Use .mcp.json for project/team config or ~/.config/mcp/mcp.json for all projects. Pi only writes compatibility imports and adapter-specific overrides into Pi-owned files when needed.",
576
- ],
577
- fingerprint: discovery.fingerprint,
578
- };
579
- }
580
-
581
- export async function openMcpSetup(
582
- state: McpExtensionState,
583
- pi: ExtensionAPI,
584
- ctx: ExtensionContext,
585
- configOverridePath?: string,
586
- mode: "empty" | "setup" = "setup",
587
- options: { includeHostConfigs?: boolean } = {},
588
- ): Promise<PanelFlowResult> {
589
- if (!ctx.hasUI) return { configChanged: false };
590
- if (!canRenderPanel(ctx)) {
591
- ctx.ui.notify(`The interactive MCP setup panel is only available in the terminal UI (current mode: ${ctx.mode}). Edit .mcp.json directly, or run /mcp status to review servers.`, "info");
592
- return { configChanged: false };
593
- }
594
- if (state.programmaticConfig) {
595
- ctx.ui.notify("MCP setup is unavailable when config is supplied by createMcpAdapter().", "info");
596
- return { configChanged: false };
597
- }
598
-
599
- const discovery = getMcpDiscoverySummary(configOverridePath, ctx.cwd, options);
600
- const onboardingState = loadOnboardingState();
601
- const { createMcpSetupPanel } = await import("./mcp-setup-panel.ts");
602
- let configChanged = false;
603
-
604
- const callbacks = {
605
- previewImports: (imports: ImportKind[]) => previewCompatibilityImports(imports, configOverridePath),
606
- previewStarterConfig: (target: SharedConfigTarget) => previewStarterSharedConfig(target, ctx.cwd),
607
- previewRepoPrompt: (target: SharedConfigTarget) => {
608
- const repoPrompt = getMcpDiscoverySummary(configOverridePath, ctx.cwd, options).repoPrompt;
609
- if (!repoPrompt.entry || !repoPrompt.targetPath || !repoPrompt.serverName) return null;
610
- return previewSharedServerEntry(getSharedConfigPath(target, ctx.cwd), repoPrompt.serverName, repoPrompt.entry);
611
- },
612
- previewKnownServer: (preset: KnownServerPreset, target: SharedConfigTarget) => previewSharedServerEntry(getSharedConfigPath(target, ctx.cwd), preset.id, preset.entry),
613
- adoptImports: async (imports: ImportKind[]) => {
614
- const result = ensureCompatibilityImports(imports, configOverridePath);
615
- if (result.added.length > 0) configChanged = true;
616
- return result;
617
- },
618
- scaffoldConfig: async (target: SharedConfigTarget) => {
619
- const path = writeStarterSharedConfig(target, ctx.cwd);
620
- configChanged = true;
621
- return { path };
622
- },
623
- addRepoPrompt: async (target: SharedConfigTarget) => {
624
- const repoPrompt = getMcpDiscoverySummary(configOverridePath, ctx.cwd, options).repoPrompt;
625
- if (!repoPrompt.entry || !repoPrompt.targetPath || !repoPrompt.serverName) {
626
- throw new Error("RepoPrompt is not available to add from this setup screen.");
627
- }
628
- const path = writeSharedServerEntry(getSharedConfigPath(target, ctx.cwd), repoPrompt.serverName, repoPrompt.entry);
629
- configChanged = true;
630
- return { path, serverName: repoPrompt.serverName };
631
- },
632
- addKnownServer: async (preset: KnownServerPreset, target: SharedConfigTarget) => {
633
- const path = writeSharedServerEntry(getSharedConfigPath(target, ctx.cwd), preset.id, preset.entry);
634
- configChanged = true;
635
- return { path, serverName: preset.name };
636
- },
637
- openPath: async (targetPath: string) => {
638
- await openPath(pi, targetPath);
639
- },
640
- markSetupCompleted: () => {
641
- persistSetupCompleted(discovery.fingerprint);
642
- },
643
- };
644
-
645
- return new Promise<PanelFlowResult>((resolve) => {
646
- ctx.ui.custom(
647
- (tui, theme, keybindings, done) => {
648
- return createMcpSetupPanel(discovery, callbacks, { mode, onboardingState, keybindings, theme }, tui, () => {
649
- done(undefined);
650
- resolve({ configChanged });
651
- });
652
- },
653
- { overlay: true, overlayOptions: { anchor: "center", width: 92 } },
654
- );
655
- });
656
- }
657
-
658
- function buildMcpPanelCallbacks(
659
- state: McpExtensionState,
660
- config: McpConfig,
661
- ctx: ExtensionContext,
662
- getOverlayHandle?: () => OverlayHandle | undefined,
663
- ): McpPanelCallbacks {
664
- // Panel-only diagnostics keep status inspection from mutating connection
665
- // failure state while allowing the existing panel failure UI to show why the
666
- // credential store could not be inspected.
667
- const authStatusFailures = new Map<string, string>();
668
-
669
- return {
670
- reconnect: (serverName: string) => reconnectServer(state, ctx, serverName),
671
- canAuthenticate: (serverName: string) => {
672
- const definition = config.mcpServers[serverName];
673
- return definition ? !isServerDisabled(definition) && supportsOAuth(definition) : false;
674
- },
675
- authenticate: async (serverName: string) => {
676
- const overlay = getOverlayHandle?.();
677
- overlay?.setHidden(true);
678
- try {
679
- return await authenticateServer(serverName, config, ctx, state.owner?.signal, state.oauthRuntime);
680
- } finally {
681
- overlay?.setHidden(false);
682
- overlay?.focus();
683
- }
684
- },
685
- getConnectionStatus: (serverName: string) => {
686
- authStatusFailures.delete(serverName);
687
- const definition = config.mcpServers[serverName];
688
- if (isServerDisabled(definition)) return "disabled";
689
- const connection = state.manager.getConnection(serverName);
690
- let serverUrl: string | undefined;
691
- try {
692
- serverUrl = definition ? resolveServerUrl(definition) : undefined;
693
- } catch {
694
- return "failed";
695
- }
696
- if (
697
- definition?.auth === "oauth"
698
- && serverUrl
699
- && definition.oauth !== false
700
- && definition.oauth?.grantType !== "client_credentials"
701
- ) {
702
- const authStatus = inspectAuthForUrl(serverName, serverUrl, state.authStorageOptions);
703
- if (authStatus.status === "unavailable") {
704
- authStatusFailures.set(serverName, authStatus.message);
705
- return "failed";
706
- }
707
- if (authStatus.status === "absent" || !authStatus.entry.tokens) {
708
- return "needs-auth";
709
- }
710
- }
711
- if (connection?.status === "needs-auth") return "needs-auth";
712
- if (connection?.status === "connected") return "connected";
713
- if (getFailureAgeSeconds(state, serverName) !== null) return "failed";
714
- return "idle";
715
- },
716
- getFailureMessage: (serverName: string) => authStatusFailures.get(serverName) ?? getFailureMessage(state, serverName),
717
- refreshCacheAfterReconnect: (serverName: string) => {
718
- const freshCache = loadMetadataCache();
719
- return freshCache?.servers?.[serverName] ?? null;
720
- },
721
- };
722
- }
723
-
724
- export async function openMcpPanel(
725
- state: McpExtensionState,
726
- pi: ExtensionAPI,
727
- ctx: ExtensionContext,
728
- configOverridePath?: string,
729
- onDirectToolsConfigChanged?: (changes: Map<string, true | string[] | false>) => void | Promise<void>,
730
- ): Promise<PanelFlowResult> {
731
- if (state.programmaticConfig) {
732
- if (ctx.hasUI) {
733
- ctx.ui.notify("MCP status is shown from the in-memory SDK config; configuration discovery is unavailable.", "info");
734
- await showStatus(state, ctx);
735
- }
736
- return { configChanged: false };
737
- }
738
- if (!canRenderPanel(ctx)) {
739
- // No overlay here, but the same information is available as text.
740
- await showStatus(state, ctx);
741
- return { configChanged: false };
742
- }
743
- if (Object.keys(state.config.mcpServers).length === 0) {
744
- return openMcpSetup(state, pi, ctx, configOverridePath, "empty", { includeHostConfigs: false });
745
- }
746
-
747
- const config = state.config;
748
- const cache = loadMetadataCache();
749
- const configPath = pi.getFlag("mcp-config") as string | undefined ?? configOverridePath;
750
- const provenanceMap = getServerProvenance(configPath, ctx.cwd);
751
- const { lines: noticeLines, fingerprint } = buildSharedConfigNoticeLines(configPath, ctx.cwd);
752
-
753
- let overlayHandle: OverlayHandle | undefined;
754
- const callbacks = buildMcpPanelCallbacks(state, config, ctx, () => overlayHandle);
755
-
756
- const { createMcpPanel } = await import("./mcp-panel.ts");
757
- let configChanged = false;
758
-
759
- await new Promise<void>((resolve) => {
760
- ctx.ui.custom(
761
- (tui, theme, keybindings, done) => {
762
- return createMcpPanel(config, cache, provenanceMap, callbacks, tui, (result: McpPanelResult) => {
763
- void (async () => {
764
- if (!result.cancelled && result.disabledChanges.size > 0) {
765
- for (const [serverName, disabled] of result.disabledChanges) {
766
- try {
767
- const override = writeProjectServerDisabledOverride(configPath, ctx.cwd, serverName, disabled);
768
- if (override.changed) {
769
- configChanged = true;
770
- }
771
- } catch (error) {
772
- ctx.ui.notify(`Failed to ${disabled ? "disable" : "enable"} server "${serverName}": ${error instanceof Error ? error.message : String(error)}`, "error");
773
- }
774
- }
775
- }
776
- if (!result.cancelled && result.changes.size > 0) {
777
- writeDirectToolsConfig(result.changes, provenanceMap, config);
778
- await onDirectToolsConfigChanged?.(result.changes);
779
- ctx.ui.notify("Direct tools updated for this session.", "info");
780
- }
781
- done(undefined);
782
- resolve();
783
- })().catch((error) => {
784
- const message = error instanceof Error ? error.message : String(error);
785
- ctx.ui.notify(`Direct tools updated, but live refresh failed: ${message}`, "error");
786
- configChanged = true;
787
- done(undefined);
788
- resolve();
789
- });
790
- }, { noticeLines, keybindings, theme });
791
- },
792
- {
793
- overlay: true,
794
- overlayOptions: { anchor: "center", width: 82 },
795
- onHandle: (handle) => { overlayHandle = handle; },
796
- },
797
- );
798
- });
799
-
800
- if (noticeLines.length > 0 && fingerprint) {
801
- markSharedConfigHintShown(fingerprint);
802
- }
803
-
804
- return { configChanged };
805
- }
806
-
807
- export async function openMcpAuthPanel(
808
- state: McpExtensionState,
809
- pi: ExtensionAPI,
810
- ctx: ExtensionContext,
811
- configOverridePath?: string,
812
- ): Promise<PanelFlowResult> {
813
- if (!ctx.hasUI) return { configChanged: false };
814
- if (!canRenderPanel(ctx)) {
815
- ctx.ui.notify(`The interactive MCP auth panel is only available in the terminal UI (current mode: ${ctx.mode}). Use /mcp-auth <server> to authenticate a specific server.`, "info");
816
- return { configChanged: false };
817
- }
818
- if (state.programmaticConfig) {
819
- ctx.ui.notify("Use /mcp-auth <server> to authenticate a server from the in-memory SDK config.", "info");
820
- return { configChanged: false };
821
- }
822
-
823
- const config = state.config;
824
- const oauthServers = Object.entries(config.mcpServers).filter(
825
- ([, definition]) => !isServerDisabled(definition) && supportsOAuth(definition),
826
- );
827
- if (oauthServers.length === 0) {
828
- ctx.ui.notify("No OAuth-capable MCP servers are configured.", "warning");
829
- return { configChanged: false };
830
- }
831
-
832
- const cache = loadMetadataCache();
833
- const configPath = pi.getFlag("mcp-config") as string | undefined ?? configOverridePath;
834
- const provenanceMap = getServerProvenance(configPath, ctx.cwd);
835
- let overlayHandle: OverlayHandle | undefined;
836
- const callbacks = buildMcpPanelCallbacks(state, config, ctx, () => overlayHandle);
837
- const { createMcpPanel } = await import("./mcp-panel.ts");
838
-
839
- await new Promise<void>((resolve) => {
840
- ctx.ui.custom(
841
- (tui, theme, keybindings, done) => {
842
- return createMcpPanel(config, cache, provenanceMap, callbacks, tui, () => {
843
- done(undefined);
844
- resolve();
845
- }, {
846
- authOnly: true,
847
- keybindings,
848
- theme,
849
- noticeLines: ["Select an OAuth MCP server and press Enter or ctrl+a to authenticate."],
850
- });
851
- },
852
- {
853
- overlay: true,
854
- overlayOptions: { anchor: "center", width: 82 },
855
- onHandle: (handle) => { overlayHandle = handle; },
856
- },
857
- );
858
- });
859
-
860
- return { configChanged: false };
861
- }