@bastani/atomic 0.9.23 → 0.9.24

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 (149) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/builtin/intercom/package.json +1 -1
  3. package/dist/builtin/mcp/CHANGELOG.md +12 -0
  4. package/dist/builtin/mcp/README.md +1 -0
  5. package/dist/builtin/mcp/index.bundle.mjs +244 -58
  6. package/dist/builtin/mcp/package.json +1 -1
  7. package/dist/builtin/subagents/package.json +1 -1
  8. package/dist/builtin/web-access/package.json +1 -1
  9. package/dist/builtin/workflows/CHANGELOG.md +22 -0
  10. package/dist/builtin/workflows/builtin/{chunk-d3xhj946.js → chunk-cpw5gtzg.js} +1 -1
  11. package/dist/builtin/workflows/builtin/{chunk-kgn5wz1c.js → chunk-e1254zvd.js} +139 -15
  12. package/dist/builtin/workflows/builtin/{chunk-hj8mh07f.js → chunk-qy6g0baf.js} +1 -1
  13. package/dist/builtin/workflows/builtin/goal.js +2 -2
  14. package/dist/builtin/workflows/builtin/index.js +3 -3
  15. package/dist/builtin/workflows/builtin/ralph.js +2 -2
  16. package/dist/builtin/workflows/package.json +1 -1
  17. package/dist/builtin/workflows/skills/prompt-engineer/SKILL.md +1 -0
  18. package/dist/builtin/workflows/skills/prompt-engineer/references/claude_sonnet_5_5.md +131 -0
  19. package/dist/builtin/workflows/src/extension/index.bundle.mjs +187 -25
  20. package/dist/builtin/workflows/src/index.js +147 -17
  21. package/dist/core/agent-session-events.js +1 -1
  22. package/dist/core/agent-session-events.js.map +1 -1
  23. package/dist/core/agent-session-state.js +5 -2
  24. package/dist/core/agent-session-state.js.map +1 -1
  25. package/dist/core/extensions/api-types.d.ts +12 -0
  26. package/dist/core/extensions/api-types.d.ts.map +1 -1
  27. package/dist/core/extensions/api-types.js.map +1 -1
  28. package/dist/core/extensions/loader-api.d.ts.map +1 -1
  29. package/dist/core/extensions/loader-api.js +42 -0
  30. package/dist/core/extensions/loader-api.js.map +1 -1
  31. package/dist/core/extensions/loader-bindings.d.ts.map +1 -1
  32. package/dist/core/extensions/loader-bindings.js +13 -7
  33. package/dist/core/extensions/loader-bindings.js.map +1 -1
  34. package/dist/core/extensions/loader-resources.d.ts +3 -0
  35. package/dist/core/extensions/loader-resources.d.ts.map +1 -1
  36. package/dist/core/extensions/loader-resources.js.map +1 -1
  37. package/dist/core/extensions/loader-runtime.d.ts.map +1 -1
  38. package/dist/core/extensions/loader-runtime.js +2 -0
  39. package/dist/core/extensions/loader-runtime.js.map +1 -1
  40. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  41. package/dist/core/extensions/loader-virtual-modules.js +2 -0
  42. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  43. package/dist/core/extensions/mcp-server-registry.d.ts +22 -0
  44. package/dist/core/extensions/mcp-server-registry.d.ts.map +1 -0
  45. package/dist/core/extensions/mcp-server-registry.js +51 -0
  46. package/dist/core/extensions/mcp-server-registry.js.map +1 -0
  47. package/dist/core/extensions/runtime-types.d.ts +3 -0
  48. package/dist/core/extensions/runtime-types.d.ts.map +1 -1
  49. package/dist/core/extensions/runtime-types.js.map +1 -1
  50. package/dist/core/keybindings.d.ts +1 -1
  51. package/dist/core/keybindings.d.ts.map +1 -1
  52. package/dist/core/keybindings.js +1 -1
  53. package/dist/core/keybindings.js.map +1 -1
  54. package/dist/core/mcp-servers.d.ts +39 -0
  55. package/dist/core/mcp-servers.d.ts.map +1 -0
  56. package/dist/core/mcp-servers.js +2 -0
  57. package/dist/core/mcp-servers.js.map +1 -0
  58. package/dist/core/model-resolver-defaults.js +2 -2
  59. package/dist/core/model-resolver-defaults.js.map +1 -1
  60. package/dist/core/package-manager-manifest.d.ts +3 -0
  61. package/dist/core/package-manager-manifest.d.ts.map +1 -1
  62. package/dist/core/package-manager-manifest.js +14 -0
  63. package/dist/core/package-manager-manifest.js.map +1 -1
  64. package/dist/core/package-manager-mcp-servers.d.ts +4 -0
  65. package/dist/core/package-manager-mcp-servers.d.ts.map +1 -0
  66. package/dist/core/package-manager-mcp-servers.js +53 -0
  67. package/dist/core/package-manager-mcp-servers.js.map +1 -0
  68. package/dist/core/package-manager-resource-accumulator.d.ts.map +1 -1
  69. package/dist/core/package-manager-resource-accumulator.js +2 -0
  70. package/dist/core/package-manager-resource-accumulator.js.map +1 -1
  71. package/dist/core/package-manager-resource-collector.d.ts.map +1 -1
  72. package/dist/core/package-manager-resource-collector.js +2 -0
  73. package/dist/core/package-manager-resource-collector.js.map +1 -1
  74. package/dist/core/package-manager-resource-patterns.d.ts +4 -0
  75. package/dist/core/package-manager-resource-patterns.d.ts.map +1 -1
  76. package/dist/core/package-manager-resource-patterns.js +36 -12
  77. package/dist/core/package-manager-resource-patterns.js.map +1 -1
  78. package/dist/core/package-manager-types.d.ts +14 -0
  79. package/dist/core/package-manager-types.d.ts.map +1 -1
  80. package/dist/core/package-manager-types.js.map +1 -1
  81. package/dist/core/package-manager.d.ts +1 -1
  82. package/dist/core/package-manager.d.ts.map +1 -1
  83. package/dist/core/package-manager.js.map +1 -1
  84. package/dist/core/resource-loader-core.d.ts +4 -0
  85. package/dist/core/resource-loader-core.d.ts.map +1 -1
  86. package/dist/core/resource-loader-core.js +7 -0
  87. package/dist/core/resource-loader-core.js.map +1 -1
  88. package/dist/core/resource-loader-helpers.d.ts.map +1 -1
  89. package/dist/core/resource-loader-helpers.js +1 -0
  90. package/dist/core/resource-loader-helpers.js.map +1 -1
  91. package/dist/core/resource-loader-internals.d.ts +2 -0
  92. package/dist/core/resource-loader-internals.d.ts.map +1 -1
  93. package/dist/core/resource-loader-internals.js.map +1 -1
  94. package/dist/core/resource-loader-package-resources.d.ts +3 -0
  95. package/dist/core/resource-loader-package-resources.d.ts.map +1 -1
  96. package/dist/core/resource-loader-package-resources.js +25 -2
  97. package/dist/core/resource-loader-package-resources.js.map +1 -1
  98. package/dist/core/resource-loader-reload.d.ts.map +1 -1
  99. package/dist/core/resource-loader-reload.js +4 -1
  100. package/dist/core/resource-loader-reload.js.map +1 -1
  101. package/dist/core/session-manager-core.d.ts +2 -0
  102. package/dist/core/session-manager-core.d.ts.map +1 -1
  103. package/dist/core/session-manager-core.js +6 -1
  104. package/dist/core/session-manager-core.js.map +1 -1
  105. package/dist/core/settings-types.d.ts +1 -0
  106. package/dist/core/settings-types.d.ts.map +1 -1
  107. package/dist/core/settings-types.js.map +1 -1
  108. package/dist/extensions/llama/provider.d.ts +1 -1
  109. package/dist/extensions/llama/provider.d.ts.map +1 -1
  110. package/dist/extensions/llama/provider.js +37 -10
  111. package/dist/extensions/llama/provider.js.map +1 -1
  112. package/dist/index.d.ts +2 -1
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.js.map +1 -1
  115. package/dist/modes/interactive/chat-input-actions.d.ts +12 -0
  116. package/dist/modes/interactive/chat-input-actions.d.ts.map +1 -1
  117. package/dist/modes/interactive/chat-input-actions.js +44 -18
  118. package/dist/modes/interactive/chat-input-actions.js.map +1 -1
  119. package/dist/modes/interactive/components/chat-session-host-editor.d.ts +11 -0
  120. package/dist/modes/interactive/components/chat-session-host-editor.d.ts.map +1 -1
  121. package/dist/modes/interactive/components/chat-session-host-editor.js +7 -1
  122. package/dist/modes/interactive/components/chat-session-host-editor.js.map +1 -1
  123. package/dist/modes/interactive/interactive-hotkeys-debug.js +1 -1
  124. package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
  125. package/dist/modes/interactive/interactive-input-handling.js +3 -1
  126. package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
  127. package/dist/modes/interactive/interactive-mode-helpers.d.ts.map +1 -1
  128. package/dist/modes/interactive/interactive-mode-helpers.js +1 -6
  129. package/dist/modes/interactive/interactive-mode-helpers.js.map +1 -1
  130. package/dist/utils/clipboard-native.d.ts +2 -0
  131. package/dist/utils/clipboard-native.d.ts.map +1 -1
  132. package/dist/utils/clipboard-native.js.map +1 -1
  133. package/dist/utils/clipboard.d.ts +7 -0
  134. package/dist/utils/clipboard.d.ts.map +1 -1
  135. package/dist/utils/clipboard.js +37 -1
  136. package/dist/utils/clipboard.js.map +1 -1
  137. package/docs/extensions/api-reference.md +19 -0
  138. package/docs/extensions.md +4 -0
  139. package/docs/keybindings.md +2 -2
  140. package/docs/llama-cpp.md +13 -0
  141. package/docs/mcp-servers.md +18 -0
  142. package/docs/models/evals.md +16 -4
  143. package/docs/packages/authoring.md +27 -0
  144. package/docs/packages/reference.md +3 -2
  145. package/docs/packages.md +3 -2
  146. package/docs/skills.md +3 -1
  147. package/examples/extensions/gondolin/package-lock.json +3 -3
  148. package/npm-shrinkwrap.json +46 -46
  149. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -2,6 +2,48 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.24] - 2026-09-29
6
+
7
+ ### Added
8
+
9
+ - Added inherited Claude Sonnet 5.5 support for Anthropic with adaptive thinking, mid-conversation effort, and a 1M context window.
10
+ - llama.cpp models are now also listed as classifier models with the same ID, so a local model can make `model: "auto"` routing decisions the way TypeSafe Jev does. Because an explicit `routerModel` resolves a classifier before a chat model, `routerModel: "llama.cpp/<id>"` now routes with the classifier instead of the chat model ([#10119](https://github.com/earendil-works/pi/pull/10119)).
11
+ - Packages can contribute MCP servers with `"atomic": { "mcpServers": "./mcp.json" }` or an inline `mcpServers` object, and extensions can add runtime-computed servers with `pi.registerMcpServer(name, config)` from the factory or `session_start`. Contributed servers are the lowest-precedence MCP layer, so a same-named server in any user or project MCP config replaces them. Package filters accept `mcpServers` name patterns, project packages contribute servers only in trusted projects, and workflow stages see the same servers ([#3355](https://github.com/bastani-inc/atomic/issues/3355)).
12
+
13
+ ### Changed
14
+
15
+ - Recorded Claude Sonnet 5.5 results in the evals snapshot used by automatic model routing: Artificial Analysis Intelligence Index rows for max, xhigh, high, medium and low effort, Cognition FrontierCode 1.1, and Anthropic's published OSWorld, AutomationBench, BenchCAD, Humanity's Last Exam (with tools) and Terminal-Bench-Science scores.
16
+
17
+ ### Fixed
18
+
19
+ - OpenCode Go now defaults to Kimi K3 (`kimi-k3`). models.dev deprecated Kimi K2.6 for OpenCode Go, which removed it from the built-in catalog, so the previous default no longer resolved.
20
+ - Together now defaults to Kimi K3 (`moonshotai/Kimi-K3`). models.dev removed Kimi K2.6 from Together, which removed it from the built-in catalog, so the previous default no longer resolved.
21
+ - Fixed extension tool schemas leaking TypeBox `~kind`/`~optional` metadata into provider requests, which made Fireworks reject every request with `JSON Schema not supported` ([#3330](https://github.com/bastani-inc/atomic/issues/3330)).
22
+ - Resuming a session no longer appends a spurious system-prompt `tools` update when only the order of the active tools changed, so the resumed session keeps its prompt cache and the model is no longer told that unchanged tools were removed ([#3346](https://github.com/bastani-inc/atomic/issues/3346)).
23
+ - Switching sessions with `/resume` or closing a session before sending a message no longer leaves a header-only session file behind ([#3347](https://github.com/bastani-inc/atomic/issues/3347)).
24
+ - Fixed pasting files copied in Finder with `Ctrl+V` inserting the file icon as an image instead of the file paths. Copied files now paste as their paths, shell-quoted in bash mode, and clipboard read errors are reported instead of pasting the icon ([#10136](https://github.com/earendil-works/pi/pull/10136)).
25
+
26
+ ## [0.9.24-alpha.1] - 2026-09-28
27
+
28
+ ### Added
29
+
30
+ - Added inherited Claude Sonnet 5.5 support for Anthropic with adaptive thinking, mid-conversation effort, and a 1M context window.
31
+ - llama.cpp models are now also listed as classifier models with the same ID, so a local model can make `model: "auto"` routing decisions the way TypeSafe Jev does. Because an explicit `routerModel` resolves a classifier before a chat model, `routerModel: "llama.cpp/<id>"` now routes with the classifier instead of the chat model ([#10119](https://github.com/earendil-works/pi/pull/10119)).
32
+ - Packages can contribute MCP servers with `"atomic": { "mcpServers": "./mcp.json" }` or an inline `mcpServers` object, and extensions can add runtime-computed servers with `pi.registerMcpServer(name, config)` from the factory or `session_start`. Contributed servers are the lowest-precedence MCP layer, so a same-named server in any user or project MCP config replaces them. Package filters accept `mcpServers` name patterns, project packages contribute servers only in trusted projects, and workflow stages see the same servers ([#3355](https://github.com/bastani-inc/atomic/issues/3355)).
33
+
34
+ ### Changed
35
+
36
+ - Recorded Claude Sonnet 5.5 results in the evals snapshot used by automatic model routing: Artificial Analysis Intelligence Index rows for max, xhigh, high, medium and low effort, Cognition FrontierCode 1.1, and Anthropic's published OSWorld, AutomationBench, BenchCAD, Humanity's Last Exam (with tools) and Terminal-Bench-Science scores.
37
+
38
+ ### Fixed
39
+
40
+ - OpenCode Go now defaults to Kimi K3 (`kimi-k3`). models.dev deprecated Kimi K2.6 for OpenCode Go, which removed it from the built-in catalog, so the previous default no longer resolved.
41
+ - Together now defaults to Kimi K3 (`moonshotai/Kimi-K3`). models.dev removed Kimi K2.6 from Together, which removed it from the built-in catalog, so the previous default no longer resolved.
42
+ - Fixed extension tool schemas leaking TypeBox `~kind`/`~optional` metadata into provider requests, which made Fireworks reject every request with `JSON Schema not supported` ([#3330](https://github.com/bastani-inc/atomic/issues/3330)).
43
+ - Resuming a session no longer appends a spurious system-prompt `tools` update when only the order of the active tools changed, so the resumed session keeps its prompt cache and the model is no longer told that unchanged tools were removed ([#3346](https://github.com/bastani-inc/atomic/issues/3346)).
44
+ - Switching sessions with `/resume` or closing a session before sending a message no longer leaves a header-only session file behind ([#3347](https://github.com/bastani-inc/atomic/issues/3347)).
45
+ - Fixed pasting files copied in Finder with `Ctrl+V` inserting the file icon as an image instead of the file paths. Copied files now paste as their paths, shell-quoted in bash mode, and clipboard read errors are reported instead of pasting the icon ([#10136](https://github.com/earendil-works/pi/pull/10136)).
46
+
5
47
  ## [0.9.23] - 2026-09-27
6
48
 
7
49
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/intercom",
3
- "version": "0.9.23",
3
+ "version": "0.9.24",
4
4
  "private": true,
5
5
  "description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
6
6
  "contributors": [
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.24] - 2026-09-29
11
+
12
+ ### Added
13
+
14
+ - MCP servers contributed by packages and by `pi.registerMcpServer()` load as the lowest-precedence config layer, including registrations made after MCP has started. A same-named server in any MCP config file replaces a contributed one, and `"disabled": true` on a server entry, alone or with other fields, removes it. `/mcp` and connection errors show which package or extension contributed each server ([#3355](https://github.com/bastani-inc/atomic/issues/3355)).
15
+
16
+ ## [0.9.24-alpha.1] - 2026-09-28
17
+
18
+ ### Added
19
+
20
+ - MCP servers contributed by packages and by `pi.registerMcpServer()` load as the lowest-precedence config layer, including registrations made after MCP has started. A same-named server in any MCP config file replaces a contributed one, and `"disabled": true` on a server entry, alone or with other fields, removes it. `/mcp` and connection errors show which package or extension contributed each server ([#3355](https://github.com/bastani-inc/atomic/issues/3355)).
21
+
10
22
  ## [0.9.20] - 2026-09-24
11
23
 
12
24
  ### Fixed
@@ -141,6 +141,7 @@ Pi-specific files are the write targets for imported or shared global servers wh
141
141
  | `directTools` | `true`, `string[]`, or `false` — register tools individually instead of through proxy |
142
142
  | `excludeTools` | `string[]` of tool names to hide (matches original names like `get_screenshot` and prefixed names like `figma_get_screenshot`) |
143
143
  | `debug` | Show server stderr (default: false) |
144
+ | `disabled` | `true` removes the server from the effective config; `{ "disabled": true }` alone turns off a server contributed by a package or extension |
144
145
 
145
146
  `timeoutMs` is an **inactivity timeout**, not a total wall-clock limit. Each MCP progress notification resets the timer, so a tool that continues reporting progress can run indefinitely. The value must be a finite number greater than zero; invalid values produce a configuration error when the MCP config loads.
146
147
 
@@ -6517,15 +6517,51 @@ function getMcpDiscoverySummary(overridePath, cwd = process.cwd()) {
6517
6517
  repoPrompt: detectRepoPrompt(summaryWithoutRepoPrompt, cwd)
6518
6518
  };
6519
6519
  }
6520
- function loadMcpConfig(overridePath, cwd = process.cwd()) {
6521
- let config = { mcpServers: {} };
6520
+ function loadMcpConfig(overridePath, cwd = process.cwd(), contributions = []) {
6521
+ let config = { mcpServers: validateContributedServers(contributions) };
6522
6522
  for (const source of getConfigSources(overridePath, cwd)) {
6523
6523
  const loaded = readValidatedConfig(source.readPath, `MCP config from ${source.readPath}`);
6524
6524
  if (!loaded)
6525
6525
  continue;
6526
6526
  config = mergeConfigs(config, expandImports(loaded, cwd));
6527
6527
  }
6528
- return config;
6528
+ return withoutDisabledServers(config);
6529
+ }
6530
+ function describeMcpContributionSource(contribution) {
6531
+ const { origin, sourceInfo } = contribution;
6532
+ if (origin === "package")
6533
+ return `package ${sourceInfo.source}`;
6534
+ return sourceInfo.origin === "package" ? `extension from package ${sourceInfo.source}` : `extension ${sourceInfo.path}`;
6535
+ }
6536
+ function getContributedServerSources(overridePath, cwd, contributions) {
6537
+ const sources = new Map;
6538
+ if (contributions.length === 0)
6539
+ return sources;
6540
+ for (const [name, provenance] of getServerProvenance(overridePath, cwd, contributions)) {
6541
+ if (provenance.kind === "contributed" && provenance.source)
6542
+ sources.set(name, provenance.source);
6543
+ }
6544
+ return sources;
6545
+ }
6546
+ function validateContributedServers(contributions) {
6547
+ const servers = {};
6548
+ for (const contribution of contributions) {
6549
+ try {
6550
+ Object.assign(servers, validateMcpServerTimeouts({ [contribution.name]: contribution.config }));
6551
+ } catch (error) {
6552
+ const reason = error instanceof Error ? error.message : String(error);
6553
+ const message = `Ignoring MCP server "${contribution.name}" from ${describeMcpContributionSource(contribution)}: ${reason}`;
6554
+ if (!reportOwnedMcpLog("warn"))
6555
+ console.warn(message);
6556
+ }
6557
+ }
6558
+ return servers;
6559
+ }
6560
+ function withoutDisabledServers(config) {
6561
+ const enabled = Object.entries(config.mcpServers).filter(([, entry]) => entry.disabled !== true);
6562
+ if (enabled.length === Object.keys(config.mcpServers).length)
6563
+ return config;
6564
+ return { ...config, mcpServers: Object.fromEntries(enabled) };
6529
6565
  }
6530
6566
  function getConfigSources(overridePath, cwd = process.cwd()) {
6531
6567
  const userPath = getPiGlobalConfigPath(overridePath);
@@ -6806,9 +6842,16 @@ function writeSharedServerEntry(filePath, serverName, entry) {
6806
6842
  writeRawConfigObject(filePath, raw);
6807
6843
  return filePath;
6808
6844
  }
6809
- function getServerProvenance(overridePath, cwd = process.cwd()) {
6845
+ function getServerProvenance(overridePath, cwd = process.cwd(), contributions = []) {
6810
6846
  const provenance = new Map;
6811
6847
  const userPath = getPiGlobalConfigPath(overridePath);
6848
+ for (const contribution of contributions) {
6849
+ provenance.set(contribution.name, {
6850
+ path: userPath,
6851
+ kind: "contributed",
6852
+ source: describeMcpContributionSource(contribution)
6853
+ });
6854
+ }
6812
6855
  for (const source of getConfigSources(overridePath, cwd)) {
6813
6856
  const loaded = readValidatedConfig(source.readPath, `MCP config from ${source.readPath}`);
6814
6857
  if (!loaded)
@@ -6822,7 +6865,7 @@ function getServerProvenance(overridePath, cwd = process.cwd()) {
6822
6865
  const imported = JSON.parse(readFileSync2(importPath, "utf-8"));
6823
6866
  const servers = extractServers(imported, importKind);
6824
6867
  for (const name of Object.keys(servers)) {
6825
- if (!provenance.has(name)) {
6868
+ if (!provenance.has(name) || provenance.get(name)?.kind === "contributed") {
6826
6869
  provenance.set(name, { path: userPath, kind: "import", importKind });
6827
6870
  }
6828
6871
  }
@@ -6854,7 +6897,7 @@ function writeDirectToolsConfig(changes, provenance, fullConfig) {
6854
6897
  const raw = readRawConfigObject(filePath);
6855
6898
  const servers = getServersObject(raw);
6856
6899
  for (const { name, value, prov } of entries) {
6857
- if (prov.kind === "import") {
6900
+ if (prov.kind === "import" || prov.kind === "contributed") {
6858
6901
  const fullDef = fullConfig.mcpServers[name];
6859
6902
  if (fullDef) {
6860
6903
  servers[name] = { ...fullDef, directTools: value };
@@ -7193,6 +7236,11 @@ class McpLifecycleManager {
7193
7236
  this.serverSettings.set(name, settings);
7194
7237
  }
7195
7238
  }
7239
+ unregisterServer(name) {
7240
+ this.allServers.delete(name);
7241
+ this.keepAliveServers.delete(name);
7242
+ this.serverSettings.delete(name);
7243
+ }
7196
7244
  setGlobalIdleTimeout(minutes) {
7197
7245
  this.globalIdleTimeout = minutes * 60 * 1000;
7198
7246
  }
@@ -7200,6 +7248,8 @@ class McpLifecycleManager {
7200
7248
  this.onIdleShutdown = callback;
7201
7249
  }
7202
7250
  startHealthChecks(intervalMs = 30000) {
7251
+ if (this.healthCheckInterval)
7252
+ return;
7203
7253
  this.healthCheckInterval = setInterval(() => {
7204
7254
  this.checkConnections();
7205
7255
  }, intervalMs);
@@ -7238,6 +7288,7 @@ class McpLifecycleManager {
7238
7288
  async gracefulShutdown() {
7239
7289
  if (this.healthCheckInterval) {
7240
7290
  clearInterval(this.healthCheckInterval);
7291
+ this.healthCheckInterval = undefined;
7241
7292
  }
7242
7293
  await this.manager.closeAll();
7243
7294
  }
@@ -22249,7 +22300,9 @@ var init_ui_resource_handler = __esm(() => {
22249
22300
  // dist/builtin/mcp/init.ts
22250
22301
  var exports_init = {};
22251
22302
  __export(exports_init, {
22303
+ applyMcpConfigChanges: () => applyMcpConfigChanges,
22252
22304
  flushMetadataCache: () => flushMetadataCache,
22305
+ formatMcpServerName: () => formatMcpServerName,
22253
22306
  getFailureAgeSeconds: () => getFailureAgeSeconds,
22254
22307
  initializeMcp: () => initializeMcp,
22255
22308
  lazyConnect: () => lazyConnect,
@@ -22260,7 +22313,8 @@ __export(exports_init, {
22260
22313
  import { existsSync as existsSync6 } from "node:fs";
22261
22314
  async function initializeMcp(pi, ctx) {
22262
22315
  const configPath = pi.getFlag("mcp-config");
22263
- const config = loadMcpConfig(configPath, ctx.cwd);
22316
+ const contributions = pi.getMcpServerContributions?.() ?? [];
22317
+ const config = loadMcpConfig(configPath, ctx.cwd, contributions);
22264
22318
  const manager = new McpServerManager;
22265
22319
  const lifecycle = new McpLifecycleManager(manager);
22266
22320
  try {
@@ -22284,6 +22338,7 @@ async function initializeMcp(pi, ctx) {
22284
22338
  lifecycle,
22285
22339
  toolMetadata,
22286
22340
  config,
22341
+ contributedSources: getContributedServerSources(configPath, ctx.cwd, contributions),
22287
22342
  failureTracker,
22288
22343
  uiResourceHandler,
22289
22344
  consentManager,
@@ -22310,16 +22365,7 @@ async function initializeMcp(pi, ctx) {
22310
22365
  }
22311
22366
  const prefix = config.settings?.toolPrefix ?? "server";
22312
22367
  for (const [name, definition] of serverEntries) {
22313
- const lifecycleMode = definition.lifecycle ?? "lazy";
22314
- const idleOverride = definition.idleTimeout ?? (lifecycleMode === "eager" ? 0 : undefined);
22315
- lifecycle.registerServer(name, definition, idleOverride !== undefined ? { idleTimeout: idleOverride } : undefined);
22316
- if (lifecycleMode === "keep-alive") {
22317
- lifecycle.markKeepAlive(name, definition);
22318
- }
22319
- if (cache?.servers?.[name] && isServerCacheValid(cache.servers[name], definition)) {
22320
- const metadata = reconstructToolMetadata(name, cache.servers[name], prefix, definition);
22321
- toolMetadata.set(name, metadata);
22322
- }
22368
+ registerServer(state, name, definition, cache, prefix);
22323
22369
  }
22324
22370
  const startupServers = serverEntries.filter(([, definition]) => {
22325
22371
  const mode = definition.lifecycle ?? "lazy";
@@ -22343,7 +22389,7 @@ async function initializeMcp(pi, ctx) {
22343
22389
  for (const { name, definition, connection, error } of results) {
22344
22390
  if (error || !connection) {
22345
22391
  if (ctx.hasUI) {
22346
- ctx.ui.notify(`MCP: Failed to connect to ${name}: ${error}`, "error");
22392
+ ctx.ui.notify(`MCP: Failed to connect to ${formatMcpServerName(state, name)}: ${error}`, "error");
22347
22393
  }
22348
22394
  reportMcpDiagnostic(pi, "MCP startup connection failed");
22349
22395
  continue;
@@ -22362,18 +22408,7 @@ async function initializeMcp(pi, ctx) {
22362
22408
  const msg = failedCount > 0 ? `MCP: ${connectedCount}/${startupServers.length} servers connected (${totalTools} tools)` : `MCP: ${connectedCount} servers connected (${totalTools} tools)`;
22363
22409
  ctx.ui.notify(msg, "info");
22364
22410
  }
22365
- lifecycle.setReconnectCallback((serverName) => {
22366
- updateServerMetadata(state, serverName);
22367
- updateMetadataCache(state, serverName);
22368
- state.failureTracker.delete(serverName);
22369
- updateStatusBar(state);
22370
- });
22371
- lifecycle.setIdleShutdownCallback((serverName) => {
22372
- const idleMinutes = getEffectiveIdleTimeoutMinutes(state, serverName);
22373
- logger.debug(`${serverName} shut down (idle ${idleMinutes}m)`);
22374
- updateStatusBar(state);
22375
- });
22376
- lifecycle.startHealthChecks();
22411
+ startLifecycle(state);
22377
22412
  return state;
22378
22413
  } catch (error) {
22379
22414
  try {
@@ -22385,6 +22420,61 @@ async function initializeMcp(pi, ctx) {
22385
22420
  throw error;
22386
22421
  }
22387
22422
  }
22423
+ function formatMcpServerName(state, name) {
22424
+ const source = state.contributedSources?.get(name);
22425
+ return source ? `${name} (${source})` : name;
22426
+ }
22427
+ function registerServer(state, name, definition, cache, prefix) {
22428
+ const lifecycleMode = definition.lifecycle ?? "lazy";
22429
+ const idleOverride = definition.idleTimeout ?? (lifecycleMode === "eager" ? 0 : undefined);
22430
+ state.lifecycle.registerServer(name, definition, idleOverride !== undefined ? { idleTimeout: idleOverride } : undefined);
22431
+ if (lifecycleMode === "keep-alive") {
22432
+ state.lifecycle.markKeepAlive(name, definition);
22433
+ }
22434
+ if (cache?.servers?.[name] && isServerCacheValid(cache.servers[name], definition)) {
22435
+ const metadata = reconstructToolMetadata(name, cache.servers[name], prefix, definition);
22436
+ state.toolMetadata.set(name, metadata);
22437
+ }
22438
+ }
22439
+ function startLifecycle(state) {
22440
+ state.lifecycle.setReconnectCallback((serverName) => {
22441
+ updateServerMetadata(state, serverName);
22442
+ updateMetadataCache(state, serverName);
22443
+ state.failureTracker.delete(serverName);
22444
+ updateStatusBar(state);
22445
+ });
22446
+ state.lifecycle.setIdleShutdownCallback((serverName) => {
22447
+ const idleMinutes = getEffectiveIdleTimeoutMinutes(state, serverName);
22448
+ logger.debug(`${serverName} shut down (idle ${idleMinutes}m)`);
22449
+ updateStatusBar(state);
22450
+ });
22451
+ state.lifecycle.startHealthChecks();
22452
+ }
22453
+ async function applyMcpConfigChanges(state, configPath, cwd, contributions) {
22454
+ const previous = state.config.mcpServers;
22455
+ const next = loadMcpConfig(configPath, cwd, contributions).mcpServers;
22456
+ const changed = [...new Set([...Object.keys(previous), ...Object.keys(next)])].filter((name) => JSON.stringify(previous[name]) !== JSON.stringify(next[name]));
22457
+ state.config = { ...state.config, mcpServers: next };
22458
+ state.contributedSources = getContributedServerSources(configPath, cwd, contributions);
22459
+ if (changed.length === 0)
22460
+ return changed;
22461
+ const cache = loadMetadataCache();
22462
+ const settings = state.config.settings;
22463
+ const prefix = settings?.toolPrefix ?? "server";
22464
+ state.lifecycle.setGlobalIdleTimeout(typeof settings?.idleTimeout === "number" ? settings.idleTimeout : 10);
22465
+ for (const name of changed) {
22466
+ await state.manager.close(name);
22467
+ state.lifecycle.unregisterServer(name);
22468
+ state.toolMetadata.delete(name);
22469
+ state.failureTracker.delete(name);
22470
+ const definition = next[name];
22471
+ if (definition)
22472
+ registerServer(state, name, definition, cache, prefix);
22473
+ }
22474
+ startLifecycle(state);
22475
+ updateStatusBar(state);
22476
+ return changed;
22477
+ }
22388
22478
  function updateServerMetadata(state, serverName) {
22389
22479
  const connection = state.manager.getConnection(serverName);
22390
22480
  if (!connection || connection.status !== "connected")
@@ -23148,6 +23238,7 @@ function buildServerStates(config, cache, provenance, callbacks, authOnly, prefi
23148
23238
  expanded: false,
23149
23239
  source: prov?.kind ?? "user",
23150
23240
  importKind: prov?.importKind,
23241
+ contributedBy: prov?.source,
23151
23242
  excludeTools: definition.excludeTools,
23152
23243
  exposeResources: definition.exposeResources !== false,
23153
23244
  connectionStatus: callbacks.getConnectionStatus(serverName),
@@ -23323,7 +23414,8 @@ function renderServerRow(context, server, isCursor) {
23323
23414
  const expandIcon = server.expanded ? "▾" : "▸";
23324
23415
  const prefix = isCursor ? fg2(t.selected, expandIcon) : fg2(t.border, server.expanded ? expandIcon : "·");
23325
23416
  const nameStr = isCursor ? bold(fg2(t.selected, server.name)) : server.name;
23326
- const importLabel = server.source === "import" ? fg2(t.description, ` (${server.importKind ?? "import"})`) : "";
23417
+ const originLabel = server.source === "import" ? ` (${server.importKind ?? "import"})` : server.source === "contributed" ? ` (${server.contributedBy ?? "contributed"})` : "";
23418
+ const importLabel = originLabel ? fg2(t.description, originLabel) : "";
23327
23419
  const statusLabel = renderConnectionStatus(context, server);
23328
23420
  if (!server.hasCachedData && !context.authOnly) {
23329
23421
  return `${prefix} ${nameStr}${importLabel} ${fg2(t.description, "(not cached)")}${statusLabel}`;
@@ -23692,9 +23784,8 @@ class McpPanel {
23692
23784
  if (item.toolIndex !== undefined) {
23693
23785
  const tool = server.tools[item.toolIndex];
23694
23786
  tool.isDirect = !tool.isDirect;
23695
- if (tool.isDirect && server.source === "import") {
23696
- this.importNotice = `Imported from ${server.importKind ?? "external"} — will copy to user config on save`;
23697
- }
23787
+ if (tool.isDirect)
23788
+ this.noteCopyToUserConfig(server);
23698
23789
  this.updateDirty();
23699
23790
  }
23700
23791
  }
@@ -23748,23 +23839,28 @@ class McpPanel {
23748
23839
  this.tui.requestRender();
23749
23840
  });
23750
23841
  }
23842
+ noteCopyToUserConfig(server) {
23843
+ if (server.source === "import") {
23844
+ this.importNotice = `Imported from ${server.importKind ?? "external"} — will copy to user config on save`;
23845
+ } else if (server.source === "contributed") {
23846
+ this.importNotice = `Contributed by ${server.contributedBy ?? "a package"} — will copy to user config on save`;
23847
+ }
23848
+ }
23751
23849
  toggleItem(item) {
23752
23850
  if (this.authOnly)
23753
23851
  return;
23754
23852
  const server = this.servers[item.serverIndex];
23755
23853
  if (item.type === "server") {
23756
23854
  const newState = !server.tools.every((tool) => tool.isDirect);
23757
- if (server.source === "import" && newState) {
23758
- this.importNotice = `Imported from ${server.importKind ?? "external"} — will copy to user config on save`;
23759
- }
23855
+ if (newState)
23856
+ this.noteCopyToUserConfig(server);
23760
23857
  for (const tool of server.tools)
23761
23858
  tool.isDirect = newState;
23762
23859
  } else if (item.toolIndex !== undefined) {
23763
23860
  const tool = server.tools[item.toolIndex];
23764
23861
  tool.isDirect = !tool.isDirect;
23765
- if (tool.isDirect && server.source === "import") {
23766
- this.importNotice = `Imported from ${server.importKind ?? "external"} — will copy to user config on save`;
23767
- }
23862
+ if (tool.isDirect)
23863
+ this.noteCopyToUserConfig(server);
23768
23864
  }
23769
23865
  this.updateDirty();
23770
23866
  }
@@ -23865,7 +23961,7 @@ async function showStatus(state, ctx) {
23865
23961
  status = "cached";
23866
23962
  }
23867
23963
  const toolSuffix = failed ? "" : ` (${toolCount} tools${status === "cached" ? ", cached" : ""})`;
23868
- lines.push(`${statusIcon} ${name}: ${status}${toolSuffix}`);
23964
+ lines.push(`${statusIcon} ${formatMcpServerName(state, name)}: ${status}${toolSuffix}`);
23869
23965
  }
23870
23966
  if (Object.keys(state.config.mcpServers).length === 0) {
23871
23967
  lines.push("No MCP servers configured");
@@ -23925,7 +24021,7 @@ async function reconnectServers(state, ctx, targetServer) {
23925
24021
  const message = error instanceof Error ? error.message : String(error);
23926
24022
  state.failureTracker.set(name, Date.now());
23927
24023
  if (ctx.hasUI) {
23928
- ctx.ui.notify(`MCP: Failed to reconnect to ${name}: ${message}`, "error");
24024
+ ctx.ui.notify(`MCP: Failed to reconnect to ${formatMcpServerName(state, name)}: ${message}`, "error");
23929
24025
  }
23930
24026
  }
23931
24027
  }
@@ -24091,7 +24187,7 @@ async function openMcpPanel(state, pi, ctx, configOverridePath) {
24091
24187
  const config = state.config;
24092
24188
  const cache = loadMetadataCache();
24093
24189
  const configPath = pi.getFlag("mcp-config") ?? configOverridePath;
24094
- const provenanceMap = getServerProvenance(configPath, ctx.cwd);
24190
+ const provenanceMap = getServerProvenance(configPath, ctx.cwd, pi.getMcpServerContributions?.() ?? []);
24095
24191
  const { lines: noticeLines, fingerprint } = buildSharedConfigNoticeLines(configPath, ctx.cwd);
24096
24192
  const callbacks = buildMcpPanelCallbacks(state, config, ctx);
24097
24193
  await Promise.resolve().then(() => init_mcp_panel());
@@ -24125,7 +24221,7 @@ async function openMcpAuthPanel(state, pi, ctx, configOverridePath) {
24125
24221
  }
24126
24222
  const cache = loadMetadataCache();
24127
24223
  const configPath = pi.getFlag("mcp-config") ?? configOverridePath;
24128
- const provenanceMap = getServerProvenance(configPath, ctx.cwd);
24224
+ const provenanceMap = getServerProvenance(configPath, ctx.cwd, pi.getMcpServerContributions?.() ?? []);
24129
24225
  const callbacks = buildMcpPanelCallbacks(state, config, ctx);
24130
24226
  await Promise.resolve().then(() => init_mcp_panel());
24131
24227
  await new Promise((resolve) => {
@@ -27076,13 +27172,29 @@ function mcpAdapter(pi) {
27076
27172
  let renderConfig;
27077
27173
  let initPromise = null;
27078
27174
  let lifecycleGeneration = 0;
27079
- let registeredDirectToolNames = new Set;
27175
+ let registeredDirectTools = new Map;
27176
+ const retiredDirectToolNames = new Set;
27080
27177
  let registeredProxyTool = false;
27081
27178
  let startupWarmupCancel = null;
27082
27179
  let activeSession = null;
27083
27180
  let stateOwner = null;
27084
27181
  const cleanupBarrier = new McpSessionCleanupBarrier;
27085
27182
  const unpublishedCleanupFailures = [];
27183
+ let contributionRevision = 0;
27184
+ let contributionRefresh = Promise.resolve();
27185
+ const getContributions = () => pi.getMcpServerContributions?.() ?? [];
27186
+ const getConfigPath = () => pi.getFlag("mcp-config") ?? earlyConfigPath;
27187
+ function forgetDirectToolRegistrations() {
27188
+ registeredDirectTools = new Map([...registeredDirectTools.keys()].map((name) => [name, ""]));
27189
+ }
27190
+ function startsWithSession(server) {
27191
+ return server?.lifecycle === "eager" || server?.lifecycle === "keep-alive";
27192
+ }
27193
+ function registerProxyToolIfNeeded(config, directToolState) {
27194
+ if (config.settings?.disableProxyTool !== true || directToolState.directToolCount === 0 || directToolState.missingConfiguredDirectToolServers.length > 0) {
27195
+ registerProxyTool();
27196
+ }
27197
+ }
27086
27198
  async function registerDirectToolsFromConfig(config, cache, subagentPolicy) {
27087
27199
  const [{ resolveDirectTools, createDirectToolExecutor, getMissingConfiguredDirectToolServers }, { truncateAtWord }] = await Promise.all([
27088
27200
  Promise.resolve().then(() => (init_direct_tools(), exports_direct_tools)),
@@ -27091,10 +27203,22 @@ function mcpAdapter(pi) {
27091
27203
  const prefix = config.settings?.toolPrefix ?? "server";
27092
27204
  const directTools = subagentPolicy?.mcpDirectTools;
27093
27205
  const directSpecs = resolveDirectTools(config, cache, prefix, directTools === undefined ? undefined : [...directTools]);
27206
+ const resolvedNames = new Set(directSpecs.map((spec) => spec.prefixedName));
27207
+ const obsoleteNames = [...registeredDirectTools.keys()].filter((name) => !resolvedNames.has(name));
27208
+ const revivedNames = directSpecs.map((spec) => spec.prefixedName).filter((name) => retiredDirectToolNames.has(name));
27209
+ const activeNames = new Set(obsoleteNames.length > 0 ? pi.getActiveTools() : []);
27210
+ for (const name of obsoleteNames) {
27211
+ registeredDirectTools.delete(name);
27212
+ if (activeNames.has(name))
27213
+ retiredDirectToolNames.add(name);
27214
+ }
27215
+ for (const name of revivedNames)
27216
+ retiredDirectToolNames.delete(name);
27094
27217
  for (const spec of directSpecs) {
27095
- if (registeredDirectToolNames.has(spec.prefixedName))
27218
+ const signature = JSON.stringify([spec.serverName, spec.originalName, spec.description, spec.inputSchema]);
27219
+ if (registeredDirectTools.get(spec.prefixedName) === signature)
27096
27220
  continue;
27097
- registeredDirectToolNames.add(spec.prefixedName);
27221
+ registeredDirectTools.set(spec.prefixedName, signature);
27098
27222
  pi.registerTool({
27099
27223
  name: spec.prefixedName,
27100
27224
  label: `MCP: ${spec.originalName}`,
@@ -27108,6 +27232,10 @@ function mcpAdapter(pi) {
27108
27232
  }
27109
27233
  const refreshTools = pi.refreshTools;
27110
27234
  refreshTools?.();
27235
+ if (obsoleteNames.length > 0 || revivedNames.length > 0) {
27236
+ const obsolete = new Set(obsoleteNames);
27237
+ pi.setActiveTools([...pi.getActiveTools().filter((name) => !obsolete.has(name)), ...revivedNames]);
27238
+ }
27111
27239
  return {
27112
27240
  directToolCount: directSpecs.length,
27113
27241
  missingConfiguredDirectToolServers: getMissingConfiguredDirectToolServers(config, cache, directTools)
@@ -27188,6 +27316,7 @@ function mcpAdapter(pi) {
27188
27316
  throw new Error(`${STALE_INITIALIZATION_PREFIX} before startup`);
27189
27317
  }
27190
27318
  let candidate = null;
27319
+ const revision = contributionRevision;
27191
27320
  try {
27192
27321
  candidate = await initializeMcp(pi, session.ctx);
27193
27322
  const initializedState = candidate;
@@ -27198,9 +27327,7 @@ function mcpAdapter(pi) {
27198
27327
  if (!isCurrentSession(session) || initPromise !== expectedPromise.current) {
27199
27328
  throw new Error(`${STALE_INITIALIZATION_PREFIX} after tool registration`);
27200
27329
  }
27201
- if (initializedState.config.settings?.disableProxyTool !== true || directToolState.directToolCount === 0 || directToolState.missingConfiguredDirectToolServers.length > 0) {
27202
- registerProxyTool();
27203
- }
27330
+ registerProxyToolIfNeeded(initializedState.config, directToolState);
27204
27331
  updateStatusBar(initializedState);
27205
27332
  let cancelWarmup = null;
27206
27333
  const warmup = scheduleMcpStartupWarmup(initializedState, {
@@ -27222,6 +27349,8 @@ function mcpAdapter(pi) {
27222
27349
  startupWarmupCancel = cancelWarmup;
27223
27350
  stateOwner = session;
27224
27351
  state = initializedState;
27352
+ if (revision !== contributionRevision)
27353
+ scheduleContributionRefresh(session);
27225
27354
  return initializedState;
27226
27355
  } catch (error) {
27227
27356
  if (candidate && state !== candidate) {
@@ -27268,6 +27397,56 @@ function mcpAdapter(pi) {
27268
27397
  });
27269
27398
  return attempt;
27270
27399
  }
27400
+ function scheduleContributionRefresh(session) {
27401
+ contributionRefresh = contributionRefresh.then(() => refreshContributedServers(session)).catch((error) => {
27402
+ if (!isCurrentSession(session) || isStaleExtensionContextError(error))
27403
+ return;
27404
+ if (!reportOwnedMcpLog("error"))
27405
+ console.error("MCP: failed to apply contributed MCP server changes", error);
27406
+ });
27407
+ }
27408
+ async function refreshContributedServers(session) {
27409
+ if (!isCurrentSession(session))
27410
+ return;
27411
+ const configPath = getConfigPath();
27412
+ const contributions = getContributions();
27413
+ const current = state;
27414
+ if (current && isOwnedState(current, session)) {
27415
+ await Promise.resolve().then(() => init_init());
27416
+ const changed = await applyMcpConfigChanges(current, configPath, session.ctx.cwd, contributions);
27417
+ renderConfig = current.config;
27418
+ if (!isOwnedState(current, session))
27419
+ return;
27420
+ registerProxyToolIfNeeded(current.config, await registerDirectTools(current, session.ctx.subagentPolicy));
27421
+ for (const name of changed) {
27422
+ if (!startsWithSession(current.config.mcpServers[name]))
27423
+ continue;
27424
+ lazyConnect(current, name).then(async (connected) => {
27425
+ if (!isOwnedState(current, session)) {
27426
+ await current.manager.close(name);
27427
+ } else if (connected) {
27428
+ registerProxyToolIfNeeded(current.config, await registerDirectTools(current, session.ctx.subagentPolicy));
27429
+ } else if (current.failureTracker.has(name)) {
27430
+ current.ui?.notify(`MCP: Failed to connect to ${formatMcpServerName(current, name)}`, "error");
27431
+ }
27432
+ }).catch(() => {
27433
+ return;
27434
+ });
27435
+ }
27436
+ return;
27437
+ }
27438
+ const config = loadMcpConfig(configPath, session.ctx.cwd, contributions);
27439
+ renderConfig = config;
27440
+ await Promise.resolve().then(() => init_metadata_cache());
27441
+ if (!isCurrentSession(session))
27442
+ return;
27443
+ registerProxyToolIfNeeded(config, await registerDirectToolsFromConfig(config, loadMetadataCache()));
27444
+ if (isCurrentSession(session) && !state && !initPromise && Object.values(config.mcpServers).some(startsWithSession)) {
27445
+ ensureMcpInitialized().catch(() => {
27446
+ return;
27447
+ });
27448
+ }
27449
+ }
27271
27450
  pi.on("session_start", async (_event, ctx) => {
27272
27451
  const generation = ++lifecycleGeneration;
27273
27452
  const previousState = state;
@@ -27276,7 +27455,7 @@ function mcpAdapter(pi) {
27276
27455
  renderConfig = undefined;
27277
27456
  stateOwner = null;
27278
27457
  initPromise = null;
27279
- registeredDirectToolNames = new Set;
27458
+ forgetDirectToolRegistrations();
27280
27459
  cancelStartupWarmup();
27281
27460
  const previousStateCleanup = cleanupSessionResources(previousState, "session_restart", "MCP: failed to shut down previous session state");
27282
27461
  const cleanup = cleanupBarrier.retain([retiredInitialization, previousStateCleanup]);
@@ -27284,8 +27463,9 @@ function mcpAdapter(pi) {
27284
27463
  await cleanup;
27285
27464
  if (!isStartCurrent())
27286
27465
  return;
27466
+ const revision = contributionRevision;
27287
27467
  try {
27288
- const config = loadMcpConfig(earlyConfigPath, ctx.cwd);
27468
+ const config = loadMcpConfig(earlyConfigPath, ctx.cwd, getContributions());
27289
27469
  await Promise.resolve().then(() => init_metadata_cache());
27290
27470
  if (!isStartCurrent())
27291
27471
  return;
@@ -27293,9 +27473,7 @@ function mcpAdapter(pi) {
27293
27473
  const directToolState = await registerDirectToolsFromConfig(config, loadMetadataCache());
27294
27474
  if (!isStartCurrent())
27295
27475
  return;
27296
- if (config.settings?.disableProxyTool !== true || directToolState.directToolCount === 0 || directToolState.missingConfiguredDirectToolServers.length > 0) {
27297
- registerProxyTool();
27298
- }
27476
+ registerProxyToolIfNeeded(config, directToolState);
27299
27477
  } catch (error) {
27300
27478
  if (!isStartCurrent() || isStaleExtensionContextError(error))
27301
27479
  return;
@@ -27305,12 +27483,20 @@ function mcpAdapter(pi) {
27305
27483
  }
27306
27484
  if (!isStartCurrent())
27307
27485
  return;
27308
- activeSession = { generation, ctx, cleanup };
27309
- if (ctx.hasUI || Object.values(renderConfig?.mcpServers ?? {}).some((server) => server.lifecycle === "eager" || server.lifecycle === "keep-alive")) {
27486
+ const session = { generation, ctx, cleanup };
27487
+ activeSession = session;
27488
+ if (ctx.hasUI || Object.values(renderConfig?.mcpServers ?? {}).some(startsWithSession)) {
27310
27489
  ensureMcpInitialized().catch(() => {
27311
27490
  return;
27312
27491
  });
27313
27492
  }
27493
+ if (revision !== contributionRevision)
27494
+ scheduleContributionRefresh(session);
27495
+ });
27496
+ pi.onMcpServerContributionsChanged?.(() => {
27497
+ contributionRevision++;
27498
+ if (activeSession)
27499
+ scheduleContributionRefresh(activeSession);
27314
27500
  });
27315
27501
  pi.on("session_shutdown", async () => {
27316
27502
  ++lifecycleGeneration;
@@ -27321,7 +27507,7 @@ function mcpAdapter(pi) {
27321
27507
  renderConfig = undefined;
27322
27508
  stateOwner = null;
27323
27509
  initPromise = null;
27324
- registeredDirectToolNames = new Set;
27510
+ forgetDirectToolRegistrations();
27325
27511
  cancelStartupWarmup();
27326
27512
  const stateCleanup = cleanupSessionResources(currentState, "session_shutdown", "MCP: session shutdown cleanup failed");
27327
27513
  const failures = [];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/mcp",
3
- "version": "0.9.23",
3
+ "version": "0.9.24",
4
4
  "private": true,
5
5
  "description": "Atomic extension that adapts MCP (Model Context Protocol) servers into the coding agent. Fork of: https://github.com/nicobailon/pi-mcp-adapter",
6
6
  "contributors": [