@herbertgao/pi-extensions 2026.9.11 → 2026.9.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/README.md +4 -4
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/node_modules/@herbertgao/pi-cc-extensions/package.json +1 -1
  4. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  5. package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
  6. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +5 -4
  7. package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +39 -16
  8. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  9. package/node_modules/@narumitw/pi-btw/README.md +21 -4
  10. package/node_modules/@narumitw/pi-btw/dist/index.ts +1668 -958
  11. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  12. package/node_modules/@narumitw/pi-btw/docs/workflows.md +10 -2
  13. package/node_modules/@narumitw/pi-btw/package.json +1 -1
  14. package/node_modules/@narumitw/pi-btw/src/btw.ts +17 -79
  15. package/node_modules/@narumitw/pi-btw/src/conversation-context.ts +74 -0
  16. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +196 -7
  17. package/node_modules/@narumitw/pi-btw/src/main-thread-updates.ts +40 -0
  18. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
  19. package/node_modules/@narumitw/pi-btw/src/settings.ts +49 -0
  20. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +9 -1
  21. package/node_modules/@narumitw/pi-btw/src/workspace-layout.ts +559 -0
  22. package/node_modules/pi-mcp-adapter/CHANGELOG.md +62 -0
  23. package/node_modules/pi-mcp-adapter/README.md +75 -7
  24. package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +193 -0
  25. package/node_modules/pi-mcp-adapter/cli.js +56 -5
  26. package/node_modules/pi-mcp-adapter/commands.ts +96 -5
  27. package/node_modules/pi-mcp-adapter/config.ts +73 -6
  28. package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +22 -28
  29. package/node_modules/pi-mcp-adapter/direct-tools.ts +13 -5
  30. package/node_modules/pi-mcp-adapter/dist/abort.d.ts +2 -0
  31. package/node_modules/pi-mcp-adapter/dist/abort.js +36 -0
  32. package/node_modules/pi-mcp-adapter/dist/abort.js.map +1 -0
  33. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +6 -0
  34. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +194 -0
  35. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +1 -0
  36. package/node_modules/pi-mcp-adapter/dist/config.d.ts +5 -0
  37. package/node_modules/pi-mcp-adapter/dist/config.js +75 -6
  38. package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
  39. package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +18 -0
  40. package/node_modules/pi-mcp-adapter/dist/consent-manager.js +88 -0
  41. package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +1 -0
  42. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +17 -0
  43. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +316 -0
  44. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +1 -0
  45. package/node_modules/pi-mcp-adapter/dist/errors.d.ts +131 -0
  46. package/node_modules/pi-mcp-adapter/dist/errors.js +278 -0
  47. package/node_modules/pi-mcp-adapter/dist/errors.js.map +1 -0
  48. package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +8 -0
  49. package/node_modules/pi-mcp-adapter/dist/http-ca.js +88 -0
  50. package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +1 -0
  51. package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +15 -0
  52. package/node_modules/pi-mcp-adapter/dist/jev-client.js +340 -0
  53. package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +1 -0
  54. package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +77 -0
  55. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +2 -0
  56. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +1 -0
  57. package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +22 -0
  58. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +85 -0
  59. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +1 -0
  60. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +2 -0
  61. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +55 -0
  62. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -0
  63. package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +56 -0
  64. package/node_modules/pi-mcp-adapter/dist/lifecycle.js +410 -0
  65. package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +1 -0
  66. package/node_modules/pi-mcp-adapter/dist/logger.d.ts +51 -0
  67. package/node_modules/pi-mcp-adapter/dist/logger.js +131 -0
  68. package/node_modules/pi-mcp-adapter/dist/logger.js.map +1 -0
  69. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +26 -0
  70. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +103 -0
  71. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +1 -0
  72. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +118 -0
  73. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +1098 -0
  74. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +1 -0
  75. package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +168 -0
  76. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +1117 -0
  77. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -0
  78. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +7 -140
  79. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -1
  80. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +53 -0
  81. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +441 -0
  82. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +1 -0
  83. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +148 -0
  84. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +689 -0
  85. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +1 -0
  86. package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +6 -0
  87. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +166 -0
  88. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +1 -0
  89. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +102 -0
  90. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +369 -0
  91. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +1 -0
  92. package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +95 -0
  93. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +242 -0
  94. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +1 -0
  95. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +2 -11
  96. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
  97. package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +6 -0
  98. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +505 -0
  99. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +1 -0
  100. package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +10 -0
  101. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +312 -0
  102. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +1 -0
  103. package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +13 -0
  104. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +97 -0
  105. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +1 -0
  106. package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +17 -0
  107. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +203 -0
  108. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +1 -0
  109. package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +11 -0
  110. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +172 -0
  111. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +1 -0
  112. package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +157 -0
  113. package/node_modules/pi-mcp-adapter/dist/server-manager.js +1743 -0
  114. package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -0
  115. package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +34 -0
  116. package/node_modules/pi-mcp-adapter/dist/session-approvals.js +140 -0
  117. package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +1 -0
  118. package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +26 -0
  119. package/node_modules/pi-mcp-adapter/dist/session-recovery.js +142 -0
  120. package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +1 -0
  121. package/node_modules/pi-mcp-adapter/dist/state.d.ts +73 -0
  122. package/node_modules/pi-mcp-adapter/dist/state.js +2 -0
  123. package/node_modules/pi-mcp-adapter/dist/state.js.map +1 -0
  124. package/node_modules/pi-mcp-adapter/dist/types.d.ts +36 -0
  125. package/node_modules/pi-mcp-adapter/dist/types.js +18 -0
  126. package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
  127. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +17 -0
  128. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +219 -0
  129. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +1 -0
  130. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +15 -0
  131. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +85 -0
  132. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +1 -0
  133. package/node_modules/pi-mcp-adapter/dist/utils.d.ts +1 -0
  134. package/node_modules/pi-mcp-adapter/dist/utils.js +13 -0
  135. package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
  136. package/node_modules/pi-mcp-adapter/elicitation-handler.ts +29 -19
  137. package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +85 -0
  138. package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +34 -0
  139. package/node_modules/pi-mcp-adapter/index.ts +129 -6
  140. package/node_modules/pi-mcp-adapter/init.ts +6 -19
  141. package/node_modules/pi-mcp-adapter/jev-client.ts +273 -0
  142. package/node_modules/pi-mcp-adapter/jev-contracts.ts +53 -0
  143. package/node_modules/pi-mcp-adapter/jev-key-store.ts +79 -0
  144. package/node_modules/pi-mcp-adapter/lifecycle.ts +14 -3
  145. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +7 -142
  146. package/node_modules/pi-mcp-adapter/mcp-code.ts +119 -11
  147. package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +4 -0
  148. package/node_modules/pi-mcp-adapter/mcp-references.ts +5 -3
  149. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +5 -0
  150. package/node_modules/pi-mcp-adapter/mcp-tasks.ts +467 -0
  151. package/node_modules/pi-mcp-adapter/metadata-cache.ts +2 -13
  152. package/node_modules/pi-mcp-adapter/namespace-tools.ts +1 -1
  153. package/node_modules/pi-mcp-adapter/package.json +17 -9
  154. package/node_modules/pi-mcp-adapter/proxy-modes.ts +319 -166
  155. package/node_modules/pi-mcp-adapter/request-headers-command.ts +6 -3
  156. package/node_modules/pi-mcp-adapter/search-ranking.ts +38 -7
  157. package/node_modules/pi-mcp-adapter/secure-keyring.ts +170 -0
  158. package/node_modules/pi-mcp-adapter/semantic-search.ts +186 -0
  159. package/node_modules/pi-mcp-adapter/server-manager.ts +293 -50
  160. package/node_modules/pi-mcp-adapter/session-approvals.ts +14 -0
  161. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +2 -0
  162. package/node_modules/pi-mcp-adapter/state.ts +3 -1
  163. package/node_modules/pi-mcp-adapter/tool-approval.ts +26 -4
  164. package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -14
  165. package/node_modules/pi-mcp-adapter/tool-registrar.ts +8 -6
  166. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +4 -1
  167. package/node_modules/pi-mcp-adapter/types.ts +53 -0
  168. package/node_modules/pi-mcp-adapter/ui-server.ts +17 -9
  169. package/node_modules/pi-mcp-adapter/utils.ts +16 -0
  170. package/node_modules/pi-multi-account/CHANGELOG.md +23 -0
  171. package/node_modules/pi-multi-account/README.md +38 -11
  172. package/node_modules/pi-multi-account/index.ts +371 -96
  173. package/node_modules/pi-multi-account/package.json +5 -4
  174. package/node_modules/pi-multi-account/provider-payload-stream.ts +36 -28
  175. package/node_modules/pi-multi-account/usage.ts +31 -2
  176. package/node_modules/pi-typesafe/README.md +3 -1
  177. package/node_modules/pi-typesafe/dist/auth.d.ts +10 -3
  178. package/node_modules/pi-typesafe/dist/auth.js +13 -7
  179. package/node_modules/pi-typesafe/dist/backends.d.ts +31 -0
  180. package/node_modules/pi-typesafe/dist/backends.js +33 -0
  181. package/node_modules/pi-typesafe/dist/client.d.ts +3 -9
  182. package/node_modules/pi-typesafe/dist/client.js +63 -39
  183. package/node_modules/pi-typesafe/dist/credentials.d.ts +11 -5
  184. package/node_modules/pi-typesafe/dist/credentials.js +15 -8
  185. package/node_modules/pi-typesafe/dist/extension.js +4 -1
  186. package/node_modules/pi-typesafe/dist/index.d.ts +1 -1
  187. package/node_modules/pi-typesafe/dist/index.js +1 -1
  188. package/node_modules/pi-typesafe/dist/login.d.ts +13 -7
  189. package/node_modules/pi-typesafe/dist/login.js +17 -8
  190. package/node_modules/pi-typesafe/dist/schema.js +19 -13
  191. package/node_modules/pi-typesafe/package.json +1 -1
  192. package/package.json +11 -10
@@ -1,32 +1,52 @@
1
1
  import type { AgentToolResult, ToolInfo } from "@earendil-works/pi-coding-agent";
2
- import { UrlElicitationRequiredError, type Client, type Progress, type RequestOptions } from "@modelcontextprotocol/client";
2
+ import { UrlElicitationRequiredError, type Client, type JsonSchemaType, type JsonSchemaValidator, type Progress, type RequestOptions } from "@modelcontextprotocol/client";
3
3
  import { createRequire } from "node:module";
4
4
  import type { McpExtensionState } from "./state.ts";
5
5
  import type { ToolMetadata, McpContent } from "./types.ts";
6
- import { getServerPrefix, isServerDisabled, parseUiPromptHandoff, type ServerEntry } from "./types.ts";
6
+ import { getServerPrefix, getToolNameCandidates, isServerDisabled, parseUiPromptHandoff, resolveToolPrefix, type ServerEntry } from "./types.ts";
7
7
  import { lazyConnect, markKeepAliveAfterConnect, notifyToolMetadataUpdated, updateServerMetadata, updateMetadataCache, getFailureAgeSeconds, updateStatusBar, clearFailure, recordFailure } from "./init.ts";
8
8
  import { abortable, throwIfAborted } from "./abort.ts";
9
9
  import { combineAbortSignals, isAbortError } from "./runtime-owner.ts";
10
- import { buildToolMetadata, getToolNames, findToolByName, formatSchema } from "./tool-metadata.ts";
10
+ import { buildToolMetadata, getToolNames, formatSchema } from "./tool-metadata.ts";
11
11
  import { renderTsShape } from "./ts-shape.ts";
12
12
  import { reconstructPromptMetadata } from "./metadata-cache.ts";
13
- import { resolveMcpResultContent, transformMcpContent, transformMcpResourceContents } from "./tool-registrar.ts";
13
+ import { resolveMcpResultContent, transformMcpResourceContents } from "./tool-registrar.ts";
14
14
  import { guardMcpOutput, guardedMcpDetails, resolveMcpOutputGuardOptions } from "./mcp-output-guard.ts";
15
15
  import { maybeStartUiSession, summarizeUiSessionResult, type UiSessionRuntime } from "./ui-session.ts";
16
16
  import { formatAuthRequiredMessage, formatMcpStatus, normalizeToolArguments, resolveServerUrl, truncateAtWord } from "./utils.ts";
17
17
  import { authenticate, completeAuthFromInput, getAuthStatus, startAuth, supportsOAuth } from "./mcp-auth-flow.ts";
18
18
  import { SessionRecoveryAuthRequiredError, withSessionRecovery } from "./session-recovery.ts";
19
+ import { callToolViaTaskSession } from "./mcp-tasks.ts";
19
20
  import { paginate, rankSuggestions, rankToolMatches, resolveSearchKeywords } from "./search-ranking.ts";
20
21
  import { ensureToolCallApproved, isToolCallApprovalRequired } from "./tool-approval.ts";
21
22
  import { isServerInActiveFailureBackoff } from "./failure-backoff.ts";
23
+ import { semanticSearch, type SemanticSearchBackend, type SemanticSearchEvaluator } from "./semantic-search.ts";
22
24
  import { getInputRequiredNeedsUiDetails } from "./errors.ts";
25
+ import { createJsonSchemaValidator } from "./json-schema-validator.ts";
23
26
 
24
27
  type ProxyToolResult = AgentToolResult<Record<string, unknown>>;
25
28
  type ClientCallToolResult = Awaited<ReturnType<Client["callTool"]>>;
26
29
  type ClientReadResourceResult = Awaited<ReturnType<Client["readResource"]>>;
27
30
 
28
31
  const require = createRequire(import.meta.url);
32
+ const proxyArgumentValidators = new WeakMap<object, JsonSchemaValidator<unknown>>();
29
33
  const MAX_REGEX_SEARCH_QUERY_LENGTH = 256;
34
+
35
+ function proxyArgumentValidationError(inputSchema: unknown, args: unknown): string | null {
36
+ if (!inputSchema || typeof inputSchema !== "object" || Array.isArray(inputSchema)) return null;
37
+ try {
38
+ let validate = proxyArgumentValidators.get(inputSchema);
39
+ if (!validate) {
40
+ validate = createJsonSchemaValidator().getValidator(inputSchema as JsonSchemaType);
41
+ proxyArgumentValidators.set(inputSchema, validate);
42
+ }
43
+ const result = validate(args);
44
+ return result.valid ? null : result.errorMessage ?? "arguments do not match the advertised input schema";
45
+ } catch {
46
+ // Preserve server-side validation for schema dialects the adapter cannot evaluate.
47
+ return null;
48
+ }
49
+ }
30
50
  const INSTRUCTIONS_PREVIEW_LENGTH = 300;
31
51
  const REGEX_SAFETY_CHECK_PARAMS = {
32
52
  attackTimeout: 50,
@@ -81,6 +101,17 @@ function getEnabledToolMatches(state: McpExtensionState, toolName: string, exact
81
101
  return matches;
82
102
  }
83
103
 
104
+ function getEnabledOriginalToolMatches(state: McpExtensionState, toolName: string): { server: string; tool: ToolMetadata }[] {
105
+ const matches: { server: string; tool: ToolMetadata }[] = [];
106
+ for (const [server, metadata] of state.toolMetadata) {
107
+ if (isServerDisabled(state.config.mcpServers[server])) continue;
108
+ for (const tool of metadata) {
109
+ if (tool.originalName === toolName) matches.push({ server, tool });
110
+ }
111
+ }
112
+ return matches;
113
+ }
114
+
84
115
  function serverBackoffResult(state: McpExtensionState, mode: string, serverName: string): ProxyToolResult {
85
116
  const failedAgo = getFailureAgeSeconds(state, serverName) ?? 0;
86
117
  const message = `Server "${serverName}" not available (last failed ${failedAgo}s ago)`;
@@ -90,28 +121,72 @@ function serverBackoffResult(state: McpExtensionState, mode: string, serverName:
90
121
  };
91
122
  }
92
123
 
93
- function getSingleToolMatch(metadata: ToolMetadata[] | undefined, toolName: string): ToolMetadata | "ambiguous" | undefined {
94
- const exactMatches = getToolMatches(metadata, toolName, true);
95
- const matches = exactMatches.length > 0 ? exactMatches : getToolMatches(metadata, toolName, false);
96
- return matches.length > 1 ? "ambiguous" : matches[0];
124
+ function getCandidateToolMatches(
125
+ metadata: ToolMetadata[] | undefined,
126
+ toolName: string,
127
+ serverName: string,
128
+ state: McpExtensionState,
129
+ ): ToolMetadata[] {
130
+ if (!metadata) return [];
131
+ const prefix = resolveToolPrefix(state.config.mcpServers[serverName], state.config.settings?.toolPrefix);
132
+ return metadata.filter((tool) => getToolNameCandidates(tool.originalName, serverName, prefix).has(toolName));
133
+ }
134
+
135
+ function getCandidateToolMatch(
136
+ metadata: ToolMetadata[] | undefined,
137
+ toolName: string,
138
+ serverName: string,
139
+ state: McpExtensionState,
140
+ ): ToolMetadata | "ambiguous" | undefined {
141
+ const exact = metadata?.filter((tool) => tool.name === toolName) ?? [];
142
+ if (exact.length > 0) return exact.length === 1 ? exact[0] : "ambiguous";
143
+ const candidates = getCandidateToolMatches(metadata, toolName, serverName, state);
144
+ return candidates.length > 1 ? "ambiguous" : candidates[0];
145
+ }
146
+
147
+ function getPrefixedServerScope(state: McpExtensionState, toolName: string): string | undefined {
148
+ const candidates = Object.entries(state.config.mcpServers)
149
+ .filter(([, definition]) => !isServerDisabled(definition))
150
+ .map(([name, definition]) => ({
151
+ name,
152
+ prefix: getServerPrefix(name, resolveToolPrefix(definition, state.config.settings?.toolPrefix)),
153
+ }))
154
+ .filter(({ prefix }) => prefix.length > 0 && toolName.startsWith(`${prefix}_`))
155
+ .sort((a, b) => b.prefix.length - a.prefix.length);
156
+ if (candidates.length === 0) return undefined;
157
+ const longest = candidates[0]!.prefix.length;
158
+ const best = candidates.filter(({ prefix }) => prefix.length === longest);
159
+ return best.length === 1 ? best[0]!.name : undefined;
97
160
  }
98
161
 
99
162
  type ServerScopedToolMatch = { tool: ToolMetadata; precedence: number } | "ambiguous";
100
163
 
101
- function getServerScopedToolMatch(metadata: ToolMetadata[] | undefined, toolName: string): ServerScopedToolMatch | undefined {
164
+ function getServerScopedToolCandidates(
165
+ metadata: ToolMetadata[] | undefined,
166
+ toolName: string,
167
+ ): { tools: ToolMetadata[]; precedence: number } | undefined {
102
168
  if (!metadata) return undefined;
103
169
  const normalizedName = toolName.replace(/-/g, "_");
104
- const matchesByPrecedence = [
105
- metadata.filter((tool) => tool.name === toolName),
106
- metadata.filter((tool) => tool.originalName === toolName),
107
- metadata.filter((tool) => tool.name.replace(/-/g, "_") === normalizedName),
108
- metadata.filter((tool) => tool.originalName.replace(/-/g, "_") === normalizedName),
109
- ];
110
- for (const [precedence, matches] of matchesByPrecedence.entries()) {
111
- if (matches.length > 1) return "ambiguous";
112
- if (matches.length === 1) return { tool: matches[0]!, precedence };
113
- }
114
- return undefined;
170
+ const exactDisplayedMatches = metadata.filter((tool) => tool.name === toolName);
171
+ if (exactDisplayedMatches.length > 0) return { tools: exactDisplayedMatches, precedence: 0 };
172
+ const exactOriginalMatches = metadata.filter((tool) => tool.originalName === toolName);
173
+ if (exactOriginalMatches.length > 0) return { tools: exactOriginalMatches, precedence: 1 };
174
+ const normalizedDisplayedMatches = metadata.filter((tool) => tool.name.replace(/-/g, "_") === normalizedName);
175
+ const normalizedOriginalMatches = metadata.filter((tool) => tool.originalName.replace(/-/g, "_") === normalizedName);
176
+ const normalizedMatches = new Set([...normalizedDisplayedMatches, ...normalizedOriginalMatches]);
177
+ if (normalizedMatches.size === 0) return undefined;
178
+ return {
179
+ tools: [...normalizedMatches],
180
+ precedence: normalizedDisplayedMatches.length > 0 ? 2 : 3,
181
+ };
182
+ }
183
+
184
+ function getServerScopedToolMatch(metadata: ToolMetadata[] | undefined, toolName: string): ServerScopedToolMatch | undefined {
185
+ const candidates = getServerScopedToolCandidates(metadata, toolName);
186
+ if (!candidates) return undefined;
187
+ return candidates.tools.length === 1
188
+ ? { tool: candidates.tools[0]!, precedence: candidates.precedence }
189
+ : "ambiguous";
115
190
  }
116
191
 
117
192
  function ambiguousToolResult(mode: "call" | "describe", toolName: string): ProxyToolResult {
@@ -122,6 +197,18 @@ function ambiguousToolResult(mode: "call" | "describe", toolName: string): Proxy
122
197
  };
123
198
  }
124
199
 
200
+ function ambiguousServerToolResult(
201
+ mode: "call" | "describe",
202
+ toolName: string,
203
+ serverName: string,
204
+ ): ProxyToolResult {
205
+ const message = `Tool "${toolName}" matches multiple tools on server "${serverName}". Use an exact displayed or upstream tool name; run mcp({ server: "${serverName}" }) to list available tools.`;
206
+ return {
207
+ content: [{ type: "text" as const, text: message }],
208
+ details: { mode, error: "ambiguous_tool", server: serverName, requestedTool: toolName, message },
209
+ };
210
+ }
211
+
125
212
  function disabledResult(mode: string, serverName: string): ProxyToolResult {
126
213
  const message = `Server "${serverName}" is disabled. Run /mcp enable ${serverName} and /reload to enable it.`;
127
214
  return {
@@ -606,23 +693,30 @@ export async function executeAuthComplete(state: McpExtensionState, serverName:
606
693
  }
607
694
  }
608
695
 
609
- export function executeDescribe(state: McpExtensionState, toolName: string): ProxyToolResult {
610
- const exactMatches = getEnabledToolMatches(state, toolName, true)
611
- .filter((match) => !isServerInActiveFailureBackoff(state, match.server));
612
- if (exactMatches.length > 1) return ambiguousToolResult("describe", toolName);
613
- if (exactMatches.length === 0 && getEnabledToolMatches(state, toolName, false).filter((match) => !isServerInActiveFailureBackoff(state, match.server)).length > 1) {
614
- return ambiguousToolResult("describe", toolName);
615
- }
616
-
617
- let serverName = exactMatches[0]?.server;
618
- let toolMeta = exactMatches[0]?.tool;
696
+ export function executeDescribe(state: McpExtensionState, toolName: string, serverOverride?: string): ProxyToolResult {
697
+ let serverName: string | undefined;
698
+ let toolMeta: ToolMetadata | undefined;
619
699
  let disabledMatch: string | undefined;
620
700
  let failedMatch: string | undefined;
621
701
 
622
- if (!toolMeta) {
702
+ if (serverOverride) {
703
+ if (!state.config.mcpServers[serverOverride]) {
704
+ return {
705
+ content: [{ type: "text" as const, text: `Server "${serverOverride}" not found. Use mcp({}) to see available servers.` }],
706
+ details: { mode: "describe", error: "server_not_found", server: serverOverride, requestedTool: toolName },
707
+ };
708
+ }
709
+ const match = getServerScopedToolMatch(state.toolMetadata.get(serverOverride), toolName);
710
+ if (match === "ambiguous") return ambiguousServerToolResult("describe", toolName, serverOverride);
711
+ if (isServerDisabled(state.config.mcpServers[serverOverride])) return disabledResult("describe", serverOverride);
712
+ if (isServerInActiveFailureBackoff(state, serverOverride)) return serverBackoffResult(state, "describe", serverOverride);
713
+ serverName = serverOverride;
714
+ toolMeta = match?.tool;
715
+ } else {
716
+ const matches: Array<{ server: string; tool: ToolMetadata; precedence: number }> = [];
623
717
  for (const [server, metadata] of state.toolMetadata.entries()) {
624
- const found = findToolByName(metadata, toolName);
625
- if (!found) continue;
718
+ const candidates = getServerScopedToolCandidates(metadata, toolName);
719
+ if (!candidates) continue;
626
720
  if (isServerDisabled(state.config.mcpServers[server])) {
627
721
  disabledMatch ??= server;
628
722
  continue;
@@ -631,20 +725,29 @@ export function executeDescribe(state: McpExtensionState, toolName: string): Pro
631
725
  failedMatch ??= server;
632
726
  continue;
633
727
  }
634
- serverName = server;
635
- toolMeta = found;
636
- break;
728
+ for (const tool of candidates.tools) matches.push({ server, tool, precedence: candidates.precedence });
729
+ }
730
+ if (matches.length > 0) {
731
+ const precedence = Math.min(...matches.map(match => match.precedence));
732
+ const bestMatches = matches.filter(match => match.precedence === precedence);
733
+ if (bestMatches.length > 1) return ambiguousToolResult("describe", toolName);
734
+ serverName = bestMatches[0]!.server;
735
+ toolMeta = bestMatches[0]!.tool;
637
736
  }
638
737
  }
639
738
 
640
739
  if (!serverName || !toolMeta) {
641
740
  if (disabledMatch) return disabledResult("describe", disabledMatch);
642
741
  if (failedMatch) return serverBackoffResult(state, "describe", failedMatch);
643
- const suggestions = rankSuggestions(state, toolName, 5);
742
+ const suggestions = rankSuggestions(state, toolName, 5, serverOverride);
644
743
  const suggestionText = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}` : "";
744
+ const scopeText = serverOverride ? ` on server "${serverOverride}"` : "";
745
+ const searchHint = serverOverride
746
+ ? `mcp({ search: "...", server: "${serverOverride}" })`
747
+ : `mcp({ search: "..." })`;
645
748
  return {
646
- content: [{ type: "text" as const, text: `Tool "${toolName}" not found. Use mcp({ search: "..." }) to search.${suggestionText}` }],
647
- details: { mode: "describe", error: "tool_not_found", requestedTool: toolName, suggestions },
749
+ content: [{ type: "text" as const, text: `Tool "${toolName}" not found${scopeText}. Use ${searchHint} to search.${suggestionText}` }],
750
+ details: { mode: "describe", error: "tool_not_found", server: serverOverride, requestedTool: toolName, suggestions },
648
751
  };
649
752
  }
650
753
 
@@ -673,6 +776,78 @@ export function executeDescribe(state: McpExtensionState, toolName: string): Pro
673
776
  };
674
777
  }
675
778
 
779
+ function renderSearchResults(
780
+ state: McpExtensionState,
781
+ query: string,
782
+ server: string | undefined,
783
+ showSchemas: boolean,
784
+ limit: number,
785
+ offset: number,
786
+ matches: Array<{ server: string; tool: ToolMetadata; score: number }>,
787
+ backend?: SemanticSearchBackend,
788
+ ): ProxyToolResult {
789
+ const page = paginate(matches, offset, limit);
790
+ if (page.total === 0) {
791
+ const connectingServers = server
792
+ ? state.config.mcpServers[server] && state.manager.isConnecting(server) ? [server] : []
793
+ : Object.keys(state.config.mcpServers)
794
+ .filter(name => !isServerDisabled(state.config.mcpServers[name]) && state.manager.isConnecting(name))
795
+ .sort((a, b) => a.localeCompare(b));
796
+ const scope = server ? ` in "${server}"` : "";
797
+ const msg = backend?.requested === "semantic" && backend.used === "semantic" && backend.abstained
798
+ ? `Jev found no suitable tool for "${query}"${scope}`
799
+ : `No tools matching "${query}"${scope}`;
800
+ const connectingMessage = connectingServers.length === 1
801
+ ? ` Server "${connectingServers[0]}" is still connecting; retry in a moment.`
802
+ : connectingServers.length > 1
803
+ ? ` Servers ${connectingServers.map(name => `"${name}"`).join(", ")} are still connecting; retry in a moment.`
804
+ : "";
805
+ return {
806
+ content: [{ type: "text" as const, text: `${msg}${connectingMessage}` }],
807
+ details: {
808
+ mode: "search", matches: [], count: 0, hasMore: false, nextOffset: null, query,
809
+ ...(backend ? { backend } : {}),
810
+ ...(connectingServers.length > 0 ? { connectingServers } : {}),
811
+ },
812
+ };
813
+ }
814
+
815
+ let text = `Found ${page.total} tool${page.total === 1 ? "" : "s"} matching "${query}":\n\n`;
816
+ for (const match of page.items) {
817
+ const approvalMarker = isToolCallApprovalRequired(state.config, match.server, match.tool, state.toolMetadata)
818
+ ? " (requires approval)"
819
+ : "";
820
+ if (showSchemas) {
821
+ text += `${match.tool.name}${approvalMarker}\n`;
822
+ text += ` ${match.tool.description || "(no description)"}\n`;
823
+ if (match.tool.inputSchema && !match.tool.resourceUri) {
824
+ const shape = renderTsShape(match.tool.inputSchema);
825
+ text += shape === null
826
+ ? `\n Parameters:\n${formatSchema(match.tool.inputSchema, " ")}\n`
827
+ : `\n Shape:\n${shape.split("\n").map(line => ` ${line}`).join("\n")}\n`;
828
+ } else if (match.tool.resourceUri) {
829
+ text += " No parameters (resource tool).\n";
830
+ }
831
+ text += "\n";
832
+ } else {
833
+ text += `- ${match.tool.name}${approvalMarker}`;
834
+ if (match.tool.description) text += ` - ${truncateAtWord(match.tool.description, 50)}`;
835
+ text += "\n";
836
+ }
837
+ }
838
+ if (page.hasMore) text += `\n${page.items.length} of ${page.total} — offset: ${page.nextOffset} for more\n`;
839
+
840
+ return {
841
+ content: [{ type: "text" as const, text: text.trim() }],
842
+ details: {
843
+ mode: "search",
844
+ matches: page.items.map(match => ({ server: match.server, tool: match.tool.name, score: match.score })),
845
+ count: page.total, hasMore: page.hasMore, nextOffset: page.nextOffset, query,
846
+ ...(backend ? { backend } : {}),
847
+ },
848
+ };
849
+ }
850
+
676
851
  export function executeSearch(
677
852
  state: McpExtensionState,
678
853
  query: string,
@@ -681,10 +856,36 @@ export function executeSearch(
681
856
  includeSchemas?: boolean,
682
857
  limit = 12,
683
858
  offset = 0,
684
- ): ProxyToolResult {
859
+ searchMode: "lexical" | "semantic" = "lexical",
860
+ signal?: AbortSignal,
861
+ semanticEvaluator?: SemanticSearchEvaluator,
862
+ ): ProxyToolResult | Promise<ProxyToolResult> {
685
863
  const showSchemas = includeSchemas !== false;
864
+ if ((searchMode as string) !== "lexical" && (searchMode as string) !== "semantic") {
865
+ return {
866
+ content: [{ type: "text" as const, text: "Search mode must be lexical or semantic." }],
867
+ details: { mode: "search", error: "invalid_search_mode", query },
868
+ };
869
+ }
686
870
  if (server && isServerDisabled(state.config.mcpServers[server])) return disabledResult("search", server);
687
871
  if (server && isServerInActiveFailureBackoff(state, server)) return serverBackoffResult(state, "search", server);
872
+ if (searchMode === "semantic" && regex) {
873
+ return {
874
+ content: [{ type: "text" as const, text: "Semantic search cannot be combined with regex search." }],
875
+ details: { mode: "search", error: "invalid_search_mode", query },
876
+ };
877
+ }
878
+ if (searchMode === "semantic") {
879
+ return semanticSearch(state, query, server, signal, semanticEvaluator).then(result => {
880
+ if (!result.ok) {
881
+ return {
882
+ content: [{ type: "text" as const, text: `Semantic search failed: ${result.error.message}` }],
883
+ details: { mode: "search", error: result.error.code, message: result.error.message, query },
884
+ };
885
+ }
886
+ return renderSearchResults(state, query, server, showSchemas, limit, offset, result.matches, result.backend);
887
+ });
888
+ }
688
889
 
689
890
  let matches: Array<{ server: string; tool: ToolMetadata; score: number }>;
690
891
  if (regex) {
@@ -748,69 +949,7 @@ export function executeSearch(
748
949
  matches = rankToolMatches(state, query, server);
749
950
  }
750
951
 
751
- const page = paginate(matches, offset, limit);
752
- if (page.total === 0) {
753
- const connectingServers = server
754
- ? state.config.mcpServers[server] && state.manager.isConnecting(server) ? [server] : []
755
- : Object.keys(state.config.mcpServers)
756
- .filter(name => !isServerDisabled(state.config.mcpServers[name]) && state.manager.isConnecting(name))
757
- .sort((a, b) => a.localeCompare(b));
758
- const msg = server ? `No tools matching "${query}" in "${server}"` : `No tools matching "${query}"`;
759
- const connectingMessage = connectingServers.length === 1
760
- ? ` Server "${connectingServers[0]}" is still connecting; retry in a moment.`
761
- : connectingServers.length > 1
762
- ? ` Servers ${connectingServers.map(name => `"${name}"`).join(", ")} are still connecting; retry in a moment.`
763
- : "";
764
- return {
765
- content: [{ type: "text" as const, text: `${msg}${connectingMessage}` }],
766
- details: {
767
- mode: "search",
768
- matches: [],
769
- count: 0,
770
- hasMore: false,
771
- nextOffset: null,
772
- query,
773
- ...(connectingServers.length > 0 ? { connectingServers } : {}),
774
- },
775
- };
776
- }
777
-
778
- let text = `Found ${page.total} tool${page.total === 1 ? "" : "s"} matching "${query}":\n\n`;
779
- for (const match of page.items) {
780
- const approvalMarker = isToolCallApprovalRequired(state.config, match.server, match.tool, state.toolMetadata)
781
- ? " (requires approval)"
782
- : "";
783
- if (showSchemas) {
784
- text += `${match.tool.name}${approvalMarker}\n`;
785
- text += ` ${match.tool.description || "(no description)"}\n`;
786
- if (match.tool.inputSchema && !match.tool.resourceUri) {
787
- const shape = renderTsShape(match.tool.inputSchema);
788
- text += shape === null
789
- ? `\n Parameters:\n${formatSchema(match.tool.inputSchema, " ")}\n`
790
- : `\n Shape:\n${shape.split("\n").map(line => ` ${line}`).join("\n")}\n`;
791
- } else if (match.tool.resourceUri) {
792
- text += " No parameters (resource tool).\n";
793
- }
794
- text += "\n";
795
- } else {
796
- text += `- ${match.tool.name}${approvalMarker}`;
797
- if (match.tool.description) text += ` - ${truncateAtWord(match.tool.description, 50)}`;
798
- text += "\n";
799
- }
800
- }
801
- if (page.hasMore) text += `\n${page.items.length} of ${page.total} — offset: ${page.nextOffset} for more\n`;
802
-
803
- return {
804
- content: [{ type: "text" as const, text: text.trim() }],
805
- details: {
806
- mode: "search",
807
- matches: page.items.map(match => ({ server: match.server, tool: match.tool.name, score: match.score })),
808
- count: page.total,
809
- hasMore: page.hasMore,
810
- nextOffset: page.nextOffset,
811
- query,
812
- },
813
- };
952
+ return renderSearchResults(state, query, server, showSchemas, limit, offset, matches);
814
953
  }
815
954
 
816
955
  export function executeList(state: McpExtensionState, server: string): ProxyToolResult {
@@ -1044,59 +1183,62 @@ export async function executeCall(
1044
1183
  };
1045
1184
  }
1046
1185
  if (serverName) {
1047
- const match = getServerScopedToolMatch(state.toolMetadata.get(serverName), toolName);
1048
- if (match === "ambiguous") return ambiguousToolResult("call", toolName);
1049
- toolMeta = match?.tool;
1186
+ const match = getCandidateToolMatch(state.toolMetadata.get(serverName), toolName, serverName, state);
1187
+ if (match === "ambiguous") return ambiguousServerToolResult("call", toolName, serverName);
1188
+ toolMeta = match;
1050
1189
  if (isServerDisabled(state.config.mcpServers[serverName])) {
1051
1190
  return disabledCallResult(serverName, toolMeta);
1052
1191
  }
1053
1192
  } else {
1054
1193
  const exactMatches = getEnabledToolMatches(state, toolName, true);
1055
1194
  if (exactMatches.length > 1) return ambiguousToolResult("call", toolName);
1056
- if (exactMatches.length === 0 && getEnabledToolMatches(state, toolName, false).length > 1) {
1057
- return ambiguousToolResult("call", toolName);
1058
- }
1059
-
1060
- let disabledMatch: { serverName: string; toolMeta: ToolMetadata } | undefined;
1061
- for (const [server, metadata] of state.toolMetadata.entries()) {
1062
- const found = metadata.find(tool => tool.name === toolName);
1063
- if (!found) continue;
1064
- if (isServerDisabled(state.config.mcpServers[server])) {
1065
- disabledMatch ??= { serverName: server, toolMeta: found };
1066
- continue;
1195
+ if (exactMatches.length === 1) {
1196
+ serverName = exactMatches[0]!.server;
1197
+ toolMeta = exactMatches[0]!.tool;
1198
+ } else {
1199
+ const originalMatches = getEnabledOriginalToolMatches(state, toolName);
1200
+ if (originalMatches.length > 1) return ambiguousToolResult("call", toolName);
1201
+ if (originalMatches.length === 1) {
1202
+ serverName = originalMatches[0]!.server;
1203
+ toolMeta = originalMatches[0]!.tool;
1204
+ } else {
1205
+ const prefixedScope = getPrefixedServerScope(state, toolName);
1206
+ if (prefixedScope) {
1207
+ serverName = prefixedScope;
1208
+ const match = getCandidateToolMatch(state.toolMetadata.get(prefixedScope), toolName, prefixedScope, state);
1209
+ if (match === "ambiguous") return ambiguousServerToolResult("call", toolName, prefixedScope);
1210
+ toolMeta = match;
1211
+ } else {
1212
+ const matches: Array<{ server: string; tool: ToolMetadata }> = [];
1213
+ for (const [server, metadata] of state.toolMetadata) {
1214
+ if (isServerDisabled(state.config.mcpServers[server])) continue;
1215
+ for (const tool of getCandidateToolMatches(metadata, toolName, server, state)) matches.push({ server, tool });
1216
+ }
1217
+ if (matches.length > 1) return ambiguousToolResult("call", toolName);
1218
+ if (matches.length === 1) {
1219
+ serverName = matches[0]!.server;
1220
+ toolMeta = matches[0]!.tool;
1221
+ }
1222
+ }
1067
1223
  }
1068
- serverName = server;
1069
- toolMeta = found;
1070
- break;
1071
1224
  }
1072
- if (!toolMeta && !disabledMatch) {
1073
- for (const [server, metadata] of state.toolMetadata.entries()) {
1074
- const found = findToolByName(metadata, toolName);
1075
- if (!found) continue;
1076
- if (isServerDisabled(state.config.mcpServers[server])) {
1077
- disabledMatch ??= { serverName: server, toolMeta: found };
1078
- continue;
1079
- }
1080
- serverName = server;
1081
- toolMeta = found;
1082
- break;
1225
+
1226
+ if (!toolMeta) {
1227
+ const disabledMatches: Array<{ server: string; tool: ToolMetadata }> = [];
1228
+ for (const [server, metadata] of state.toolMetadata) {
1229
+ if (!isServerDisabled(state.config.mcpServers[server])) continue;
1230
+ for (const tool of getCandidateToolMatches(metadata, toolName, server, state)) disabledMatches.push({ server, tool });
1083
1231
  }
1232
+ if (disabledMatches.length === 1) return disabledCallResult(disabledMatches[0]!.server, disabledMatches[0]!.tool);
1084
1233
  }
1085
- if (!toolMeta && disabledMatch) return disabledCallResult(disabledMatch.serverName, disabledMatch.toolMeta);
1086
1234
  }
1087
1235
 
1088
1236
  if (serverName && !toolMeta) {
1089
1237
  const connected = await lazyConnect(state, serverName, ownedSignal);
1090
1238
  if (connected) {
1091
- if (serverOverride) {
1092
- const match = getServerScopedToolMatch(state.toolMetadata.get(serverName), toolName);
1093
- if (match === "ambiguous") return ambiguousToolResult("call", toolName);
1094
- toolMeta = match?.tool;
1095
- } else {
1096
- const match = getSingleToolMatch(state.toolMetadata.get(serverName), toolName);
1097
- if (match === "ambiguous") return ambiguousToolResult("call", toolName);
1098
- toolMeta = match;
1099
- }
1239
+ const match = getCandidateToolMatch(state.toolMetadata.get(serverName), toolName, serverName, state);
1240
+ if (match === "ambiguous") return ambiguousServerToolResult("call", toolName, serverName);
1241
+ toolMeta = match;
1100
1242
  } else {
1101
1243
  const needsAuthConnection = state.manager.getConnection(serverName);
1102
1244
  if (needsAuthConnection?.status === "needs-auth") {
@@ -1114,11 +1256,11 @@ export async function executeCall(
1114
1256
  clearFailure(state, serverName);
1115
1257
  const connectedAfterAuth = await lazyConnect(state, serverName, ownedSignal);
1116
1258
  if (connectedAfterAuth) {
1117
- const match = getServerScopedToolMatch(state.toolMetadata.get(serverName), toolName);
1118
- if (match === "ambiguous") return ambiguousToolResult("call", toolName);
1119
- toolMeta = match?.tool;
1259
+ const match = getCandidateToolMatch(state.toolMetadata.get(serverName), toolName, serverName, state);
1260
+ if (match === "ambiguous") return ambiguousServerToolResult("call", toolName, serverName);
1261
+ toolMeta = match;
1120
1262
  if (!toolMeta) {
1121
- const suggestions = rankSuggestions(state, toolName, 5);
1263
+ const suggestions = rankSuggestions(state, toolName, 5, serverName);
1122
1264
  const suggestionText = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}` : "";
1123
1265
  return {
1124
1266
  content: [{ type: "text" as const, text: `Tool "${toolName}" not found on "${serverName}" after reconnect.${suggestionText}` }],
@@ -1192,7 +1334,7 @@ export async function executeCall(
1192
1334
  lazyExactMatches.push({ serverName: configuredServer, toolMeta: exactMatches[0]! });
1193
1335
  continue;
1194
1336
  }
1195
- const fallbackMatches = getToolMatches(metadata, toolName, false);
1337
+ const fallbackMatches = getCandidateToolMatches(metadata, toolName, configuredServer, state);
1196
1338
  if (fallbackMatches.length > 1) return ambiguousToolResult("call", toolName);
1197
1339
  if (fallbackMatches.length === 1) lazyFallbackMatches.push({ serverName: configuredServer, toolMeta: fallbackMatches[0]! });
1198
1340
  }
@@ -1223,7 +1365,7 @@ export async function executeCall(
1223
1365
  } else {
1224
1366
  msg += ` Use mcp({ search: "..." }) to search.`;
1225
1367
  }
1226
- const suggestions = rankSuggestions(state, toolName, 5);
1368
+ const suggestions = rankSuggestions(state, toolName, 5, serverOverride);
1227
1369
  if (suggestions.length > 0) msg += ` Did you mean: ${suggestions.join(", ")}`;
1228
1370
  return {
1229
1371
  content: [{ type: "text" as const, text: msg }],
@@ -1233,7 +1375,7 @@ export async function executeCall(
1233
1375
 
1234
1376
  const callIdentity = toolMeta.resourceUri
1235
1377
  ? { server: serverName, resourceUri: toolMeta.resourceUri }
1236
- : { server: serverName, tool: toolMeta.originalName };
1378
+ : { server: serverName, tool: toolMeta.originalName, canonicalTool: toolMeta.name };
1237
1379
 
1238
1380
  let connection = state.manager.getConnection(serverName);
1239
1381
  if (connection?.status === "needs-auth") {
@@ -1313,21 +1455,15 @@ export async function executeCall(
1313
1455
  if (!restored) notifyToolMetadataUpdated(state, serverName, "proxy-call-reconnect");
1314
1456
  markKeepAliveAfterConnect(state, serverName);
1315
1457
  updateStatusBar(state);
1316
- if (serverOverride) {
1317
- const match = getServerScopedToolMatch(state.toolMetadata.get(serverName), toolName);
1318
- if (match === "ambiguous") return ambiguousToolResult("call", toolName);
1319
- toolMeta = match?.tool;
1320
- } else {
1321
- const match = getSingleToolMatch(state.toolMetadata.get(serverName), toolName);
1322
- if (match === "ambiguous") return ambiguousToolResult("call", toolName);
1323
- toolMeta = match;
1324
- }
1458
+ const match = getCandidateToolMatch(state.toolMetadata.get(serverName), toolName, serverName, state);
1459
+ if (match === "ambiguous") return ambiguousServerToolResult("call", toolName, serverName);
1460
+ toolMeta = match;
1325
1461
  if (!toolMeta) {
1326
1462
  const available = getToolNames(state, serverName);
1327
1463
  const hint = available.length > 0
1328
1464
  ? `Available tools on "${serverName}": ${available.join(", ")}`
1329
1465
  : `Server "${serverName}" has no tools.`;
1330
- const suggestions = rankSuggestions(state, toolName, 5);
1466
+ const suggestions = rankSuggestions(state, toolName, 5, serverName);
1331
1467
  const suggestionText = suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}` : "";
1332
1468
  return {
1333
1469
  content: [{ type: "text" as const, text: `Tool "${toolName}" not found on "${serverName}" after reconnect. ${hint}${suggestionText}` }],
@@ -1350,6 +1486,18 @@ export async function executeCall(
1350
1486
  }
1351
1487
 
1352
1488
  const normalizedArgs = toolMeta.resourceUri ? args ?? {} : normalizeToolArguments(args);
1489
+ const validationError = toolMeta.resourceUri ? null : proxyArgumentValidationError(toolMeta.inputSchema, normalizedArgs);
1490
+ if (validationError) {
1491
+ const schemaText = `\n\nExpected parameters:\n${formatSchema(toolMeta.inputSchema)}`;
1492
+ const guarded = await guardMcpOutput(
1493
+ [{ type: "text" as const, text: validationError }],
1494
+ { ...resolveMcpOutputGuardOptions(state.config.settings), prefix: "Failed to call tool: ", suffix: schemaText },
1495
+ );
1496
+ return {
1497
+ content: guarded.content,
1498
+ details: { mode: "call", error: "call_failed", ...callIdentity, message: validationError, ...guardedMcpDetails(guarded) },
1499
+ };
1500
+ }
1353
1501
  const approval = await ensureToolCallApproved(
1354
1502
  state,
1355
1503
  serverName,
@@ -1467,6 +1615,15 @@ export async function executeCall(
1467
1615
  serverName,
1468
1616
  async (conn) => {
1469
1617
  await state.manager.ensureListen?.(serverName, conn);
1618
+ if (conn.taskSession) {
1619
+ return await callToolViaTaskSession(conn.taskSession, {
1620
+ name: toolMeta.originalName,
1621
+ args: normalizedArgs ?? {},
1622
+ meta: uiSession?.requestMeta,
1623
+ signal: ownedSignal,
1624
+ requestTimeoutMs: requestOptions?.timeout,
1625
+ }) as unknown as ClientCallToolResult;
1626
+ }
1470
1627
  return abortable(conn.client.callTool({
1471
1628
  name: toolMeta.originalName,
1472
1629
  arguments: normalizedArgs,
@@ -1487,11 +1644,9 @@ export async function executeCall(
1487
1644
 
1488
1645
  if (toolMeta.uiResourceUri) {
1489
1646
  if (result.isError) {
1490
- const mcpContent = (result.content ?? []) as McpContent[];
1491
- const content = transformMcpContent(mcpContent, state.owner?.signal);
1647
+ const content = resolveMcpResultContent(result as Record<string, unknown>, state.owner?.signal);
1492
1648
  const outputContent = content.length > 0 ? content : [{ type: "text" as const, text: "(empty result)" }];
1493
- const schemaText = toolMeta.inputSchema ? `\n\nExpected parameters:\n${formatSchema(toolMeta.inputSchema)}` : "";
1494
- const guarded = await guardMcpOutput(outputContent, { ...outputGuardOptions, prefix: "Error: ", suffix: schemaText, emptyTextFallback: "Tool execution failed", rawMcpResult: result });
1649
+ const guarded = await guardMcpOutput(outputContent, { ...outputGuardOptions, prefix: "Error: ", emptyTextFallback: "Tool execution failed", rawMcpResult: result });
1495
1650
  return {
1496
1651
  content: guarded.content,
1497
1652
  details: { mode: "call", error: "tool_error", ...callIdentity, ...guardedMcpDetails(guarded) },
@@ -1516,11 +1671,9 @@ export async function executeCall(
1516
1671
  }
1517
1672
 
1518
1673
  if (result.isError) {
1519
- const mcpContent = (result.content ?? []) as McpContent[];
1520
- const content = transformMcpContent(mcpContent, state.owner?.signal);
1674
+ const content = resolveMcpResultContent(result as Record<string, unknown>, state.owner?.signal);
1521
1675
  const outputContent = content.length > 0 ? content : [{ type: "text" as const, text: "(empty result)" }];
1522
- const schemaText = toolMeta.inputSchema ? `\n\nExpected parameters:\n${formatSchema(toolMeta.inputSchema)}` : "";
1523
- const guarded = await guardMcpOutput(outputContent, { ...outputGuardOptions, prefix: "Error: ", suffix: schemaText, emptyTextFallback: "Tool execution failed", rawMcpResult: result });
1676
+ const guarded = await guardMcpOutput(outputContent, { ...outputGuardOptions, prefix: "Error: ", emptyTextFallback: "Tool execution failed", rawMcpResult: result });
1524
1677
  return {
1525
1678
  content: guarded.content,
1526
1679
  details: { mode: "call", error: "tool_error", ...callIdentity, ...guardedMcpDetails(guarded) },