@fitchmultz/pi-mcp-adapter 5.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -1
- package/OAUTH.md +3 -3
- package/README.md +153 -85
- package/app-bridge.bundle.js +75 -33
- package/commands.ts +9 -6
- package/direct-tools.ts +25 -358
- package/dist/app-bridge.bundle.js +75 -33
- package/dist/commands.js +9 -6
- package/dist/direct-tools.d.ts +2 -4
- package/dist/direct-tools.js +21 -319
- package/dist/gateway-arguments.d.ts +33 -0
- package/dist/gateway-arguments.js +63 -0
- package/dist/index.js +181 -276
- package/dist/init.js +6 -20
- package/dist/mcp-code.d.ts +2 -1
- package/dist/mcp-code.js +87 -41
- package/dist/mcp-output-guard.d.ts +57 -2
- package/dist/mcp-output-guard.js +129 -24
- package/dist/mcp-panel.d.ts +1 -1
- package/dist/mcp-panel.js +34 -97
- package/dist/mcp-script-worker.mjs +5 -2
- package/dist/mcp-status.js +2 -1
- package/dist/metadata-cache.d.ts +1 -1
- package/dist/metadata-cache.js +25 -88
- package/dist/prompts.d.ts +1 -1
- package/dist/prompts.js +1 -1
- package/dist/proxy-modes.d.ts +40 -6
- package/dist/proxy-modes.js +270 -485
- package/dist/resource-tools.d.ts +15 -0
- package/dist/resource-tools.js +26 -1
- package/dist/sampling-handler.js +4 -1
- package/dist/search-ranking.d.ts +0 -2
- package/dist/search-ranking.js +66 -137
- package/dist/state.d.ts +1 -1
- package/dist/tool-loader.d.ts +32 -0
- package/dist/tool-loader.js +152 -0
- package/dist/tool-metadata.d.ts +67 -0
- package/dist/tool-metadata.js +23 -17
- package/dist/tool-registrar.d.ts +4 -4
- package/dist/tool-registrar.js +18 -13
- package/dist/tool-result-renderer.d.ts +3 -0
- package/dist/tool-result-renderer.js +8 -3
- package/dist/types.d.ts +11 -36
- package/dist/utils.js +2 -4
- package/gateway-arguments.ts +73 -0
- package/index.ts +166 -299
- package/init.ts +6 -20
- package/mcp-code.ts +71 -45
- package/mcp-output-guard.ts +145 -27
- package/mcp-panel.ts +38 -109
- package/mcp-script-worker.mjs +5 -2
- package/mcp-status.ts +2 -1
- package/metadata-cache.ts +31 -93
- package/package.json +14 -9
- package/prompts.ts +1 -1
- package/proxy-modes.ts +265 -517
- package/resource-tools.ts +36 -1
- package/sampling-handler.ts +4 -1
- package/scripts/build-app-bridge.mjs +15 -0
- package/scripts/build.mjs +3 -2
- package/scripts/prepare.mjs +1 -0
- package/search-ranking.ts +71 -135
- package/skills/mcp-scripting/SKILL.md +67 -20
- package/state.ts +1 -1
- package/tool-loader.ts +153 -0
- package/tool-metadata.ts +22 -17
- package/tool-registrar.ts +18 -13
- package/tool-result-renderer.ts +10 -2
- package/types.ts +15 -40
- package/utils.ts +2 -4
- package/dist/ts-shape.d.ts +0 -2
- package/dist/ts-shape.js +0 -147
- package/ts-shape.ts +0 -145
package/commands.ts
CHANGED
|
@@ -38,7 +38,8 @@ export async function showStatus(state: McpExtensionState, ctx: ExtensionContext
|
|
|
38
38
|
}
|
|
39
39
|
const connection = state.manager.getConnection(name);
|
|
40
40
|
const metadata = state.toolMetadata.get(name);
|
|
41
|
-
const toolCount = metadata?.length ?? 0;
|
|
41
|
+
const toolCount = metadata?.filter(tool => tool.resourceUri === undefined).length ?? 0;
|
|
42
|
+
const resourceCount = metadata?.filter(tool => tool.resourceUri !== undefined).length ?? 0;
|
|
42
43
|
const failedAgo = getFailureAgeSeconds(state, name);
|
|
43
44
|
let status = "not connected";
|
|
44
45
|
let statusIcon = "○";
|
|
@@ -59,7 +60,9 @@ export async function showStatus(state: McpExtensionState, ctx: ExtensionContext
|
|
|
59
60
|
status = "cached";
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
const toolSuffix = failed ? "" :
|
|
63
|
+
const toolSuffix = failed ? "" : metadata === undefined
|
|
64
|
+
? " (undiscovered)"
|
|
65
|
+
: ` (${toolCount} tool${toolCount === 1 ? "" : "s"}, ${resourceCount} resource${resourceCount === 1 ? "" : "s"}${status === "cached" ? ", cached" : ""})`;
|
|
63
66
|
lines.push(`${statusIcon} ${name}: ${status}${toolSuffix}`);
|
|
64
67
|
}
|
|
65
68
|
|
|
@@ -113,19 +116,19 @@ export async function showTools(state: McpExtensionState, ctx: ExtensionContext)
|
|
|
113
116
|
|
|
114
117
|
const allTools = [...state.toolMetadata.entries()]
|
|
115
118
|
.filter(([serverName]) => !isServerDisabled(state.config.mcpServers[serverName]))
|
|
116
|
-
.flatMap(([, metadata]) => metadata.map(
|
|
119
|
+
.flatMap(([, metadata]) => metadata.filter(tool => tool.resourceUri === undefined).map(tool => tool.name));
|
|
117
120
|
|
|
118
121
|
if (allTools.length === 0) {
|
|
119
|
-
ctx.ui.notify("No MCP tools
|
|
122
|
+
ctx.ui.notify("No MCP tools discovered. Use mcp_search to discover tools.", "info");
|
|
120
123
|
return;
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
const lines = [
|
|
124
|
-
"MCP Tools:",
|
|
127
|
+
"Discovered MCP Tools:",
|
|
125
128
|
"",
|
|
126
129
|
...allTools.map(t => ` ${t}`),
|
|
127
130
|
"",
|
|
128
|
-
`Total: ${allTools.length}
|
|
131
|
+
`Total: ${allTools.length} tool${allTools.length === 1 ? "" : "s"}`,
|
|
129
132
|
];
|
|
130
133
|
|
|
131
134
|
ctx.ui.notify(lines.join("\n"), "info");
|
package/direct-tools.ts
CHANGED
|
@@ -1,244 +1,29 @@
|
|
|
1
1
|
import type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { McpExtensionState } from "./state.ts";
|
|
3
|
-
import type { DirectToolSpec, McpAdapterOptions, McpConfig
|
|
4
|
-
import type { MetadataCache } from "./metadata-cache.ts";
|
|
5
|
-
import { lazyConnect, getFailureAgeSeconds, clearFailure, recordFailure, updateStatusBar } from "./init.ts";
|
|
3
|
+
import type { DirectToolSpec, McpAdapterOptions, McpConfig } from "./types.ts";
|
|
6
4
|
import { abortable, throwIfAborted } from "./abort.ts";
|
|
7
|
-
import {
|
|
5
|
+
import { parseDirectToolSelectors } from "./metadata-cache.ts";
|
|
8
6
|
export { getMissingConfiguredDirectToolServers } from "./metadata-cache.ts";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const BUILTIN_NAMES = new Set(["read", "bash", "edit", "write", "grep", "find", "ls", "mcp"]);
|
|
19
|
-
export const DIRECT_TOOLS_ADVISORY_THRESHOLD = 75;
|
|
20
|
-
const advisedDirectToolSets = new Set<string>();
|
|
21
|
-
|
|
22
|
-
type DirectAutoAuthResult =
|
|
23
|
-
| { status: "skipped" }
|
|
24
|
-
| { status: "success" }
|
|
25
|
-
| { status: "failed"; message: string };
|
|
26
|
-
|
|
27
|
-
function getDirectAuthRequiredMessage(
|
|
28
|
-
state: McpExtensionState,
|
|
29
|
-
serverName: string,
|
|
30
|
-
defaultMessage = `MCP server "${serverName}" requires OAuth authentication. Run mcp({ action: "auth-start", server: "${serverName}" }) to get a browser URL, or /mcp-auth ${serverName} in an interactive local session.`,
|
|
31
|
-
): string {
|
|
32
|
-
const oauth = state.config.mcpServers[serverName]?.oauth;
|
|
33
|
-
if (oauth && oauth.crossAppAccess) defaultMessage = `MCP server "${serverName}" requires enterprise authorization. Check oauth.crossAppAccess IdP configuration and ID-token source, then retry.`;
|
|
34
|
-
return formatAuthRequiredMessage(state.config, serverName, defaultMessage);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function getDirectAuthFailedMessage(state: McpExtensionState, serverName: string, message: string): string {
|
|
38
|
-
const customGuidance = state.config.settings?.authRequiredMessage;
|
|
39
|
-
const oauth = state.config.mcpServers[serverName]?.oauth;
|
|
40
|
-
if (customGuidance || (oauth && oauth.crossAppAccess)) {
|
|
41
|
-
return `OAuth authentication failed for "${serverName}": ${message}. ${getDirectAuthRequiredMessage(state, serverName)}`;
|
|
42
|
-
}
|
|
43
|
-
return `OAuth authentication failed for "${serverName}": ${message}. Run mcp({ action: "auth-start", server: "${serverName}" }) to get a browser URL, or /mcp-auth ${serverName} in an interactive local session.`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function attemptDirectAutoAuth(
|
|
47
|
-
state: McpExtensionState,
|
|
48
|
-
serverName: string,
|
|
49
|
-
signal?: AbortSignal,
|
|
50
|
-
): Promise<DirectAutoAuthResult> {
|
|
51
|
-
if (state.config.settings?.autoAuth !== true) {
|
|
52
|
-
return { status: "skipped" };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const definition = state.config.mcpServers[serverName];
|
|
56
|
-
if (!definition || isServerDisabled(definition) || !supportsOAuth(definition)) {
|
|
57
|
-
return { status: "skipped" };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let serverUrl: string | undefined;
|
|
61
|
-
try {
|
|
62
|
-
serverUrl = resolveServerUrl(definition);
|
|
63
|
-
} catch (error) {
|
|
64
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
65
|
-
return { status: "failed", message: getDirectAuthFailedMessage(state, serverName, message) };
|
|
66
|
-
}
|
|
67
|
-
if (!serverUrl) {
|
|
68
|
-
return { status: "skipped" };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (!state.ui && !isNonInteractiveOAuth(definition.oauth)) {
|
|
72
|
-
return {
|
|
73
|
-
status: "failed",
|
|
74
|
-
message: getDirectAuthRequiredMessage(
|
|
75
|
-
state,
|
|
76
|
-
serverName,
|
|
77
|
-
`MCP server "${serverName}" requires OAuth authentication. Run mcp({ action: "auth-start", server: "${serverName}" }) to get a browser URL, or /mcp-auth ${serverName} in an interactive local session.`,
|
|
78
|
-
),
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
if (state.authStorageOptions) {
|
|
84
|
-
await authenticate(
|
|
85
|
-
serverName,
|
|
86
|
-
serverUrl,
|
|
87
|
-
definition,
|
|
88
|
-
signal
|
|
89
|
-
? { authStorageOptions: state.authStorageOptions, signal, runtime: state.oauthRuntime }
|
|
90
|
-
: { authStorageOptions: state.authStorageOptions, runtime: state.oauthRuntime },
|
|
91
|
-
);
|
|
92
|
-
} else {
|
|
93
|
-
await authenticate(serverName, serverUrl, definition, {
|
|
94
|
-
...(signal ? { signal } : {}),
|
|
95
|
-
runtime: state.oauthRuntime,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
return { status: "success" };
|
|
99
|
-
} catch (error) {
|
|
100
|
-
if (isAbortError(error, signal)) throw error;
|
|
101
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
102
|
-
return {
|
|
103
|
-
status: "failed",
|
|
104
|
-
message: getDirectAuthFailedMessage(state, serverName, message),
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function resolveDirectTools(
|
|
110
|
-
config: McpConfig,
|
|
111
|
-
cache: MetadataCache | null,
|
|
112
|
-
prefix: ToolPrefix,
|
|
113
|
-
envOverride?: string[],
|
|
114
|
-
): DirectToolSpec[] {
|
|
115
|
-
const specs: DirectToolSpec[] = [];
|
|
116
|
-
if (!cache) return specs;
|
|
117
|
-
|
|
118
|
-
const seenNames = new Set<string>();
|
|
119
|
-
|
|
120
|
-
const envSelection = envOverride ? parseDirectToolSelectors(envOverride) : null;
|
|
121
|
-
const globalDirect = config.settings?.directTools;
|
|
122
|
-
|
|
123
|
-
for (const [serverName, definition] of Object.entries(config.mcpServers)) {
|
|
124
|
-
if (isServerDisabled(definition)) continue;
|
|
125
|
-
const serverCache = cache.servers[serverName];
|
|
126
|
-
if (!serverCache || !isServerCacheValid(serverCache, definition)) continue;
|
|
127
|
-
|
|
128
|
-
let toolFilter: true | string[] | false = false;
|
|
129
|
-
|
|
130
|
-
if (envSelection) {
|
|
131
|
-
if (envSelection.servers.has(serverName)) {
|
|
132
|
-
toolFilter = true;
|
|
133
|
-
} else if (envSelection.tools.has(serverName)) {
|
|
134
|
-
toolFilter = [...envSelection.tools.get(serverName)!];
|
|
135
|
-
}
|
|
136
|
-
} else {
|
|
137
|
-
if (definition.directTools !== undefined) {
|
|
138
|
-
toolFilter = definition.directTools;
|
|
139
|
-
} else if (globalDirect) {
|
|
140
|
-
toolFilter = globalDirect;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (!toolFilter) continue;
|
|
145
|
-
|
|
146
|
-
const effectivePrefix = resolveToolPrefix(definition, prefix);
|
|
147
|
-
|
|
148
|
-
for (const tool of serverCache.tools ?? []) {
|
|
149
|
-
if (toolFilter !== true && !toolFilter.includes(tool.name)) continue;
|
|
150
|
-
if (!isToolAllowed(tool.name, serverName, effectivePrefix, definition.includeTools, definition.excludeTools)) continue;
|
|
151
|
-
const prefixedName = formatToolName(tool.name, serverName, effectivePrefix);
|
|
152
|
-
if (BUILTIN_NAMES.has(prefixedName)) {
|
|
153
|
-
console.warn(`MCP: skipping direct tool "${prefixedName}" (collides with builtin)`);
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
if (seenNames.has(prefixedName)) {
|
|
157
|
-
console.warn(`MCP: skipping duplicate direct tool "${prefixedName}" from "${serverName}"`);
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
seenNames.add(prefixedName);
|
|
161
|
-
specs.push({
|
|
162
|
-
serverName,
|
|
163
|
-
originalName: tool.name,
|
|
164
|
-
prefixedName,
|
|
165
|
-
description: tool.description ?? "",
|
|
166
|
-
...(tool.inputSchema !== undefined ? { inputSchema: tool.inputSchema } : {}),
|
|
167
|
-
...(tool.annotations !== undefined ? { annotations: tool.annotations } : {}),
|
|
168
|
-
...(tool.uiResourceUri !== undefined ? { uiResourceUri: tool.uiResourceUri } : {}),
|
|
169
|
-
...(tool.uiStreamMode !== undefined ? { uiStreamMode: tool.uiStreamMode } : {}),
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (definition.exposeResources !== false) {
|
|
174
|
-
for (const resource of serverCache.resources ?? []) {
|
|
175
|
-
const baseName = `read_${resourceNameToToolName(resource.name)}`;
|
|
176
|
-
if (toolFilter !== true && !toolFilter.includes(baseName)) continue;
|
|
177
|
-
if (!isToolAllowed(baseName, serverName, effectivePrefix, definition.includeTools, definition.excludeTools)) continue;
|
|
178
|
-
const prefixedName = formatToolName(baseName, serverName, effectivePrefix);
|
|
179
|
-
if (BUILTIN_NAMES.has(prefixedName)) {
|
|
180
|
-
console.warn(`MCP: skipping direct resource tool "${prefixedName}" (collides with builtin)`);
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
if (seenNames.has(prefixedName)) {
|
|
184
|
-
console.warn(`MCP: skipping duplicate direct resource tool "${prefixedName}" from "${serverName}"`);
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
seenNames.add(prefixedName);
|
|
188
|
-
specs.push({
|
|
189
|
-
serverName,
|
|
190
|
-
originalName: baseName,
|
|
191
|
-
prefixedName,
|
|
192
|
-
description: resource.description ?? `Read resource: ${resource.uri}`,
|
|
193
|
-
resourceUri: resource.uri,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
7
|
+
import { executeCall } from "./proxy-modes.ts";
|
|
8
|
+
import { isServerDisabled } from "./types.ts";
|
|
9
|
+
|
|
10
|
+
export function directToolSelection(config: McpConfig, serverName: string, envOverride?: string[]): true | string[] | false {
|
|
11
|
+
if (envOverride) {
|
|
12
|
+
const selection = parseDirectToolSelectors(envOverride);
|
|
13
|
+
if (selection.servers.has(serverName)) return true;
|
|
14
|
+
return selection.tools.has(serverName) ? [...selection.tools.get(serverName)!] : false;
|
|
197
15
|
}
|
|
198
|
-
|
|
199
|
-
const advisoryKey = specs.map(spec => spec.prefixedName).sort().join("\0");
|
|
200
|
-
if (specs.length >= DIRECT_TOOLS_ADVISORY_THRESHOLD && !advisedDirectToolSets.has(advisoryKey)) {
|
|
201
|
-
advisedDirectToolSets.add(advisoryKey);
|
|
202
|
-
console.warn(`MCP: ${specs.length} direct tools resolved. Each direct tool adds prompt context; README guidance recommends targeted sets of 5-20 tools and using the proxy or an explicit string[] when 75+ direct tools would be registered.`);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return specs;
|
|
16
|
+
return config.mcpServers[serverName]?.directTools ?? config.settings?.directTools ?? false;
|
|
206
17
|
}
|
|
207
18
|
|
|
208
19
|
export function buildProxyDescription(config: McpConfig): string {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
.
|
|
215
|
-
|
|
216
|
-
.sort();
|
|
217
|
-
if (enabledServers.length > 0) {
|
|
218
|
-
desc += `\nConfigured servers (call mcp({}) for live status): ${enabledServers.join(", ")}\n`;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const disabledServers = Object.entries(config.mcpServers)
|
|
222
|
-
.filter(([, definition]) => isServerDisabled(definition))
|
|
223
|
-
.map(([serverName]) => serverName);
|
|
224
|
-
if (disabledServers.length > 0) {
|
|
225
|
-
desc += `\nDisabled servers (enable with /mcp enable <server> and /reload): ${disabledServers.join(", ")}\n`;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
desc += `\nUsage:\n`;
|
|
229
|
-
desc += ` mcp({ }) → Show server status\n`;
|
|
230
|
-
desc += ` mcp({ server: "name", limit: 12 }) → Browse a server's tools (use offset for more)\n`;
|
|
231
|
-
desc += ` mcp({ search: "query" }) → Search MCP tools by name/description\n`;
|
|
232
|
-
desc += ` mcp({ describe: "tool_name" }) → Show tool details and parameters\n`;
|
|
233
|
-
desc += ` mcp({ instructions: "name" }) → Show full server usage instructions\n`;
|
|
234
|
-
desc += ` mcp({ connect: "server-name" }) → Connect to a server and refresh metadata\n`;
|
|
235
|
-
desc += ` mcp({ tool: "name", args: { key: "value" } }) → Call a tool (object args; JSON string also accepted)\n`;
|
|
236
|
-
desc += ` mcp({ action: "ui-messages" }) → Retrieve accumulated messages from completed UI sessions\n`;
|
|
237
|
-
desc += ` mcp({ action: "auth-start", server: "name" }) → Start manual OAuth and get a browser URL\n`;
|
|
238
|
-
desc += ` mcp({ action: "auth-complete", server: "name", args: { redirectUrl: "..." } }) → Complete manual OAuth\n`;
|
|
239
|
-
desc += `\nMode: action > tool (call) > connect > describe > instructions > search > server (list) > nothing (status)`;
|
|
240
|
-
|
|
241
|
-
return desc;
|
|
20
|
+
const servers = Object.entries(config.mcpServers).filter(([, definition]) => !isServerDisabled(definition)).map(([name]) => name).sort();
|
|
21
|
+
return [
|
|
22
|
+
"MCP gateway. Use mcp_search to discover and load typed tools; mcp_script for multi-call composition. Native Pi tools are called directly.",
|
|
23
|
+
`Configured servers: ${servers.join(", ") || "none"}. Catalog discovery is lazy.`,
|
|
24
|
+
"Actions: status; list/search/describe; call (tool,args,server?); connect (server); instructions (server); resources (server); read-resource (server,uri); read-result (ref,path?,fields?,offset?,limit?); auth-start/auth-complete (server,args?); ui-messages.",
|
|
25
|
+
"Use call when a discovered direct tool is unavailable in this host. read-result reads a retained response without repeating the remote operation.",
|
|
26
|
+
].join("\n");
|
|
242
27
|
}
|
|
243
28
|
|
|
244
29
|
type DirectToolExecute = (
|
|
@@ -279,130 +64,12 @@ export function createDirectToolExecutor(
|
|
|
279
64
|
};
|
|
280
65
|
}
|
|
281
66
|
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const ownedSignal = combineAbortSignals(state.owner?.signal, signal);
|
|
292
|
-
throwIfAborted(ownedSignal);
|
|
293
|
-
let connected = await lazyConnect(state, spec.serverName, ownedSignal);
|
|
294
|
-
let autoAuthAttempted = false;
|
|
295
|
-
|
|
296
|
-
const needsAuthConnection = state.manager.getConnection(spec.serverName);
|
|
297
|
-
if (!connected && needsAuthConnection?.status === "needs-auth") {
|
|
298
|
-
autoAuthAttempted = true;
|
|
299
|
-
const autoAuth = await attemptDirectAutoAuth(state, spec.serverName, ownedSignal);
|
|
300
|
-
if (autoAuth.status === "failed") {
|
|
301
|
-
return {
|
|
302
|
-
content: [{ type: "text" as const, text: autoAuth.message }],
|
|
303
|
-
details: { error: "auth_required", server: spec.serverName, message: autoAuth.message },
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
if (autoAuth.status === "success") {
|
|
307
|
-
clearFailure(state, spec.serverName);
|
|
308
|
-
try {
|
|
309
|
-
const liveDefinition = state.config.mcpServers[spec.serverName];
|
|
310
|
-
if (liveDefinition) {
|
|
311
|
-
await state.manager.reconnect(spec.serverName, liveDefinition, needsAuthConnection, ownedSignal);
|
|
312
|
-
connected = true;
|
|
313
|
-
}
|
|
314
|
-
} catch (error) {
|
|
315
|
-
if (isAbortError(error, ownedSignal)) throwIfAborted(ownedSignal);
|
|
316
|
-
recordFailure(state, spec.serverName, error instanceof Error ? error.message : String(error));
|
|
317
|
-
updateStatusBar(state);
|
|
318
|
-
}
|
|
319
|
-
if (connected) connected = await lazyConnect(state, spec.serverName, ownedSignal);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (!connected) {
|
|
324
|
-
const authConnection = state.manager.getConnection(spec.serverName);
|
|
325
|
-
if (authConnection?.status === "needs-auth") {
|
|
326
|
-
const message = getDirectAuthRequiredMessage(state, spec.serverName);
|
|
327
|
-
return {
|
|
328
|
-
content: [{ type: "text" as const, text: message }],
|
|
329
|
-
details: { error: "auth_required", server: spec.serverName, message, autoAuthAttempted },
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
const failedAgo = getFailureAgeSeconds(state, spec.serverName);
|
|
333
|
-
return {
|
|
334
|
-
content: [{ type: "text" as const, text: `MCP server "${spec.serverName}" not available${failedAgo !== null ? ` (failed ${failedAgo}s ago)` : ""}` }],
|
|
335
|
-
details: { error: "server_unavailable", server: spec.serverName },
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const connection = state.manager.getConnection(spec.serverName);
|
|
340
|
-
if (!connection || connection.status !== "connected") {
|
|
341
|
-
return {
|
|
342
|
-
content: [{ type: "text" as const, text: `MCP server "${spec.serverName}" not connected` }],
|
|
343
|
-
details: { error: "not_connected", server: spec.serverName },
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const approval = await ensureToolCallApproved(state, spec.serverName, {
|
|
348
|
-
name: spec.prefixedName,
|
|
349
|
-
originalName: spec.originalName,
|
|
350
|
-
description: spec.description,
|
|
351
|
-
...(spec.inputSchema !== undefined ? { inputSchema: spec.inputSchema } : {}),
|
|
352
|
-
...(spec.resourceUri !== undefined ? { resourceUri: spec.resourceUri } : {}),
|
|
353
|
-
...(spec.uiResourceUri !== undefined ? { uiResourceUri: spec.uiResourceUri } : {}),
|
|
354
|
-
...(spec.uiStreamMode !== undefined ? { uiStreamMode: spec.uiStreamMode } : {}),
|
|
355
|
-
}, params, ownedSignal);
|
|
356
|
-
if (approval.ok === false) {
|
|
357
|
-
const denied = approval.reason === "denied";
|
|
358
|
-
const message = denied
|
|
359
|
-
? `The user declined approval to run MCP tool "${spec.originalName}" on server "${spec.serverName}".`
|
|
360
|
-
: `MCP tool "${spec.originalName}" on server "${spec.serverName}" is approval-gated and requires an interactive session.`;
|
|
361
|
-
return {
|
|
362
|
-
content: [{ type: "text" as const, text: message }],
|
|
363
|
-
details: {
|
|
364
|
-
error: denied ? "approval_denied" : "approval_required",
|
|
365
|
-
server: spec.serverName,
|
|
366
|
-
tool: spec.originalName,
|
|
367
|
-
},
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const recoverAuthConnection: NonNullable<SessionRecoveryDeps["onNeedsAuth"]> = async (_serverName, recoverySignal = ownedSignal, challenge) => {
|
|
372
|
-
throwIfAborted(recoverySignal);
|
|
373
|
-
const current = state.manager.getConnection(spec.serverName);
|
|
374
|
-
if (current?.status === "connected" && current !== challenge?.connection) return current;
|
|
375
|
-
|
|
376
|
-
if (!autoAuthAttempted) {
|
|
377
|
-
autoAuthAttempted = true;
|
|
378
|
-
const autoAuth = await attemptDirectAutoAuth(state, spec.serverName, recoverySignal);
|
|
379
|
-
throwIfAborted(recoverySignal);
|
|
380
|
-
if (autoAuth.status === "failed") {
|
|
381
|
-
throw new SessionRecoveryAuthRequiredError(spec.serverName, autoAuth.message);
|
|
382
|
-
}
|
|
383
|
-
if (autoAuth.status === "success") {
|
|
384
|
-
const liveDefinition = state.config.mcpServers[spec.serverName];
|
|
385
|
-
if (!current || !liveDefinition) return undefined;
|
|
386
|
-
await state.manager.reconnect(spec.serverName, liveDefinition, current, recoverySignal);
|
|
387
|
-
clearFailure(state, spec.serverName);
|
|
388
|
-
const reconnected = await lazyConnect(state, spec.serverName, recoverySignal);
|
|
389
|
-
return reconnected ? state.manager.getConnection(spec.serverName) : undefined;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
return challenge ? undefined : state.manager.getConnection(spec.serverName);
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
return runToolCall(state, spec.serverName, spec, params, {
|
|
396
|
-
toolCallId,
|
|
397
|
-
...(beforeExecute ? { beforeDispatch: (callSignal, operation) => beforeExecute(toolCallId, { ...ctx, signal: callSignal }, operation) } : {}),
|
|
398
|
-
detailsBase: spec.resourceUri
|
|
399
|
-
? { server: spec.serverName, resourceUri: spec.resourceUri }
|
|
400
|
-
: { server: spec.serverName, tool: spec.originalName },
|
|
401
|
-
ownedSignal,
|
|
402
|
-
signal,
|
|
403
|
-
recoverAuthConnection,
|
|
404
|
-
authRequiredMessage: () => getDirectAuthRequiredMessage(state, spec.serverName),
|
|
405
|
-
autoAuthAttempted: () => autoAuthAttempted,
|
|
406
|
-
});
|
|
67
|
+
const result = await executeCall(
|
|
68
|
+
state, spec.originalName, params, spec.serverName, undefined, signal, { toolCallId },
|
|
69
|
+
beforeExecute ? (callSignal, operation) => beforeExecute(toolCallId, { ...ctx, signal: callSignal }, operation) : undefined,
|
|
70
|
+
{ exactOriginalName: true, ...(spec.resourceUri ? { resourceUri: spec.resourceUri } : {}) },
|
|
71
|
+
);
|
|
72
|
+
const { mode: _mode, ...details } = result.details;
|
|
73
|
+
return { ...result, details };
|
|
407
74
|
};
|
|
408
75
|
}
|