@earendil-works/pi-coding-agent 0.99.1 → 1.0.0

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 (221) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/dist/bundle/chunks/anthropic-messages-UID2PALB.js +6 -0
  3. package/dist/bundle/chunks/anthropic.js +2 -2
  4. package/dist/bundle/chunks/{azure-openai-responses-EN5WOBET.js → azure-openai-responses-AFMVM6NP.js} +1 -1
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
  6. package/dist/bundle/chunks/{chunk-AXPY26X7.js → chunk-2KTBZM5G.js} +14 -28
  7. package/dist/bundle/chunks/{chunk-LRS5S6EK.js → chunk-2VNGUWBO.js} +2 -2
  8. package/dist/bundle/chunks/chunk-33XOIQ5N.js +1204 -0
  9. package/dist/bundle/chunks/{chunk-W2DOMZSC.js → chunk-3LH3ZQKY.js} +1 -1
  10. package/dist/bundle/chunks/chunk-4KU537KD.js +2 -0
  11. package/dist/bundle/chunks/chunk-6D7LHLKG.js +2 -0
  12. package/dist/bundle/chunks/chunk-7FH3BDRG.js +82 -0
  13. package/dist/bundle/chunks/{chunk-LLMKQWZS.js → chunk-IBCPYVFK.js} +7 -9
  14. package/dist/bundle/chunks/chunk-KDN6RCEQ.js +4 -0
  15. package/dist/bundle/chunks/chunk-LD2P6SFZ.js +2 -0
  16. package/dist/bundle/chunks/{chunk-PEHWOV5U.js → chunk-LTXEMVMA.js} +18 -78
  17. package/dist/bundle/chunks/{chunk-J4CWPC4N.js → chunk-OSKPX3Z5.js} +1 -1
  18. package/dist/bundle/chunks/chunk-PRD6WYBT.js +16 -0
  19. package/dist/bundle/chunks/chunk-VIV3CWXG.js +2 -0
  20. package/dist/bundle/chunks/{cli-AZXNGIZH.js → cli-QOMC26IJ.js} +8 -6
  21. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-XJQQYGGU.js → cloudflare-workers-ai-system-one-NPQN263E.js} +1 -1
  22. package/dist/bundle/chunks/codemode-worker.js +67 -8
  23. package/dist/bundle/chunks/execute-GVRSEGHL.js +367 -0
  24. package/dist/bundle/chunks/github-copilot.js +1 -1
  25. package/dist/bundle/chunks/{google-generative-ai-CQAKKJTA.js → google-generative-ai-APTPZOQL.js} +1 -1
  26. package/dist/bundle/chunks/{google-vertex-AE36YXJU.js → google-vertex-MXCOSOY3.js} +1 -1
  27. package/dist/bundle/chunks/{llama-cpp-classify-PJJYKPLD.js → llama-cpp-classify-XT44PI3C.js} +1 -1
  28. package/dist/bundle/chunks/{mistral-conversations-KEYTXJM7.js → mistral-conversations-DKVFNQL6.js} +1 -1
  29. package/dist/bundle/chunks/openai-chatgpt.js +1 -1
  30. package/dist/bundle/chunks/{openai-codex-responses-JQTCOVEG.js → openai-codex-responses-GK67DQQJ.js} +1 -1
  31. package/dist/bundle/chunks/openai-codex.js +1 -1
  32. package/dist/bundle/chunks/{openai-completions-XW2Q5HVC.js → openai-completions-JXDDPZ23.js} +1 -1
  33. package/dist/bundle/chunks/{openai-responses-6AXWFL75.js → openai-responses-CQDDLDQF.js} +1 -1
  34. package/dist/bundle/chunks/{openrouter-images-TPQ4V6OR.js → openrouter-images-ZVXWCSIM.js} +1 -1
  35. package/dist/bundle/chunks/openrouter.js +1 -1
  36. package/dist/bundle/chunks/pi-logo-animation-MPKZE4EK.js +2 -0
  37. package/dist/bundle/chunks/radius.js +1 -1
  38. package/dist/bundle/chunks/runtime-2SO2JDWM.js +2 -0
  39. package/dist/bundle/chunks/{typesafe-system-one-E7A4HNJI.js → typesafe-system-one-A4CZTQWZ.js} +1 -1
  40. package/dist/bundle/chunks/virtual-modules-6ZMK7GDO.js +2 -0
  41. package/dist/bundle/cli-runtime.js +1 -1
  42. package/dist/bundle/index.js +1 -1
  43. package/dist/bundle/rpc-entry.js +1 -1
  44. package/dist/cli/args.js +2 -2
  45. package/dist/cli/args.js.map +1 -1
  46. package/dist/config.d.ts +5 -8
  47. package/dist/config.d.ts.map +1 -1
  48. package/dist/config.js +16 -13
  49. package/dist/config.js.map +1 -1
  50. package/dist/core/agent-session.d.ts +15 -1
  51. package/dist/core/agent-session.d.ts.map +1 -1
  52. package/dist/core/agent-session.js +51 -10
  53. package/dist/core/agent-session.js.map +1 -1
  54. package/dist/core/extensions/loader.d.ts.map +1 -1
  55. package/dist/core/extensions/loader.js +13 -1
  56. package/dist/core/extensions/loader.js.map +1 -1
  57. package/dist/core/extensions/types.d.ts +6 -1
  58. package/dist/core/extensions/types.d.ts.map +1 -1
  59. package/dist/core/extensions/types.js.map +1 -1
  60. package/dist/core/mcp-servers.d.ts +34 -7
  61. package/dist/core/mcp-servers.d.ts.map +1 -1
  62. package/dist/core/mcp-servers.js +52 -11
  63. package/dist/core/mcp-servers.js.map +1 -1
  64. package/dist/core/model-registry.d.ts +3 -1
  65. package/dist/core/model-registry.d.ts.map +1 -1
  66. package/dist/core/model-registry.js +4 -0
  67. package/dist/core/model-registry.js.map +1 -1
  68. package/dist/core/model-runtime.d.ts +6 -0
  69. package/dist/core/model-runtime.d.ts.map +1 -1
  70. package/dist/core/model-runtime.js +22 -18
  71. package/dist/core/model-runtime.js.map +1 -1
  72. package/dist/core/radius.d.ts +2 -0
  73. package/dist/core/radius.d.ts.map +1 -1
  74. package/dist/core/radius.js +2 -0
  75. package/dist/core/radius.js.map +1 -1
  76. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  77. package/dist/core/remote-catalog-provider.js +4 -9
  78. package/dist/core/remote-catalog-provider.js.map +1 -1
  79. package/dist/core/sdk.d.ts.map +1 -1
  80. package/dist/core/sdk.js +1 -0
  81. package/dist/core/sdk.js.map +1 -1
  82. package/dist/core/settings-manager.d.ts +7 -5
  83. package/dist/core/settings-manager.d.ts.map +1 -1
  84. package/dist/core/settings-manager.js +3 -2
  85. package/dist/core/settings-manager.js.map +1 -1
  86. package/dist/core/system-prompt.js +1 -1
  87. package/dist/core/system-prompt.js.map +1 -1
  88. package/dist/core/tools/bash.d.ts.map +1 -1
  89. package/dist/core/tools/bash.js +3 -5
  90. package/dist/core/tools/bash.js.map +1 -1
  91. package/dist/core/tools/renderers/bash.d.ts.map +1 -1
  92. package/dist/core/tools/renderers/bash.js +11 -31
  93. package/dist/core/tools/renderers/bash.js.map +1 -1
  94. package/dist/core/virtual-models.d.ts +2 -0
  95. package/dist/core/virtual-models.d.ts.map +1 -1
  96. package/dist/core/virtual-models.js +19 -12
  97. package/dist/core/virtual-models.js.map +1 -1
  98. package/dist/extensions/codemode/execute.d.ts.map +1 -1
  99. package/dist/extensions/codemode/execute.js +197 -51
  100. package/dist/extensions/codemode/execute.js.map +1 -1
  101. package/dist/extensions/codemode/renderer.d.ts.map +1 -1
  102. package/dist/extensions/codemode/renderer.js +43 -25
  103. package/dist/extensions/codemode/renderer.js.map +1 -1
  104. package/dist/extensions/codemode/tool.d.ts +12 -11
  105. package/dist/extensions/codemode/tool.d.ts.map +1 -1
  106. package/dist/extensions/codemode/tool.js +73 -129
  107. package/dist/extensions/codemode/tool.js.map +1 -1
  108. package/dist/extensions/codemode/worker.d.ts +1 -1
  109. package/dist/extensions/codemode/worker.js +1 -1
  110. package/dist/extensions/codemode/worker.js.map +1 -1
  111. package/dist/extensions/mcp/cli.d.ts.map +1 -1
  112. package/dist/extensions/mcp/cli.js +20 -5
  113. package/dist/extensions/mcp/cli.js.map +1 -1
  114. package/dist/extensions/mcp/config.d.ts +4 -2
  115. package/dist/extensions/mcp/config.d.ts.map +1 -1
  116. package/dist/extensions/mcp/config.js +14 -2
  117. package/dist/extensions/mcp/config.js.map +1 -1
  118. package/dist/extensions/mcp/index.d.ts +32 -8
  119. package/dist/extensions/mcp/index.d.ts.map +1 -1
  120. package/dist/extensions/mcp/index.js +220 -56
  121. package/dist/extensions/mcp/index.js.map +1 -1
  122. package/dist/extensions/mcp/oauth.d.ts +10 -7
  123. package/dist/extensions/mcp/oauth.d.ts.map +1 -1
  124. package/dist/extensions/mcp/oauth.js +56 -31
  125. package/dist/extensions/mcp/oauth.js.map +1 -1
  126. package/dist/extensions/mcp/runtime.d.ts +2 -0
  127. package/dist/extensions/mcp/runtime.d.ts.map +1 -1
  128. package/dist/extensions/mcp/runtime.js +16 -9
  129. package/dist/extensions/mcp/runtime.js.map +1 -1
  130. package/dist/extensions/mcp/tools.d.ts +5 -4
  131. package/dist/extensions/mcp/tools.d.ts.map +1 -1
  132. package/dist/extensions/mcp/tools.js +33 -15
  133. package/dist/extensions/mcp/tools.js.map +1 -1
  134. package/dist/extensions/mcp/ui.d.ts.map +1 -1
  135. package/dist/extensions/mcp/ui.js +4 -2
  136. package/dist/extensions/mcp/ui.js.map +1 -1
  137. package/dist/extensions/tool-search/tool.d.ts +5 -4
  138. package/dist/extensions/tool-search/tool.d.ts.map +1 -1
  139. package/dist/extensions/tool-search/tool.js +7 -28
  140. package/dist/extensions/tool-search/tool.js.map +1 -1
  141. package/dist/index.d.ts +1 -1
  142. package/dist/index.d.ts.map +1 -1
  143. package/dist/index.js.map +1 -1
  144. package/dist/main.d.ts.map +1 -1
  145. package/dist/main.js +6 -0
  146. package/dist/main.js.map +1 -1
  147. package/dist/modes/interactive/components/oauth-selector.d.ts +8 -2
  148. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  149. package/dist/modes/interactive/components/oauth-selector.js +24 -21
  150. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  151. package/dist/modes/interactive/components/pi-logo-animation.d.ts +68 -0
  152. package/dist/modes/interactive/components/pi-logo-animation.d.ts.map +1 -0
  153. package/dist/modes/interactive/components/pi-logo-animation.js +1086 -0
  154. package/dist/modes/interactive/components/pi-logo-animation.js.map +1 -0
  155. package/dist/modes/interactive/components/pi-logo-animation.lazy.d.ts +7 -0
  156. package/dist/modes/interactive/components/pi-logo-animation.lazy.d.ts.map +1 -0
  157. package/dist/modes/interactive/components/pi-logo-animation.lazy.js +12 -0
  158. package/dist/modes/interactive/components/pi-logo-animation.lazy.js.map +1 -0
  159. package/dist/modes/interactive/components/pi-logo.d.ts +7 -0
  160. package/dist/modes/interactive/components/pi-logo.d.ts.map +1 -1
  161. package/dist/modes/interactive/components/pi-logo.js +13 -1
  162. package/dist/modes/interactive/components/pi-logo.js.map +1 -1
  163. package/dist/modes/interactive/components/radius-login-selector.d.ts +15 -0
  164. package/dist/modes/interactive/components/radius-login-selector.d.ts.map +1 -0
  165. package/dist/modes/interactive/components/radius-login-selector.js +85 -0
  166. package/dist/modes/interactive/components/radius-login-selector.js.map +1 -0
  167. package/dist/modes/interactive/components/settings-selector.d.ts +3 -3
  168. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  169. package/dist/modes/interactive/components/settings-selector.js +5 -5
  170. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  171. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  172. package/dist/modes/interactive/components/user-message.js +5 -4
  173. package/dist/modes/interactive/components/user-message.js.map +1 -1
  174. package/dist/modes/interactive/components/visual-truncate.d.ts +27 -3
  175. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -1
  176. package/dist/modes/interactive/components/visual-truncate.js +39 -6
  177. package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
  178. package/dist/modes/interactive/interactive-mode.d.ts +10 -0
  179. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  180. package/dist/modes/interactive/interactive-mode.js +153 -40
  181. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  182. package/dist/modes/interactive/theme/system-theme.d.ts +2 -1
  183. package/dist/modes/interactive/theme/system-theme.d.ts.map +1 -1
  184. package/dist/modes/interactive/theme/system-theme.js +16 -5
  185. package/dist/modes/interactive/theme/system-theme.js.map +1 -1
  186. package/docs/cli.md +8 -18
  187. package/docs/codemode.md +207 -0
  188. package/docs/docs.json +5 -1
  189. package/docs/environment-variables.md +2 -2
  190. package/docs/extensions.md +3 -3
  191. package/docs/index.md +1 -1
  192. package/docs/mcp.md +142 -82
  193. package/docs/models.md +22 -1
  194. package/docs/providers.md +18 -6
  195. package/docs/sdk.md +2 -2
  196. package/docs/settings.md +5 -3
  197. package/docs/usage.md +1 -1
  198. package/examples/extensions/built-in-tool-renderer.ts +16 -40
  199. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  200. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  201. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  202. package/examples/extensions/gondolin/package-lock.json +2 -2
  203. package/examples/extensions/gondolin/package.json +1 -1
  204. package/examples/extensions/minimal-mode.ts +24 -96
  205. package/examples/extensions/sandbox/package-lock.json +2 -2
  206. package/examples/extensions/sandbox/package.json +1 -1
  207. package/examples/extensions/with-deps/package-lock.json +2 -2
  208. package/examples/extensions/with-deps/package.json +1 -1
  209. package/examples/sdk/14-codemode-mcp.ts +2 -2
  210. package/npm-shrinkwrap.json +25 -30
  211. package/package.json +10 -10
  212. package/dist/bundle/chunks/anthropic-messages-A6EXUBG6.js +0 -6
  213. package/dist/bundle/chunks/chunk-GOPCREV2.js +0 -4
  214. package/dist/bundle/chunks/chunk-GUORCHFS.js +0 -1435
  215. package/dist/bundle/chunks/chunk-HSPCVFST.js +0 -2
  216. package/dist/bundle/chunks/chunk-IMTXZUO7.js +0 -82
  217. package/dist/bundle/chunks/chunk-SLDA2X2H.js +0 -2
  218. package/dist/bundle/chunks/chunk-XNGRGP62.js +0 -2
  219. package/dist/bundle/chunks/execute-7XXPZAWW.js +0 -308
  220. package/dist/bundle/chunks/runtime-G5WJPRBM.js +0 -2
  221. package/dist/bundle/chunks/virtual-modules-CSWRO37N.js +0 -2
@@ -3,12 +3,15 @@
3
3
  *
4
4
  * Connects the servers from `mcp.json` and the servers extensions register with
5
5
  * `pi.registerMcpServer()` when a session starts, and servers registered later right away. A server
6
- * in `mcp.json` takes precedence over a registered server of the same name. Tools are registered as
7
- * `mcp__<server>__<tool>`. By default (`"exposure": "codemode"`) the tools are only callable from
8
- * codemode scripts, which keeps large MCP tool lists out of the model's tool declarations; the
9
- * codemode tool is activated for that unless `autoEnableCodemode` is false. `"codemode-deferred"`
10
- * leaves them out of the codemode description as well. `"deferred"` declares them to the model once
11
- * the `tool_search` tool loads them, and activates `tool_search` instead of codemode.
6
+ * in `mcp.json` takes precedence over a registered server of the same name. Connections run in the
7
+ * background: the first prompt waits only for servers with `direct` tools, and codemode scripts,
8
+ * `tool_search`, and the resource tools wait for the servers they need when they run. Tools are
9
+ * registered as `mcp__<server>__<tool>`. By default (`"exposure": "codemode"`) the tools are only
10
+ * callable from codemode scripts, which keeps MCP tools out of the model's tool declarations and
11
+ * the codemode description: scripts find the tools with `searchTools()` and the server instructions
12
+ * with `describeNamespace()`. The codemode tool is activated for that unless `autoEnableCodemode` is
13
+ * false. `"deferred"` declares the tools to the model once the `tool_search` tool loads them, and
14
+ * activates `tool_search` instead of codemode.
12
15
  * `"exposure": "direct"` declares them to the model right away, and `"hidden"` makes them
13
16
  * unreachable. `toolExposure` overrides the exposure of single tools. Servers with resources are
14
17
  * reached through Codex's `list_mcp_resources`, `list_mcp_resource_templates`, and
@@ -41,11 +44,32 @@ export interface McpExtensionOptions {
41
44
  /** Saves `/mcp` changes to the server's config file. Defaults to editing its `mcp.json`. */
42
45
  updateConfig?: (entry: McpServerEntry, patch: McpServerConfigPatch) => void;
43
46
  /**
44
- * How long the first prompt waits for servers that are still connecting at startup, in
45
- * milliseconds. Their tools become available when they connect. Default: 10000.
47
+ * How long the first prompt waits for servers with `direct` tools that are still connecting at
48
+ * startup, in milliseconds. Their tools become available when they connect. Other servers are
49
+ * waited for when a script or search needs them. Default: 10000.
46
50
  */
47
51
  startupWaitMs?: number;
48
52
  }
53
+ /** Name of the system prompt section that lists the servers whose tools are not declared. */
54
+ export declare const MCP_SERVERS_SECTION = "mcp_servers";
55
+ /**
56
+ * Characters of the whole section. Descriptions shrink to fit; when the server lines alone do not
57
+ * fit, the last servers are left out and counted in a closing line.
58
+ */
59
+ export declare const MAX_SERVERS_SECTION_CHARS = 4096;
60
+ /** What the `mcp_servers` section needs of a server. */
61
+ export interface McpServerListing {
62
+ entry: McpServerEntry;
63
+ connection?: {
64
+ instructions?: string;
65
+ };
66
+ }
67
+ /**
68
+ * The `mcp_servers` section: every enabled server with codemode or deferred tools, with how its
69
+ * tools are reached and a one-line summary. The model learns of the servers from it, since neither
70
+ * codemode nor tool_search lists them. Undefined when there are no such servers.
71
+ */
72
+ export declare function renderServersSection(servers: readonly McpServerListing[]): string | undefined;
49
73
  export declare function createMcpExtension(options?: McpExtensionOptions): ExtensionFactory;
50
74
  declare const _default: ExtensionFactory;
51
75
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH,OAAO,KAAK,EAGX,gBAAgB,EAChB,gBAAgB,EAEhB,MAAM,gCAAgC,CAAC;AAIxC,OAAO,EAEN,KAAK,eAAe,EAGpB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EAEnB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,uBAAuB,EAAmB,MAAM,YAAY,CAAC;AAI3E,OAAO,KAAK,EAAqC,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAI3F,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,WAAW,mBAAmB;IACnC,uFAAuF;IACvF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,eAAe,CAAC;IACxD,qFAAqF;IACrF,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,0DAA0D;IAC1D,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,4FAA4F;IAC5F,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC5E;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAqFD,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,gBAAgB,CAizBtF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAMH,OAAO,KAAK,EAGX,gBAAgB,EAChB,gBAAgB,EAEhB,MAAM,gCAAgC,CAAC;AAMxC,OAAO,EAEN,KAAK,eAAe,EAGpB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EAEnB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,uBAAuB,EAAmB,MAAM,YAAY,CAAC;AAS3E,OAAO,KAAK,EAAqC,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAI3F,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,WAAW,mBAAmB;IACnC,uFAAuF;IACvF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,eAAe,CAAC;IACxD,qFAAqF;IACrF,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,0DAA0D;IAC1D,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,4FAA4F;IAC5F,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC5E;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AA4DD,6FAA6F;AAC7F,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AAGjD;;;GAGG;AACH,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAe9C,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,cAAc,CAAC;IACtB,UAAU,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvC;AAQD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,GAAG,SAAS,CA0B7F;AA2DD,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,gBAAgB,CAs4BtF"}
@@ -3,12 +3,15 @@
3
3
  *
4
4
  * Connects the servers from `mcp.json` and the servers extensions register with
5
5
  * `pi.registerMcpServer()` when a session starts, and servers registered later right away. A server
6
- * in `mcp.json` takes precedence over a registered server of the same name. Tools are registered as
7
- * `mcp__<server>__<tool>`. By default (`"exposure": "codemode"`) the tools are only callable from
8
- * codemode scripts, which keeps large MCP tool lists out of the model's tool declarations; the
9
- * codemode tool is activated for that unless `autoEnableCodemode` is false. `"codemode-deferred"`
10
- * leaves them out of the codemode description as well. `"deferred"` declares them to the model once
11
- * the `tool_search` tool loads them, and activates `tool_search` instead of codemode.
6
+ * in `mcp.json` takes precedence over a registered server of the same name. Connections run in the
7
+ * background: the first prompt waits only for servers with `direct` tools, and codemode scripts,
8
+ * `tool_search`, and the resource tools wait for the servers they need when they run. Tools are
9
+ * registered as `mcp__<server>__<tool>`. By default (`"exposure": "codemode"`) the tools are only
10
+ * callable from codemode scripts, which keeps MCP tools out of the model's tool declarations and
11
+ * the codemode description: scripts find the tools with `searchTools()` and the server instructions
12
+ * with `describeNamespace()`. The codemode tool is activated for that unless `autoEnableCodemode` is
13
+ * false. `"deferred"` declares the tools to the model once the `tool_search` tool loads them, and
14
+ * activates `tool_search` instead of codemode.
12
15
  * `"exposure": "direct"` declares them to the model right away, and `"hidden"` makes them
13
16
  * unreachable. `toolExposure` overrides the exposure of single tools. Servers with resources are
14
17
  * reached through Codex's `list_mcp_resources`, `list_mcp_resource_templates`, and
@@ -23,19 +26,25 @@
23
26
  * current session for registered servers.
24
27
  */
25
28
  import { join, resolve } from "node:path";
29
+ import { hyperlink } from "@earendil-works/pi-tui";
26
30
  import { getAgentDir } from "../../config.js";
31
+ import { mcpNamespace } from "../../core/mcp-servers.js";
27
32
  import { openBrowser } from "../../utils/open-browser.js";
28
33
  import { CODEMODE_TOOL_NAME, isCodemodeTool } from "../codemode/tool.js";
29
34
  import { isToolSearchTool, TOOL_SEARCH_TOOL_NAME } from "../tool-search/tool.js";
30
35
  import { getMcpToolExposure, loadMcpConfig, updateMcpServerConfig, } from "./config.js";
31
- import { createMcpResourceToolDefinitions } from "./resources.js";
36
+ import { createMcpResourceToolDefinitions, LIST_MCP_RESOURCE_TEMPLATES_TOOL, LIST_MCP_RESOURCES_TOOL, READ_MCP_RESOURCE_TOOL, } from "./resources.js";
32
37
  import { loadMcpRuntime } from "./runtime.lazy.js";
33
38
  import { createMcpToolDefinition, createMcpToolName } from "./tools.js";
34
39
  import { showMcpManager } from "./ui.js";
35
40
  const DEFAULT_STARTUP_WAIT_MS = 10_000;
41
+ const RESOURCE_TOOL_NAMES = new Set([
42
+ LIST_MCP_RESOURCES_TOOL,
43
+ LIST_MCP_RESOURCE_TEMPLATES_TOOL,
44
+ READ_MCP_RESOURCE_TOOL,
45
+ ]);
36
46
  const EXPOSURE_DESCRIPTIONS = {
37
- codemode: "called from codemode scripts, listed in the codemode description",
38
- "codemode-deferred": "called from codemode scripts, not listed; scripts find them with searchTools()",
47
+ codemode: "called from codemode scripts, which find them with searchTools()",
39
48
  deferred: "not declared until tool_search loads them, then called directly; no codemode needed",
40
49
  direct: "declared to the model like built-in tools",
41
50
  };
@@ -51,6 +60,86 @@ function isEnabled(server) {
51
60
  function exposureOf(entry) {
52
61
  return entry.config.exposure ?? "codemode";
53
62
  }
63
+ /** Exposures the server's tools can have, known from its config before it connects. */
64
+ function configuredExposures(entry) {
65
+ return new Set([exposureOf(entry), ...Object.values(entry.config.toolExposure ?? {})]);
66
+ }
67
+ /** Whether some of the server's tools are declared to the model, so the first prompt waits for them. */
68
+ function hasDirectTools(entry) {
69
+ return configuredExposures(entry).has("direct");
70
+ }
71
+ /** Whether some of the server's tools are reached through codemode or tool_search. */
72
+ function hasIndirectTools(entry) {
73
+ const exposures = configuredExposures(entry);
74
+ return exposures.has("codemode") || exposures.has("deferred");
75
+ }
76
+ /** Name of the system prompt section that lists the servers whose tools are not declared. */
77
+ export const MCP_SERVERS_SECTION = "mcp_servers";
78
+ /** Characters of a server description in the section, as Codex allows for deferred namespaces. */
79
+ const MAX_SERVER_DESCRIPTION_CHARS = 250;
80
+ /**
81
+ * Characters of the whole section. Descriptions shrink to fit; when the server lines alone do not
82
+ * fit, the last servers are left out and counted in a closing line.
83
+ */
84
+ export const MAX_SERVERS_SECTION_CHARS = 4096;
85
+ /** The section's first line. It explains only the ways of reaching tools that the listed servers use. */
86
+ function serversSectionIntro(reaches) {
87
+ let intro = "MCP servers whose tools are not declared to you.";
88
+ if (reaches.has("codemode"))
89
+ intro += " Call the tools of `codemode` servers from codemode scripts.";
90
+ if (reaches.has("tool_search"))
91
+ intro += " Load the tools of `tool_search` servers with `tool_search`.";
92
+ return intro;
93
+ }
94
+ function truncate(text, max) {
95
+ if (text.length <= max)
96
+ return text;
97
+ return max <= 1 ? "" : `${text.slice(0, max - 1).trimEnd()}…`;
98
+ }
99
+ /** First line of the configured description, or of the server instructions once connected. */
100
+ function serverSummary(server) {
101
+ const text = server.entry.config.description?.trim() || server.connection?.instructions || "";
102
+ return (text.split("\n", 1)[0] ?? "").trim();
103
+ }
104
+ /**
105
+ * The `mcp_servers` section: every enabled server with codemode or deferred tools, with how its
106
+ * tools are reached and a one-line summary. The model learns of the servers from it, since neither
107
+ * codemode nor tool_search lists them. Undefined when there are no such servers.
108
+ */
109
+ export function renderServersSection(servers) {
110
+ const listed = servers
111
+ .filter((server) => isEnabled(server) && hasIndirectTools(server.entry))
112
+ .sort((a, b) => a.entry.name.localeCompare(b.entry.name));
113
+ if (listed.length === 0)
114
+ return undefined;
115
+ const reaches = listed.map((server) => configuredExposures(server.entry).has("codemode") ? "codemode" : "tool_search");
116
+ const intro = serversSectionIntro(new Set(reaches));
117
+ const heads = listed.map((server, index) => `- ${mcpNamespace(server.entry.name)} (${reaches[index]})`);
118
+ const omitted = (count) => count > 0 ? [`- … ${count} more server${count === 1 ? "" : "s"}; find their tools with searchTools()`] : [];
119
+ // Characters of the intro, the first `kept` server lines without descriptions, and the omission line.
120
+ const size = (kept) => [intro, ...heads.slice(0, kept), ...omitted(listed.length - kept)].join("\n").length;
121
+ let kept = listed.length;
122
+ while (kept > 0 && size(kept) > MAX_SERVERS_SECTION_CHARS)
123
+ kept--;
124
+ // Each description also takes a ": " separator.
125
+ const perServer = kept === 0
126
+ ? 0
127
+ : Math.min(MAX_SERVER_DESCRIPTION_CHARS, Math.floor((MAX_SERVERS_SECTION_CHARS - size(kept)) / kept) - 2);
128
+ const lines = listed.slice(0, kept).map((server, index) => {
129
+ const summary = perServer > 0 ? truncate(serverSummary(server), perServer) : "";
130
+ return summary ? `${heads[index]}: ${summary}` : heads[index];
131
+ });
132
+ return [intro, ...lines, ...omitted(listed.length - kept)].join("\n");
133
+ }
134
+ /**
135
+ * Whether a codemode script needs the server: it names the server's namespace, or searches,
136
+ * enumerates, or describes tools or namespaces, which may name the server in other forms.
137
+ */
138
+ function scriptNeedsServer(code, server) {
139
+ if (/\b(searchTools|describeNamespace|describeTool|ALL_TOOLS)\b/.test(code))
140
+ return true;
141
+ return code.includes(mcpNamespace(server));
142
+ }
54
143
  /** Short state for lists and the startup report. `withError` appends the first line of a failure. */
55
144
  function describeState(server, withError = true) {
56
145
  if (!isEnabled(server))
@@ -121,6 +210,8 @@ export function createMcpExtension(options = {}) {
121
210
  /** Working directory of the session, for stdio servers. */
122
211
  let sessionCwd = process.cwd();
123
212
  let credentials = options.credentials;
213
+ /** The session's model registry, which resolves `auth.provider` tokens. */
214
+ let modelRegistry;
124
215
  let serverLog;
125
216
  const openUrl = options.openUrl ?? openBrowser;
126
217
  const updateConfig = options.updateConfig ?? ((entry, patch) => updateMcpServerConfig(entry.source, entry.name, patch));
@@ -142,9 +233,9 @@ export function createMcpExtension(options = {}) {
142
233
  const registered = [];
143
234
  const overriddenNames = [];
144
235
  for (const { name, config, extensionPath } of pi.getMcpServers()) {
145
- const configured = configuredEntries.find((entry) => entry.name === name);
236
+ const configured = configuredEntries.find((entry) => mcpNamespace(entry.name) === mcpNamespace(name));
146
237
  if (configured) {
147
- overriddenNames.push(`"${name}" registered by ${extensionPath} is overridden by ${configured.source}`);
238
+ overriddenNames.push(`"${name}" registered by ${extensionPath} is overridden by "${configured.name}" in ${configured.source}`);
148
239
  continue;
149
240
  }
150
241
  registered.push({
@@ -171,17 +262,23 @@ export function createMcpExtension(options = {}) {
171
262
  const registerTools = (connection) => {
172
263
  const server = connection.entry.name;
173
264
  const entry = findServer(server)?.entry ?? connection.entry;
174
- const namespaceName = `mcp__${server}`;
265
+ const description = entry.config.description?.trim();
175
266
  const namespace = {
176
- name: namespaceName,
177
- description: connection.instructions ?? `Tools in the ${namespaceName} namespace.`,
267
+ name: mcpNamespace(server),
268
+ ...(description ? { description } : {}),
269
+ ...(connection.instructions ? { instructions: connection.instructions } : {}),
178
270
  };
179
271
  const previous = serverTools.get(server) ?? new Set();
180
272
  const current = new Set();
273
+ // Like Codex, all tools whose names sanitize to the same name get the hash suffix, so which
274
+ // one would keep the plain name does not depend on the order of the list.
275
+ const plain = [...new Set(connection.tools.map((tool) => tool.name))].map((tool) => createMcpToolName(server, tool));
181
276
  const assignName = (tool, owner) => {
182
277
  const name = createMcpToolName(server, tool, (candidate) => {
183
278
  const existing = toolOwners.get(candidate);
184
- return (existing !== undefined && existing !== owner) || current.has(candidate);
279
+ return ((existing !== undefined && existing !== owner) ||
280
+ current.has(candidate) ||
281
+ plain.indexOf(candidate) !== plain.lastIndexOf(candidate));
185
282
  });
186
283
  toolOwners.set(name, owner);
187
284
  current.add(name);
@@ -232,7 +329,7 @@ export function createMcpExtension(options = {}) {
232
329
  */
233
330
  const syncResourceTools = () => {
234
331
  const exposures = new Set(serversWithResources().map((server) => exposureOf(server.entry)));
235
- const exposure = ["direct", "codemode", "codemode-deferred", "deferred"].find((candidate) => exposures.has(candidate));
332
+ const exposure = ["direct", "codemode", "deferred"].find((candidate) => exposures.has(candidate));
236
333
  const next = exposure ?? "hidden";
237
334
  if (next === resourceToolsExposure || (resourceToolsExposure === undefined && next === "hidden"))
238
335
  return;
@@ -249,21 +346,19 @@ export function createMcpExtension(options = {}) {
249
346
  /**
250
347
  * Tools that are not declared to the model are reached through the codemode tool (scripts call
251
348
  * them) or the tool_search tool (it declares them). Either reaches every such tool. Activate the
252
- * one the tools' exposure asks for: codemode for `codemode` and `codemode-deferred` unless
253
- * `autoEnableCodemode` is false, tool_search for `deferred`.
349
+ * one the tools' exposure asks for: codemode for `codemode` unless `autoEnableCodemode` is false,
350
+ * tool_search for `deferred`.
254
351
  */
255
352
  const ensureDiscoveryActive = (ctx) => {
353
+ // From the config, so the tool is active before the servers connect. Resource tools share
354
+ // their server's exposure.
256
355
  const exposures = new Set();
257
- for (const { connection, entry } of servers) {
258
- if (connection?.state !== "connected")
259
- continue;
260
- // Resource tools share the server's exposure.
261
- if (connection.hasResources)
262
- exposures.add(exposureOf(entry));
263
- for (const tool of connection.tools)
264
- exposures.add(getMcpToolExposure(entry.config, tool.name));
356
+ for (const server of servers) {
357
+ if (isEnabled(server))
358
+ for (const exposure of configuredExposures(server.entry))
359
+ exposures.add(exposure);
265
360
  }
266
- const needsCodemode = exposures.has("codemode") || exposures.has("codemode-deferred");
361
+ const needsCodemode = exposures.has("codemode");
267
362
  const needsToolSearch = exposures.has("deferred");
268
363
  if (!needsCodemode && !needsToolSearch)
269
364
  return;
@@ -299,7 +394,7 @@ export function createMcpExtension(options = {}) {
299
394
  const tokensAtSignIn = new Map();
300
395
  const storedTokens = (connection) => {
301
396
  const url = connection.oauthUrl;
302
- return url && credentials ? JSON.stringify(credentials.tokens(url) ?? null) : "null";
397
+ return url && credentials ? JSON.stringify(credentials.tokens(connection.name, url) ?? null) : "null";
303
398
  };
304
399
  const onConnectionChange = (connection) => {
305
400
  if (connection.state !== "needs-auth")
@@ -326,6 +421,7 @@ export function createMcpExtension(options = {}) {
326
421
  cwd: sessionCwd,
327
422
  createTransport: options.createTransport ?? runtime.createDefaultTransport,
328
423
  credentials: getCredentials(runtime),
424
+ providerToken: async (provider) => modelRegistry?.getApiKeyForProvider(provider),
329
425
  log: getServerLog(runtime),
330
426
  onTools: registerTools,
331
427
  onChange: onConnectionChange,
@@ -334,6 +430,40 @@ export function createMcpExtension(options = {}) {
334
430
  emitChange();
335
431
  return connection;
336
432
  };
433
+ /**
434
+ * Connect the server in the background. `server.ready` settles when it connected or failed;
435
+ * failures show in its state. It starts after `after`; `isCurrent` stops it when the session ended
436
+ * meanwhile. The returned promise rejects when the MCP runtime cannot be loaded.
437
+ */
438
+ const startConnection = (server, isCurrent, after) => {
439
+ const ready = (async () => {
440
+ await after;
441
+ if (!isCurrent())
442
+ return;
443
+ const connection = await createConnection(server);
444
+ if (!isCurrent())
445
+ return;
446
+ await connection.getClient().catch(() => undefined);
447
+ })();
448
+ server.ready = ready.catch(() => undefined);
449
+ return ready;
450
+ };
451
+ /** Wait for servers still connecting, until they settle or `signal` aborts. */
452
+ const waitForServers = async (waiting, signal) => {
453
+ const ready = waiting.flatMap((server) => (server.ready ? [server.ready] : []));
454
+ if (ready.length === 0 || signal?.aborted)
455
+ return;
456
+ let onAbort;
457
+ await Promise.race([
458
+ Promise.all(ready),
459
+ new Promise((resolve) => {
460
+ onAbort = () => resolve();
461
+ signal?.addEventListener("abort", onAbort, { once: true });
462
+ }),
463
+ ]);
464
+ if (onAbort)
465
+ signal?.removeEventListener("abort", onAbort);
466
+ };
337
467
  /**
338
468
  * One message for everything that needs the user after startup, or only for `only`, servers
339
469
  * that connected later.
@@ -374,7 +504,7 @@ export function createMcpExtension(options = {}) {
374
504
  try {
375
505
  await runtime.signInMcpServer({
376
506
  serverUrl: url,
377
- store: getCredentials(runtime).forServer(url),
507
+ store: getCredentials(runtime).forServer(server.entry.name, url),
378
508
  settings: connection.oauthSettings(),
379
509
  challenge: connection.challenge,
380
510
  prompt,
@@ -400,7 +530,7 @@ export function createMcpExtension(options = {}) {
400
530
  const url = connection?.oauthUrl;
401
531
  if (!connection || !url)
402
532
  return false;
403
- const removed = getCredentials(await loadMcpRuntime()).remove(url);
533
+ const removed = getCredentials(await loadMcpRuntime()).remove(server.entry.name, url);
404
534
  await connection.signOut();
405
535
  return removed;
406
536
  };
@@ -429,8 +559,7 @@ export function createMcpExtension(options = {}) {
429
559
  await connection?.close();
430
560
  return undefined;
431
561
  }
432
- const connection = await createConnection(server);
433
- await connection.getClient().catch(() => undefined);
562
+ await startConnection(server, () => true);
434
563
  return undefined;
435
564
  };
436
565
  const setExposure = (server, exposure) => {
@@ -692,7 +821,11 @@ export function createMcpExtension(options = {}) {
692
821
  }
693
822
  const failure = await signIn(server, {
694
823
  showAuthorizationUrl: (url) => {
695
- ctx.ui.notify(`Sign in to MCP server "${name}" in your browser:\n${url.href}`, "info");
824
+ // Long URLs wrap, which some terminals cannot open; a short link line stays on one line.
825
+ const lines = ctx.mode === "tui"
826
+ ? `${hyperlink(url.href, url.href)}\n${hyperlink(process.platform === "darwin" ? "Cmd+click to open" : "Ctrl+click to open", url.href)}`
827
+ : url.href;
828
+ ctx.ui.notify(`Sign in to MCP server "${name}" in your browser:\n${lines}`, "info");
696
829
  openUrl(url.href);
697
830
  },
698
831
  promptForRedirectUrl: (signal) => ctx.ui.input(`Waiting for sign-in to "${name}". If the browser cannot reach this machine, paste the URL it was redirected to.`, "http://127.0.0.1:.../callback?code=...", { signal }),
@@ -711,6 +844,7 @@ export function createMcpExtension(options = {}) {
711
844
  warnedUnreachable = false;
712
845
  waitedForStartup = false;
713
846
  sessionCwd = ctx.cwd;
847
+ modelRegistry = ctx.modelRegistry;
714
848
  const current = ++generation;
715
849
  sessionActive = true;
716
850
  configuredEntries = loaded.servers;
@@ -718,6 +852,9 @@ export function createMcpExtension(options = {}) {
718
852
  overridden = registered.overridden;
719
853
  servers = [...loaded.servers.map((entry) => ({ entry })), ...registered.servers];
720
854
  emitChange();
855
+ // Codemode or tool_search is activated from the config: the first prompt does not wait for
856
+ // servers whose tools are not declared to the model, and scripts or searches wait for them.
857
+ ensureDiscoveryActive(ctx);
721
858
  const enabled = servers.filter(isEnabled);
722
859
  if (enabled.length === 0) {
723
860
  reportProblems(ctx);
@@ -725,19 +862,12 @@ export function createMcpExtension(options = {}) {
725
862
  }
726
863
  // The MCP client loads only now, so sessions without servers never pay for it. Waiting one
727
864
  // event loop turn lets the first render happen before loading and connecting.
728
- pending = new Promise((resolve) => setImmediate(resolve))
729
- .then(() => loadMcpRuntime())
730
- .then(async () => {
731
- if (current !== generation)
732
- return;
733
- const started = await Promise.all(enabled.map((server) => createConnection(server)));
734
- if (current !== generation)
735
- return;
736
- await Promise.allSettled(started.map((connection) => connection.getClient()));
737
- if (current !== generation)
738
- return;
739
- ensureDiscoveryActive(ctx);
740
- reportProblems(ctx);
865
+ const runtime = new Promise((resolve) => setImmediate(resolve)).then(() => loadMcpRuntime());
866
+ const isCurrent = () => current === generation;
867
+ pending = Promise.all(enabled.map((server) => startConnection(server, isCurrent, runtime)))
868
+ .then(() => {
869
+ if (isCurrent())
870
+ reportProblems(ctx);
741
871
  })
742
872
  .catch((error) => {
743
873
  // The session may have been disposed meanwhile, which makes ctx stale.
@@ -747,16 +877,19 @@ export function createMcpExtension(options = {}) {
747
877
  catch { }
748
878
  });
749
879
  });
750
- // The first prompt waits for startup connections so their tools are available to it, but not
751
- // indefinitely: a slow or hanging server must not hold up the prompt.
752
- pi.on("before_agent_start", async (_event, ctx) => {
753
- const startup = pending;
754
- if (!startup || waitedForStartup)
880
+ // The first prompt waits for servers whose tools are declared to the model, so they are declared
881
+ // in its first request, but not indefinitely: a slow or hanging server must not hold up the
882
+ // prompt. Other servers are waited for when a script or search needs them (below).
883
+ const waitForDirectServers = async (ctx) => {
884
+ if (waitedForStartup)
755
885
  return;
756
886
  waitedForStartup = true;
887
+ const ready = servers.flatMap((server) => isEnabled(server) && hasDirectTools(server.entry) && server.ready ? [server.ready] : []);
888
+ if (ready.length === 0)
889
+ return;
757
890
  let timer;
758
891
  const finished = await Promise.race([
759
- startup.then(() => true),
892
+ Promise.all(ready).then(() => true),
760
893
  new Promise((resolve) => {
761
894
  timer = setTimeout(() => resolve(false), startupWaitMs);
762
895
  }),
@@ -765,6 +898,38 @@ export function createMcpExtension(options = {}) {
765
898
  if (!finished) {
766
899
  ctx.ui.notify("MCP servers are still connecting; their tools become available once connected.", "info");
767
900
  }
901
+ };
902
+ // Every prompt lists the servers in the `mcp_servers` section as they are when it starts. Pi
903
+ // appends the section to the conversation when it changed, for example after a server connected.
904
+ pi.on("before_agent_start", async (event, ctx) => {
905
+ await waitForDirectServers(ctx);
906
+ const { sections } = event.systemPromptOptions;
907
+ const section = renderServersSection(servers);
908
+ if (section)
909
+ sections[MCP_SERVERS_SECTION] = section;
910
+ else
911
+ delete sections[MCP_SERVERS_SECTION];
912
+ });
913
+ // A codemode script waits for the servers it names, or for every server when it searches or
914
+ // enumerates tools, so their tools are registered before the script runs. tool_search and the
915
+ // resource tools reach every server, so they wait for all of them.
916
+ pi.on("tool_call", async (event, ctx) => {
917
+ const tool = pi.getAllTools().find((candidate) => candidate.name === event.toolName);
918
+ if (!tool)
919
+ return;
920
+ const pendingServers = servers.filter((server) => isEnabled(server) && server.connection?.state !== "connected" && server.ready);
921
+ if (pendingServers.length === 0)
922
+ return;
923
+ let waiting = [];
924
+ if (isCodemodeTool(tool)) {
925
+ const { code } = event.input;
926
+ const source = typeof code === "string" ? code : "";
927
+ waiting = pendingServers.filter((server) => scriptNeedsServer(source, server.entry.name));
928
+ }
929
+ else if (isToolSearchTool(tool) || RESOURCE_TOOL_NAMES.has(tool.name)) {
930
+ waiting = pendingServers;
931
+ }
932
+ await waitForServers(waiting, ctx.signal);
768
933
  });
769
934
  // Pick up sign-ins done outside the session, such as `pi mcp login` run by the agent.
770
935
  pi.on("turn_start", async (_event, ctx) => {
@@ -788,17 +953,17 @@ export function createMcpExtension(options = {}) {
788
953
  const added = registered.servers.filter((server) => !findServer(server.entry.name));
789
954
  servers.push(...added);
790
955
  emitChange();
956
+ ensureDiscoveryActive(ctx);
791
957
  await Promise.all(removed.map((server) => server.connection?.close()));
792
958
  const connecting = added.filter(isEnabled);
793
959
  if (current !== generation || connecting.length === 0)
794
960
  return;
795
961
  try {
796
- const started = await Promise.all(connecting.map((server) => createConnection(server)));
962
+ await Promise.all(connecting.map((server) => startConnection(server, () => current === generation)));
797
963
  if (current !== generation) {
798
- await Promise.all(started.map((connection) => connection.close()));
964
+ await Promise.all(connecting.map((server) => server.connection?.close()));
799
965
  return;
800
966
  }
801
- await Promise.allSettled(started.map((connection) => connection.getClient()));
802
967
  }
803
968
  catch (error) {
804
969
  ctx.ui.notify(`MCP failed to load: ${errorMessage(error)}`, "error");
@@ -806,7 +971,6 @@ export function createMcpExtension(options = {}) {
806
971
  }
807
972
  if (current !== generation)
808
973
  return;
809
- ensureDiscoveryActive(ctx);
810
974
  reportProblems(ctx, connecting);
811
975
  });
812
976
  pi.on("session_shutdown", async () => {