@herbertgao/pi-extensions 2026.9.16 → 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 (267) 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/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +11 -53
  11. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +99 -99
  12. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  13. package/node_modules/@tifan/pi-handoff/package.json +1 -1
  14. package/node_modules/@tifan/pi-handoff/src/index.ts +56 -26
  15. package/node_modules/@tifan/pi-mermaid-open/package.json +1 -1
  16. package/node_modules/@tifan/pi-preferred-thinking/package.json +1 -1
  17. package/node_modules/@tifan/pi-recap/package.json +1 -1
  18. package/node_modules/@tifan/pi-rename/package.json +1 -1
  19. package/node_modules/pi-antigravity/package.json +2 -2
  20. package/node_modules/pi-lens/package.json +158 -158
  21. package/node_modules/pi-multi-account/package.json +2 -2
  22. package/node_modules/pi-typesafe/README.md +1 -1
  23. package/node_modules/pi-typesafe/dist/auth.d.ts +9 -5
  24. package/node_modules/pi-typesafe/dist/auth.js +7 -3
  25. package/node_modules/pi-typesafe/dist/backends.d.ts +33 -3
  26. package/node_modules/pi-typesafe/dist/backends.js +95 -1
  27. package/node_modules/pi-typesafe/dist/client.d.ts +5 -5
  28. package/node_modules/pi-typesafe/dist/client.js +12 -9
  29. package/node_modules/pi-typesafe/dist/credentials.d.ts +14 -8
  30. package/node_modules/pi-typesafe/dist/credentials.js +10 -7
  31. package/node_modules/pi-typesafe/dist/errors.d.ts +2 -2
  32. package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
  33. package/node_modules/pi-typesafe/dist/index.js +1 -1
  34. package/node_modules/pi-typesafe/dist/login.d.ts +2 -2
  35. package/node_modules/pi-typesafe/dist/login.js +2 -2
  36. package/node_modules/pi-typesafe/package.json +5 -5
  37. package/node_modules/pi-web-access/package.json +2 -2
  38. package/node_modules/remote-pi/package.json +5 -1
  39. package/package.json +16 -28
  40. package/node_modules/pi-mcp-adapter/CHANGELOG.md +0 -1013
  41. package/node_modules/pi-mcp-adapter/LICENSE +0 -21
  42. package/node_modules/pi-mcp-adapter/OAUTH.md +0 -369
  43. package/node_modules/pi-mcp-adapter/README.md +0 -1021
  44. package/node_modules/pi-mcp-adapter/abort.ts +0 -34
  45. package/node_modules/pi-mcp-adapter/agent-dir.ts +0 -68
  46. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +0 -416
  47. package/node_modules/pi-mcp-adapter/agent-plugin-provenance.ts +0 -36
  48. package/node_modules/pi-mcp-adapter/app-bridge.bundle.js +0 -67
  49. package/node_modules/pi-mcp-adapter/banner.png +0 -0
  50. package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +0 -193
  51. package/node_modules/pi-mcp-adapter/claude-plugin-loader.ts +0 -365
  52. package/node_modules/pi-mcp-adapter/cli.js +0 -454
  53. package/node_modules/pi-mcp-adapter/commands.ts +0 -861
  54. package/node_modules/pi-mcp-adapter/config.ts +0 -1565
  55. package/node_modules/pi-mcp-adapter/consent-manager.ts +0 -95
  56. package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +0 -244
  57. package/node_modules/pi-mcp-adapter/direct-tools.ts +0 -428
  58. package/node_modules/pi-mcp-adapter/dist/abort.d.ts +0 -2
  59. package/node_modules/pi-mcp-adapter/dist/abort.js +0 -36
  60. package/node_modules/pi-mcp-adapter/dist/abort.js.map +0 -1
  61. package/node_modules/pi-mcp-adapter/dist/agent-dir.d.ts +0 -10
  62. package/node_modules/pi-mcp-adapter/dist/agent-dir.js +0 -63
  63. package/node_modules/pi-mcp-adapter/dist/agent-dir.js.map +0 -1
  64. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.d.ts +0 -8
  65. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js +0 -398
  66. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js.map +0 -1
  67. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.d.ts +0 -1
  68. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js +0 -31
  69. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js.map +0 -1
  70. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +0 -6
  71. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +0 -194
  72. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +0 -1
  73. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.d.ts +0 -9
  74. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js +0 -362
  75. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js.map +0 -1
  76. package/node_modules/pi-mcp-adapter/dist/config.d.ts +0 -134
  77. package/node_modules/pi-mcp-adapter/dist/config.js +0 -1361
  78. package/node_modules/pi-mcp-adapter/dist/config.js.map +0 -1
  79. package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +0 -18
  80. package/node_modules/pi-mcp-adapter/dist/consent-manager.js +0 -88
  81. package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +0 -1
  82. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +0 -17
  83. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +0 -316
  84. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +0 -1
  85. package/node_modules/pi-mcp-adapter/dist/errors.d.ts +0 -131
  86. package/node_modules/pi-mcp-adapter/dist/errors.js +0 -278
  87. package/node_modules/pi-mcp-adapter/dist/errors.js.map +0 -1
  88. package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +0 -8
  89. package/node_modules/pi-mcp-adapter/dist/http-ca.js +0 -88
  90. package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +0 -1
  91. package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +0 -14
  92. package/node_modules/pi-mcp-adapter/dist/jev-client.js +0 -367
  93. package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +0 -1
  94. package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +0 -77
  95. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +0 -2
  96. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +0 -1
  97. package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +0 -51
  98. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +0 -205
  99. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +0 -1
  100. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +0 -2
  101. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +0 -68
  102. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +0 -1
  103. package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +0 -56
  104. package/node_modules/pi-mcp-adapter/dist/lifecycle.js +0 -410
  105. package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +0 -1
  106. package/node_modules/pi-mcp-adapter/dist/logger.d.ts +0 -51
  107. package/node_modules/pi-mcp-adapter/dist/logger.js +0 -131
  108. package/node_modules/pi-mcp-adapter/dist/logger.js.map +0 -1
  109. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +0 -26
  110. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +0 -103
  111. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +0 -1
  112. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +0 -118
  113. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +0 -1097
  114. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +0 -1
  115. package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +0 -170
  116. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +0 -1142
  117. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +0 -1
  118. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.d.ts +0 -24
  119. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +0 -203
  120. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +0 -1
  121. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +0 -53
  122. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +0 -441
  123. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +0 -1
  124. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +0 -148
  125. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +0 -689
  126. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +0 -1
  127. package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +0 -6
  128. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +0 -166
  129. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +0 -1
  130. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +0 -102
  131. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +0 -369
  132. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +0 -1
  133. package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +0 -95
  134. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +0 -242
  135. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +0 -1
  136. package/node_modules/pi-mcp-adapter/dist/metadata-cache.d.ts +0 -19
  137. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +0 -301
  138. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +0 -1
  139. package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +0 -6
  140. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +0 -505
  141. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +0 -1
  142. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.d.ts +0 -2
  143. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js +0 -144
  144. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js.map +0 -1
  145. package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +0 -10
  146. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +0 -312
  147. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +0 -1
  148. package/node_modules/pi-mcp-adapter/dist/resource-tools.d.ts +0 -1
  149. package/node_modules/pi-mcp-adapter/dist/resource-tools.js +0 -15
  150. package/node_modules/pi-mcp-adapter/dist/resource-tools.js.map +0 -1
  151. package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +0 -13
  152. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +0 -97
  153. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +0 -1
  154. package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +0 -17
  155. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +0 -203
  156. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +0 -1
  157. package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +0 -11
  158. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +0 -172
  159. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +0 -1
  160. package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +0 -157
  161. package/node_modules/pi-mcp-adapter/dist/server-manager.js +0 -1745
  162. package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +0 -1
  163. package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +0 -34
  164. package/node_modules/pi-mcp-adapter/dist/session-approvals.js +0 -140
  165. package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +0 -1
  166. package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +0 -26
  167. package/node_modules/pi-mcp-adapter/dist/session-recovery.js +0 -142
  168. package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +0 -1
  169. package/node_modules/pi-mcp-adapter/dist/state.d.ts +0 -73
  170. package/node_modules/pi-mcp-adapter/dist/state.js +0 -2
  171. package/node_modules/pi-mcp-adapter/dist/state.js.map +0 -1
  172. package/node_modules/pi-mcp-adapter/dist/types.d.ts +0 -642
  173. package/node_modules/pi-mcp-adapter/dist/types.js +0 -331
  174. package/node_modules/pi-mcp-adapter/dist/types.js.map +0 -1
  175. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.d.ts +0 -6
  176. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js +0 -37
  177. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js.map +0 -1
  178. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +0 -17
  179. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +0 -219
  180. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +0 -1
  181. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.d.ts +0 -104
  182. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js +0 -59
  183. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js.map +0 -1
  184. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.d.ts +0 -4
  185. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js +0 -27
  186. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js.map +0 -1
  187. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +0 -15
  188. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +0 -85
  189. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +0 -1
  190. package/node_modules/pi-mcp-adapter/dist/utils.d.ts +0 -41
  191. package/node_modules/pi-mcp-adapter/dist/utils.js +0 -417
  192. package/node_modules/pi-mcp-adapter/dist/utils.js.map +0 -1
  193. package/node_modules/pi-mcp-adapter/elicitation-handler.ts +0 -358
  194. package/node_modules/pi-mcp-adapter/error-signal.ts +0 -32
  195. package/node_modules/pi-mcp-adapter/errors.ts +0 -364
  196. package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +0 -85
  197. package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +0 -34
  198. package/node_modules/pi-mcp-adapter/failure-backoff.ts +0 -23
  199. package/node_modules/pi-mcp-adapter/glimpse-ui.ts +0 -80
  200. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -515
  201. package/node_modules/pi-mcp-adapter/http-ca.ts +0 -84
  202. package/node_modules/pi-mcp-adapter/index.ts +0 -2106
  203. package/node_modules/pi-mcp-adapter/init.ts +0 -721
  204. package/node_modules/pi-mcp-adapter/jev-client.ts +0 -300
  205. package/node_modules/pi-mcp-adapter/jev-contracts.ts +0 -53
  206. package/node_modules/pi-mcp-adapter/jev-key-store.ts +0 -192
  207. package/node_modules/pi-mcp-adapter/json-schema-validator.ts +0 -83
  208. package/node_modules/pi-mcp-adapter/lazy-loader.ts +0 -7
  209. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -457
  210. package/node_modules/pi-mcp-adapter/logger.ts +0 -169
  211. package/node_modules/pi-mcp-adapter/mcp-auth-fetch.ts +0 -113
  212. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +0 -1296
  213. package/node_modules/pi-mcp-adapter/mcp-auth.ts +0 -1287
  214. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +0 -250
  215. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +0 -534
  216. package/node_modules/pi-mcp-adapter/mcp-code.ts +0 -495
  217. package/node_modules/pi-mcp-adapter/mcp-install.ts +0 -60
  218. package/node_modules/pi-mcp-adapter/mcp-keyring-helper.cjs +0 -89
  219. package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +0 -819
  220. package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +0 -511
  221. package/node_modules/pi-mcp-adapter/mcp-panel-theme.ts +0 -104
  222. package/node_modules/pi-mcp-adapter/mcp-panel.ts +0 -1099
  223. package/node_modules/pi-mcp-adapter/mcp-probe.ts +0 -201
  224. package/node_modules/pi-mcp-adapter/mcp-references.ts +0 -373
  225. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +0 -137
  226. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +0 -747
  227. package/node_modules/pi-mcp-adapter/mcp-status.ts +0 -105
  228. package/node_modules/pi-mcp-adapter/mcp-tasks.ts +0 -467
  229. package/node_modules/pi-mcp-adapter/mcp-trace.ts +0 -306
  230. package/node_modules/pi-mcp-adapter/metadata-cache.ts +0 -362
  231. package/node_modules/pi-mcp-adapter/namespace-tools.ts +0 -334
  232. package/node_modules/pi-mcp-adapter/npx-resolver.ts +0 -543
  233. package/node_modules/pi-mcp-adapter/oauth-handler.ts +0 -30
  234. package/node_modules/pi-mcp-adapter/oauth.ts +0 -52
  235. package/node_modules/pi-mcp-adapter/onboarding-state.ts +0 -76
  236. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +0 -156
  237. package/node_modules/pi-mcp-adapter/package.json +0 -219
  238. package/node_modules/pi-mcp-adapter/panel-keys.ts +0 -53
  239. package/node_modules/pi-mcp-adapter/prompts.ts +0 -385
  240. package/node_modules/pi-mcp-adapter/proxy-modes.ts +0 -1735
  241. package/node_modules/pi-mcp-adapter/request-headers-command.ts +0 -333
  242. package/node_modules/pi-mcp-adapter/resource-tools.ts +0 -17
  243. package/node_modules/pi-mcp-adapter/runtime-owner.ts +0 -106
  244. package/node_modules/pi-mcp-adapter/sampling-handler.ts +0 -261
  245. package/node_modules/pi-mcp-adapter/sandbox-proxy-template.ts +0 -144
  246. package/node_modules/pi-mcp-adapter/search-ranking.ts +0 -309
  247. package/node_modules/pi-mcp-adapter/secure-keyring.ts +0 -170
  248. package/node_modules/pi-mcp-adapter/semantic-search.ts +0 -186
  249. package/node_modules/pi-mcp-adapter/server-manager.ts +0 -2088
  250. package/node_modules/pi-mcp-adapter/session-approvals.ts +0 -201
  251. package/node_modules/pi-mcp-adapter/session-recovery.ts +0 -158
  252. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +0 -49
  253. package/node_modules/pi-mcp-adapter/state.ts +0 -71
  254. package/node_modules/pi-mcp-adapter/tool-approval.ts +0 -205
  255. package/node_modules/pi-mcp-adapter/tool-metadata.ts +0 -302
  256. package/node_modules/pi-mcp-adapter/tool-registrar.ts +0 -255
  257. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +0 -573
  258. package/node_modules/pi-mcp-adapter/ts-shape.ts +0 -149
  259. package/node_modules/pi-mcp-adapter/types.ts +0 -1120
  260. package/node_modules/pi-mcp-adapter/ui-app-bridge-helpers.ts +0 -40
  261. package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +0 -266
  262. package/node_modules/pi-mcp-adapter/ui-server.ts +0 -993
  263. package/node_modules/pi-mcp-adapter/ui-session.ts +0 -624
  264. package/node_modules/pi-mcp-adapter/ui-stream-types.ts +0 -89
  265. package/node_modules/pi-mcp-adapter/ui-tool-visibility.ts +0 -25
  266. package/node_modules/pi-mcp-adapter/unix-socket-transport.ts +0 -85
  267. 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
- }