@humain/terminal 0.0.14 → 0.0.15

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 (41) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/bundle/chunks/{chunk-6TW5AYGH.js → chunk-QMTWJZBT.js} +251 -39
  3. package/dist/bundle/cli.js +1 -1
  4. package/dist/bundle/index.js +1 -1
  5. package/dist/bundle/rpc-entry.js +1 -1
  6. package/dist/core/tools/subagent.d.ts.map +1 -1
  7. package/dist/core/tools/subagent.js +30 -9
  8. package/dist/core/tools/subagent.js.map +1 -1
  9. package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +27 -0
  10. package/dist/humain/mcp-adapter/vendor/README.md +13 -13
  11. package/dist/humain/mcp-adapter/vendor/commands.ts +58 -21
  12. package/dist/humain/mcp-adapter/vendor/config.ts +18 -4
  13. package/dist/humain/mcp-adapter/vendor/direct-tools.ts +26 -6
  14. package/dist/humain/mcp-adapter/vendor/dist/config.d.ts +4 -0
  15. package/dist/humain/mcp-adapter/vendor/dist/config.js +13 -4
  16. package/dist/humain/mcp-adapter/vendor/dist/config.js.map +1 -1
  17. package/dist/humain/mcp-adapter/vendor/dist/types.d.ts +11 -3
  18. package/dist/humain/mcp-adapter/vendor/dist/types.js.map +1 -1
  19. package/dist/humain/mcp-adapter/vendor/error-signal.ts +15 -4
  20. package/dist/humain/mcp-adapter/vendor/errors.ts +141 -0
  21. package/dist/humain/mcp-adapter/vendor/host-html-template.ts +58 -5
  22. package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +1017 -160
  23. package/dist/humain/mcp-adapter/vendor/index.ts +2 -3
  24. package/dist/humain/mcp-adapter/vendor/init.ts +5 -0
  25. package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +30 -9
  26. package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +66 -28
  27. package/dist/humain/mcp-adapter/vendor/mcp-status.ts +2 -0
  28. package/dist/humain/mcp-adapter/vendor/package.json +3 -2
  29. package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +66 -13
  30. package/dist/humain/mcp-adapter/vendor/sandbox-proxy-template.ts +217 -0
  31. package/dist/humain/mcp-adapter/vendor/server-manager.ts +337 -6
  32. package/dist/humain/mcp-adapter/vendor/skills/mcp-scripting/SKILL.md +1 -0
  33. package/dist/humain/mcp-adapter/vendor/types.ts +18 -3
  34. package/dist/humain/mcp-adapter/vendor/ui-resource-handler.ts +18 -2
  35. package/dist/humain/mcp-adapter/vendor/ui-server.ts +179 -54
  36. package/dist/humain/mcp-adapter/vendor/ui-session.ts +28 -8
  37. package/npm-shrinkwrap.json +2 -2
  38. package/package.json +1 -1
  39. package/src/humain/mcp-adapter/UPSTREAM.md +1 -1
  40. package/src/humain/mcp-adapter/upstream.json +4 -4
  41. package/src/humain/mcp-adapter/vendor/CHANGELOG.md +27 -0
@@ -32,9 +32,9 @@ The adapter reads standard MCP files automatically. No extra setup needed if you
32
32
 
33
33
  | You already have... | What happens |
34
34
  |---------------------|--------------|
35
- | `.mcp.json` or `~/.config/mcp/mcp.json` | Pi uses it immediately. The first time you open `/mcp`, you'll see a short heads-up explaining which file Pi detected and that Pi only writes adapter-specific overrides to its own files. |
35
+ | `.mcp.json` or `~/.config/mcp/mcp.json` | Pi uses it immediately. Use `.mcp.json` for project/team sharing and `~/.config/mcp/mcp.json` for all projects. The first time you open `/mcp`, you'll see a short heads-up explaining which file Pi detected and that Pi only writes adapter-specific overrides to its own files. |
36
36
  | Host-specific configs (Cursor, Claude Code, Codex, etc.) but no standard MCP files | Run `/mcp setup` to adopt those host configs into Pi. The setup flow shows exactly what it found, lets you pick which ones to import, and previews the exact file changes before writing. |
37
- | Nothing configured yet | Run `/mcp setup` to scaffold a minimal `.mcp.json`, add a curated known server, quick-add RepoPrompt, or inspect what the adapter discovered on your machine. |
37
+ | Nothing configured yet | Run `/mcp setup`, choose project `.mcp.json` or global `~/.config/mcp/mcp.json`, then scaffold a minimal config, add a curated known server, quick-add RepoPrompt, or inspect what the adapter discovered on your machine. |
38
38
 
39
39
  If you prefer the terminal, you can also run `pi-mcp-adapter init` after install to scan for host-specific configs and add missing compatibility imports to the Pi agent dir (`~/.pi/agent/mcp.json` by default, or `$PI_CODING_AGENT_DIR/mcp.json` when set).
40
40
 
@@ -53,16 +53,16 @@ Preferred project config: `.mcp.json`
53
53
  }
54
54
  ```
55
55
 
56
- Preferred user-global shared config: `~/.config/mcp/mcp.json`. Pi also reads the tool-agnostic global paths `~/.agents/mcp.json` and `~/.agents/mcp/mcp.json`.
56
+ Preferred user-global shared config: `~/.config/mcp/mcp.json` (for all projects). Pi also reads the tool-agnostic global paths `~/.agents/mcp.json` and `~/.agents/mcp/mcp.json` as compatibility inputs.
57
57
 
58
- Pi also reads Pi-owned override files for settings and host-specific compatibility:
58
+ Pi-owned files are not additional normal setup choices. They hold Pi-specific settings, compatibility imports, and adapter-only overrides:
59
59
 
60
60
  - `<Pi agent dir>/mcp.json` — Pi global override (`~/.pi/agent/mcp.json` by default)
61
61
  - `.pi/mcp.json` — Pi project override
62
62
 
63
- Host-specific configs are detected and shown by `/mcp setup` and `pi-mcp-adapter init`, but they are not loaded automatically. The normal `/mcp` panel does not scan host-specific files when `settings.hostConfigDiscovery` is `"off"`. To explicitly opt in to host-config fallback discovery, set `settings.hostConfigDiscovery` to `"on"` or run `pi-mcp-adapter init --discover-host-configs`. The default is `"off"`; `"prompt"` is available for integrations that want detection without activation. Host configs are lower precedence than every shared and Pi-owned source, and `/mcp setup` continues to offer explicit import adoption. Discovery reports source paths, provenance, and same-name conflicts; it never writes to external host files or silently launches commands from them.
63
+ Host-specific configs are detected and shown by `/mcp setup` and `pi-mcp-adapter init`, but they are compatibility inputs rather than normal setup paths and are not loaded automatically. The normal `/mcp` panel does not scan host-specific files when `settings.hostConfigDiscovery` is `"off"`. To explicitly opt in to host-config fallback discovery, set `settings.hostConfigDiscovery` to `"on"` or run `pi-mcp-adapter init --discover-host-configs`. The default is `"off"`; `"prompt"` is available for integrations that want detection without activation. Host configs are lower precedence than every shared and Pi-owned source, and `/mcp setup` continues to offer explicit import adoption. Discovery reports source paths, provenance, and same-name conflicts; it never writes to external host files or silently launches commands from them.
64
64
 
65
- Precedence is:
65
+ Precedence is (later entries win):
66
66
 
67
67
  1. `~/.config/mcp/mcp.json`
68
68
  2. `~/.agents/mcp.json`
@@ -302,7 +302,7 @@ Use `"auto"` to probe for MCP 2026-07-28 and conservatively fall back to the cla
302
302
 
303
303
  Use `"2026-07-28"` to pin that revision. Pinning has no legacy or SSE fallback and fails if the server does not offer the requested version.
304
304
 
305
- The stable SDK handles era-specific request envelopes, result decoding, list-changed subscriptions, cancellation, and multi-round-trip sampling/elicitation. The adapter keeps strict OAuth issuer validation in every mode. Adapter-level roots support, standard MCP logging presentation, and configuration/UI for protocol cache hints are not yet implemented.
305
+ The stable SDK handles era-specific request envelopes, result decoding, list-changed subscriptions, cancellation, and multi-round-trip sampling/elicitation. The SDK's embedded-input progress callback does not expose the originating tool or resource identity, so the adapter cannot maintain a durable per-tool waiting status row; interactive sessions keep the existing input dialog visible, and proxy calls show request progress when UI is available. The adapter keeps strict OAuth issuer validation in every mode. Adapter-level roots support, standard MCP logging presentation, and configuration/UI for protocol cache hints are not yet implemented.
306
306
 
307
307
  For pre-registered browser OAuth clients, set `oauth.redirectUri` to the exact callback registered with the provider, for example `"http://localhost:3118/callback"`. Dynamic clients normally omit it and use a lazy OS-assigned localhost callback port. A configured `https://` callback runs in manual mode because the adapter cannot receive a callback on another host. After authorization, copy the full callback URL from the browser address bar and paste it into `/mcp-auth` or `mcp({ action: "auth-complete", ... })`.
308
308
 
@@ -412,7 +412,7 @@ When any enabled server uses `eager` or `keep-alive`, initialization also starts
412
412
  | `strictDirectToolArguments` | Validate direct-tool inputs against their advertised schemas and recover one JSON string layer for object and array properties (default: false). |
413
413
  | `directToolResultDetails` | Direct-tool result details: `"lean"` (default) or `"bounded"` to retain the guarded raw MCP result. |
414
414
  | `warnOnLargeDirectTools` | Show the advisory when 75 or more direct tools resolve (default: `true`). Set to `false` to suppress only this advisory. |
415
- | `freezeDirectTools` | Keep direct-tool registration stable after the initial sync so automatic reconnects and list-change notifications do not rebuild the system prompt. Use `mcp({ connect: "server" })` or `/mcp reconnect <server>` to refresh deliberately. Default: false. |
415
+ | `freezeDirectTools` | Keep direct-tool registration stable after the initial sync so metadata updates and explicit reconnects do not rebuild the system prompt. Proxy/search/cache metadata still refreshes. Default: false. |
416
416
  | `scriptMode` | Register the MCP-only `mcpScript` plain-JavaScript tool (default: true). Set to `false` to hide it. |
417
417
  | `disableProxyTool` | Hide the `mcp` proxy tool once configured direct tools are fully available from cache. |
418
418
  | `autoAuth` | Auto-run OAuth on `connect`/tool calls when a server needs auth, then retry once (default: false). |
@@ -484,7 +484,7 @@ Set `"outputGuard": false` — or the env kill switch `MCP_OUTPUT_GUARD=0` — t
484
484
 
485
485
  For multi-call MCP work, write ordinary JavaScript: discover, inspect, call, loop, filter, chain, or fan out, then return one result. Run that code with the default-on `mcpScript` tool. For a single MCP call, search, describe, status check, or auth action, use `mcp` instead. Set `settings.scriptMode` to `false` to hide the scripting tool.
486
486
 
487
- The bundled `mcp-scripting` skill is a separate Pi package resource. To hide that skill while keeping the adapter extension installed, replace the package entry in Pi settings with the object form and disable package skills:
487
+ The bundled `mcp-scripting` skill is a separate Pi package resource and is manual-only by default, so its description is not added to the model's automatic skill context. Use `/skill:mcp-scripting` when you want its detailed workflow. To remove the manual skill command as well while keeping the adapter extension and `mcpScript` tool installed, replace the package entry in Pi settings with the object form and disable package skills:
488
488
 
489
489
  ```json
490
490
  {
@@ -624,13 +624,13 @@ Models sometimes encode an object or array argument as a JSON string. Set `setti
624
624
 
625
625
  Set `settings.directToolResultDetails` to `"bounded"` when an extension needs structured MCP result fields in Pi's direct-tool result details. The same output guard limits apply. Small leading structured fields stay available, while large fields receive bounded summaries and the complete guarded result follows the output guard's spill-file policy. The default `"lean"` mode keeps the existing server and tool metadata only.
626
626
 
627
- If prompt-cache stability matters more than automatic direct-tool hot-loading, set `settings.freezeDirectTools` to `true`. The initial direct-tool sync still runs, but later automatic reconnects, lazy-connects, and list-change notifications keep the registered tool surface unchanged. Deliberate refreshes through `mcp({ connect: "server" })` or `/mcp reconnect <server>` still update direct tools.
627
+ If prompt-cache stability matters more than direct-tool hot-loading, set `settings.freezeDirectTools` to `true`. The initial direct-tool sync still runs, but later metadata updates and explicit reconnects keep the registered tool surface unchanged while proxy/search/cache metadata refreshes normally.
628
628
 
629
629
  When you change direct-tool toggles in `/mcp`, the extension updates direct tool registration in the current session. Broader setup writes from `/mcp setup` still use Pi's normal reload flow because they can add or restructure MCP config files.
630
630
 
631
- **Interactive configuration:** Run `/mcp` to open an interactive panel showing all servers with connection status, tools, and direct/proxy toggles. You can reconnect servers and toggle tools between direct and proxy from the same overlay. For OAuth, press Enter on a server that needs auth or `ctrl+a` on any OAuth server. The Save action defaults to `ctrl+s` and can be remapped with the `mcp.panel.save` keybinding.
631
+ **Interactive configuration:** Run `/mcp` to open an interactive panel showing all servers with connection status, tools, and direct/proxy toggles. You can reconnect servers, toggle tools between direct and proxy, and enable or disable servers (`ctrl+d`) from the same overlay. For OAuth, press Enter on a server that needs auth or `ctrl+a` on any OAuth server. The Save action defaults to `ctrl+s` and can be remapped with the `mcp.panel.save` keybinding.
632
632
 
633
- **Guided first-run setup:** Run `/mcp setup` to inspect detected shared MCP files, adopt compatibility imports from other hosts, open discovered config paths, preview exact before/after file diffs for writes, scaffold a minimal project `.mcp.json`, add a curated known server (DeepWiki, Context7, Notion, GitHub, or Chrome DevTools), or quick-add RepoPrompt into a standard/shared MCP file.
633
+ **Guided first-run setup:** Run `/mcp setup` to choose the normal write target for new shared servers — project `.mcp.json` or global `~/.config/mcp/mcp.json` — inspect detected shared MCP files, adopt compatibility imports from other hosts, open discovered config paths, preview exact before/after file diffs for writes, scaffold a minimal selected config, add a curated known server (DeepWiki, Context7, Notion, GitHub, or Chrome DevTools), or quick-add RepoPrompt into a standard/shared MCP file.
634
634
 
635
635
  **Subagent integration:** If you use the subagent extension, agents can request direct MCP tools in their frontmatter with `mcp:server-name` syntax. See the subagent README for details.
636
636
 
@@ -712,7 +712,7 @@ Supported compatibility imports: `cursor`, `claude-code`, `claude-desktop`, `ope
712
712
 
713
713
  ### Project Config
714
714
 
715
- Prefer `.mcp.json` for project-local shared MCP config. Use `.pi/mcp.json` only when you need a Pi-specific project override. Project files override both user-global shared MCP config and Pi global overrides.
715
+ Prefer `.mcp.json` for project-local shared MCP config and `~/.config/mcp/mcp.json` for user-global shared MCP config. Use `.pi/mcp.json` only when you need a Pi-specific project override. Project files override both user-global shared MCP config and Pi global overrides.
716
716
 
717
717
  ## Usage
718
718
 
@@ -4,17 +4,19 @@ import type { McpExtensionState } from "./state.ts";
4
4
  import { isServerDisabled, type McpAuthResult, type McpConfig, type McpPanelCallbacks, type McpPanelResult, type ImportKind } from "./types.ts";
5
5
  import {
6
6
  ensureCompatibilityImports,
7
+ getSharedConfigPath,
7
8
  getMcpDiscoverySummary,
8
9
  getMcpStandardConfigSummary,
9
- getProjectConfigPath,
10
10
  type KnownServerPreset,
11
+ type SharedConfigTarget,
11
12
  getServerProvenance,
12
13
  previewCompatibilityImports,
13
14
  previewSharedServerEntry,
14
- previewStarterProjectConfig,
15
+ previewStarterSharedConfig,
15
16
  writeDirectToolsConfig,
17
+ writeProjectServerDisabledOverride,
16
18
  writeSharedServerEntry,
17
- writeStarterProjectConfig,
19
+ writeStarterSharedConfig,
18
20
  } from "./config.ts";
19
21
  import { markKeepAliveAfterConnect, notifyToolMetadataUpdated, updateMetadataCache, updateStatusBar, getFailureAgeSeconds, getFailureMessage, clearFailure, recordFailure } from "./init.ts";
20
22
  import { isServerInActiveFailureBackoff } from "./failure-backoff.ts";
@@ -48,6 +50,13 @@ export async function showStatus(state: McpExtensionState, ctx: ExtensionContext
48
50
  if (!ctx.hasUI) return;
49
51
 
50
52
  const lines: string[] = ["MCP Server Status:", ""];
53
+ if (!state.programmaticConfig) {
54
+ lines.push(
55
+ "Shared MCP config: .mcp.json for this project/team or ~/.config/mcp/mcp.json for all projects.",
56
+ "Pi-owned files hold compatibility imports and adapter-specific overrides.",
57
+ "",
58
+ );
59
+ }
51
60
 
52
61
  for (const name of Object.keys(state.config.mcpServers)) {
53
62
  const definition = state.config.mcpServers[name];
@@ -59,12 +68,22 @@ export async function showStatus(state: McpExtensionState, ctx: ExtensionContext
59
68
  const metadata = state.toolMetadata.get(name);
60
69
  const toolCount = metadata?.length ?? 0;
61
70
  const failedAgo = getFailureAgeSeconds(state, name);
62
- let status = "not connected";
71
+ let status = "not listening (disconnected)";
63
72
  let statusIcon = "○";
64
73
  let failed = false;
65
74
 
66
75
  if (connection?.status === "connected") {
67
- status = "connected";
76
+ status = connection.listenState === "active"
77
+ ? connection.listenCatalogStale
78
+ ? "connected; listen active; catalog may be stale"
79
+ : "connected; listen active"
80
+ : connection.listenState === "dropped"
81
+ ? "connected; catalog may be stale — will reconcile on next keep-alive or tool use"
82
+ : connection.listenState === "re-establishing"
83
+ ? "connected; re-establishing listen"
84
+ : connection.listenState === "legacy"
85
+ ? "connected; legacy notification path"
86
+ : "connected; not listening for catalog updates";
68
87
  statusIcon = "✓";
69
88
  } else if (connection?.status === "needs-auth") {
70
89
  status = "needs auth";
@@ -75,16 +94,20 @@ export async function showStatus(state: McpExtensionState, ctx: ExtensionContext
75
94
  statusIcon = "✗";
76
95
  failed = true;
77
96
  } else if (metadata !== undefined) {
78
- status = "cached";
97
+ status = "cached; not listening (disconnected)";
79
98
  }
80
99
 
81
- const toolSuffix = failed ? "" : ` (${toolCount} tools${status === "cached" ? ", cached" : ""})`;
100
+ const toolSuffix = failed ? "" : ` (${toolCount} tools${status.startsWith("cached") ? ", cached" : ""})`;
82
101
  lines.push(`${statusIcon} ${name}: ${status}${toolSuffix}`);
83
102
  }
84
103
 
104
+ if (state.config.settings?.freezeDirectTools === true) {
105
+ lines.push("", "Direct tools frozen; active registrations may differ from current metadata.");
106
+ }
107
+
85
108
  if (Object.keys(state.config.mcpServers).length === 0) {
86
109
  lines.push("No MCP servers configured");
87
- lines.push("Run /mcp setup to adopt imports or scaffold a starter .mcp.json");
110
+ lines.push("Run /mcp setup to add a server to .mcp.json or ~/.config/mcp/mcp.json");
88
111
  }
89
112
 
90
113
  ctx.ui.notify(lines.join("\n"), "info");
@@ -450,16 +473,18 @@ export interface PanelFlowResult {
450
473
  function buildSharedConfigNoticeLines(configOverridePath: string | undefined, cwd: string): { lines: string[]; fingerprint: string | null } {
451
474
  const discovery = getMcpStandardConfigSummary(configOverridePath, cwd);
452
475
  const onboardingState = loadOnboardingState();
453
- if (!discovery.hasSharedServers || onboardingState.sharedConfigHintShown) {
476
+ const sharedSources = discovery.sources.filter((source) =>
477
+ (source.id === "shared-project" || source.id === "shared-global") && source.serverCount > 0,
478
+ );
479
+ if (sharedSources.length === 0 || onboardingState.sharedConfigHintShown) {
454
480
  return { lines: [], fingerprint: null };
455
481
  }
456
482
 
457
- const sharedSources = discovery.sources.filter((source) => source.kind === "shared" && source.serverCount > 0);
458
483
  const sourceList = sharedSources.map((source) => source.path).join(", ");
459
484
  return {
460
485
  lines: [
461
486
  `Using standard MCP config from ${sourceList}.`,
462
- "Pi only writes compatibility imports and adapter-specific overrides into Pi-owned files when needed.",
487
+ "Use .mcp.json for project/team config or ~/.config/mcp/mcp.json for all projects. Pi only writes compatibility imports and adapter-specific overrides into Pi-owned files when needed.",
463
488
  ],
464
489
  fingerprint: discovery.fingerprint,
465
490
  };
@@ -490,34 +515,34 @@ export async function openMcpSetup(
490
515
 
491
516
  const callbacks = {
492
517
  previewImports: (imports: ImportKind[]) => previewCompatibilityImports(imports, configOverridePath),
493
- previewStarterProject: () => previewStarterProjectConfig(ctx.cwd),
494
- previewRepoPrompt: () => {
518
+ previewStarterConfig: (target: SharedConfigTarget) => previewStarterSharedConfig(target, ctx.cwd),
519
+ previewRepoPrompt: (target: SharedConfigTarget) => {
495
520
  const repoPrompt = getMcpDiscoverySummary(configOverridePath, ctx.cwd, options).repoPrompt;
496
521
  if (!repoPrompt.entry || !repoPrompt.targetPath || !repoPrompt.serverName) return null;
497
- return previewSharedServerEntry(repoPrompt.targetPath, repoPrompt.serverName, repoPrompt.entry);
522
+ return previewSharedServerEntry(getSharedConfigPath(target, ctx.cwd), repoPrompt.serverName, repoPrompt.entry);
498
523
  },
499
- previewKnownServer: (preset: KnownServerPreset) => previewSharedServerEntry(getProjectConfigPath(ctx.cwd), preset.id, preset.entry),
524
+ previewKnownServer: (preset: KnownServerPreset, target: SharedConfigTarget) => previewSharedServerEntry(getSharedConfigPath(target, ctx.cwd), preset.id, preset.entry),
500
525
  adoptImports: async (imports: ImportKind[]) => {
501
526
  const result = ensureCompatibilityImports(imports, configOverridePath);
502
527
  if (result.added.length > 0) configChanged = true;
503
528
  return result;
504
529
  },
505
- scaffoldProjectConfig: async () => {
506
- const path = writeStarterProjectConfig(ctx.cwd);
530
+ scaffoldConfig: async (target: SharedConfigTarget) => {
531
+ const path = writeStarterSharedConfig(target, ctx.cwd);
507
532
  configChanged = true;
508
533
  return { path };
509
534
  },
510
- addRepoPrompt: async () => {
535
+ addRepoPrompt: async (target: SharedConfigTarget) => {
511
536
  const repoPrompt = getMcpDiscoverySummary(configOverridePath, ctx.cwd, options).repoPrompt;
512
537
  if (!repoPrompt.entry || !repoPrompt.targetPath || !repoPrompt.serverName) {
513
538
  throw new Error("RepoPrompt is not available to add from this setup screen.");
514
539
  }
515
- const path = writeSharedServerEntry(repoPrompt.targetPath, repoPrompt.serverName, repoPrompt.entry);
540
+ const path = writeSharedServerEntry(getSharedConfigPath(target, ctx.cwd), repoPrompt.serverName, repoPrompt.entry);
516
541
  configChanged = true;
517
542
  return { path, serverName: repoPrompt.serverName };
518
543
  },
519
- addKnownServer: async (preset: KnownServerPreset) => {
520
- const path = writeSharedServerEntry(getProjectConfigPath(ctx.cwd), preset.id, preset.entry);
544
+ addKnownServer: async (preset: KnownServerPreset, target: SharedConfigTarget) => {
545
+ const path = writeSharedServerEntry(getSharedConfigPath(target, ctx.cwd), preset.id, preset.entry);
521
546
  configChanged = true;
522
547
  return { path, serverName: preset.name };
523
548
  },
@@ -648,6 +673,18 @@ export async function openMcpPanel(
648
673
  (tui, _theme, keybindings, done) => {
649
674
  return createMcpPanel(config, cache, provenanceMap, callbacks, tui, (result: McpPanelResult) => {
650
675
  void (async () => {
676
+ if (!result.cancelled && result.disabledChanges.size > 0) {
677
+ for (const [serverName, disabled] of result.disabledChanges) {
678
+ try {
679
+ const override = writeProjectServerDisabledOverride(configPath, ctx.cwd, serverName, disabled);
680
+ if (override.changed) {
681
+ configChanged = true;
682
+ }
683
+ } catch (error) {
684
+ ctx.ui.notify(`Failed to ${disabled ? "disable" : "enable"} server "${serverName}": ${error instanceof Error ? error.message : String(error)}`, "error");
685
+ }
686
+ }
687
+ }
651
688
  if (!result.cancelled && result.changes.size > 0) {
652
689
  writeDirectToolsConfig(result.changes, provenanceMap, config);
653
690
  await onDirectToolsConfigChanged?.(result.changes);
@@ -175,6 +175,8 @@ export interface ConfigWritePreview {
175
175
  diffText: string;
176
176
  }
177
177
 
178
+ export type SharedConfigTarget = "project" | "global";
179
+
178
180
  export function getPiGlobalConfigPath(overridePath?: string): string {
179
181
  return overridePath ? resolve(overridePath) : getAgentPath("mcp.json");
180
182
  }
@@ -191,6 +193,10 @@ export function getProjectPiConfigPath(cwd = process.cwd()): string {
191
193
  return resolve(cwd, getConfigDirName(), PROJECT_PI_CONFIG_NAME);
192
194
  }
193
195
 
196
+ export function getSharedConfigPath(target: SharedConfigTarget, cwd = process.cwd()): string {
197
+ return target === "project" ? getProjectConfigPath(cwd) : getGenericGlobalConfigPath();
198
+ }
199
+
194
200
  export function getConfigDiscoveryPaths(overridePath?: string, cwd = process.cwd()): ConfigDiscoveryPath[] {
195
201
  return getConfigSources(overridePath, cwd).map((source) => ({
196
202
  label: source.label,
@@ -1159,19 +1165,27 @@ export function buildStarterProjectConfig(): McpConfig {
1159
1165
  };
1160
1166
  }
1161
1167
 
1162
- export function previewStarterProjectConfig(cwd = process.cwd()): ConfigWritePreview {
1163
- const targetPath = getProjectConfigPath(cwd);
1168
+ export function previewStarterSharedConfig(target: SharedConfigTarget, cwd = process.cwd()): ConfigWritePreview {
1169
+ const targetPath = getSharedConfigPath(target, cwd);
1164
1170
  const nextRaw = { mcpServers: buildStarterProjectConfig().mcpServers };
1165
1171
  return buildConfigWritePreview(targetPath, nextRaw);
1166
1172
  }
1167
1173
 
1168
- export function writeStarterProjectConfig(cwd = process.cwd()): string {
1169
- const targetPath = getProjectConfigPath(cwd);
1174
+ export function writeStarterSharedConfig(target: SharedConfigTarget, cwd = process.cwd()): string {
1175
+ const targetPath = getSharedConfigPath(target, cwd);
1170
1176
  const raw = { mcpServers: buildStarterProjectConfig().mcpServers };
1171
1177
  writeRawConfigObject(targetPath, raw);
1172
1178
  return targetPath;
1173
1179
  }
1174
1180
 
1181
+ export function previewStarterProjectConfig(cwd = process.cwd()): ConfigWritePreview {
1182
+ return previewStarterSharedConfig("project", cwd);
1183
+ }
1184
+
1185
+ export function writeStarterProjectConfig(cwd = process.cwd()): string {
1186
+ return writeStarterSharedConfig("project", cwd);
1187
+ }
1188
+
1175
1189
  export function previewSharedServerEntry(filePath: string, serverName: string, entry: ServerEntry): ConfigWritePreview {
1176
1190
  const raw = readRawConfigObject(filePath);
1177
1191
  const nextRaw = { ...raw };
@@ -20,6 +20,7 @@ import { SessionRecoveryAuthRequiredError, withSessionRecovery } from "./session
20
20
  import { combineAbortSignals, isAbortError } from "./runtime-owner.ts";
21
21
  import { ensureToolCallApproved } from "./tool-approval.ts";
22
22
  import { Check, Errors } from "typebox/value";
23
+ import { getInputRequiredNeedsUiDetails } from "./errors.ts";
23
24
 
24
25
  type ClientCallToolResult = Awaited<ReturnType<Client["callTool"]>>;
25
26
  type ClientReadResourceResult = Awaited<ReturnType<Client["readResource"]>>;
@@ -486,7 +487,13 @@ export function createDirectToolExecutor(
486
487
  onNeedsAuth: recoverAuthConnection,
487
488
  },
488
489
  spec.serverName,
489
- (conn) => conn.client.readResource({ uri: spec.resourceUri! }, requestOptions),
490
+ async (conn) => {
491
+ const refreshRead = await state.manager.prepareResourceUse?.(spec.serverName, spec.resourceUri!, conn);
492
+ return conn.client.readResource(
493
+ { uri: spec.resourceUri! },
494
+ refreshRead ? { ...requestOptions, cacheMode: "refresh" } : requestOptions,
495
+ );
496
+ },
490
497
  );
491
498
  const content = transformMcpResourceContents(result.contents ?? [], state.owner?.signal);
492
499
  const guarded = await guardMcpOutput(content.length > 0 ? content : [{ type: "text" as const, text: "(empty resource)" }], {
@@ -520,11 +527,14 @@ export function createDirectToolExecutor(
520
527
  onNeedsAuth: recoverAuthConnection,
521
528
  },
522
529
  spec.serverName,
523
- (conn) => abortable(conn.client.callTool({
524
- name: spec.originalName,
525
- arguments: normalizedParams,
526
- _meta: uiSession?.requestMeta,
527
- }, requestOptions), ownedSignal),
530
+ async (conn) => {
531
+ await state.manager.ensureListen?.(spec.serverName, conn);
532
+ return abortable(conn.client.callTool({
533
+ name: spec.originalName,
534
+ arguments: normalizedParams,
535
+ _meta: uiSession?.requestMeta,
536
+ }, requestOptions), ownedSignal);
537
+ },
528
538
  );
529
539
  uiSession?.sendToolResult(result as unknown as import("@modelcontextprotocol/client").CallToolResult);
530
540
 
@@ -596,6 +606,16 @@ export function createDirectToolExecutor(
596
606
  details: { error: "url_elicitation_required", server: spec.serverName, action },
597
607
  };
598
608
  }
609
+ const inputRequired = getInputRequiredNeedsUiDetails(error, spec.resourceUri
610
+ ? { server: spec.serverName, resourceUri: spec.resourceUri }
611
+ : { server: spec.serverName, tool: spec.originalName });
612
+ if (inputRequired) {
613
+ uiSession?.sendToolCancelled(inputRequired.message);
614
+ return {
615
+ content: [{ type: "text" as const, text: inputRequired.message }],
616
+ details: { ...inputRequired },
617
+ };
618
+ }
599
619
  const message = error instanceof Error ? error.message : String(error);
600
620
  const aborted = isAbortError(error, ownedSignal);
601
621
  if (!aborted) {
@@ -85,10 +85,12 @@ export interface ConfigWritePreview {
85
85
  afterText: string;
86
86
  diffText: string;
87
87
  }
88
+ export type SharedConfigTarget = "project" | "global";
88
89
  export declare function getPiGlobalConfigPath(overridePath?: string): string;
89
90
  export declare function getGenericGlobalConfigPath(): string;
90
91
  export declare function getProjectConfigPath(cwd?: string): string;
91
92
  export declare function getProjectPiConfigPath(cwd?: string): string;
93
+ export declare function getSharedConfigPath(target: SharedConfigTarget, cwd?: string): string;
92
94
  export declare function getConfigDiscoveryPaths(overridePath?: string, cwd?: string): ConfigDiscoveryPath[];
93
95
  export declare function findAvailableImportConfigs(cwd?: string): DiscoveredImportConfig[];
94
96
  export declare function getMcpStandardConfigSummary(overridePath?: string, cwd?: string): McpStandardConfigSummary;
@@ -113,6 +115,8 @@ export declare function ensureCompatibilityImports(importKinds: ImportKind[], ov
113
115
  added: ImportKind[];
114
116
  };
115
117
  export declare function buildStarterProjectConfig(): McpConfig;
118
+ export declare function previewStarterSharedConfig(target: SharedConfigTarget, cwd?: string): ConfigWritePreview;
119
+ export declare function writeStarterSharedConfig(target: SharedConfigTarget, cwd?: string): string;
116
120
  export declare function previewStarterProjectConfig(cwd?: string): ConfigWritePreview;
117
121
  export declare function writeStarterProjectConfig(cwd?: string): string;
118
122
  export declare function previewSharedServerEntry(filePath: string, serverName: string, entry: ServerEntry): ConfigWritePreview;
@@ -93,6 +93,9 @@ export function getProjectConfigPath(cwd = process.cwd()) {
93
93
  export function getProjectPiConfigPath(cwd = process.cwd()) {
94
94
  return resolve(cwd, getConfigDirName(), PROJECT_PI_CONFIG_NAME);
95
95
  }
96
+ export function getSharedConfigPath(target, cwd = process.cwd()) {
97
+ return target === "project" ? getProjectConfigPath(cwd) : getGenericGlobalConfigPath();
98
+ }
96
99
  export function getConfigDiscoveryPaths(overridePath, cwd = process.cwd()) {
97
100
  return getConfigSources(overridePath, cwd).map((source) => ({
98
101
  label: source.label,
@@ -976,17 +979,23 @@ export function buildStarterProjectConfig() {
976
979
  mcpServers: {},
977
980
  };
978
981
  }
979
- export function previewStarterProjectConfig(cwd = process.cwd()) {
980
- const targetPath = getProjectConfigPath(cwd);
982
+ export function previewStarterSharedConfig(target, cwd = process.cwd()) {
983
+ const targetPath = getSharedConfigPath(target, cwd);
981
984
  const nextRaw = { mcpServers: buildStarterProjectConfig().mcpServers };
982
985
  return buildConfigWritePreview(targetPath, nextRaw);
983
986
  }
984
- export function writeStarterProjectConfig(cwd = process.cwd()) {
985
- const targetPath = getProjectConfigPath(cwd);
987
+ export function writeStarterSharedConfig(target, cwd = process.cwd()) {
988
+ const targetPath = getSharedConfigPath(target, cwd);
986
989
  const raw = { mcpServers: buildStarterProjectConfig().mcpServers };
987
990
  writeRawConfigObject(targetPath, raw);
988
991
  return targetPath;
989
992
  }
993
+ export function previewStarterProjectConfig(cwd = process.cwd()) {
994
+ return previewStarterSharedConfig("project", cwd);
995
+ }
996
+ export function writeStarterProjectConfig(cwd = process.cwd()) {
997
+ return writeStarterSharedConfig("project", cwd);
998
+ }
990
999
  export function previewSharedServerEntry(filePath, serverName, entry) {
991
1000
  const raw = readRawConfigObject(filePath);
992
1001
  const nextRaw = { ...raw };