pi-mcp-adapter 2.25.0 → 2.26.1
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 +22 -0
- package/README.md +10 -5
- package/agent-dir.ts +12 -4
- package/cli.js +25 -4
- package/commands.ts +26 -0
- package/config.ts +4 -4
- package/direct-tools.ts +18 -15
- package/index.ts +71 -27
- package/init.ts +26 -2
- package/lifecycle.ts +257 -24
- package/mcp-setup-panel.ts +2 -1
- package/metadata-cache.ts +18 -8
- package/package.json +2 -1
- package/request-headers-command.ts +336 -0
- package/server-manager.ts +126 -1
- package/session-recovery.ts +7 -0
- package/tool-approval.ts +15 -2
- package/tool-metadata.ts +38 -18
- package/tool-result-renderer.ts +44 -4
- package/types.ts +93 -10
package/lifecycle.ts
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
|
+
import { SdkError, SdkErrorCode } from "@modelcontextprotocol/client";
|
|
1
2
|
import { isServerDisabled, type ServerDefinition } from "./types.ts";
|
|
2
|
-
import type { McpServerManager } from "./server-manager.ts";
|
|
3
|
+
import type { McpServerManager, ServerConnection } from "./server-manager.ts";
|
|
3
4
|
import { hasPendingAuth } from "./mcp-auth-flow.ts";
|
|
4
5
|
import { logger } from "./logger.ts";
|
|
5
|
-
import { formatTerminalError, sanitizeTerminalText } from "./utils.ts";
|
|
6
|
+
import { formatTerminalError, parallelLimit, sanitizeTerminalText } from "./utils.ts";
|
|
7
|
+
import { isTerminatedSession } from "./session-recovery.ts";
|
|
6
8
|
|
|
7
|
-
export type ReconnectCallback = (serverName: string) => void
|
|
9
|
+
export type ReconnectCallback = (serverName: string) => void | Promise<void>;
|
|
8
10
|
export type ReconnectFailureCallback = (serverName: string, error: unknown) => void;
|
|
11
|
+
export type HealthRestoredCallback = (serverName: string) => void | Promise<void>;
|
|
12
|
+
export type AuthRequiredCallback = (serverName: string) => void | Promise<void>;
|
|
13
|
+
|
|
14
|
+
const KEEP_ALIVE_RETRY_BASE_MS = 30_000;
|
|
15
|
+
const KEEP_ALIVE_RETRY_MAX_MS = 5 * 60_000;
|
|
16
|
+
const KEEP_ALIVE_CHECK_CONCURRENCY = 10;
|
|
17
|
+
|
|
18
|
+
interface RetryState {
|
|
19
|
+
attempts: number;
|
|
20
|
+
nextAttemptAt: number;
|
|
21
|
+
connection: ServerConnection | undefined;
|
|
22
|
+
status: ServerConnection["status"] | undefined;
|
|
23
|
+
}
|
|
9
24
|
|
|
10
25
|
export class McpLifecycleManager {
|
|
11
26
|
private keepAliveServers = new Map<string, ServerDefinition>();
|
|
@@ -15,9 +30,14 @@ export class McpLifecycleManager {
|
|
|
15
30
|
private healthCheckInterval: NodeJS.Timeout | undefined;
|
|
16
31
|
private onReconnect: ReconnectCallback | undefined;
|
|
17
32
|
private onReconnectFailure: ReconnectFailureCallback | undefined;
|
|
33
|
+
private onHealthRestored: HealthRestoredCallback | undefined;
|
|
34
|
+
private onAuthRequired: AuthRequiredCallback | undefined;
|
|
18
35
|
private onIdleShutdown: ((serverName: string) => void) | undefined;
|
|
19
36
|
private activeHealthCheck: Promise<void> | undefined;
|
|
37
|
+
private activeConvergence: Promise<void> | undefined;
|
|
20
38
|
private shutdownPromise: Promise<void> | undefined;
|
|
39
|
+
private retryStates = new Map<string, RetryState>();
|
|
40
|
+
private pendingMetadataPublications = new Set<string>();
|
|
21
41
|
private stopped = false;
|
|
22
42
|
private removeHealthAbortListener: (() => void) | undefined;
|
|
23
43
|
|
|
@@ -34,6 +54,14 @@ export class McpLifecycleManager {
|
|
|
34
54
|
this.onReconnectFailure = callback;
|
|
35
55
|
}
|
|
36
56
|
|
|
57
|
+
setHealthRestoredCallback(callback: HealthRestoredCallback): void {
|
|
58
|
+
this.onHealthRestored = callback;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setAuthRequiredCallback(callback: AuthRequiredCallback): void {
|
|
62
|
+
this.onAuthRequired = callback;
|
|
63
|
+
}
|
|
64
|
+
|
|
37
65
|
markKeepAlive(name: string, definition: ServerDefinition): void {
|
|
38
66
|
if (isServerDisabled(definition)) return;
|
|
39
67
|
this.keepAliveServers.set(name, definition);
|
|
@@ -82,29 +110,23 @@ export class McpLifecycleManager {
|
|
|
82
110
|
this.healthCheckInterval.unref();
|
|
83
111
|
}
|
|
84
112
|
|
|
85
|
-
|
|
113
|
+
async ensureConverged(signal?: AbortSignal): Promise<void> {
|
|
86
114
|
if (this.stopped || signal?.aborted) return;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
await this.manager.connect(name, definition, signal);
|
|
97
|
-
if (this.stopped || signal?.aborted) return;
|
|
98
|
-
logger.debug(`Reconnected to ${name}`);
|
|
99
|
-
this.onReconnect?.(name);
|
|
100
|
-
} catch (error) {
|
|
101
|
-
if (this.stopped || signal?.aborted) return;
|
|
102
|
-
this.onReconnectFailure?.(name, error);
|
|
103
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
104
|
-
console.error(`MCP: Failed to reconnect to ${name}: ${sanitizeTerminalText(message)}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
115
|
+
if (this.activeConvergence) return this.activeConvergence;
|
|
116
|
+
|
|
117
|
+
const check = this.checkKeepAliveConnections(signal);
|
|
118
|
+
this.activeConvergence = check;
|
|
119
|
+
try {
|
|
120
|
+
await check;
|
|
121
|
+
} finally {
|
|
122
|
+
if (this.activeConvergence === check) this.activeConvergence = undefined;
|
|
107
123
|
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async checkConnections(signal?: AbortSignal): Promise<void> {
|
|
127
|
+
if (this.stopped || signal?.aborted) return;
|
|
128
|
+
await this.ensureConverged(signal);
|
|
129
|
+
if (this.stopped || signal?.aborted) return;
|
|
108
130
|
|
|
109
131
|
for (const [name] of this.allServers) {
|
|
110
132
|
if (this.keepAliveServers.has(name)) continue;
|
|
@@ -117,6 +139,205 @@ export class McpLifecycleManager {
|
|
|
117
139
|
}
|
|
118
140
|
}
|
|
119
141
|
|
|
142
|
+
private async checkKeepAliveConnections(signal?: AbortSignal): Promise<void> {
|
|
143
|
+
if (this.stopped || signal?.aborted) return;
|
|
144
|
+
await parallelLimit(
|
|
145
|
+
[...this.keepAliveServers],
|
|
146
|
+
KEEP_ALIVE_CHECK_CONCURRENCY,
|
|
147
|
+
([name, definition]) => this.checkKeepAliveConnection(name, definition, signal),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private async checkKeepAliveConnection(
|
|
152
|
+
name: string,
|
|
153
|
+
definition: ServerDefinition,
|
|
154
|
+
signal?: AbortSignal,
|
|
155
|
+
retrySuperseded = true,
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
if (isServerDisabled(definition) || this.stopped || signal?.aborted) return;
|
|
158
|
+
const connection = this.manager.getConnection(name);
|
|
159
|
+
if (connection?.status === "needs-auth") {
|
|
160
|
+
this.pendingMetadataPublications.delete(name);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (!this.shouldAttemptConnection(name, connection)) return;
|
|
164
|
+
if (!connection || connection.status !== "connected") {
|
|
165
|
+
if (this.hasPendingAuthForServer(name)) {
|
|
166
|
+
logger.debug(`Skipping reconnect for ${name} while OAuth authorization is pending`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
let freshConnection: ServerConnection;
|
|
170
|
+
try {
|
|
171
|
+
freshConnection = await this.manager.connect(name, definition, signal);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (this.stopped || signal?.aborted) return;
|
|
174
|
+
this.reportConnectionFailure(name, error, "reconnect", this.manager.getConnection(name));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (this.stopped || signal?.aborted) return;
|
|
178
|
+
if (freshConnection.status === "needs-auth") {
|
|
179
|
+
await this.notifyAuthRequired(name);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (freshConnection.status !== "connected") {
|
|
183
|
+
this.reportConnectionFailure(
|
|
184
|
+
name,
|
|
185
|
+
new Error(`MCP server ${name} did not return a connected session`),
|
|
186
|
+
"reconnect",
|
|
187
|
+
freshConnection,
|
|
188
|
+
);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
logger.debug(`Reconnected to ${name}`);
|
|
192
|
+
await this.publishConnectedMetadata(name, freshConnection);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (this.pendingMetadataPublications.has(name)) {
|
|
197
|
+
await this.publishConnectedMetadata(name, connection);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (!definition.url) return;
|
|
201
|
+
const hadSessionId = (connection.transport as { sessionId?: string } | undefined)?.sessionId != null;
|
|
202
|
+
let refreshResult: Awaited<ReturnType<McpServerManager["refreshTools"]>>;
|
|
203
|
+
try {
|
|
204
|
+
refreshResult = await this.manager.refreshTools(name, connection, signal);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (this.stopped || signal?.aborted) return;
|
|
207
|
+
const current = this.manager.getConnection(name);
|
|
208
|
+
if (current !== connection || connection.status !== "connected") {
|
|
209
|
+
await this.handleSupersededConnection(name, definition, connection, signal, retrySuperseded);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (!shouldReconnectAfterRefresh(error, hadSessionId)) {
|
|
213
|
+
this.reportConnectionFailure(name, error, "refresh", connection);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (this.hasPendingAuthForServer(name)) {
|
|
217
|
+
logger.debug(`Skipping reconnect for ${name} while OAuth authorization is pending`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
let freshConnection: ServerConnection;
|
|
221
|
+
try {
|
|
222
|
+
freshConnection = await this.manager.reconnect(name, definition, connection, signal);
|
|
223
|
+
} catch (reconnectError) {
|
|
224
|
+
if (this.stopped || signal?.aborted) return;
|
|
225
|
+
this.reportConnectionFailure(name, reconnectError, "reconnect", this.manager.getConnection(name));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (this.stopped || signal?.aborted) return;
|
|
229
|
+
if (freshConnection.status === "needs-auth") {
|
|
230
|
+
await this.notifyAuthRequired(name);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (freshConnection.status !== "connected") {
|
|
234
|
+
this.reportConnectionFailure(
|
|
235
|
+
name,
|
|
236
|
+
new Error(`MCP server ${name} did not return a connected session`),
|
|
237
|
+
"reconnect",
|
|
238
|
+
freshConnection,
|
|
239
|
+
);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
logger.debug(`Reconnected stale MCP session for ${name}`);
|
|
243
|
+
await this.publishConnectedMetadata(name, freshConnection);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (refreshResult === "superseded") {
|
|
248
|
+
await this.handleSupersededConnection(name, definition, connection, signal, retrySuperseded);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (this.retryStates.delete(name)) {
|
|
252
|
+
await this.onHealthRestored?.(name);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private async handleSupersededConnection(
|
|
257
|
+
name: string,
|
|
258
|
+
definition: ServerDefinition,
|
|
259
|
+
staleConnection: ServerConnection,
|
|
260
|
+
signal: AbortSignal | undefined,
|
|
261
|
+
retrySuperseded: boolean,
|
|
262
|
+
): Promise<void> {
|
|
263
|
+
const current = this.manager.getConnection(name);
|
|
264
|
+
if (current === staleConnection && current.status === "connected") {
|
|
265
|
+
if (this.retryStates.delete(name)) await this.onHealthRestored?.(name);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (current?.status === "connected") {
|
|
269
|
+
await this.publishConnectedMetadata(name, current);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (current?.status === "needs-auth") {
|
|
273
|
+
await this.notifyAuthRequired(name);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (retrySuperseded) {
|
|
277
|
+
await this.checkKeepAliveConnection(name, definition, signal, false);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private async publishConnectedMetadata(name: string, connection: ServerConnection): Promise<void> {
|
|
282
|
+
this.pendingMetadataPublications.add(name);
|
|
283
|
+
try {
|
|
284
|
+
await this.onReconnect?.(name);
|
|
285
|
+
this.pendingMetadataPublications.delete(name);
|
|
286
|
+
this.retryStates.delete(name);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
if (this.stopped) return;
|
|
289
|
+
this.reportConnectionFailure(name, error, "publish", connection);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private async notifyAuthRequired(name: string): Promise<void> {
|
|
294
|
+
this.pendingMetadataPublications.delete(name);
|
|
295
|
+
this.retryStates.delete(name);
|
|
296
|
+
try {
|
|
297
|
+
await this.onAuthRequired?.(name);
|
|
298
|
+
} catch (error) {
|
|
299
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
300
|
+
logger.debug(`MCP: auth-required callback failed for ${name}: ${sanitizeTerminalText(message)}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private shouldAttemptConnection(name: string, connection: ServerConnection | undefined): boolean {
|
|
305
|
+
const retry = this.retryStates.get(name);
|
|
306
|
+
if (!retry) return true;
|
|
307
|
+
if (retry.connection !== connection || retry.status !== connection?.status) {
|
|
308
|
+
this.retryStates.delete(name);
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
return Date.now() >= retry.nextAttemptAt;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private reportConnectionFailure(
|
|
315
|
+
name: string,
|
|
316
|
+
error: unknown,
|
|
317
|
+
action: "refresh" | "reconnect" | "publish",
|
|
318
|
+
connection: ServerConnection | undefined,
|
|
319
|
+
): void {
|
|
320
|
+
const attempts = (this.retryStates.get(name)?.attempts ?? 0) + 1;
|
|
321
|
+
const delay = Math.min(
|
|
322
|
+
KEEP_ALIVE_RETRY_BASE_MS * 2 ** Math.min(attempts - 1, 10),
|
|
323
|
+
KEEP_ALIVE_RETRY_MAX_MS,
|
|
324
|
+
);
|
|
325
|
+
this.retryStates.set(name, {
|
|
326
|
+
attempts,
|
|
327
|
+
nextAttemptAt: Date.now() + delay,
|
|
328
|
+
connection,
|
|
329
|
+
status: connection?.status,
|
|
330
|
+
});
|
|
331
|
+
this.onReconnectFailure?.(name, error);
|
|
332
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
333
|
+
const target = action === "reconnect"
|
|
334
|
+
? `reconnect to ${name}`
|
|
335
|
+
: action === "publish"
|
|
336
|
+
? `publish metadata for ${name}`
|
|
337
|
+
: `refresh ${name}`;
|
|
338
|
+
console.error(`MCP: Failed to ${target}: ${sanitizeTerminalText(message)}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
120
341
|
private getIdleTimeout(name: string): number {
|
|
121
342
|
const perServer = this.serverSettings.get(name)?.idleTimeout;
|
|
122
343
|
if (perServer !== undefined) return perServer * 60 * 1000;
|
|
@@ -137,11 +358,23 @@ export class McpLifecycleManager {
|
|
|
137
358
|
this.removeHealthAbortListener = undefined;
|
|
138
359
|
await this.activeHealthCheck;
|
|
139
360
|
this.activeHealthCheck = undefined;
|
|
361
|
+
await this.activeConvergence;
|
|
362
|
+
this.activeConvergence = undefined;
|
|
140
363
|
this.onReconnect = undefined;
|
|
141
364
|
this.onReconnectFailure = undefined;
|
|
365
|
+
this.onHealthRestored = undefined;
|
|
366
|
+
this.onAuthRequired = undefined;
|
|
142
367
|
this.onIdleShutdown = undefined;
|
|
368
|
+
this.retryStates.clear();
|
|
369
|
+
this.pendingMetadataPublications.clear();
|
|
143
370
|
if (typeof this.manager.closeAll === "function") {
|
|
144
371
|
await this.manager.closeAll();
|
|
145
372
|
}
|
|
146
373
|
}
|
|
147
374
|
}
|
|
375
|
+
|
|
376
|
+
function shouldReconnectAfterRefresh(error: unknown, hadSessionId: boolean): boolean {
|
|
377
|
+
if (isTerminatedSession(error, hadSessionId)) return true;
|
|
378
|
+
return error instanceof SdkError
|
|
379
|
+
&& (error.code === SdkErrorCode.NotConnected || error.code === SdkErrorCode.ConnectionClosed);
|
|
380
|
+
}
|
package/mcp-setup-panel.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
2
2
|
import { createPanelKeys, type PanelKeybindings, type PanelKeys } from "./panel-keys.ts";
|
|
3
3
|
import type { ImportKind } from "./types.ts";
|
|
4
|
+
import { getConfigDirName } from "./agent-dir.ts";
|
|
4
5
|
import { KNOWN_SERVER_PRESETS, type ConfigWritePreview, type KnownServerPreset, type McpDiscoverySummary } from "./config.ts";
|
|
5
6
|
import type { McpOnboardingState } from "./onboarding-state.ts";
|
|
6
7
|
|
|
@@ -556,7 +557,7 @@ export class McpSetupPanel {
|
|
|
556
557
|
"3. ~/.agents/mcp/mcp.json",
|
|
557
558
|
"4. <Pi agent dir>/mcp.json",
|
|
558
559
|
"5. .mcp.json",
|
|
559
|
-
|
|
560
|
+
`6. ${getConfigDirName()}/mcp.json`,
|
|
560
561
|
`Host discovery: ${this.discovery.hostConfigDiscovery}. Conflicts reported: ${this.discovery.conflicts.length}.`,
|
|
561
562
|
...this.discovery.conflicts.slice(0, 8).map((conflict) =>
|
|
562
563
|
`${conflict.serverName}: ${conflict.sources.map((source) => source.path).join(" -> ")} (winner: ${conflict.winner.path})`,
|
package/metadata-cache.ts
CHANGED
|
@@ -19,11 +19,12 @@ import type {
|
|
|
19
19
|
ToolMetadata,
|
|
20
20
|
PromptMetadata,
|
|
21
21
|
} from "./types.ts";
|
|
22
|
-
import { formatPromptCommandName, formatToolName, getToolNameCandidates, isServerDisabled, isToolAllowed, resolveToolPrefix, type ToolPrefix } from "./types.ts";
|
|
22
|
+
import { createToolSelectorCandidateIndex, formatPromptCommandName, formatToolName, getToolNameCandidates, isServerDisabled, isToolAllowed, resolveToolPrefix, type ToolPrefix } from "./types.ts";
|
|
23
23
|
import { resourceNameToToolName } from "./resource-tools.ts";
|
|
24
24
|
import {
|
|
25
25
|
extractToolUiStreamMode,
|
|
26
26
|
interpolateEnvRecord,
|
|
27
|
+
interpolateEnvVars,
|
|
27
28
|
resolveBearerToken,
|
|
28
29
|
resolveConfigPath,
|
|
29
30
|
resolveServerUrl,
|
|
@@ -90,6 +91,14 @@ export function computeServerHash(definition: ServerEntry): string {
|
|
|
90
91
|
cwd: resolveConfigPath(definition.cwd),
|
|
91
92
|
url: resolveServerUrl(definition),
|
|
92
93
|
headers: interpolateEnvRecord(definition.headers),
|
|
94
|
+
requestHeadersCommand: definition.requestHeadersCommand
|
|
95
|
+
? {
|
|
96
|
+
command: interpolateEnvVars(definition.requestHeadersCommand.command),
|
|
97
|
+
args: definition.requestHeadersCommand.args?.map(interpolateEnvVars),
|
|
98
|
+
env: interpolateEnvRecord(definition.requestHeadersCommand.env),
|
|
99
|
+
timeoutMs: definition.requestHeadersCommand.timeoutMs,
|
|
100
|
+
}
|
|
101
|
+
: undefined,
|
|
93
102
|
auth: definition.auth,
|
|
94
103
|
protocolVersion: definition.protocolVersion,
|
|
95
104
|
bearerToken: resolveBearerToken(definition),
|
|
@@ -184,8 +193,10 @@ export function reconstructToolMetadata(
|
|
|
184
193
|
const metadata: ToolMetadata[] = [];
|
|
185
194
|
const seenNames = new Set<string>();
|
|
186
195
|
const effectivePrefix = resolveToolPrefix(definition, prefix);
|
|
187
|
-
const
|
|
188
|
-
|
|
196
|
+
const hasToolFilters =
|
|
197
|
+
(Array.isArray(definition.includeTools) && definition.includeTools.length > 0) ||
|
|
198
|
+
(Array.isArray(definition.excludeTools) && definition.excludeTools.length > 0);
|
|
199
|
+
const selectorCandidateIndex = hasToolFilters && configuredServers && cache ? (() => {
|
|
189
200
|
const candidates = new Set<string>();
|
|
190
201
|
for (const [otherServerName, otherDefinition] of Object.entries(configuredServers)) {
|
|
191
202
|
const otherEntry = cache.servers[otherServerName];
|
|
@@ -202,16 +213,15 @@ export function reconstructToolMetadata(
|
|
|
202
213
|
}
|
|
203
214
|
}
|
|
204
215
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
};
|
|
216
|
+
return createToolSelectorCandidateIndex(candidates);
|
|
217
|
+
})() : undefined;
|
|
208
218
|
|
|
209
219
|
for (const tool of entry.tools ?? []) {
|
|
210
220
|
if (!tool?.name) continue;
|
|
211
221
|
if (!isUiToolVisibleToModel(tool.uiVisibility)) {
|
|
212
222
|
continue;
|
|
213
223
|
}
|
|
214
|
-
if (!isToolAllowed(tool.name, serverName, effectivePrefix, definition.includeTools, definition.excludeTools,
|
|
224
|
+
if (!isToolAllowed(tool.name, serverName, effectivePrefix, definition.includeTools, definition.excludeTools, selectorCandidateIndex)) {
|
|
215
225
|
continue;
|
|
216
226
|
}
|
|
217
227
|
|
|
@@ -236,7 +246,7 @@ export function reconstructToolMetadata(
|
|
|
236
246
|
for (const resource of entry.resources ?? []) {
|
|
237
247
|
if (!resource?.name || !resource?.uri) continue;
|
|
238
248
|
const baseName = `read_${resourceNameToToolName(resource.name)}`;
|
|
239
|
-
if (!isToolAllowed(baseName, serverName, effectivePrefix, definition.includeTools, definition.excludeTools,
|
|
249
|
+
if (!isToolAllowed(baseName, serverName, effectivePrefix, definition.includeTools, definition.excludeTools, selectorCandidateIndex)) {
|
|
240
250
|
continue;
|
|
241
251
|
}
|
|
242
252
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mcp-adapter",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.26.1",
|
|
4
4
|
"description": "MCP (Model Context Protocol) adapter extension for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.ts",
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
"ui-app-bridge-helpers.ts",
|
|
90
90
|
"config.ts",
|
|
91
91
|
"server-manager.ts",
|
|
92
|
+
"request-headers-command.ts",
|
|
92
93
|
"unix-socket-transport.ts",
|
|
93
94
|
"json-schema-validator.ts",
|
|
94
95
|
"session-recovery.ts",
|