@akon-labs/nexus-cli 0.1.9 → 0.1.10
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.
- package/CHANGELOG.md +21 -0
- package/LICENSE +1 -0
- package/README.md +3 -2
- package/dist/app-bridge.bundle.js +67 -0
- package/dist/cli.js +20263 -2158
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +1 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auto-model.d.ts +1 -1
- package/dist/core/auto-model.d.ts.map +1 -1
- package/dist/core/auto-model.js +1 -1
- package/dist/core/auto-model.js.map +1 -1
- package/dist/core/model-resolver.d.ts +2 -2
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +3 -3
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-manager.d.ts +15 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +50 -0
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +3 -3
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +5 -5
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/websearch.d.ts.map +1 -1
- package/dist/core/tools/websearch.js +3 -3
- package/dist/core/tools/websearch.js.map +1 -1
- package/dist/extensions/gitnexus/gateway.d.ts +6 -6
- package/dist/extensions/gitnexus/gateway.d.ts.map +1 -1
- package/dist/extensions/gitnexus/gateway.js +4 -4
- package/dist/extensions/gitnexus/gateway.js.map +1 -1
- package/dist/extensions/gitnexus/index.d.ts.map +1 -1
- package/dist/extensions/gitnexus/index.js +4 -2
- package/dist/extensions/gitnexus/index.js.map +1 -1
- package/dist/extensions/mcp/abort.d.ts +3 -0
- package/dist/extensions/mcp/abort.d.ts.map +1 -0
- package/dist/extensions/mcp/abort.js +41 -0
- package/dist/extensions/mcp/abort.js.map +1 -0
- package/dist/extensions/mcp/agent-dir.d.ts +6 -0
- package/dist/extensions/mcp/agent-dir.d.ts.map +1 -0
- package/dist/extensions/mcp/agent-dir.js +18 -0
- package/dist/extensions/mcp/agent-dir.js.map +1 -0
- package/dist/extensions/mcp/agent-plugin-loader.d.ts +9 -0
- package/dist/extensions/mcp/agent-plugin-loader.d.ts.map +1 -0
- package/dist/extensions/mcp/agent-plugin-loader.js +384 -0
- package/dist/extensions/mcp/agent-plugin-loader.js.map +1 -0
- package/dist/extensions/mcp/app-bridge.bundle.js +67 -0
- package/dist/extensions/mcp/commands.d.ts +27 -0
- package/dist/extensions/mcp/commands.d.ts.map +1 -0
- package/dist/extensions/mcp/commands.js +629 -0
- package/dist/extensions/mcp/commands.js.map +1 -0
- package/dist/extensions/mcp/config.d.ts +121 -38
- package/dist/extensions/mcp/config.d.ts.map +1 -1
- package/dist/extensions/mcp/config.js +1030 -70
- package/dist/extensions/mcp/config.js.map +1 -1
- package/dist/extensions/mcp/consent-manager.d.ts +14 -0
- package/dist/extensions/mcp/consent-manager.d.ts.map +1 -0
- package/dist/extensions/mcp/consent-manager.js +60 -0
- package/dist/extensions/mcp/consent-manager.js.map +1 -0
- package/dist/extensions/mcp/direct-tools.d.ts +11 -0
- package/dist/extensions/mcp/direct-tools.d.ts.map +1 -0
- package/dist/extensions/mcp/direct-tools.js +562 -0
- package/dist/extensions/mcp/direct-tools.js.map +1 -0
- package/dist/extensions/mcp/elicitation-handler.d.ts +18 -0
- package/dist/extensions/mcp/elicitation-handler.d.ts.map +1 -0
- package/dist/extensions/mcp/elicitation-handler.js +320 -0
- package/dist/extensions/mcp/elicitation-handler.js.map +1 -0
- package/dist/extensions/mcp/error-signal.d.ts +16 -0
- package/dist/extensions/mcp/error-signal.d.ts.map +1 -0
- package/dist/extensions/mcp/error-signal.js +22 -0
- package/dist/extensions/mcp/error-signal.js.map +1 -0
- package/dist/extensions/mcp/errors.d.ts +104 -0
- package/dist/extensions/mcp/errors.d.ts.map +1 -0
- package/dist/extensions/mcp/errors.js +171 -0
- package/dist/extensions/mcp/errors.js.map +1 -0
- package/dist/extensions/mcp/glimpse-ui.d.ts +10 -0
- package/dist/extensions/mcp/glimpse-ui.d.ts.map +1 -0
- package/dist/extensions/mcp/glimpse-ui.js +67 -0
- package/dist/extensions/mcp/glimpse-ui.js.map +1 -0
- package/dist/extensions/mcp/host-html-template.d.ts +17 -0
- package/dist/extensions/mcp/host-html-template.d.ts.map +1 -0
- package/dist/extensions/mcp/host-html-template.js +442 -0
- package/dist/extensions/mcp/host-html-template.js.map +1 -0
- package/dist/extensions/mcp/index.d.ts +20 -15
- package/dist/extensions/mcp/index.d.ts.map +1 -1
- package/dist/extensions/mcp/index.js +1064 -82
- package/dist/extensions/mcp/index.js.map +1 -1
- package/dist/extensions/mcp/init.d.ts +28 -0
- package/dist/extensions/mcp/init.d.ts.map +1 -0
- package/dist/extensions/mcp/init.js +631 -0
- package/dist/extensions/mcp/init.js.map +1 -0
- package/dist/extensions/mcp/json-schema-validator.d.ts +3 -0
- package/dist/extensions/mcp/json-schema-validator.d.ts.map +1 -0
- package/dist/extensions/mcp/json-schema-validator.js +53 -0
- package/dist/extensions/mcp/json-schema-validator.js.map +1 -0
- package/dist/extensions/mcp/lifecycle.d.ts +56 -0
- package/dist/extensions/mcp/lifecycle.d.ts.map +1 -0
- package/dist/extensions/mcp/lifecycle.js +395 -0
- package/dist/extensions/mcp/lifecycle.js.map +1 -0
- package/dist/extensions/mcp/logger.d.ts +52 -0
- package/dist/extensions/mcp/logger.d.ts.map +1 -0
- package/dist/extensions/mcp/logger.js +134 -0
- package/dist/extensions/mcp/logger.js.map +1 -0
- package/dist/extensions/mcp/mcp-auth-flow.d.ts +117 -0
- package/dist/extensions/mcp/mcp-auth-flow.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-auth-flow.js +869 -0
- package/dist/extensions/mcp/mcp-auth-flow.js.map +1 -0
- package/dist/extensions/mcp/mcp-auth.d.ts +162 -0
- package/dist/extensions/mcp/mcp-auth.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-auth.js +897 -0
- package/dist/extensions/mcp/mcp-auth.js.map +1 -0
- package/dist/extensions/mcp/mcp-bearer-store.d.ts +25 -0
- package/dist/extensions/mcp/mcp-bearer-store.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-bearer-store.js +350 -0
- package/dist/extensions/mcp/mcp-bearer-store.js.map +1 -0
- package/dist/extensions/mcp/mcp-callback-server.d.ts +54 -0
- package/dist/extensions/mcp/mcp-callback-server.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-callback-server.js +454 -0
- package/dist/extensions/mcp/mcp-callback-server.js.map +1 -0
- package/dist/extensions/mcp/mcp-code.d.ts +48 -0
- package/dist/extensions/mcp/mcp-code.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-code.js +358 -0
- package/dist/extensions/mcp/mcp-code.js.map +1 -0
- package/dist/extensions/mcp/mcp-keyring-helper.cjs +89 -0
- package/dist/extensions/mcp/mcp-oauth-provider.d.ts +135 -0
- package/dist/extensions/mcp/mcp-oauth-provider.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-oauth-provider.js +491 -0
- package/dist/extensions/mcp/mcp-oauth-provider.js.map +1 -0
- package/dist/extensions/mcp/mcp-output-guard.d.ts +59 -0
- package/dist/extensions/mcp/mcp-output-guard.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-output-guard.js +343 -0
- package/dist/extensions/mcp/mcp-output-guard.js.map +1 -0
- package/dist/extensions/mcp/mcp-panel.d.ts +71 -0
- package/dist/extensions/mcp/mcp-panel.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-panel.js +973 -0
- package/dist/extensions/mcp/mcp-panel.js.map +1 -0
- package/dist/extensions/mcp/mcp-probe.d.ts +7 -0
- package/dist/extensions/mcp/mcp-probe.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-probe.js +155 -0
- package/dist/extensions/mcp/mcp-probe.js.map +1 -0
- package/dist/extensions/mcp/mcp-script-worker.mjs +130 -0
- package/dist/extensions/mcp/mcp-setup-panel.d.ts +85 -0
- package/dist/extensions/mcp/mcp-setup-panel.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-setup-panel.js +618 -0
- package/dist/extensions/mcp/mcp-setup-panel.js.map +1 -0
- package/dist/extensions/mcp/mcp-status.d.ts +12 -0
- package/dist/extensions/mcp/mcp-status.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-status.js +98 -0
- package/dist/extensions/mcp/mcp-status.js.map +1 -0
- package/dist/extensions/mcp/mcp-trace.d.ts +95 -0
- package/dist/extensions/mcp/mcp-trace.d.ts.map +1 -0
- package/dist/extensions/mcp/mcp-trace.js +252 -0
- package/dist/extensions/mcp/mcp-trace.js.map +1 -0
- package/dist/extensions/mcp/metadata-cache.d.ts +20 -0
- package/dist/extensions/mcp/metadata-cache.d.ts.map +1 -0
- package/dist/extensions/mcp/metadata-cache.js +318 -0
- package/dist/extensions/mcp/metadata-cache.js.map +1 -0
- package/dist/extensions/mcp/npx-resolver.d.ts +7 -0
- package/dist/extensions/mcp/npx-resolver.d.ts.map +1 -0
- package/dist/extensions/mcp/npx-resolver.js +505 -0
- package/dist/extensions/mcp/npx-resolver.js.map +1 -0
- package/dist/extensions/mcp/oauth.d.ts +21 -0
- package/dist/extensions/mcp/oauth.d.ts.map +1 -0
- package/dist/extensions/mcp/oauth.js +15 -0
- package/dist/extensions/mcp/oauth.js.map +1 -0
- package/dist/extensions/mcp/onboarding-state.d.ts +13 -0
- package/dist/extensions/mcp/onboarding-state.d.ts.map +1 -0
- package/dist/extensions/mcp/onboarding-state.js +65 -0
- package/dist/extensions/mcp/onboarding-state.js.map +1 -0
- package/dist/extensions/mcp/package-mcp-loader.d.ts +3 -0
- package/dist/extensions/mcp/package-mcp-loader.d.ts.map +1 -0
- package/dist/extensions/mcp/package-mcp-loader.js +170 -0
- package/dist/extensions/mcp/package-mcp-loader.js.map +1 -0
- package/dist/extensions/mcp/panel-keys.d.ts +21 -0
- package/dist/extensions/mcp/panel-keys.d.ts.map +1 -0
- package/dist/extensions/mcp/panel-keys.js +29 -0
- package/dist/extensions/mcp/panel-keys.js.map +1 -0
- package/dist/extensions/mcp/prompts.d.ts +63 -0
- package/dist/extensions/mcp/prompts.d.ts.map +1 -0
- package/dist/extensions/mcp/prompts.js +297 -0
- package/dist/extensions/mcp/prompts.js.map +1 -0
- package/dist/extensions/mcp/proxy-modes.d.ts +15 -0
- package/dist/extensions/mcp/proxy-modes.d.ts.map +1 -0
- package/dist/extensions/mcp/proxy-modes.js +1421 -0
- package/dist/extensions/mcp/proxy-modes.js.map +1 -0
- package/dist/extensions/mcp/request-headers-command.d.ts +11 -0
- package/dist/extensions/mcp/request-headers-command.d.ts.map +1 -0
- package/dist/extensions/mcp/request-headers-command.js +334 -0
- package/dist/extensions/mcp/request-headers-command.js.map +1 -0
- package/dist/extensions/mcp/resource-tools.d.ts +2 -0
- package/dist/extensions/mcp/resource-tools.d.ts.map +1 -0
- package/dist/extensions/mcp/resource-tools.js +15 -0
- package/dist/extensions/mcp/resource-tools.js.map +1 -0
- package/dist/extensions/mcp/runtime-owner.d.ts +14 -0
- package/dist/extensions/mcp/runtime-owner.d.ts.map +1 -0
- package/dist/extensions/mcp/runtime-owner.js +84 -0
- package/dist/extensions/mcp/runtime-owner.js.map +1 -0
- package/dist/extensions/mcp/sampling-handler.d.ts +18 -0
- package/dist/extensions/mcp/sampling-handler.d.ts.map +1 -0
- package/dist/extensions/mcp/sampling-handler.js +229 -0
- package/dist/extensions/mcp/sampling-handler.js.map +1 -0
- package/dist/extensions/mcp/search-ranking.d.ts +25 -0
- package/dist/extensions/mcp/search-ranking.d.ts.map +1 -0
- package/dist/extensions/mcp/search-ranking.js +234 -0
- package/dist/extensions/mcp/search-ranking.js.map +1 -0
- package/dist/extensions/mcp/server-manager.d.ts +116 -0
- package/dist/extensions/mcp/server-manager.d.ts.map +1 -0
- package/dist/extensions/mcp/server-manager.js +1052 -0
- package/dist/extensions/mcp/server-manager.js.map +1 -0
- package/dist/extensions/mcp/session-recovery.d.ts +27 -0
- package/dist/extensions/mcp/session-recovery.d.ts.map +1 -0
- package/dist/extensions/mcp/session-recovery.js +140 -0
- package/dist/extensions/mcp/session-recovery.js.map +1 -0
- package/dist/extensions/mcp/skills/mcp-scripting/SKILL.md +38 -0
- package/dist/extensions/mcp/state.d.ts +62 -0
- package/dist/extensions/mcp/state.d.ts.map +1 -0
- package/dist/extensions/mcp/state.js +2 -0
- package/dist/extensions/mcp/state.js.map +1 -0
- package/dist/extensions/mcp/tool-approval.d.ts +11 -0
- package/dist/extensions/mcp/tool-approval.d.ts.map +1 -0
- package/dist/extensions/mcp/tool-approval.js +156 -0
- package/dist/extensions/mcp/tool-approval.js.map +1 -0
- package/dist/extensions/mcp/tool-metadata.d.ts +11 -0
- package/dist/extensions/mcp/tool-metadata.d.ts.map +1 -0
- package/dist/extensions/mcp/tool-metadata.js +264 -0
- package/dist/extensions/mcp/tool-metadata.js.map +1 -0
- package/dist/extensions/mcp/tool-registrar.d.ts +21 -0
- package/dist/extensions/mcp/tool-registrar.d.ts.map +1 -0
- package/dist/extensions/mcp/tool-registrar.js +229 -0
- package/dist/extensions/mcp/tool-registrar.js.map +1 -0
- package/dist/extensions/mcp/tool-result-renderer.d.ts +83 -0
- package/dist/extensions/mcp/tool-result-renderer.d.ts.map +1 -0
- package/dist/extensions/mcp/tool-result-renderer.js +345 -0
- package/dist/extensions/mcp/tool-result-renderer.js.map +1 -0
- package/dist/extensions/mcp/ts-shape.d.ts +3 -0
- package/dist/extensions/mcp/ts-shape.d.ts.map +1 -0
- package/dist/extensions/mcp/ts-shape.js +160 -0
- package/dist/extensions/mcp/ts-shape.js.map +1 -0
- package/dist/extensions/mcp/types.d.ts +521 -0
- package/dist/extensions/mcp/types.d.ts.map +1 -0
- package/dist/extensions/mcp/types.js +294 -0
- package/dist/extensions/mcp/types.js.map +1 -0
- package/dist/extensions/mcp/ui-app-bridge-helpers.d.ts +7 -0
- package/dist/extensions/mcp/ui-app-bridge-helpers.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-app-bridge-helpers.js +37 -0
- package/dist/extensions/mcp/ui-app-bridge-helpers.js.map +1 -0
- package/dist/extensions/mcp/ui-resource-handler.d.ts +18 -0
- package/dist/extensions/mcp/ui-resource-handler.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-resource-handler.js +210 -0
- package/dist/extensions/mcp/ui-resource-handler.js.map +1 -0
- package/dist/extensions/mcp/ui-server.d.ts +52 -0
- package/dist/extensions/mcp/ui-server.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-server.js +740 -0
- package/dist/extensions/mcp/ui-server.js.map +1 -0
- package/dist/extensions/mcp/ui-session.d.ts +40 -0
- package/dist/extensions/mcp/ui-session.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-session.js +477 -0
- package/dist/extensions/mcp/ui-session.js.map +1 -0
- package/dist/extensions/mcp/ui-stream-types.d.ts +105 -0
- package/dist/extensions/mcp/ui-stream-types.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-stream-types.js +61 -0
- package/dist/extensions/mcp/ui-stream-types.js.map +1 -0
- package/dist/extensions/mcp/ui-tool-visibility.d.ts +5 -0
- package/dist/extensions/mcp/ui-tool-visibility.d.ts.map +1 -0
- package/dist/extensions/mcp/ui-tool-visibility.js +27 -0
- package/dist/extensions/mcp/ui-tool-visibility.js.map +1 -0
- package/dist/extensions/mcp/unix-socket-transport.d.ts +15 -0
- package/dist/extensions/mcp/unix-socket-transport.d.ts.map +1 -0
- package/dist/extensions/mcp/unix-socket-transport.js +85 -0
- package/dist/extensions/mcp/unix-socket-transport.js.map +1 -0
- package/dist/extensions/mcp/utils.d.ts +32 -0
- package/dist/extensions/mcp/utils.d.ts.map +1 -0
- package/dist/extensions/mcp/utils.js +336 -0
- package/dist/extensions/mcp/utils.js.map +1 -0
- package/dist/extensions/subagent/index.d.ts +61 -1
- package/dist/extensions/subagent/index.d.ts.map +1 -1
- package/dist/extensions/subagent/index.js +177 -4
- package/dist/extensions/subagent/index.js.map +1 -1
- package/dist/extensions/web-access.d.ts.map +1 -1
- package/dist/extensions/web-access.js +1 -1
- package/dist/extensions/web-access.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20057 -1947
- package/dist/index.js.map +1 -1
- package/dist/mcp-keyring-helper.cjs +89 -0
- package/dist/mcp-script-worker.mjs +130 -0
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +1 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/response-separator.d.ts +1 -1
- package/dist/modes/interactive/components/response-separator.d.ts.map +1 -1
- package/dist/modes/interactive/components/response-separator.js +1 -1
- package/dist/modes/interactive/components/response-separator.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +7 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/rpc-entry.js +20263 -2158
- package/dist/skills/mcp-scripting/SKILL.md +38 -0
- package/dist/vendor/ai.js +280 -205
- package/docs/mcp.md +178 -0
- package/docs/pi-subagents.md +75 -0
- package/docs/quickstart.md +1 -1
- package/docs/usage.md +2 -1
- package/examples/extensions/pi-subagents-worker-policy.ts +86 -0
- package/npm-shrinkwrap.json +763 -6
- package/package.json +15 -4
- package/dist/extensions/mcp/client.d.ts +0 -86
- package/dist/extensions/mcp/client.d.ts.map +0 -1
- package/dist/extensions/mcp/client.js +0 -275
- package/dist/extensions/mcp/client.js.map +0 -1
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { lazyConnect } from "./init.js";
|
|
2
|
+
import { logger } from "./logger.js";
|
|
3
|
+
import { isServerCacheValid, loadMetadataCache, reconstructPromptMetadata } from "./metadata-cache.js";
|
|
4
|
+
import { formatPromptCommandName, isServerDisabled } from "./types.js";
|
|
5
|
+
import { truncateAtWord } from "./utils.js";
|
|
6
|
+
/**
|
|
7
|
+
* Resolve prompt metadata for slash-command registration at extension load
|
|
8
|
+
* time. Mirrors `resolveDirectTools`: reads the persistent metadata cache so
|
|
9
|
+
* commands are available before any server connects.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveCachedPrompts(config) {
|
|
12
|
+
const cache = loadMetadataCache();
|
|
13
|
+
if (!cache?.servers)
|
|
14
|
+
return [];
|
|
15
|
+
const prefix = config.settings?.toolPrefix ?? "server";
|
|
16
|
+
const specs = [];
|
|
17
|
+
for (const [serverName, entry] of Object.entries(cache.servers)) {
|
|
18
|
+
const definition = config.mcpServers[serverName];
|
|
19
|
+
if (!definition || isServerDisabled(definition))
|
|
20
|
+
continue;
|
|
21
|
+
if (!entry?.prompts?.length || !isServerCacheValid(entry, definition))
|
|
22
|
+
continue;
|
|
23
|
+
specs.push(...reconstructPromptMetadata(serverName, entry.prompts, prefix, definition));
|
|
24
|
+
}
|
|
25
|
+
return specs;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse a slash-command argument string into positional and named MCP prompt
|
|
29
|
+
* arguments. Supports bash-style quoting so callers can pass values with
|
|
30
|
+
* spaces:
|
|
31
|
+
*
|
|
32
|
+
* /mcp__demo__brief today "important tasks"
|
|
33
|
+
* /mcp__demo__brief day=today topic="important tasks"
|
|
34
|
+
*/
|
|
35
|
+
export function parsePromptArgs(input) {
|
|
36
|
+
const positional = [];
|
|
37
|
+
const named = {};
|
|
38
|
+
const tokens = tokenizeArgs(input);
|
|
39
|
+
for (const token of tokens) {
|
|
40
|
+
const eq = findUnquotedEquals(token);
|
|
41
|
+
if (eq > 0) {
|
|
42
|
+
const key = token.slice(0, eq).trim();
|
|
43
|
+
const value = stripQuotes(token.slice(eq + 1).trim());
|
|
44
|
+
if (key) {
|
|
45
|
+
named[key] = value;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
positional.push(stripQuotes(token));
|
|
50
|
+
}
|
|
51
|
+
return { positional, named };
|
|
52
|
+
}
|
|
53
|
+
function tokenizeArgs(input) {
|
|
54
|
+
const tokens = [];
|
|
55
|
+
let current = "";
|
|
56
|
+
let quote = null;
|
|
57
|
+
let escaped = false;
|
|
58
|
+
for (const char of input) {
|
|
59
|
+
if (escaped) {
|
|
60
|
+
current += char;
|
|
61
|
+
escaped = false;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (char === "\\" && quote !== "'") {
|
|
65
|
+
escaped = true;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (quote) {
|
|
69
|
+
current += char;
|
|
70
|
+
if (char === quote)
|
|
71
|
+
quote = null;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (char === '"' || char === "'") {
|
|
75
|
+
quote = char;
|
|
76
|
+
current += char;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (/\s/.test(char)) {
|
|
80
|
+
if (current.length > 0) {
|
|
81
|
+
tokens.push(current);
|
|
82
|
+
current = "";
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
current += char;
|
|
87
|
+
}
|
|
88
|
+
if (current.length > 0)
|
|
89
|
+
tokens.push(current);
|
|
90
|
+
return tokens;
|
|
91
|
+
}
|
|
92
|
+
function findUnquotedEquals(token) {
|
|
93
|
+
let quote = null;
|
|
94
|
+
for (let i = 0; i < token.length; i++) {
|
|
95
|
+
const ch = token[i];
|
|
96
|
+
if (quote) {
|
|
97
|
+
if (ch === quote)
|
|
98
|
+
quote = null;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (ch === '"' || ch === "'")
|
|
102
|
+
quote = ch;
|
|
103
|
+
else if (ch === "=")
|
|
104
|
+
return i;
|
|
105
|
+
}
|
|
106
|
+
return -1;
|
|
107
|
+
}
|
|
108
|
+
function stripQuotes(value) {
|
|
109
|
+
if (value.length >= 2 && (value.startsWith('"') || value.startsWith("'")) && value.endsWith(value.charAt(0))) {
|
|
110
|
+
return value.slice(1, -1);
|
|
111
|
+
}
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Map positional and named arguments onto the prompt's declared argument
|
|
116
|
+
* list. Named arguments win over positional when both target the same slot.
|
|
117
|
+
* Missing required arguments produce a helpful usage message that the
|
|
118
|
+
* command handler surfaces via `ctx.ui.notify`.
|
|
119
|
+
*/
|
|
120
|
+
export function resolvePromptArgs(metadata, parsed) {
|
|
121
|
+
const args = {};
|
|
122
|
+
const declared = metadata.arguments;
|
|
123
|
+
let positionalIndex = 0;
|
|
124
|
+
for (const argDef of declared) {
|
|
125
|
+
const value = parsed.named[argDef.name] ?? parsed.positional[positionalIndex++];
|
|
126
|
+
if (value !== undefined && value !== "") {
|
|
127
|
+
args[argDef.name] = value;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Preserve named arguments the prompt did not declare so servers with
|
|
131
|
+
// permissive schemas still receive them. The MCP spec allows arbitrary
|
|
132
|
+
// string key/values in `prompts/get` params.arguments.
|
|
133
|
+
for (const [key, value] of Object.entries(parsed.named)) {
|
|
134
|
+
if (!(key in args))
|
|
135
|
+
args[key] = value;
|
|
136
|
+
}
|
|
137
|
+
const missing = declared.filter((a) => a.required && (args[a.name] === undefined || args[a.name] === ""));
|
|
138
|
+
if (missing.length > 0) {
|
|
139
|
+
return { ok: false, error: buildUsageMessage(metadata, missing) };
|
|
140
|
+
}
|
|
141
|
+
return { ok: true, args };
|
|
142
|
+
}
|
|
143
|
+
function buildUsageMessage(metadata, missing) {
|
|
144
|
+
const usage = metadata.arguments.map((a) => (a.required ? `<${a.name}>` : `[${a.name}]`)).join(" ");
|
|
145
|
+
const missingList = missing.map((a) => a.name).join(", ");
|
|
146
|
+
return `Missing required argument${missing.length > 1 ? "s" : ""}: ${missingList}.\nUsage: /${metadata.commandName} ${usage}`.trim();
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Flatten a `GetPromptResult` into a single string suitable for
|
|
150
|
+
* `nexus.sendUserMessage`. MCP prompts can contain multiple messages with
|
|
151
|
+
* mixed roles; we preserve role attribution as inline markers so the model
|
|
152
|
+
* still sees the intended conversational shape without needing a
|
|
153
|
+
* multi-message replay API on the nexus side.
|
|
154
|
+
*/
|
|
155
|
+
export function formatPromptResult(result) {
|
|
156
|
+
const lines = [];
|
|
157
|
+
for (const message of result.messages) {
|
|
158
|
+
const text = extractMessageText(message);
|
|
159
|
+
if (!text)
|
|
160
|
+
continue;
|
|
161
|
+
if (message.role === "user" && result.messages.length === 1) {
|
|
162
|
+
lines.push(text);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
lines.push(`[${message.role}] ${text}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return lines.join("\n\n").trim();
|
|
169
|
+
}
|
|
170
|
+
function extractMessageText(message) {
|
|
171
|
+
const content = message.content;
|
|
172
|
+
if (!content || typeof content !== "object")
|
|
173
|
+
return "";
|
|
174
|
+
switch (content.type) {
|
|
175
|
+
case "text":
|
|
176
|
+
return content.text ?? "";
|
|
177
|
+
case "resource": {
|
|
178
|
+
const resource = content.resource;
|
|
179
|
+
if (!resource)
|
|
180
|
+
return "";
|
|
181
|
+
if ("text" in resource && typeof resource.text === "string") {
|
|
182
|
+
return `[resource ${resource.uri}]\n${resource.text}`;
|
|
183
|
+
}
|
|
184
|
+
return `[resource ${resource.uri}]`;
|
|
185
|
+
}
|
|
186
|
+
case "resource_link":
|
|
187
|
+
return `[resource_link ${content.uri ?? ""}${content.name ? ` — ${content.name}` : ""}]`;
|
|
188
|
+
case "image":
|
|
189
|
+
return `[image ${content.mimeType ?? "unknown"}${content.data ? " (embedded)" : ""}]`;
|
|
190
|
+
case "audio":
|
|
191
|
+
return `[audio ${content.mimeType ?? "unknown"}]`;
|
|
192
|
+
default:
|
|
193
|
+
return "";
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Build the nexus command definition for a single MCP prompt. Registered once
|
|
198
|
+
* per prompt at extension load time from the metadata cache; the same
|
|
199
|
+
* factory is reused by tests to invoke the handler without a running nexus.
|
|
200
|
+
*/
|
|
201
|
+
export function createPromptCommand(nexus, getState, metadata) {
|
|
202
|
+
const description = buildCommandDescription(metadata);
|
|
203
|
+
return {
|
|
204
|
+
description,
|
|
205
|
+
handler: async (args, ctx) => {
|
|
206
|
+
const state = getState();
|
|
207
|
+
if (!state) {
|
|
208
|
+
if (ctx.hasUI)
|
|
209
|
+
ctx.ui.notify("MCP not initialized", "error");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const liveMetadata = findLivePromptMetadata(state, metadata.serverName, metadata.originalName);
|
|
213
|
+
if (state.promptMetadataLive?.has(metadata.serverName) && !liveMetadata) {
|
|
214
|
+
if (ctx.hasUI) {
|
|
215
|
+
ctx.ui.notify(`MCP prompt "${metadata.originalName}" is no longer advertised by server "${metadata.serverName}". Run /mcp reconnect to refresh.`, "error");
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const live = liveMetadata ?? metadata;
|
|
220
|
+
const parsed = parsePromptArgs(args ?? "");
|
|
221
|
+
const resolved = resolvePromptArgs(live, parsed);
|
|
222
|
+
if (!resolved.ok) {
|
|
223
|
+
if (ctx.hasUI)
|
|
224
|
+
ctx.ui.notify(resolved.error ?? "Invalid prompt arguments", "error");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const promptArgs = resolved.args ?? {};
|
|
228
|
+
if (!state.config.mcpServers[metadata.serverName]) {
|
|
229
|
+
if (ctx.hasUI) {
|
|
230
|
+
ctx.ui.notify(`MCP prompt "${live.originalName}" is no longer configured. Run /mcp reconnect to refresh.`, "error");
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const connected = await lazyConnect(state, metadata.serverName, ctx.signal);
|
|
235
|
+
if (!connected) {
|
|
236
|
+
if (ctx.hasUI) {
|
|
237
|
+
const conn = state.manager.getConnection(metadata.serverName);
|
|
238
|
+
const message = conn?.status === "needs-auth"
|
|
239
|
+
? `MCP server "${metadata.serverName}" needs authentication. Run /mcp-auth ${metadata.serverName}.`
|
|
240
|
+
: `MCP server "${metadata.serverName}" is not available. Run /mcp reconnect ${metadata.serverName}.`;
|
|
241
|
+
ctx.ui.notify(message, "error");
|
|
242
|
+
}
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const refreshed = findLivePromptMetadata(state, metadata.serverName, metadata.originalName);
|
|
246
|
+
if (state.promptMetadataLive?.has(metadata.serverName) && !refreshed) {
|
|
247
|
+
if (ctx.hasUI) {
|
|
248
|
+
ctx.ui.notify(`MCP prompt "${metadata.originalName}" is no longer advertised by server "${metadata.serverName}". Run /mcp reconnect to refresh.`, "error");
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const dispatchMetadata = refreshed ?? live;
|
|
253
|
+
let result;
|
|
254
|
+
try {
|
|
255
|
+
result = await state.manager.getPrompt(metadata.serverName, dispatchMetadata.originalName, Object.keys(promptArgs).length > 0 ? promptArgs : undefined, ctx.signal);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
259
|
+
logger.debug(`MCP prompt "${live.originalName}" on ${metadata.serverName} failed: ${message}`);
|
|
260
|
+
if (ctx.hasUI) {
|
|
261
|
+
ctx.ui.notify(`MCP prompt "${live.originalName}" failed: ${message}`, "error");
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const text = formatPromptResult(result);
|
|
266
|
+
if (!text) {
|
|
267
|
+
if (ctx.hasUI) {
|
|
268
|
+
ctx.ui.notify(`MCP prompt "${live.originalName}" returned no text content.`, "warning");
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
nexus.sendUserMessage(text);
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function findLivePromptMetadata(state, serverName, originalName) {
|
|
277
|
+
return state.promptMetadata?.get(serverName)?.find((p) => p.originalName === originalName);
|
|
278
|
+
}
|
|
279
|
+
function buildCommandDescription(metadata) {
|
|
280
|
+
const base = metadata.description || metadata.title || `MCP prompt from ${metadata.serverName}`;
|
|
281
|
+
return truncateAtWord(`MCP: ${base}`, 120) || `MCP prompt from ${metadata.serverName}`;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Public helper used by `/mcp prompts` to render the list of prompts known
|
|
285
|
+
* to the adapter, whether from a live connection or the metadata cache.
|
|
286
|
+
*/
|
|
287
|
+
export function listAllPromptMetadata(state) {
|
|
288
|
+
const flat = [];
|
|
289
|
+
for (const list of state.promptMetadata?.values() ?? [])
|
|
290
|
+
flat.push(...list);
|
|
291
|
+
flat.sort((a, b) => a.commandName.localeCompare(b.commandName));
|
|
292
|
+
return flat;
|
|
293
|
+
}
|
|
294
|
+
// Re-exported so index.ts can format cache-only prompt-command names without
|
|
295
|
+
// duplicating the namespace logic.
|
|
296
|
+
export { formatPromptCommandName };
|
|
297
|
+
//# sourceMappingURL=prompts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../../src/extensions/mcp/prompts.ts"],"names":[],"mappings":"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","sourcesContent":["import type { GetPromptResult, PromptMessage } from \"@modelcontextprotocol/client\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.ts\";\nimport { lazyConnect } from \"./init.ts\";\nimport { logger } from \"./logger.ts\";\nimport { isServerCacheValid, loadMetadataCache, reconstructPromptMetadata } from \"./metadata-cache.ts\";\nimport type { McpExtensionState } from \"./state.ts\";\nimport { formatPromptCommandName, isServerDisabled, type McpConfig, type PromptMetadata } from \"./types.ts\";\nimport { truncateAtWord } from \"./utils.ts\";\n\n/**\n * Resolve prompt metadata for slash-command registration at extension load\n * time. Mirrors `resolveDirectTools`: reads the persistent metadata cache so\n * commands are available before any server connects.\n */\nexport function resolveCachedPrompts(config: McpConfig): PromptMetadata[] {\n\tconst cache = loadMetadataCache();\n\tif (!cache?.servers) return [];\n\n\tconst prefix = config.settings?.toolPrefix ?? \"server\";\n\tconst specs: PromptMetadata[] = [];\n\n\tfor (const [serverName, entry] of Object.entries(cache.servers)) {\n\t\tconst definition = config.mcpServers[serverName];\n\t\tif (!definition || isServerDisabled(definition)) continue;\n\t\tif (!entry?.prompts?.length || !isServerCacheValid(entry, definition)) continue;\n\t\tspecs.push(...reconstructPromptMetadata(serverName, entry.prompts, prefix, definition));\n\t}\n\n\treturn specs;\n}\n\n/**\n * Parse a slash-command argument string into positional and named MCP prompt\n * arguments. Supports bash-style quoting so callers can pass values with\n * spaces:\n *\n * /mcp__demo__brief today \"important tasks\"\n * /mcp__demo__brief day=today topic=\"important tasks\"\n */\nexport function parsePromptArgs(input: string): { positional: string[]; named: Record<string, string> } {\n\tconst positional: string[] = [];\n\tconst named: Record<string, string> = {};\n\n\tconst tokens = tokenizeArgs(input);\n\tfor (const token of tokens) {\n\t\tconst eq = findUnquotedEquals(token);\n\t\tif (eq > 0) {\n\t\t\tconst key = token.slice(0, eq).trim();\n\t\t\tconst value = stripQuotes(token.slice(eq + 1).trim());\n\t\t\tif (key) {\n\t\t\t\tnamed[key] = value;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tpositional.push(stripQuotes(token));\n\t}\n\n\treturn { positional, named };\n}\n\nfunction tokenizeArgs(input: string): string[] {\n\tconst tokens: string[] = [];\n\tlet current = \"\";\n\tlet quote: '\"' | \"'\" | null = null;\n\tlet escaped = false;\n\n\tfor (const char of input) {\n\t\tif (escaped) {\n\t\t\tcurrent += char;\n\t\t\tescaped = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (char === \"\\\\\" && quote !== \"'\") {\n\t\t\tescaped = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (quote) {\n\t\t\tcurrent += char;\n\t\t\tif (char === quote) quote = null;\n\t\t\tcontinue;\n\t\t}\n\t\tif (char === '\"' || char === \"'\") {\n\t\t\tquote = char;\n\t\t\tcurrent += char;\n\t\t\tcontinue;\n\t\t}\n\t\tif (/\\s/.test(char)) {\n\t\t\tif (current.length > 0) {\n\t\t\t\ttokens.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tcurrent += char;\n\t}\n\n\tif (current.length > 0) tokens.push(current);\n\treturn tokens;\n}\n\nfunction findUnquotedEquals(token: string): number {\n\tlet quote: '\"' | \"'\" | null = null;\n\tfor (let i = 0; i < token.length; i++) {\n\t\tconst ch = token[i];\n\t\tif (quote) {\n\t\t\tif (ch === quote) quote = null;\n\t\t\tcontinue;\n\t\t}\n\t\tif (ch === '\"' || ch === \"'\") quote = ch;\n\t\telse if (ch === \"=\") return i;\n\t}\n\treturn -1;\n}\n\nfunction stripQuotes(value: string): string {\n\tif (value.length >= 2 && (value.startsWith('\"') || value.startsWith(\"'\")) && value.endsWith(value.charAt(0))) {\n\t\treturn value.slice(1, -1);\n\t}\n\treturn value;\n}\n\nexport interface ResolvedPromptArgs {\n\tok: boolean;\n\t/** Present when `ok === true`. */\n\targs?: Record<string, string>;\n\t/** Present when `ok === false`. */\n\terror?: string;\n}\n\n/**\n * Map positional and named arguments onto the prompt's declared argument\n * list. Named arguments win over positional when both target the same slot.\n * Missing required arguments produce a helpful usage message that the\n * command handler surfaces via `ctx.ui.notify`.\n */\nexport function resolvePromptArgs(\n\tmetadata: PromptMetadata,\n\tparsed: { positional: string[]; named: Record<string, string> },\n): ResolvedPromptArgs {\n\tconst args: Record<string, string> = {};\n\n\tconst declared = metadata.arguments;\n\tlet positionalIndex = 0;\n\tfor (const argDef of declared) {\n\t\tconst value = parsed.named[argDef.name] ?? parsed.positional[positionalIndex++];\n\t\tif (value !== undefined && value !== \"\") {\n\t\t\targs[argDef.name] = value;\n\t\t}\n\t}\n\n\t// Preserve named arguments the prompt did not declare so servers with\n\t// permissive schemas still receive them. The MCP spec allows arbitrary\n\t// string key/values in `prompts/get` params.arguments.\n\tfor (const [key, value] of Object.entries(parsed.named)) {\n\t\tif (!(key in args)) args[key] = value;\n\t}\n\n\tconst missing = declared.filter((a) => a.required && (args[a.name] === undefined || args[a.name] === \"\"));\n\tif (missing.length > 0) {\n\t\treturn { ok: false, error: buildUsageMessage(metadata, missing) };\n\t}\n\n\treturn { ok: true, args };\n}\n\nfunction buildUsageMessage(metadata: PromptMetadata, missing: PromptMetadata[\"arguments\"]): string {\n\tconst usage = metadata.arguments.map((a) => (a.required ? `<${a.name}>` : `[${a.name}]`)).join(\" \");\n\tconst missingList = missing.map((a) => a.name).join(\", \");\n\treturn `Missing required argument${missing.length > 1 ? \"s\" : \"\"}: ${missingList}.\\nUsage: /${metadata.commandName} ${usage}`.trim();\n}\n\n/**\n * Flatten a `GetPromptResult` into a single string suitable for\n * `nexus.sendUserMessage`. MCP prompts can contain multiple messages with\n * mixed roles; we preserve role attribution as inline markers so the model\n * still sees the intended conversational shape without needing a\n * multi-message replay API on the nexus side.\n */\nexport function formatPromptResult(result: GetPromptResult): string {\n\tconst lines: string[] = [];\n\tfor (const message of result.messages) {\n\t\tconst text = extractMessageText(message);\n\t\tif (!text) continue;\n\t\tif (message.role === \"user\" && result.messages.length === 1) {\n\t\t\tlines.push(text);\n\t\t} else {\n\t\t\tlines.push(`[${message.role}] ${text}`);\n\t\t}\n\t}\n\treturn lines.join(\"\\n\\n\").trim();\n}\n\nfunction extractMessageText(message: PromptMessage): string {\n\tconst content = message.content;\n\tif (!content || typeof content !== \"object\") return \"\";\n\tswitch (content.type) {\n\t\tcase \"text\":\n\t\t\treturn content.text ?? \"\";\n\t\tcase \"resource\": {\n\t\t\tconst resource = content.resource;\n\t\t\tif (!resource) return \"\";\n\t\t\tif (\"text\" in resource && typeof resource.text === \"string\") {\n\t\t\t\treturn `[resource ${resource.uri}]\\n${resource.text}`;\n\t\t\t}\n\t\t\treturn `[resource ${resource.uri}]`;\n\t\t}\n\t\tcase \"resource_link\":\n\t\t\treturn `[resource_link ${content.uri ?? \"\"}${content.name ? ` — ${content.name}` : \"\"}]`;\n\t\tcase \"image\":\n\t\t\treturn `[image ${content.mimeType ?? \"unknown\"}${content.data ? \" (embedded)\" : \"\"}]`;\n\t\tcase \"audio\":\n\t\t\treturn `[audio ${content.mimeType ?? \"unknown\"}]`;\n\t\tdefault:\n\t\t\treturn \"\";\n\t}\n}\n\n/**\n * Build the nexus command definition for a single MCP prompt. Registered once\n * per prompt at extension load time from the metadata cache; the same\n * factory is reused by tests to invoke the handler without a running nexus.\n */\nexport function createPromptCommand(\n\tnexus: ExtensionAPI,\n\tgetState: () => McpExtensionState | null,\n\tmetadata: PromptMetadata,\n) {\n\tconst description = buildCommandDescription(metadata);\n\n\treturn {\n\t\tdescription,\n\t\thandler: async (args: string, ctx: ExtensionCommandContext) => {\n\t\t\tconst state = getState();\n\t\t\tif (!state) {\n\t\t\t\tif (ctx.hasUI) ctx.ui.notify(\"MCP not initialized\", \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst liveMetadata = findLivePromptMetadata(state, metadata.serverName, metadata.originalName);\n\t\t\tif (state.promptMetadataLive?.has(metadata.serverName) && !liveMetadata) {\n\t\t\t\tif (ctx.hasUI) {\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`MCP prompt \"${metadata.originalName}\" is no longer advertised by server \"${metadata.serverName}\". Run /mcp reconnect to refresh.`,\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst live = liveMetadata ?? metadata;\n\t\t\tconst parsed = parsePromptArgs(args ?? \"\");\n\t\t\tconst resolved = resolvePromptArgs(live, parsed);\n\t\t\tif (!resolved.ok) {\n\t\t\t\tif (ctx.hasUI) ctx.ui.notify(resolved.error ?? \"Invalid prompt arguments\", \"error\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst promptArgs = resolved.args ?? {};\n\n\t\t\tif (!state.config.mcpServers[metadata.serverName]) {\n\t\t\t\tif (ctx.hasUI) {\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`MCP prompt \"${live.originalName}\" is no longer configured. Run /mcp reconnect to refresh.`,\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst connected = await lazyConnect(state, metadata.serverName, ctx.signal);\n\t\t\tif (!connected) {\n\t\t\t\tif (ctx.hasUI) {\n\t\t\t\t\tconst conn = state.manager.getConnection(metadata.serverName);\n\t\t\t\t\tconst message =\n\t\t\t\t\t\tconn?.status === \"needs-auth\"\n\t\t\t\t\t\t\t? `MCP server \"${metadata.serverName}\" needs authentication. Run /mcp-auth ${metadata.serverName}.`\n\t\t\t\t\t\t\t: `MCP server \"${metadata.serverName}\" is not available. Run /mcp reconnect ${metadata.serverName}.`;\n\t\t\t\t\tctx.ui.notify(message, \"error\");\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst refreshed = findLivePromptMetadata(state, metadata.serverName, metadata.originalName);\n\t\t\tif (state.promptMetadataLive?.has(metadata.serverName) && !refreshed) {\n\t\t\t\tif (ctx.hasUI) {\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`MCP prompt \"${metadata.originalName}\" is no longer advertised by server \"${metadata.serverName}\". Run /mcp reconnect to refresh.`,\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst dispatchMetadata = refreshed ?? live;\n\t\t\tlet result: GetPromptResult;\n\t\t\ttry {\n\t\t\t\tresult = await state.manager.getPrompt(\n\t\t\t\t\tmetadata.serverName,\n\t\t\t\t\tdispatchMetadata.originalName,\n\t\t\t\t\tObject.keys(promptArgs).length > 0 ? promptArgs : undefined,\n\t\t\t\t\tctx.signal,\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tlogger.debug(`MCP prompt \"${live.originalName}\" on ${metadata.serverName} failed: ${message}`);\n\t\t\t\tif (ctx.hasUI) {\n\t\t\t\t\tctx.ui.notify(`MCP prompt \"${live.originalName}\" failed: ${message}`, \"error\");\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst text = formatPromptResult(result);\n\t\t\tif (!text) {\n\t\t\t\tif (ctx.hasUI) {\n\t\t\t\t\tctx.ui.notify(`MCP prompt \"${live.originalName}\" returned no text content.`, \"warning\");\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tnexus.sendUserMessage(text);\n\t\t},\n\t};\n}\n\nfunction findLivePromptMetadata(\n\tstate: McpExtensionState,\n\tserverName: string,\n\toriginalName: string,\n): PromptMetadata | undefined {\n\treturn state.promptMetadata?.get(serverName)?.find((p) => p.originalName === originalName);\n}\n\nfunction buildCommandDescription(metadata: PromptMetadata): string {\n\tconst base = metadata.description || metadata.title || `MCP prompt from ${metadata.serverName}`;\n\treturn truncateAtWord(`MCP: ${base}`, 120) || `MCP prompt from ${metadata.serverName}`;\n}\n\n/**\n * Public helper used by `/mcp prompts` to render the list of prompts known\n * to the adapter, whether from a live connection or the metadata cache.\n */\nexport function listAllPromptMetadata(state: McpExtensionState): PromptMetadata[] {\n\tconst flat: PromptMetadata[] = [];\n\tfor (const list of state.promptMetadata?.values() ?? []) flat.push(...list);\n\tflat.sort((a, b) => a.commandName.localeCompare(b.commandName));\n\treturn flat;\n}\n\n// Re-exported so index.ts can format cache-only prompt-command names without\n// duplicating the namespace logic.\nexport { formatPromptCommandName };\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AgentToolResult, ToolInfo } from "../../core/extensions/types.ts";
|
|
2
|
+
import type { McpExtensionState } from "./state.ts";
|
|
3
|
+
type ProxyToolResult = AgentToolResult<Record<string, unknown>>;
|
|
4
|
+
export declare function executeUiMessages(state: McpExtensionState): ProxyToolResult;
|
|
5
|
+
export declare function executeStatus(state: McpExtensionState): ProxyToolResult;
|
|
6
|
+
export declare function executeAuthStart(state: McpExtensionState, serverName: string, signal?: AbortSignal): Promise<ProxyToolResult>;
|
|
7
|
+
export declare function executeAuthComplete(state: McpExtensionState, serverName: string, input: string, signal?: AbortSignal): Promise<ProxyToolResult>;
|
|
8
|
+
export declare function executeDescribe(state: McpExtensionState, toolName: string): ProxyToolResult;
|
|
9
|
+
export declare function executeSearch(state: McpExtensionState, query: string, regex?: boolean, server?: string, includeSchemas?: boolean, limit?: number, offset?: number): ProxyToolResult;
|
|
10
|
+
export declare function executeList(state: McpExtensionState, server: string): ProxyToolResult;
|
|
11
|
+
export declare function executeInstructions(state: McpExtensionState, server: string): ProxyToolResult;
|
|
12
|
+
export declare function executeConnect(state: McpExtensionState, serverName: string, signal?: AbortSignal): Promise<ProxyToolResult>;
|
|
13
|
+
export declare function executeCall(state: McpExtensionState, toolName: string, args?: Record<string, unknown>, serverOverride?: string, getNexusTools?: () => ToolInfo[], signal?: AbortSignal, origin?: "proxy" | "script"): Promise<ProxyToolResult>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=proxy-modes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy-modes.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/proxy-modes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAmBhF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAgBpD,KAAK,eAAe,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AA8KhE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,eAAe,CAsF3E;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,GAAG,eAAe,CAsEvE;AAED,wBAAsB,gBAAgB,CACrC,KAAK,EAAE,iBAAiB,EACxB,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,eAAe,CAAC,CAuD1B;AAED,wBAAsB,mBAAmB,CACxC,KAAK,EAAE,iBAAiB,EACxB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,eAAe,CAAC,CAuD1B;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,GAAG,eAAe,CA+D3F;AAED,wBAAgB,aAAa,CAC5B,KAAK,EAAE,iBAAiB,EACxB,KAAK,EAAE,MAAM,EACb,KAAK,CAAC,EAAE,OAAO,EACf,MAAM,CAAC,EAAE,MAAM,EACf,cAAc,CAAC,EAAE,OAAO,EACxB,KAAK,SAAK,EACV,MAAM,SAAI,GACR,eAAe,CA+IjB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,CAkGrF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,CAqC7F;AAED,wBAAsB,cAAc,CACnC,KAAK,EAAE,iBAAiB,EACxB,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,eAAe,CAAC,CAyF1B;AAED,wBAAsB,WAAW,CAChC,KAAK,EAAE,iBAAiB,EACxB,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,cAAc,CAAC,EAAE,MAAM,EACvB,aAAa,CAAC,EAAE,MAAM,QAAQ,EAAE,EAChC,MAAM,CAAC,EAAE,WAAW,EACpB,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,GACzB,OAAO,CAAC,eAAe,CAAC,CAunB1B","sourcesContent":["import { type CallToolResult, type Client, UrlElicitationRequiredError } from \"@modelcontextprotocol/client\";\nimport { checkSync, type Diagnostics } from \"recheck\";\nimport type { AgentToolResult, ToolInfo } from \"../../core/extensions/types.ts\";\nimport { abortable, throwIfAborted } from \"./abort.ts\";\nimport {\n\tclearFailure,\n\tgetFailureAgeSeconds,\n\tlazyConnect,\n\tmarkKeepAliveAfterConnect,\n\tnotifyToolMetadataUpdated,\n\trecordFailure,\n\tupdateMetadataCache,\n\tupdateServerMetadata,\n\tupdateStatusBar,\n} from \"./init.ts\";\nimport { authenticate, completeAuthFromInput, startAuth, supportsOAuth } from \"./mcp-auth-flow.ts\";\nimport { guardedMcpDetails, guardMcpOutput, resolveMcpOutputGuardOptions } from \"./mcp-output-guard.ts\";\nimport { reconstructPromptMetadata } from \"./metadata-cache.ts\";\nimport { combineAbortSignals, isAbortError } from \"./runtime-owner.ts\";\nimport { paginate, rankSuggestions, rankToolMatches, resolveSearchKeywords } from \"./search-ranking.ts\";\nimport { SessionRecoveryAuthRequiredError, withSessionRecovery } from \"./session-recovery.ts\";\nimport type { McpExtensionState } from \"./state.ts\";\nimport { ensureToolCallApproved, isToolCallApprovalRequired } from \"./tool-approval.ts\";\nimport { buildToolMetadata, findToolByName, formatSchema, getToolNames } from \"./tool-metadata.ts\";\nimport { resolveMcpResultContent, transformMcpContent, transformMcpResourceContents } from \"./tool-registrar.ts\";\nimport { renderTsShape } from \"./ts-shape.ts\";\nimport type { McpContent, ToolMetadata } from \"./types.ts\";\nimport { getServerPrefix, isServerDisabled, parseUiPromptHandoff } from \"./types.ts\";\nimport { maybeStartUiSession, summarizeUiSessionResult, type UiSessionRuntime } from \"./ui-session.ts\";\nimport {\n\tformatAuthRequiredMessage,\n\tformatMcpStatus,\n\tnormalizeToolArguments,\n\tresolveServerUrl,\n\ttruncateAtWord,\n} from \"./utils.ts\";\n\ntype ProxyToolResult = AgentToolResult<Record<string, unknown>>;\ntype ClientCallToolResult = Awaited<ReturnType<Client[\"callTool\"]>>;\ntype ClientReadResourceResult = Awaited<ReturnType<Client[\"readResource\"]>>;\n\nconst MAX_REGEX_SEARCH_QUERY_LENGTH = 256;\nconst INSTRUCTIONS_PREVIEW_LENGTH = 300;\nconst REGEX_SAFETY_CHECK_PARAMS = {\n\tattackTimeout: 50,\n\tincubationTimeout: 50,\n\ttimeout: 250,\n} as const;\n\ntype AutoAuthResult = { status: \"skipped\" } | { status: \"success\" } | { status: \"failed\"; message: string };\n\nfunction getToolMatches(metadata: ToolMetadata[] | undefined, toolName: string, exact: boolean): ToolMetadata[] {\n\tif (!metadata) return [];\n\tif (exact) return metadata.filter((tool) => tool.name === toolName);\n\tconst normalizedName = toolName.replace(/-/g, \"_\");\n\treturn metadata.filter((tool) => tool.name.replace(/-/g, \"_\") === normalizedName);\n}\n\nfunction getEnabledToolMatches(\n\tstate: McpExtensionState,\n\ttoolName: string,\n\texact: boolean,\n): { server: string; tool: ToolMetadata }[] {\n\tconst matches: { server: string; tool: ToolMetadata }[] = [];\n\tfor (const [server, metadata] of state.toolMetadata) {\n\t\tif (isServerDisabled(state.config.mcpServers[server])) continue;\n\t\tfor (const tool of getToolMatches(metadata, toolName, exact)) matches.push({ server, tool });\n\t}\n\treturn matches;\n}\n\nfunction getSingleToolMatch(\n\tmetadata: ToolMetadata[] | undefined,\n\ttoolName: string,\n): ToolMetadata | \"ambiguous\" | undefined {\n\tconst exactMatches = getToolMatches(metadata, toolName, true);\n\tconst matches = exactMatches.length > 0 ? exactMatches : getToolMatches(metadata, toolName, false);\n\treturn matches.length > 1 ? \"ambiguous\" : matches[0];\n}\n\nfunction ambiguousToolResult(mode: \"call\" | \"describe\", toolName: string): ProxyToolResult {\n\tconst message = `Tool \"${toolName}\" matches multiple servers. Specify a server.`;\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text: message }],\n\t\tdetails: { mode, error: \"ambiguous_tool\", requestedTool: toolName, message },\n\t};\n}\n\nfunction disabledResult(mode: string, serverName: string): ProxyToolResult {\n\tconst message = `Server \"${serverName}\" is disabled. Run /mcp enable ${serverName} and /reload to enable it.`;\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text: message }],\n\t\tdetails: { mode, error: \"server_disabled\", server: serverName, message },\n\t};\n}\n\nfunction getAuthRequiredMessage(\n\tstate: McpExtensionState,\n\tserverName: string,\n\tdefaultMessage = `Server \"${serverName}\" requires OAuth authentication. Run mcp({ action: \"auth-start\", server: \"${serverName}\" }) to get a browser URL, or /mcp-auth ${serverName} in an interactive local session.`,\n): string {\n\treturn formatAuthRequiredMessage(state.config, serverName, defaultMessage);\n}\n\nfunction getAuthFailedMessage(state: McpExtensionState, serverName: string, message: string): string {\n\tconst customGuidance = state.config.settings?.authRequiredMessage;\n\tif (customGuidance) {\n\t\treturn `OAuth authentication failed for \"${serverName}\": ${message}. ${getAuthRequiredMessage(state, serverName)}`;\n\t}\n\treturn `OAuth authentication failed for \"${serverName}\": ${message}. Run mcp({ action: \"auth-start\", server: \"${serverName}\" }) to get a browser URL, or /mcp-auth ${serverName} in an interactive local session.`;\n}\n\nfunction getRedirectPort(authorizationUrl: string): number | undefined {\n\ttry {\n\t\tconst redirectUri = new URL(authorizationUrl).searchParams.get(\"redirect_uri\");\n\t\tif (!redirectUri) return undefined;\n\t\tconst port = Number.parseInt(new URL(redirectUri).port, 10);\n\t\treturn Number.isInteger(port) ? port : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction formatManualAuthInstructions(serverName: string, authorizationUrl: string): string {\n\tconst port = getRedirectPort(authorizationUrl);\n\tconst portNote = port\n\t\t? `\\nThe redirect URL will use local port ${port}. On a remote server it is expected for that localhost page to fail locally; copy the address bar URL anyway.`\n\t\t: \"\";\n\n\treturn [\n\t\t`MCP OAuth required for \"${serverName}\".`,\n\t\t\"\",\n\t\t\"Open this URL in your local browser:\",\n\t\t\"\",\n\t\tauthorizationUrl,\n\t\t\"\",\n\t\t\"After approving, copy the full redirected localhost URL from your browser address bar and send it back with:\",\n\t\t`mcp({ action: \"auth-complete\", server: \"${serverName}\", args: { redirectUrl: \"PASTE_REDIRECT_URL_HERE\" } })`,\n\t\t\"\",\n\t\t'You can also pass just the `code` query parameter as `args: { code: \"PASTE_CODE_HERE\" }`. JSON-string args remain supported.',\n\t\tportNote.trimEnd(),\n\t]\n\t\t.filter(Boolean)\n\t\t.join(\"\\n\");\n}\n\nasync function attemptAutoAuth(\n\tstate: McpExtensionState,\n\tserverName: string,\n\tsignal?: AbortSignal,\n): Promise<AutoAuthResult> {\n\tif (state.config.settings?.autoAuth !== true) {\n\t\treturn { status: \"skipped\" };\n\t}\n\n\tconst definition = state.config.mcpServers[serverName];\n\tif (!definition || isServerDisabled(definition) || !supportsOAuth(definition)) {\n\t\treturn { status: \"skipped\" };\n\t}\n\n\tlet serverUrl: string | undefined;\n\ttry {\n\t\tserverUrl = resolveServerUrl(definition);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\treturn { status: \"failed\", message: getAuthFailedMessage(state, serverName, message) };\n\t}\n\tif (!serverUrl) {\n\t\treturn { status: \"skipped\" };\n\t}\n\n\tconst grantType = definition.oauth ? (definition.oauth.grantType ?? \"authorization_code\") : \"authorization_code\";\n\tif (!state.ui && grantType !== \"client_credentials\") {\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\tmessage: getAuthRequiredMessage(\n\t\t\t\tstate,\n\t\t\t\tserverName,\n\t\t\t\t`Server \"${serverName}\" requires OAuth authentication. Run mcp({ action: \"auth-start\", server: \"${serverName}\" }) to get a browser URL, or /mcp-auth ${serverName} in an interactive local session.`,\n\t\t\t),\n\t\t};\n\t}\n\n\ttry {\n\t\tif (state.authStorageOptions) {\n\t\t\tawait authenticate(\n\t\t\t\tserverName,\n\t\t\t\tserverUrl,\n\t\t\t\tdefinition,\n\t\t\t\tsignal\n\t\t\t\t\t? { authStorageOptions: state.authStorageOptions, signal, runtime: state.oauthRuntime }\n\t\t\t\t\t: { authStorageOptions: state.authStorageOptions, runtime: state.oauthRuntime },\n\t\t\t);\n\t\t} else {\n\t\t\tif (signal) {\n\t\t\t\tawait authenticate(serverName, serverUrl, definition, { signal, runtime: state.oauthRuntime });\n\t\t\t} else {\n\t\t\t\tawait authenticate(serverName, serverUrl, definition, { runtime: state.oauthRuntime });\n\t\t\t}\n\t\t}\n\t\treturn { status: \"success\" };\n\t} catch (error) {\n\t\tif (isAbortError(error, signal)) throw error;\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\treturn {\n\t\t\tstatus: \"failed\",\n\t\t\tmessage: getAuthFailedMessage(state, serverName, message),\n\t\t};\n\t}\n}\n\nexport function executeUiMessages(state: McpExtensionState): ProxyToolResult {\n\tconst sessions = state.completedUiSessions;\n\n\tif (sessions.length === 0) {\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\" as const, text: \"No UI session messages available.\" }],\n\t\t\tdetails: { sessions: 0 },\n\t\t};\n\t}\n\n\tconst output: string[] = [];\n\toutput.push(`UI Session Messages (${sessions.length} session${sessions.length > 1 ? \"s\" : \"\"}):\\n`);\n\n\tconst allPrompts: string[] = [];\n\tconst allIntents = sessions.flatMap((session) => session.messages.intents);\n\tconst allContexts = sessions.flatMap((session) => session.messages.contexts);\n\tconst parsedHandoffs: Array<{ intent: string; params: Record<string, unknown>; raw: string }> = [];\n\n\tfor (const session of sessions) {\n\t\tconst timestamp = session.completedAt.toLocaleTimeString();\n\t\toutput.push(`\\n## ${session.serverName} / ${session.toolName} (${timestamp}, ${session.reason})`);\n\n\t\tconst plainPrompts: string[] = [];\n\t\tfor (const prompt of session.messages.prompts) {\n\t\t\tallPrompts.push(prompt);\n\t\t\tconst handoff = parseUiPromptHandoff(prompt);\n\t\t\tif (handoff) {\n\t\t\t\tparsedHandoffs.push(handoff);\n\t\t\t} else {\n\t\t\t\tplainPrompts.push(prompt);\n\t\t\t}\n\t\t}\n\n\t\tif (plainPrompts.length > 0) {\n\t\t\toutput.push(\"\\n### Prompts:\");\n\t\t\tfor (const prompt of plainPrompts) {\n\t\t\t\toutput.push(`- ${prompt}`);\n\t\t\t}\n\t\t}\n\n\t\tconst intentsForSession = [\n\t\t\t...session.messages.intents,\n\t\t\t...session.messages.prompts\n\t\t\t\t.map((prompt) => parseUiPromptHandoff(prompt))\n\t\t\t\t.filter((handoff): handoff is NonNullable<typeof handoff> => !!handoff)\n\t\t\t\t.map((handoff) => ({ intent: handoff.intent, params: handoff.params })),\n\t\t];\n\n\t\tif (intentsForSession.length > 0) {\n\t\t\toutput.push(\"\\n### Intents:\");\n\t\t\tfor (const intent of intentsForSession) {\n\t\t\t\tconst params = intent.params ? ` (${JSON.stringify(intent.params)})` : \"\";\n\t\t\t\toutput.push(`- ${intent.intent}${params}`);\n\t\t\t}\n\t\t}\n\n\t\tconst contexts = session.messages.contexts;\n\t\tif (contexts.length > 0) {\n\t\t\toutput.push(\"\\n### Context updates:\");\n\t\t\tfor (const context of contexts) {\n\t\t\t\toutput.push(`- ${context.summary}${context.truncated ? \" (truncated)\" : \"\"}`);\n\t\t\t}\n\t\t}\n\n\t\tif (session.messages.notifications.length > 0) {\n\t\t\toutput.push(\"\\n### Notifications:\");\n\t\t\tfor (const notification of session.messages.notifications) {\n\t\t\t\toutput.push(`- ${notification}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst count = sessions.length;\n\tstate.completedUiSessions = [];\n\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text: output.join(\"\\n\") }],\n\t\tdetails: {\n\t\t\tsessions: count,\n\t\t\tprompts: allPrompts,\n\t\t\tintents: [...allIntents, ...parsedHandoffs.map(({ intent, params }) => ({ intent, params }))],\n\t\t\tcontexts: allContexts,\n\t\t\thandoffs: parsedHandoffs,\n\t\t\tcleared: true,\n\t\t},\n\t};\n}\n\nexport function executeStatus(state: McpExtensionState): ProxyToolResult {\n\tconst servers: Array<{\n\t\tname: string;\n\t\tstatus: string;\n\t\ttoolCount: number;\n\t\tfailedAgo: number | null;\n\t\tdisabled?: boolean;\n\t}> = [];\n\n\tfor (const name of Object.keys(state.config.mcpServers)) {\n\t\tconst definition = state.config.mcpServers[name];\n\t\tconst disabled = isServerDisabled(definition);\n\t\tconst connection = disabled ? undefined : state.manager.getConnection(name);\n\t\tconst metadata = disabled ? undefined : state.toolMetadata.get(name);\n\t\tconst toolCount = metadata?.length ?? 0;\n\t\tconst failedAgo = disabled ? null : getFailureAgeSeconds(state, name);\n\t\tlet status = disabled ? \"disabled\" : \"not connected\";\n\t\tif (!disabled && connection?.status === \"connected\") {\n\t\t\tstatus = \"connected\";\n\t\t} else if (!disabled && connection?.status === \"needs-auth\") {\n\t\t\tstatus = \"needs-auth\";\n\t\t} else if (!disabled && failedAgo !== null) {\n\t\t\tstatus = \"failed\";\n\t\t} else if (!disabled && metadata !== undefined) {\n\t\t\tstatus = \"cached\";\n\t\t}\n\n\t\tservers.push({ name, status, toolCount, failedAgo, ...(disabled ? { disabled: true } : {}) });\n\t}\n\n\tconst disabledCount = servers.filter((s) => s.disabled).length;\n\tconst enabledServers = servers.filter((s) => !s.disabled);\n\tconst totalTools = enabledServers.reduce((sum, s) => sum + s.toolCount, 0);\n\tconst connectedCount = enabledServers.filter((s) => s.status === \"connected\").length;\n\n\tlet text = `MCP: ${connectedCount}/${enabledServers.length} servers, ${totalTools} tools`;\n\tif (disabledCount > 0) text += ` (${disabledCount} disabled)`;\n\ttext += \"\\n\\n\";\n\tfor (const server of servers) {\n\t\tif (server.disabled) {\n\t\t\ttext += `⊘ ${server.name} (disabled)\\n`;\n\t\t\tcontinue;\n\t\t}\n\t\tif (server.status === \"connected\") {\n\t\t\ttext += `✓ ${server.name} (${server.toolCount} tools)\\n`;\n\t\t\tcontinue;\n\t\t}\n\t\tif (server.status === \"needs-auth\") {\n\t\t\ttext += `⚠ ${server.name} (needs auth)\\n`;\n\t\t\tcontinue;\n\t\t}\n\t\tif (server.status === \"cached\") {\n\t\t\ttext += `○ ${server.name} (${server.toolCount} tools, cached)\\n`;\n\t\t\tcontinue;\n\t\t}\n\t\tif (server.status === \"failed\") {\n\t\t\ttext += `✗ ${server.name} (failed ${server.failedAgo ?? 0}s ago)\\n`;\n\t\t\tcontinue;\n\t\t}\n\t\ttext += `○ ${server.name} (not connected)\\n`;\n\t}\n\n\tif (servers.length > 0) {\n\t\ttext += `\\nmcp({ server: \"name\" }) to list tools, mcp({ search: \"...\" }) to search`;\n\t}\n\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text: text.trim() }],\n\t\tdetails: { mode: \"status\", servers, totalTools, connectedCount, disabledCount },\n\t};\n}\n\nexport async function executeAuthStart(\n\tstate: McpExtensionState,\n\tserverName: string,\n\tsignal?: AbortSignal,\n): Promise<ProxyToolResult> {\n\tconst ownedSignal = combineAbortSignals(state.owner?.signal, signal);\n\tthrowIfAborted(ownedSignal);\n\tconst definition = state.config.mcpServers[serverName];\n\tif (!definition) {\n\t\treturn {\n\t\t\tcontent: [\n\t\t\t\t{ type: \"text\" as const, text: `Server \"${serverName}\" not found. Use mcp({}) to see available servers.` },\n\t\t\t],\n\t\t\tdetails: { mode: \"auth-start\", error: \"not_found\", server: serverName },\n\t\t};\n\t}\n\tif (isServerDisabled(definition)) return disabledResult(\"auth-start\", serverName);\n\n\ttry {\n\t\tconst serverUrl = resolveServerUrl(definition);\n\t\tif (!serverUrl || !supportsOAuth(definition)) {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: `Server \"${serverName}\" is not configured for OAuth over HTTP.` }],\n\t\t\t\tdetails: { mode: \"auth-start\", error: \"oauth_not_supported\", server: serverName },\n\t\t\t};\n\t\t}\n\n\t\tconst { authorizationUrl } = state.authStorageOptions\n\t\t\t? ownedSignal\n\t\t\t\t? await startAuth(serverName, serverUrl, definition, {\n\t\t\t\t\t\tauthStorageOptions: state.authStorageOptions,\n\t\t\t\t\t\tsignal: ownedSignal,\n\t\t\t\t\t\truntime: state.oauthRuntime,\n\t\t\t\t\t})\n\t\t\t\t: await startAuth(serverName, serverUrl, definition, {\n\t\t\t\t\t\tauthStorageOptions: state.authStorageOptions,\n\t\t\t\t\t\truntime: state.oauthRuntime,\n\t\t\t\t\t})\n\t\t\t: ownedSignal\n\t\t\t\t? await startAuth(serverName, serverUrl, definition, { signal: ownedSignal, runtime: state.oauthRuntime })\n\t\t\t\t: await startAuth(serverName, serverUrl, definition, { runtime: state.oauthRuntime });\n\t\tif (!authorizationUrl) {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: `OAuth authentication successful for \"${serverName}\".` }],\n\t\t\t\tdetails: { mode: \"auth-start\", server: serverName, authenticated: true },\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\" as const, text: formatManualAuthInstructions(serverName, authorizationUrl) }],\n\t\t\tdetails: { mode: \"auth-start\", server: serverName, authorizationUrl },\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\" as const, text: `Failed to start OAuth for \"${serverName}\": ${message}` }],\n\t\t\tdetails: { mode: \"auth-start\", error: \"auth_start_failed\", server: serverName, message },\n\t\t};\n\t}\n}\n\nexport async function executeAuthComplete(\n\tstate: McpExtensionState,\n\tserverName: string,\n\tinput: string,\n\tsignal?: AbortSignal,\n): Promise<ProxyToolResult> {\n\tconst ownedSignal = combineAbortSignals(state.owner?.signal, signal);\n\tthrowIfAborted(ownedSignal);\n\tconst definition = state.config.mcpServers[serverName];\n\tif (!definition) {\n\t\treturn {\n\t\t\tcontent: [\n\t\t\t\t{ type: \"text\" as const, text: `Server \"${serverName}\" not found. Use mcp({}) to see available servers.` },\n\t\t\t],\n\t\t\tdetails: { mode: \"auth-complete\", error: \"not_found\", server: serverName },\n\t\t};\n\t}\n\tif (isServerDisabled(definition)) return disabledResult(\"auth-complete\", serverName);\n\n\ttry {\n\t\tconst status = state.authStorageOptions\n\t\t\t? ownedSignal\n\t\t\t\t? await completeAuthFromInput(serverName, input, {\n\t\t\t\t\t\tauthStorageOptions: state.authStorageOptions,\n\t\t\t\t\t\tsignal: ownedSignal,\n\t\t\t\t\t\truntime: state.oauthRuntime,\n\t\t\t\t\t})\n\t\t\t\t: await completeAuthFromInput(serverName, input, {\n\t\t\t\t\t\tauthStorageOptions: state.authStorageOptions,\n\t\t\t\t\t\truntime: state.oauthRuntime,\n\t\t\t\t\t})\n\t\t\t: ownedSignal\n\t\t\t\t? await completeAuthFromInput(serverName, input, { signal: ownedSignal, runtime: state.oauthRuntime })\n\t\t\t\t: await completeAuthFromInput(serverName, input, { runtime: state.oauthRuntime });\n\t\tif (status !== \"authenticated\") {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: `OAuth authentication did not complete for \"${serverName}\".` }],\n\t\t\t\tdetails: { mode: \"auth-complete\", error: \"not_authenticated\", server: serverName, status },\n\t\t\t};\n\t\t}\n\n\t\tawait state.manager.close(serverName);\n\t\tclearFailure(state, serverName);\n\t\tupdateStatusBar(state);\n\t\treturn {\n\t\t\tcontent: [\n\t\t\t\t{\n\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\ttext: `OAuth authentication successful for \"${serverName}\". Run mcp({ connect: \"${serverName}\" }) to connect with the new token.`,\n\t\t\t\t},\n\t\t\t],\n\t\t\tdetails: { mode: \"auth-complete\", server: serverName, authenticated: true },\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\" as const, text: `Failed to complete OAuth for \"${serverName}\": ${message}` }],\n\t\t\tdetails: { mode: \"auth-complete\", error: \"auth_complete_failed\", server: serverName, message },\n\t\t};\n\t}\n}\n\nexport function executeDescribe(state: McpExtensionState, toolName: string): ProxyToolResult {\n\tconst exactMatches = getEnabledToolMatches(state, toolName, true);\n\tif (exactMatches.length > 1) return ambiguousToolResult(\"describe\", toolName);\n\tif (exactMatches.length === 0 && getEnabledToolMatches(state, toolName, false).length > 1) {\n\t\treturn ambiguousToolResult(\"describe\", toolName);\n\t}\n\n\tlet serverName = exactMatches[0]?.server;\n\tlet toolMeta = exactMatches[0]?.tool;\n\tlet disabledMatch: string | undefined;\n\n\tif (!toolMeta) {\n\t\tfor (const [server, metadata] of state.toolMetadata.entries()) {\n\t\t\tconst found = findToolByName(metadata, toolName);\n\t\t\tif (!found) continue;\n\t\t\tif (isServerDisabled(state.config.mcpServers[server])) {\n\t\t\t\tdisabledMatch ??= server;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tserverName = server;\n\t\t\ttoolMeta = found;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!serverName || !toolMeta) {\n\t\tif (disabledMatch) return disabledResult(\"describe\", disabledMatch);\n\t\tconst suggestions = rankSuggestions(state, toolName, 5);\n\t\tconst suggestionText = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(\", \")}` : \"\";\n\t\treturn {\n\t\t\tcontent: [\n\t\t\t\t{\n\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\ttext: `Tool \"${toolName}\" not found. Use mcp({ search: \"...\" }) to search.${suggestionText}`,\n\t\t\t\t},\n\t\t\t],\n\t\t\tdetails: { mode: \"describe\", error: \"tool_not_found\", requestedTool: toolName, suggestions },\n\t\t};\n\t}\n\n\tconst approvalMarker = isToolCallApprovalRequired(state.config, serverName, toolMeta, state.toolMetadata)\n\t\t? \" (requires approval)\"\n\t\t: \"\";\n\tlet text = `${toolMeta.name}${approvalMarker}\\n`;\n\ttext += `Server: ${serverName}\\n`;\n\tif (toolMeta.resourceUri) {\n\t\ttext += `Type: Resource (reads from ${toolMeta.resourceUri})\\n`;\n\t}\n\ttext += `\\n${toolMeta.description || \"(no description)\"}\\n`;\n\n\tif (toolMeta.inputSchema && !toolMeta.resourceUri) {\n\t\tconst shape = renderTsShape(toolMeta.inputSchema);\n\t\ttext += shape === null ? `\\nParameters:\\n${formatSchema(toolMeta.inputSchema)}` : `\\nShape:\\n${shape}`;\n\t} else if (toolMeta.resourceUri) {\n\t\ttext += `\\nNo parameters required (resource tool).`;\n\t} else {\n\t\ttext += `\\nNo parameters defined.`;\n\t}\n\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text: text.trim() }],\n\t\tdetails: { mode: \"describe\", tool: toolMeta, server: serverName },\n\t};\n}\n\nexport function executeSearch(\n\tstate: McpExtensionState,\n\tquery: string,\n\tregex?: boolean,\n\tserver?: string,\n\tincludeSchemas?: boolean,\n\tlimit = 12,\n\toffset = 0,\n): ProxyToolResult {\n\tconst showSchemas = includeSchemas !== false;\n\tif (server && isServerDisabled(state.config.mcpServers[server])) return disabledResult(\"search\", server);\n\n\tlet matches: Array<{ server: string; tool: ToolMetadata; score: number }>;\n\tif (regex) {\n\t\tlet pattern: RegExp;\n\t\ttry {\n\t\t\tif (query.length > MAX_REGEX_SEARCH_QUERY_LENGTH) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\ttext: `Regex query is too long; maximum length is ${MAX_REGEX_SEARCH_QUERY_LENGTH} characters.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: { mode: \"search\", error: \"query_too_long\", query, maxLength: MAX_REGEX_SEARCH_QUERY_LENGTH },\n\t\t\t\t};\n\t\t\t}\n\t\t\tpattern = new RegExp(query, \"i\");\n\t\t\tlet safety: Diagnostics;\n\t\t\ttry {\n\t\t\t\tsafety = checkSync(query, \"i\", REGEX_SAFETY_CHECK_PARAMS);\n\t\t\t} catch (error) {\n\t\t\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\" as const, text: \"Regex query rejected because safety analysis failed.\" }],\n\t\t\t\t\tdetails: { mode: \"search\", error: \"unsafe_pattern\", query, reason },\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (safety.status !== \"safe\") {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\" as const, text: `Regex query rejected as unsafe (${safety.status}).` }],\n\t\t\t\t\tdetails: { mode: \"search\", error: \"unsafe_pattern\", query, safetyStatus: safety.status },\n\t\t\t\t};\n\t\t\t}\n\t\t} catch {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: `Invalid regex: ${query}` }],\n\t\t\t\tdetails: { mode: \"search\", error: \"invalid_pattern\", query },\n\t\t\t};\n\t\t}\n\n\t\tmatches = [];\n\t\tconst globalPrefix = state.config.settings?.toolPrefix ?? \"server\";\n\t\tfor (const [serverName, metadata] of state.toolMetadata.entries()) {\n\t\t\tconst definition = state.config.mcpServers[serverName];\n\t\t\tif (isServerDisabled(definition)) continue;\n\t\t\tif (server && serverName !== server) continue;\n\t\t\tfor (const tool of metadata) {\n\t\t\t\tconst matched =\n\t\t\t\t\tpattern.test(tool.name) ||\n\t\t\t\t\tpattern.test(tool.description) ||\n\t\t\t\t\tresolveSearchKeywords(definition, tool.originalName, serverName, globalPrefix).some((keyword) =>\n\t\t\t\t\t\tpattern.test(keyword),\n\t\t\t\t\t);\n\t\t\t\tif (matched) matches.push({ server: serverName, tool, score: 0 });\n\t\t\t}\n\t\t}\n\t} else if (query.trim().length === 0) {\n\t\tif (!server) {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: \"Search query cannot be empty\" }],\n\t\t\t\tdetails: { mode: \"search\", error: \"empty_query\" },\n\t\t\t};\n\t\t}\n\t\tmatches = (state.toolMetadata.get(server) ?? [])\n\t\t\t.map((tool) => ({ server, tool, score: 0 }))\n\t\t\t.sort((a, b) => a.tool.name.localeCompare(b.tool.name));\n\t} else {\n\t\tmatches = rankToolMatches(state, query, server);\n\t}\n\n\tconst page = paginate(matches, offset, limit);\n\tif (page.total === 0) {\n\t\tconst connectingServers = server\n\t\t\t? state.config.mcpServers[server] && state.manager.isConnecting(server)\n\t\t\t\t? [server]\n\t\t\t\t: []\n\t\t\t: Object.keys(state.config.mcpServers)\n\t\t\t\t\t.filter((name) => !isServerDisabled(state.config.mcpServers[name]) && state.manager.isConnecting(name))\n\t\t\t\t\t.sort((a, b) => a.localeCompare(b));\n\t\tconst msg = server ? `No tools matching \"${query}\" in \"${server}\"` : `No tools matching \"${query}\"`;\n\t\tconst connectingMessage =\n\t\t\tconnectingServers.length === 1\n\t\t\t\t? ` Server \"${connectingServers[0]}\" is still connecting; retry in a moment.`\n\t\t\t\t: connectingServers.length > 1\n\t\t\t\t\t? ` Servers ${connectingServers.map((name) => `\"${name}\"`).join(\", \")} are still connecting; retry in a moment.`\n\t\t\t\t\t: \"\";\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\" as const, text: `${msg}${connectingMessage}` }],\n\t\t\tdetails: {\n\t\t\t\tmode: \"search\",\n\t\t\t\tmatches: [],\n\t\t\t\tcount: 0,\n\t\t\t\thasMore: false,\n\t\t\t\tnextOffset: null,\n\t\t\t\tquery,\n\t\t\t\t...(connectingServers.length > 0 ? { connectingServers } : {}),\n\t\t\t},\n\t\t};\n\t}\n\n\tlet text = `Found ${page.total} tool${page.total === 1 ? \"\" : \"s\"} matching \"${query}\":\\n\\n`;\n\tfor (const match of page.items) {\n\t\tconst approvalMarker = isToolCallApprovalRequired(state.config, match.server, match.tool, state.toolMetadata)\n\t\t\t? \" (requires approval)\"\n\t\t\t: \"\";\n\t\tif (showSchemas) {\n\t\t\ttext += `${match.tool.name}${approvalMarker}\\n`;\n\t\t\ttext += ` ${match.tool.description || \"(no description)\"}\\n`;\n\t\t\tif (match.tool.inputSchema && !match.tool.resourceUri) {\n\t\t\t\tconst shape = renderTsShape(match.tool.inputSchema);\n\t\t\t\ttext +=\n\t\t\t\t\tshape === null\n\t\t\t\t\t\t? `\\n Parameters:\\n${formatSchema(match.tool.inputSchema, \" \")}\\n`\n\t\t\t\t\t\t: `\\n Shape:\\n${shape\n\t\t\t\t\t\t\t\t.split(\"\\n\")\n\t\t\t\t\t\t\t\t.map((line) => ` ${line}`)\n\t\t\t\t\t\t\t\t.join(\"\\n\")}\\n`;\n\t\t\t} else if (match.tool.resourceUri) {\n\t\t\t\ttext += \" No parameters (resource tool).\\n\";\n\t\t\t}\n\t\t\ttext += \"\\n\";\n\t\t} else {\n\t\t\ttext += `- ${match.tool.name}${approvalMarker}`;\n\t\t\tif (match.tool.description) text += ` - ${truncateAtWord(match.tool.description, 50)}`;\n\t\t\ttext += \"\\n\";\n\t\t}\n\t}\n\tif (page.hasMore) text += `\\n${page.items.length} of ${page.total} — offset: ${page.nextOffset} for more\\n`;\n\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text: text.trim() }],\n\t\tdetails: {\n\t\t\tmode: \"search\",\n\t\t\tmatches: page.items.map((match) => ({ server: match.server, tool: match.tool.name, score: match.score })),\n\t\t\tcount: page.total,\n\t\t\thasMore: page.hasMore,\n\t\t\tnextOffset: page.nextOffset,\n\t\t\tquery,\n\t\t},\n\t};\n}\n\nexport function executeList(state: McpExtensionState, server: string): ProxyToolResult {\n\tconst definition = state.config.mcpServers[server];\n\tif (!definition) {\n\t\treturn {\n\t\t\tcontent: [\n\t\t\t\t{ type: \"text\" as const, text: `Server \"${server}\" not found. Use mcp({}) to see available servers.` },\n\t\t\t],\n\t\t\tdetails: { mode: \"list\", server, tools: [], count: 0, error: \"not_found\" },\n\t\t};\n\t}\n\tif (isServerDisabled(definition)) return disabledResult(\"list\", server);\n\n\tconst metadata = state.toolMetadata.get(server);\n\tconst toolNames = metadata?.map((m) => m.name) ?? [];\n\tconst connection = state.manager.getConnection(server);\n\tconst instructions = state.serverInstructions.get(server);\n\tlet instructionsText = \"\";\n\tif (instructions) {\n\t\tconst preview = truncateAtWord(instructions, INSTRUCTIONS_PREVIEW_LENGTH);\n\t\tinstructionsText = `\\n\\nServer instructions:\\n${preview}`;\n\t\tif (preview !== instructions) {\n\t\t\tinstructionsText += `\\nUse mcp({ instructions: \"${server}\" }) for the full text.`;\n\t\t}\n\t}\n\n\tif (toolNames.length === 0) {\n\t\tif (connection?.status === \"connected\") {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: `Server \"${server}\" has no tools.${instructionsText}` }],\n\t\t\t\tdetails: { mode: \"list\", server, tools: [], count: 0, hasInstructions: Boolean(instructions) },\n\t\t\t};\n\t\t}\n\t\tif (metadata !== undefined) {\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: `Server \"${server}\" has no cached tools (not connected).${instructionsText}`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: {\n\t\t\t\t\tmode: \"list\",\n\t\t\t\t\tserver,\n\t\t\t\t\ttools: [],\n\t\t\t\t\tcount: 0,\n\t\t\t\t\tcached: true,\n\t\t\t\t\thasInstructions: Boolean(instructions),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tcontent: [\n\t\t\t\t{\n\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\ttext: `Server \"${server}\" is configured but not connected. Use mcp({ connect: \"${server}\" }) or /mcp reconnect ${server} to retry.${instructionsText}`,\n\t\t\t\t},\n\t\t\t],\n\t\t\tdetails: {\n\t\t\t\tmode: \"list\",\n\t\t\t\tserver,\n\t\t\t\ttools: [],\n\t\t\t\tcount: 0,\n\t\t\t\terror: \"not_connected\",\n\t\t\t\thasInstructions: Boolean(instructions),\n\t\t\t},\n\t\t};\n\t}\n\n\tconst cachedNote = connection?.status === \"connected\" ? \"\" : \" (not connected, cached)\";\n\tlet text = `${server} (${toolNames.length} tools${cachedNote}):\\n\\n`;\n\n\tconst descMap = new Map<string, string>();\n\tif (metadata) {\n\t\tfor (const m of metadata) {\n\t\t\tdescMap.set(m.name, m.description);\n\t\t}\n\t}\n\n\tfor (const tool of toolNames) {\n\t\tconst desc = descMap.get(tool) ?? \"\";\n\t\tconst truncated = truncateAtWord(desc, 50);\n\t\ttext += `- ${tool}`;\n\t\tif (truncated) text += ` - ${truncated}`;\n\t\ttext += \"\\n\";\n\t}\n\n\ttext += instructionsText;\n\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text: text.trim() }],\n\t\tdetails: {\n\t\t\tmode: \"list\",\n\t\t\tserver,\n\t\t\ttools: toolNames,\n\t\t\tcount: toolNames.length,\n\t\t\thasInstructions: Boolean(instructions),\n\t\t},\n\t};\n}\n\nexport function executeInstructions(state: McpExtensionState, server: string): ProxyToolResult {\n\tconst definition = state.config.mcpServers[server];\n\tif (!definition) {\n\t\treturn {\n\t\t\tcontent: [\n\t\t\t\t{ type: \"text\" as const, text: `Server \"${server}\" not found. Use mcp({}) to see available servers.` },\n\t\t\t],\n\t\t\tdetails: { mode: \"instructions\", server, error: \"not_found\" },\n\t\t};\n\t}\n\tif (isServerDisabled(definition)) return disabledResult(\"instructions\", server);\n\n\tconst instructions = state.serverInstructions.get(server);\n\tif (instructions) {\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\" as const, text: `${server} instructions:\\n\\n${instructions}` }],\n\t\t\tdetails: { mode: \"instructions\", server, length: instructions.length },\n\t\t};\n\t}\n\n\tconst connection = state.manager.getConnection(server);\n\tif (connection?.status === \"connected\") {\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\" as const, text: `Server \"${server}\" does not provide instructions.` }],\n\t\t\tdetails: { mode: \"instructions\", server, error: \"no_instructions\" },\n\t\t};\n\t}\n\n\treturn {\n\t\tcontent: [\n\t\t\t{\n\t\t\t\ttype: \"text\" as const,\n\t\t\t\ttext: `No instructions cached for \"${server}\". Use mcp({ connect: \"${server}\" }) to connect and refresh.`,\n\t\t\t},\n\t\t],\n\t\tdetails: { mode: \"instructions\", server, error: \"not_connected\" },\n\t};\n}\n\nexport async function executeConnect(\n\tstate: McpExtensionState,\n\tserverName: string,\n\tsignal?: AbortSignal,\n): Promise<ProxyToolResult> {\n\tconst ownedSignal = combineAbortSignals(state.owner?.signal, signal);\n\tthrowIfAborted(ownedSignal);\n\tconst definition = state.config.mcpServers[serverName];\n\tif (!definition) {\n\t\treturn {\n\t\t\tcontent: [\n\t\t\t\t{ type: \"text\" as const, text: `Server \"${serverName}\" not found. Use mcp({}) to see available servers.` },\n\t\t\t],\n\t\t\tdetails: { mode: \"connect\", error: \"not_found\", server: serverName },\n\t\t};\n\t}\n\tif (isServerDisabled(definition)) return disabledResult(\"connect\", serverName);\n\n\ttry {\n\t\tif (state.ui) {\n\t\t\tstate.ui.setStatus(\"mcp\", formatMcpStatus(state.config, `connecting to ${serverName}...`));\n\t\t}\n\t\tconst currentConnection = state.manager.getConnection(serverName);\n\t\tlet connection =\n\t\t\tcurrentConnection?.status === \"connected\"\n\t\t\t\t? await state.manager.reconnect(serverName, definition, currentConnection, ownedSignal)\n\t\t\t\t: await state.manager.connect(serverName, definition, ownedSignal);\n\t\tif (connection.status === \"needs-auth\") {\n\t\t\tconst autoAuth = await attemptAutoAuth(state, serverName, ownedSignal);\n\t\t\tif (autoAuth.status === \"failed\") {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\" as const, text: autoAuth.message }],\n\t\t\t\t\tdetails: { mode: \"connect\", error: \"auth_required\", server: serverName, message: autoAuth.message },\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (autoAuth.status === \"success\") {\n\t\t\t\tawait state.manager.close(serverName);\n\t\t\t\tthrowIfAborted(ownedSignal);\n\t\t\t\tconnection = ownedSignal\n\t\t\t\t\t? await state.manager.connect(serverName, definition, ownedSignal)\n\t\t\t\t\t: await state.manager.connect(serverName, definition);\n\t\t\t}\n\t\t\tif (connection.status === \"needs-auth\") {\n\t\t\t\tconst message = getAuthRequiredMessage(state, serverName);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\" as const, text: message }],\n\t\t\t\t\tdetails: { mode: \"connect\", error: \"auth_required\", server: serverName, message },\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tconst prefix = state.config.settings?.toolPrefix ?? \"server\";\n\t\tconst { metadata } = buildToolMetadata(\n\t\t\tconnection.tools,\n\t\t\tconnection.resources,\n\t\t\tdefinition,\n\t\t\tserverName,\n\t\t\tprefix,\n\t\t\tstate.config.mcpServers,\n\t\t\tstate.toolMetadata,\n\t\t);\n\t\tstate.toolMetadata.set(serverName, metadata);\n\t\tif (!connection.promptDiscoveryFailed) {\n\t\t\tstate.promptMetadata?.set(\n\t\t\t\tserverName,\n\t\t\t\treconstructPromptMetadata(serverName, connection.prompts ?? [], prefix, definition),\n\t\t\t);\n\t\t\tstate.promptMetadataLive?.add(serverName);\n\t\t}\n\t\tif (connection.instructions) {\n\t\t\tstate.serverInstructions.set(serverName, connection.instructions);\n\t\t} else {\n\t\t\tstate.serverInstructions.delete(serverName);\n\t\t}\n\t\tupdateMetadataCache(state, serverName);\n\t\tnotifyToolMetadataUpdated(state, serverName, \"proxy-connect\");\n\t\tmarkKeepAliveAfterConnect(state, serverName);\n\t\tclearFailure(state, serverName);\n\t\tupdateStatusBar(state);\n\t\treturn executeList(state, serverName);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tif (!isAbortError(error, ownedSignal)) recordFailure(state, serverName, message);\n\t\tupdateStatusBar(state);\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\" as const, text: `Failed to connect to \"${serverName}\": ${message}` }],\n\t\t\tdetails: {\n\t\t\t\tmode: \"connect\",\n\t\t\t\terror: isAbortError(error, ownedSignal) ? \"aborted\" : \"connect_failed\",\n\t\t\t\tserver: serverName,\n\t\t\t\tmessage,\n\t\t\t},\n\t\t};\n\t}\n}\n\nexport async function executeCall(\n\tstate: McpExtensionState,\n\ttoolName: string,\n\targs?: Record<string, unknown>,\n\tserverOverride?: string,\n\tgetNexusTools?: () => ToolInfo[],\n\tsignal?: AbortSignal,\n\torigin?: \"proxy\" | \"script\",\n): Promise<ProxyToolResult> {\n\tconst ownedSignal = combineAbortSignals(state.owner?.signal, signal);\n\tthrowIfAborted(ownedSignal);\n\tlet serverName: string | undefined = serverOverride;\n\tlet toolMeta: ToolMetadata | undefined;\n\tlet autoAuthAttempted = false;\n\tconst prefixMode = state.config.settings?.toolPrefix ?? \"server\";\n\tconst disabledCallResult = (disabledServer: string, metadata?: ToolMetadata): ProxyToolResult => {\n\t\tif (!metadata) {\n\t\t\tconst message = `Server \"${disabledServer}\" is disabled. Run /mcp enable ${disabledServer} and /reload to enable it.`;\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: message }],\n\t\t\t\tdetails: {\n\t\t\t\t\tmode: \"call\",\n\t\t\t\t\terror: \"server_disabled\",\n\t\t\t\t\tserver: disabledServer,\n\t\t\t\t\trequestedTool: toolName,\n\t\t\t\t\tmessage,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tconst message = `Server \"${disabledServer}\" is disabled. Run /mcp enable ${disabledServer} and /reload to enable it.`;\n\t\tconst identity = metadata.resourceUri\n\t\t\t? { server: disabledServer, resourceUri: metadata.resourceUri }\n\t\t\t: { server: disabledServer, tool: metadata.originalName };\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\" as const, text: message }],\n\t\t\tdetails: { mode: \"call\", error: \"server_disabled\", ...identity, message },\n\t\t};\n\t};\n\n\tif (serverName && !state.config.mcpServers[serverName]) {\n\t\treturn {\n\t\t\tcontent: [\n\t\t\t\t{ type: \"text\" as const, text: `Server \"${serverName}\" not found. Use mcp({}) to see available servers.` },\n\t\t\t],\n\t\t\tdetails: { mode: \"call\", error: \"server_not_found\", server: serverName, requestedTool: toolName },\n\t\t};\n\t}\n\tif (serverName) {\n\t\tconst match = getSingleToolMatch(state.toolMetadata.get(serverName), toolName);\n\t\tif (match === \"ambiguous\") return ambiguousToolResult(\"call\", toolName);\n\t\ttoolMeta = match;\n\t\tif (isServerDisabled(state.config.mcpServers[serverName])) {\n\t\t\treturn disabledCallResult(serverName, toolMeta);\n\t\t}\n\t} else {\n\t\tconst exactMatches = getEnabledToolMatches(state, toolName, true);\n\t\tif (exactMatches.length > 1) return ambiguousToolResult(\"call\", toolName);\n\t\tif (exactMatches.length === 0 && getEnabledToolMatches(state, toolName, false).length > 1) {\n\t\t\treturn ambiguousToolResult(\"call\", toolName);\n\t\t}\n\n\t\tlet disabledMatch: { serverName: string; toolMeta: ToolMetadata } | undefined;\n\t\tfor (const [server, metadata] of state.toolMetadata.entries()) {\n\t\t\tconst found = metadata.find((tool) => tool.name === toolName);\n\t\t\tif (!found) continue;\n\t\t\tif (isServerDisabled(state.config.mcpServers[server])) {\n\t\t\t\tdisabledMatch ??= { serverName: server, toolMeta: found };\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tserverName = server;\n\t\t\ttoolMeta = found;\n\t\t\tbreak;\n\t\t}\n\t\tif (!toolMeta && !disabledMatch) {\n\t\t\tfor (const [server, metadata] of state.toolMetadata.entries()) {\n\t\t\t\tconst found = findToolByName(metadata, toolName);\n\t\t\t\tif (!found) continue;\n\t\t\t\tif (isServerDisabled(state.config.mcpServers[server])) {\n\t\t\t\t\tdisabledMatch ??= { serverName: server, toolMeta: found };\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tserverName = server;\n\t\t\t\ttoolMeta = found;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!toolMeta && disabledMatch) return disabledCallResult(disabledMatch.serverName, disabledMatch.toolMeta);\n\t}\n\n\tif (serverName && !toolMeta) {\n\t\tconst connected = await lazyConnect(state, serverName, ownedSignal);\n\t\tif (connected) {\n\t\t\tconst match = getSingleToolMatch(state.toolMetadata.get(serverName), toolName);\n\t\t\tif (match === \"ambiguous\") return ambiguousToolResult(\"call\", toolName);\n\t\t\ttoolMeta = match;\n\t\t} else {\n\t\t\tconst needsAuthConnection = state.manager.getConnection(serverName);\n\t\t\tif (needsAuthConnection?.status === \"needs-auth\") {\n\t\t\t\tif (!autoAuthAttempted) {\n\t\t\t\t\tautoAuthAttempted = true;\n\t\t\t\t\tconst autoAuth = await attemptAutoAuth(state, serverName, ownedSignal);\n\t\t\t\t\tif (autoAuth.status === \"failed\") {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: autoAuth.message }],\n\t\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\t\tmode: \"call\",\n\t\t\t\t\t\t\t\terror: \"auth_required\",\n\t\t\t\t\t\t\t\tserver: serverName,\n\t\t\t\t\t\t\t\trequestedTool: toolName,\n\t\t\t\t\t\t\t\tmessage: autoAuth.message,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tif (autoAuth.status === \"success\") {\n\t\t\t\t\t\tawait state.manager.close(serverName);\n\t\t\t\t\t\tclearFailure(state, serverName);\n\t\t\t\t\t\tconst connectedAfterAuth = await lazyConnect(state, serverName, ownedSignal);\n\t\t\t\t\t\tif (connectedAfterAuth) {\n\t\t\t\t\t\t\tconst match = getSingleToolMatch(state.toolMetadata.get(serverName), toolName);\n\t\t\t\t\t\t\tif (match === \"ambiguous\") return ambiguousToolResult(\"call\", toolName);\n\t\t\t\t\t\t\ttoolMeta = match;\n\t\t\t\t\t\t\tif (!toolMeta) {\n\t\t\t\t\t\t\t\tconst suggestions = rankSuggestions(state, toolName, 5);\n\t\t\t\t\t\t\t\tconst suggestionText = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(\", \")}` : \"\";\n\t\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\t\t\t\t\ttext: `Tool \"${toolName}\" not found on \"${serverName}\" after reconnect.${suggestionText}`,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\t\t\t\tmode: \"call\",\n\t\t\t\t\t\t\t\t\t\terror: \"tool_not_found_after_reconnect\",\n\t\t\t\t\t\t\t\t\t\tserver: serverName,\n\t\t\t\t\t\t\t\t\t\trequestedTool: toolName,\n\t\t\t\t\t\t\t\t\t\tsuggestions,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!toolMeta && state.manager.getConnection(serverName)?.status === \"needs-auth\") {\n\t\t\t\t\tconst message = getAuthRequiredMessage(state, serverName);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: message }],\n\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\tmode: \"call\",\n\t\t\t\t\t\t\terror: \"auth_required\",\n\t\t\t\t\t\t\tserver: serverName,\n\t\t\t\t\t\t\trequestedTool: toolName,\n\t\t\t\t\t\t\tmessage,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!toolMeta) {\n\t\t\t\tconst failedAgo = getFailureAgeSeconds(state, serverName);\n\t\t\t\tif (failedAgo !== null) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\t\ttext: `Server \"${serverName}\" not available (last failed ${failedAgo}s ago)`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdetails: { mode: \"call\", error: \"server_backoff\", server: serverName, requestedTool: toolName },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlet prefixMatchedServer: string | undefined;\n\n\tif (!serverName && !toolMeta && prefixMode !== \"none\") {\n\t\tconst lazyExactMatches: { serverName: string; toolMeta: ToolMetadata }[] = [];\n\t\tconst lazyFallbackMatches: { serverName: string; toolMeta: ToolMetadata }[] = [];\n\t\tconst candidates = Object.keys(state.config.mcpServers)\n\t\t\t.filter((name) => !isServerDisabled(state.config.mcpServers[name]))\n\t\t\t.map((name) => ({ name, prefix: getServerPrefix(name, prefixMode) }))\n\t\t\t.filter((c) => c.prefix && toolName.startsWith(`${c.prefix}_`))\n\t\t\t.sort((a, b) => b.prefix.length - a.prefix.length);\n\n\t\tfor (const { name: configuredServer } of candidates) {\n\t\t\tconst existingConnection = state.manager.getConnection(configuredServer);\n\t\t\tconst failedAgo = getFailureAgeSeconds(state, configuredServer);\n\t\t\tif (failedAgo !== null && existingConnection?.status !== \"needs-auth\") continue;\n\n\t\t\tlet connected = await lazyConnect(state, configuredServer, ownedSignal);\n\t\t\tif (\n\t\t\t\t!connected &&\n\t\t\t\tstate.manager.getConnection(configuredServer)?.status === \"needs-auth\" &&\n\t\t\t\t!autoAuthAttempted\n\t\t\t) {\n\t\t\t\tautoAuthAttempted = true;\n\t\t\t\tconst autoAuth = await attemptAutoAuth(state, configuredServer, ownedSignal);\n\t\t\t\tif (autoAuth.status === \"failed\") {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: autoAuth.message }],\n\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\tmode: \"call\",\n\t\t\t\t\t\t\terror: \"auth_required\",\n\t\t\t\t\t\t\tserver: configuredServer,\n\t\t\t\t\t\t\trequestedTool: toolName,\n\t\t\t\t\t\t\tmessage: autoAuth.message,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (autoAuth.status === \"success\") {\n\t\t\t\t\tawait state.manager.close(configuredServer);\n\t\t\t\t\tclearFailure(state, configuredServer);\n\t\t\t\t\tconnected = await lazyConnect(state, configuredServer, ownedSignal);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!connected) continue;\n\t\t\tif (!prefixMatchedServer) prefixMatchedServer = configuredServer;\n\t\t\tconst metadata = state.toolMetadata.get(configuredServer);\n\t\t\tconst exactMatches = getToolMatches(metadata, toolName, true);\n\t\t\tif (exactMatches.length > 1) return ambiguousToolResult(\"call\", toolName);\n\t\t\tif (exactMatches.length === 1) {\n\t\t\t\tlazyExactMatches.push({ serverName: configuredServer, toolMeta: exactMatches[0]! });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst fallbackMatches = getToolMatches(metadata, toolName, false);\n\t\t\tif (fallbackMatches.length > 1) return ambiguousToolResult(\"call\", toolName);\n\t\t\tif (fallbackMatches.length === 1)\n\t\t\t\tlazyFallbackMatches.push({ serverName: configuredServer, toolMeta: fallbackMatches[0]! });\n\t\t}\n\t\tconst lazyMatches = lazyExactMatches.length > 0 ? lazyExactMatches : lazyFallbackMatches;\n\t\tif (lazyMatches.length > 1) return ambiguousToolResult(\"call\", toolName);\n\t\tif (lazyMatches.length === 1) {\n\t\t\tserverName = lazyMatches[0]!.serverName;\n\t\t\ttoolMeta = lazyMatches[0]!.toolMeta;\n\t\t}\n\t}\n\n\tif (!serverName || !toolMeta) {\n\t\tconst nativeTool = !serverOverride\n\t\t\t? getNexusTools?.().find((tool) => tool.name === toolName && tool.name !== \"mcp\")\n\t\t\t: undefined;\n\t\tif (nativeTool) {\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: `\"${toolName}\" is a native Nexus tool. Call ${toolName} directly instead of using mcp({ tool: \"${toolName}\" }).`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: { mode: \"call\", error: \"native_tool\", requestedTool: toolName },\n\t\t\t};\n\t\t}\n\n\t\tconst hintServer = serverName ?? prefixMatchedServer;\n\t\tconst available = hintServer ? getToolNames(state, hintServer) : [];\n\t\tlet msg = `Tool \"${toolName}\" not found.`;\n\t\tif (available.length > 0) {\n\t\t\tmsg += ` Server \"${hintServer}\" has: ${available.join(\", \")}`;\n\t\t} else {\n\t\t\tmsg += ` Use mcp({ search: \"...\" }) to search.`;\n\t\t}\n\t\tconst suggestions = rankSuggestions(state, toolName, 5);\n\t\tif (suggestions.length > 0) msg += ` Did you mean: ${suggestions.join(\", \")}`;\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\" as const, text: msg }],\n\t\t\tdetails: { mode: \"call\", error: \"tool_not_found\", requestedTool: toolName, hintServer, suggestions },\n\t\t};\n\t}\n\n\tconst callIdentity = toolMeta.resourceUri\n\t\t? { server: serverName, resourceUri: toolMeta.resourceUri }\n\t\t: { server: serverName, tool: toolMeta.originalName };\n\n\tlet connection = state.manager.getConnection(serverName);\n\tif (connection?.status === \"needs-auth\") {\n\t\tif (!autoAuthAttempted) {\n\t\t\tautoAuthAttempted = true;\n\t\t\tconst autoAuth = await attemptAutoAuth(state, serverName, ownedSignal);\n\t\t\tif (autoAuth.status === \"failed\") {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\" as const, text: autoAuth.message }],\n\t\t\t\t\tdetails: { mode: \"call\", error: \"auth_required\", ...callIdentity, message: autoAuth.message },\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (autoAuth.status === \"success\") {\n\t\t\t\tawait state.manager.close(serverName);\n\t\t\t\tclearFailure(state, serverName);\n\t\t\t\tconnection = state.manager.getConnection(serverName);\n\t\t\t}\n\t\t}\n\n\t\tif (connection?.status === \"needs-auth\") {\n\t\t\tconst message = getAuthRequiredMessage(state, serverName);\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: message }],\n\t\t\t\tdetails: { mode: \"call\", error: \"auth_required\", ...callIdentity, message },\n\t\t\t};\n\t\t}\n\t}\n\tif (!connection || connection.status !== \"connected\") {\n\t\tconst failedAgo = getFailureAgeSeconds(state, serverName);\n\t\tif (failedAgo !== null) {\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{ type: \"text\" as const, text: `Server \"${serverName}\" not available (last failed ${failedAgo}s ago)` },\n\t\t\t\t],\n\t\t\t\tdetails: { mode: \"call\", error: \"server_backoff\", ...callIdentity },\n\t\t\t};\n\t\t}\n\n\t\tconst definition = state.config.mcpServers[serverName];\n\t\tif (!definition) {\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: `Server \"${serverName}\" not connected` }],\n\t\t\t\tdetails: { mode: \"call\", error: \"server_not_connected\", ...callIdentity },\n\t\t\t};\n\t\t}\n\n\t\ttry {\n\t\t\tif (state.ui) {\n\t\t\t\tstate.ui.setStatus(\"mcp\", formatMcpStatus(state.config, `connecting to ${serverName}...`));\n\t\t\t}\n\t\t\tconnection = await state.manager.connect(serverName, definition, ownedSignal);\n\t\t\tif (connection.status === \"needs-auth\") {\n\t\t\t\tif (!autoAuthAttempted) {\n\t\t\t\t\tautoAuthAttempted = true;\n\t\t\t\t\tconst autoAuth = await attemptAutoAuth(state, serverName, ownedSignal);\n\t\t\t\t\tif (autoAuth.status === \"failed\") {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: autoAuth.message }],\n\t\t\t\t\t\t\tdetails: { mode: \"call\", error: \"auth_required\", ...callIdentity, message: autoAuth.message },\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t\tif (autoAuth.status === \"success\") {\n\t\t\t\t\t\tawait state.manager.close(serverName);\n\t\t\t\t\t\tconnection = await state.manager.connect(serverName, definition, ownedSignal);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (connection.status === \"needs-auth\") {\n\t\t\t\t\tconst message = getAuthRequiredMessage(state, serverName);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\" as const, text: message }],\n\t\t\t\t\t\tdetails: { mode: \"call\", error: \"auth_required\", ...callIdentity, message },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t\tclearFailure(state, serverName);\n\t\t\tupdateServerMetadata(state, serverName);\n\t\t\tupdateMetadataCache(state, serverName);\n\t\t\tnotifyToolMetadataUpdated(state, serverName, \"proxy-call-reconnect\");\n\t\t\tmarkKeepAliveAfterConnect(state, serverName);\n\t\t\tupdateStatusBar(state);\n\t\t\tconst match = getSingleToolMatch(state.toolMetadata.get(serverName), toolName);\n\t\t\tif (match === \"ambiguous\") return ambiguousToolResult(\"call\", toolName);\n\t\t\ttoolMeta = match;\n\t\t\tif (!toolMeta) {\n\t\t\t\tconst available = getToolNames(state, serverName);\n\t\t\t\tconst hint =\n\t\t\t\t\tavailable.length > 0\n\t\t\t\t\t\t? `Available tools on \"${serverName}\": ${available.join(\", \")}`\n\t\t\t\t\t\t: `Server \"${serverName}\" has no tools.`;\n\t\t\t\tconst suggestions = rankSuggestions(state, toolName, 5);\n\t\t\t\tconst suggestionText = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(\", \")}` : \"\";\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\t\ttext: `Tool \"${toolName}\" not found on \"${serverName}\" after reconnect. ${hint}${suggestionText}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tmode: \"call\",\n\t\t\t\t\t\terror: \"tool_not_found_after_reconnect\",\n\t\t\t\t\t\tserver: serverName,\n\t\t\t\t\t\trequestedTool: toolName,\n\t\t\t\t\t\tsuggestions,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tif (!isAbortError(error, ownedSignal)) recordFailure(state, serverName, message);\n\t\t\tupdateStatusBar(state);\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: `Failed to connect to \"${serverName}\": ${message}` }],\n\t\t\t\tdetails: {\n\t\t\t\t\tmode: \"call\",\n\t\t\t\t\terror: isAbortError(error, ownedSignal) ? \"aborted\" : \"connect_failed\",\n\t\t\t\t\t...callIdentity,\n\t\t\t\t\tmessage,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t}\n\n\tif (isServerDisabled(state.config.mcpServers[serverName])) {\n\t\treturn disabledCallResult(serverName, toolMeta);\n\t}\n\n\tconst normalizedArgs = toolMeta.resourceUri ? (args ?? {}) : normalizeToolArguments(args);\n\tconst approval = await ensureToolCallApproved(\n\t\tstate,\n\t\tserverName,\n\t\ttoolMeta,\n\t\tnormalizedArgs,\n\t\townedSignal,\n\t\torigin ?? (toolMeta.resourceUri ? \"resource\" : \"proxy\"),\n\t);\n\tif (approval.ok === false) {\n\t\tconst denied = approval.reason === \"denied\";\n\t\tconst message = denied\n\t\t\t? `The user declined approval to run MCP tool \"${toolMeta.originalName}\" on server \"${serverName}\".`\n\t\t\t: `MCP tool \"${toolMeta.originalName}\" on server \"${serverName}\" is approval-gated and requires an interactive session.`;\n\t\treturn {\n\t\t\tcontent: [{ type: \"text\" as const, text: message }],\n\t\t\tdetails: {\n\t\t\t\tmode: \"call\",\n\t\t\t\terror: denied ? \"approval_denied\" : \"approval_required\",\n\t\t\t\tserver: serverName,\n\t\t\t\ttool: toolMeta.originalName,\n\t\t\t},\n\t\t};\n\t}\n\n\tlet uiSession: UiSessionRuntime | null = null;\n\tconst requestOptions =\n\t\tstate.manager.getRequestOptions?.(serverName, ownedSignal) ?? (ownedSignal ? { signal: ownedSignal } : undefined);\n\n\tconst outputGuardOptions = resolveMcpOutputGuardOptions(state.config.settings);\n\tconst recoverAuthConnection = async () => {\n\t\tconst current = state.manager.getConnection(serverName);\n\t\tif (current?.status === \"connected\") return current;\n\n\t\tif (!autoAuthAttempted) {\n\t\t\tautoAuthAttempted = true;\n\t\t\tconst autoAuth = await attemptAutoAuth(state, serverName, ownedSignal);\n\t\t\tif (autoAuth.status === \"failed\") {\n\t\t\t\tthrow new SessionRecoveryAuthRequiredError(serverName, autoAuth.message);\n\t\t\t}\n\t\t\tif (autoAuth.status === \"success\") {\n\t\t\t\tconst definition = state.config.mcpServers[serverName];\n\t\t\t\tif (!definition) return undefined;\n\t\t\t\tconst afterAuth = state.manager.getConnection(serverName);\n\t\t\t\tif (afterAuth?.status === \"connected\") return afterAuth;\n\t\t\t\tif (afterAuth?.status === \"needs-auth\") {\n\t\t\t\t\tawait state.manager.close(serverName);\n\t\t\t\t}\n\t\t\t\tclearFailure(state, serverName);\n\t\t\t\tconnection = await state.manager.connect(serverName, definition, ownedSignal);\n\t\t\t\treturn connection;\n\t\t\t}\n\t\t}\n\t\treturn state.manager.getConnection(serverName);\n\t};\n\n\ttry {\n\t\tstate.manager.touch(serverName);\n\t\tstate.manager.incrementInFlight(serverName);\n\n\t\tif (toolMeta.resourceUri) {\n\t\t\tconst result = await withSessionRecovery<ClientReadResourceResult>(\n\t\t\t\t{\n\t\t\t\t\tmanager: state.manager,\n\t\t\t\t\tconfig: state.config,\n\t\t\t\t\t...(ownedSignal ? { signal: ownedSignal } : {}),\n\t\t\t\t\tonNeedsAuth: recoverAuthConnection,\n\t\t\t\t},\n\t\t\t\tserverName,\n\t\t\t\t(conn) => conn.client.readResource({ uri: toolMeta.resourceUri! }, requestOptions),\n\t\t\t);\n\t\t\tconst content = transformMcpResourceContents(result.contents ?? [], state.owner?.signal);\n\t\t\tconst guarded = await guardMcpOutput(\n\t\t\t\tcontent.length > 0 ? content : [{ type: \"text\" as const, text: \"(empty resource)\" }],\n\t\t\t\toutputGuardOptions,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tcontent: guarded.content,\n\t\t\t\tdetails: { mode: \"call\", ...callIdentity, ...guardedMcpDetails(guarded) },\n\t\t\t};\n\t\t}\n\n\t\tuiSession = toolMeta.uiResourceUri\n\t\t\t? await maybeStartUiSession(state, {\n\t\t\t\t\tserverName,\n\t\t\t\t\ttoolName: toolMeta.originalName,\n\t\t\t\t\ttoolArgs: normalizedArgs,\n\t\t\t\t\tuiResourceUri: toolMeta.uiResourceUri,\n\t\t\t\t\t...(toolMeta.uiStreamMode !== undefined ? { streamMode: toolMeta.uiStreamMode } : {}),\n\t\t\t\t\t...(signal ? { signal } : {}),\n\t\t\t\t\tonNeedsAuth: recoverAuthConnection,\n\t\t\t\t})\n\t\t\t: null;\n\n\t\tconst result = await withSessionRecovery<ClientCallToolResult>(\n\t\t\t{\n\t\t\t\tmanager: state.manager,\n\t\t\t\tconfig: state.config,\n\t\t\t\t...(ownedSignal ? { signal: ownedSignal } : {}),\n\t\t\t\tonNeedsAuth: recoverAuthConnection,\n\t\t\t},\n\t\t\tserverName,\n\t\t\t(conn) =>\n\t\t\t\tabortable(\n\t\t\t\t\tconn.client.callTool(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tname: toolMeta.originalName,\n\t\t\t\t\t\t\targuments: normalizedArgs,\n\t\t\t\t\t\t\t_meta: uiSession?.requestMeta,\n\t\t\t\t\t\t},\n\t\t\t\t\t\trequestOptions,\n\t\t\t\t\t),\n\t\t\t\t\townedSignal,\n\t\t\t\t),\n\t\t);\n\n\t\tif (toolMeta.uiResourceUri) {\n\t\t\tuiSession?.sendToolResult(result as unknown as CallToolResult);\n\n\t\t\tif (result.isError) {\n\t\t\t\tconst mcpContent = (result.content ?? []) as McpContent[];\n\t\t\t\tconst content = transformMcpContent(mcpContent, state.owner?.signal);\n\t\t\t\tconst outputContent = content.length > 0 ? content : [{ type: \"text\" as const, text: \"(empty result)\" }];\n\t\t\t\tconst schemaText = toolMeta.inputSchema\n\t\t\t\t\t? `\\n\\nExpected parameters:\\n${formatSchema(toolMeta.inputSchema)}`\n\t\t\t\t\t: \"\";\n\t\t\t\tconst guarded = await guardMcpOutput(outputContent, {\n\t\t\t\t\t...outputGuardOptions,\n\t\t\t\t\tprefix: \"Error: \",\n\t\t\t\t\tsuffix: schemaText,\n\t\t\t\t\temptyTextFallback: \"Tool execution failed\",\n\t\t\t\t\trawMcpResult: result,\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: guarded.content,\n\t\t\t\t\tdetails: { mode: \"call\", error: \"tool_error\", ...callIdentity, ...guardedMcpDetails(guarded) },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst content = resolveMcpResultContent(result as Record<string, unknown>, state.owner?.signal);\n\t\t\tconst outputContent = content.length > 0 ? content : [{ type: \"text\" as const, text: \"(empty result)\" }];\n\t\t\tconst uiSummary = summarizeUiSessionResult(uiSession);\n\t\t\tconst guarded = await guardMcpOutput(outputContent, {\n\t\t\t\t...outputGuardOptions,\n\t\t\t\tsuffix: `\\n\\n${uiSummary.message}`,\n\t\t\t\trawMcpResult: result,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tcontent: guarded.content,\n\t\t\t\tdetails: {\n\t\t\t\t\tmode: \"call\",\n\t\t\t\t\t...guardedMcpDetails(guarded),\n\t\t\t\t\t...callIdentity,\n\t\t\t\t\tuiOpen: uiSummary.uiOpen,\n\t\t\t\t\tuiViewer: uiSummary.uiViewer,\n\t\t\t\t\tuiUrl: uiSummary.uiUrl,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (result.isError) {\n\t\t\tconst mcpContent = (result.content ?? []) as McpContent[];\n\t\t\tconst content = transformMcpContent(mcpContent, state.owner?.signal);\n\t\t\tconst outputContent = content.length > 0 ? content : [{ type: \"text\" as const, text: \"(empty result)\" }];\n\t\t\tconst schemaText = toolMeta.inputSchema\n\t\t\t\t? `\\n\\nExpected parameters:\\n${formatSchema(toolMeta.inputSchema)}`\n\t\t\t\t: \"\";\n\t\t\tconst guarded = await guardMcpOutput(outputContent, {\n\t\t\t\t...outputGuardOptions,\n\t\t\t\tprefix: \"Error: \",\n\t\t\t\tsuffix: schemaText,\n\t\t\t\temptyTextFallback: \"Tool execution failed\",\n\t\t\t\trawMcpResult: result,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tcontent: guarded.content,\n\t\t\t\tdetails: { mode: \"call\", error: \"tool_error\", ...callIdentity, ...guardedMcpDetails(guarded) },\n\t\t\t};\n\t\t}\n\n\t\tconst content = resolveMcpResultContent(result as Record<string, unknown>, state.owner?.signal);\n\t\tconst outputContent = content.length > 0 ? content : [{ type: \"text\" as const, text: \"(empty result)\" }];\n\t\tconst guarded = await guardMcpOutput(outputContent, { ...outputGuardOptions, rawMcpResult: result });\n\t\treturn {\n\t\t\tcontent: guarded.content,\n\t\t\tdetails: { mode: \"call\", ...guardedMcpDetails(guarded), ...callIdentity },\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof SessionRecoveryAuthRequiredError) {\n\t\t\tconst message = error.authMessage ?? getAuthRequiredMessage(state, serverName);\n\t\t\tuiSession?.sendToolCancelled(message);\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: message }],\n\t\t\t\tdetails: { mode: \"call\", error: \"auth_required\", ...callIdentity, message, autoAuthAttempted },\n\t\t\t};\n\t\t}\n\t\tif (error instanceof UrlElicitationRequiredError) {\n\t\t\tconst action = await state.manager.handleUrlElicitationRequired(serverName, error);\n\t\t\tconst message =\n\t\t\t\taction === \"accept\"\n\t\t\t\t\t? \"The original MCP tool did not run. Complete the opened browser interaction, then retry the tool.\"\n\t\t\t\t\t: `The URL interaction was ${action === \"decline\" ? \"declined\" : \"cancelled\"}.`;\n\t\t\tuiSession?.sendToolCancelled(message);\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\" as const, text: message }],\n\t\t\t\tdetails: { mode: \"call\", error: \"url_elicitation_required\", ...callIdentity, action },\n\t\t\t};\n\t\t}\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tuiSession?.sendToolCancelled(message);\n\n\t\tconst schemaText = toolMeta.inputSchema ? `\\n\\nExpected parameters:\\n${formatSchema(toolMeta.inputSchema)}` : \"\";\n\t\tconst guarded = await guardMcpOutput([{ type: \"text\" as const, text: message }], {\n\t\t\t...outputGuardOptions,\n\t\t\tprefix: \"Failed to call tool: \",\n\t\t\tsuffix: schemaText,\n\t\t});\n\n\t\treturn {\n\t\t\tcontent: guarded.content,\n\t\t\tdetails: {\n\t\t\t\tmode: \"call\",\n\t\t\t\terror: isAbortError(error, ownedSignal) ? \"aborted\" : \"call_failed\",\n\t\t\t\t...callIdentity,\n\t\t\t\tmessage: guarded.outputGuard ? \"output truncated; see outputGuard.fullOutputPath\" : message,\n\t\t\t\t...guardedMcpDetails(guarded),\n\t\t\t},\n\t\t};\n\t} finally {\n\t\tif (uiSession?.reused) {\n\t\t\tuiSession.close();\n\t\t}\n\t\tstate.manager.decrementInFlight(serverName);\n\t\tstate.manager.touch(serverName);\n\t}\n}\n"]}
|