@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.
- package/README.md +4 -4
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +1 -1
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +5 -4
- package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +39 -16
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +21 -4
- package/node_modules/@narumitw/pi-btw/dist/index.ts +1668 -958
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
- package/node_modules/@narumitw/pi-btw/docs/workflows.md +10 -2
- package/node_modules/@narumitw/pi-btw/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/src/btw.ts +17 -79
- package/node_modules/@narumitw/pi-btw/src/conversation-context.ts +74 -0
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +196 -7
- package/node_modules/@narumitw/pi-btw/src/main-thread-updates.ts +40 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
- package/node_modules/@narumitw/pi-btw/src/settings.ts +49 -0
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +9 -1
- package/node_modules/@narumitw/pi-btw/src/workspace-layout.ts +559 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +62 -0
- package/node_modules/pi-mcp-adapter/README.md +75 -7
- package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +193 -0
- package/node_modules/pi-mcp-adapter/cli.js +56 -5
- package/node_modules/pi-mcp-adapter/commands.ts +96 -5
- package/node_modules/pi-mcp-adapter/config.ts +73 -6
- package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +22 -28
- package/node_modules/pi-mcp-adapter/direct-tools.ts +13 -5
- package/node_modules/pi-mcp-adapter/dist/abort.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js +36 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +194 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.d.ts +5 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +75 -6
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +18 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +316 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/errors.d.ts +131 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js +278 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +8 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js +340 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +77 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +22 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +55 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +56 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js +410 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/logger.d.ts +51 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js +131 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +103 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +118 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +1098 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +168 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +1117 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +7 -140
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +53 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +441 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +148 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +689 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +166 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +102 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +369 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +95 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +242 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +2 -11
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +505 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +10 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +312 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +13 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +97 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +203 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +11 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +172 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +157 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js +1743 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +34 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js +140 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js +142 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/state.d.ts +73 -0
- package/node_modules/pi-mcp-adapter/dist/state.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/state.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +36 -0
- package/node_modules/pi-mcp-adapter/dist/types.js +18 -0
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +219 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +13 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
- package/node_modules/pi-mcp-adapter/elicitation-handler.ts +29 -19
- package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +85 -0
- package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +34 -0
- package/node_modules/pi-mcp-adapter/index.ts +129 -6
- package/node_modules/pi-mcp-adapter/init.ts +6 -19
- package/node_modules/pi-mcp-adapter/jev-client.ts +273 -0
- package/node_modules/pi-mcp-adapter/jev-contracts.ts +53 -0
- package/node_modules/pi-mcp-adapter/jev-key-store.ts +79 -0
- package/node_modules/pi-mcp-adapter/lifecycle.ts +14 -3
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +7 -142
- package/node_modules/pi-mcp-adapter/mcp-code.ts +119 -11
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-references.ts +5 -3
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +5 -0
- package/node_modules/pi-mcp-adapter/mcp-tasks.ts +467 -0
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +2 -13
- package/node_modules/pi-mcp-adapter/namespace-tools.ts +1 -1
- package/node_modules/pi-mcp-adapter/package.json +17 -9
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +319 -166
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +6 -3
- package/node_modules/pi-mcp-adapter/search-ranking.ts +38 -7
- package/node_modules/pi-mcp-adapter/secure-keyring.ts +170 -0
- package/node_modules/pi-mcp-adapter/semantic-search.ts +186 -0
- package/node_modules/pi-mcp-adapter/server-manager.ts +293 -50
- package/node_modules/pi-mcp-adapter/session-approvals.ts +14 -0
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +2 -0
- package/node_modules/pi-mcp-adapter/state.ts +3 -1
- package/node_modules/pi-mcp-adapter/tool-approval.ts +26 -4
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -14
- package/node_modules/pi-mcp-adapter/tool-registrar.ts +8 -6
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +4 -1
- package/node_modules/pi-mcp-adapter/types.ts +53 -0
- package/node_modules/pi-mcp-adapter/ui-server.ts +17 -9
- package/node_modules/pi-mcp-adapter/utils.ts +16 -0
- package/node_modules/pi-multi-account/CHANGELOG.md +23 -0
- package/node_modules/pi-multi-account/README.md +38 -11
- package/node_modules/pi-multi-account/index.ts +371 -96
- package/node_modules/pi-multi-account/package.json +5 -4
- package/node_modules/pi-multi-account/provider-payload-stream.ts +36 -28
- package/node_modules/pi-multi-account/usage.ts +31 -2
- package/node_modules/pi-typesafe/README.md +3 -1
- package/node_modules/pi-typesafe/dist/auth.d.ts +10 -3
- package/node_modules/pi-typesafe/dist/auth.js +13 -7
- package/node_modules/pi-typesafe/dist/backends.d.ts +31 -0
- package/node_modules/pi-typesafe/dist/backends.js +33 -0
- package/node_modules/pi-typesafe/dist/client.d.ts +3 -9
- package/node_modules/pi-typesafe/dist/client.js +63 -39
- package/node_modules/pi-typesafe/dist/credentials.d.ts +11 -5
- package/node_modules/pi-typesafe/dist/credentials.js +15 -8
- package/node_modules/pi-typesafe/dist/extension.js +4 -1
- package/node_modules/pi-typesafe/dist/index.d.ts +1 -1
- package/node_modules/pi-typesafe/dist/index.js +1 -1
- package/node_modules/pi-typesafe/dist/login.d.ts +13 -7
- package/node_modules/pi-typesafe/dist/login.js +17 -8
- package/node_modules/pi-typesafe/dist/schema.js +19 -13
- package/node_modules/pi-typesafe/package.json +1 -1
- 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,
|
|
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,
|
|
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
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
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
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
611
|
-
|
|
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 (
|
|
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
|
|
625
|
-
if (!
|
|
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
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1048
|
-
if (match === "ambiguous") return
|
|
1049
|
-
toolMeta = match
|
|
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 ===
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
if (isServerDisabled(state.config.mcpServers[server]))
|
|
1077
|
-
|
|
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
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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 =
|
|
1118
|
-
if (match === "ambiguous") return
|
|
1119
|
-
toolMeta = match
|
|
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 =
|
|
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
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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) },
|