@fitchmultz/pi-mcp-adapter 8.0.5 → 8.0.7
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 +140 -7
- package/OAUTH.md +13 -16
- package/README.md +8 -8
- package/cli.js +67 -28
- package/dist/abort.js +18 -9
- package/dist/client-operations.js +4 -2
- package/dist/commands.js +77 -43
- package/dist/config.js +194 -105
- package/dist/connection-runtime.d.ts +2 -2
- package/dist/connection-runtime.js +29 -18
- package/dist/consent-manager.d.ts +2 -2
- package/dist/consent-manager.js +5 -2
- package/dist/direct-tools.d.ts +1 -1
- package/dist/direct-tools.js +13 -7
- package/dist/elicitation-handler.d.ts +2 -3
- package/dist/elicitation-handler.js +113 -58
- package/dist/gateway-arguments.d.ts +1 -1
- package/dist/gateway-arguments.js +90 -23
- package/dist/glimpse-ui.js +10 -5
- package/dist/host-html-template.d.ts +3 -1
- package/dist/host-html-template.js +9 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.js +264 -119
- package/dist/init.d.ts +1 -3
- package/dist/init.js +91 -51
- package/dist/legacy-migration.d.ts +1 -1
- package/dist/legacy-migration.js +17 -12
- package/dist/lifecycle.d.ts +3 -3
- package/dist/lifecycle.js +33 -17
- package/dist/logger.d.ts +2 -2
- package/dist/logger.js +4 -2
- package/dist/mcp-auth-flow.js +150 -58
- package/dist/mcp-auth.d.ts +7 -7
- package/dist/mcp-auth.js +189 -138
- package/dist/mcp-callback-server.js +19 -8
- package/dist/mcp-code.js +168 -54
- package/dist/mcp-keyring-helper.cjs +57 -23
- package/dist/mcp-oauth-provider.d.ts +6 -7
- package/dist/mcp-oauth-provider.js +146 -72
- package/dist/mcp-oauth-runtime.js +29 -16
- package/dist/mcp-output-guard.js +193 -65
- package/dist/mcp-output-preview.js +41 -20
- package/dist/mcp-panel.d.ts +9 -9
- package/dist/mcp-panel.js +141 -70
- package/dist/mcp-script-worker.mjs +135 -43
- package/dist/mcp-setup-panel.d.ts +8 -8
- package/dist/mcp-setup-panel.js +130 -44
- package/dist/mcp-status.js +14 -7
- package/dist/mcp-trace.d.ts +10 -5
- package/dist/mcp-trace.js +127 -61
- package/dist/metadata-cache.d.ts +3 -4
- package/dist/metadata-cache.js +83 -43
- package/dist/npx-resolver.js +101 -60
- package/dist/onboarding-state.js +4 -2
- package/dist/plain-result-reader.js +78 -31
- package/dist/prompts.js +36 -20
- package/dist/proxy-modes.js +670 -193
- package/dist/resource-tools.js +30 -9
- package/dist/runtime-owner.js +36 -18
- package/dist/sampling-handler.d.ts +1 -2
- package/dist/sampling-handler.js +41 -21
- package/dist/search-ranking.d.ts +1 -1
- package/dist/search-ranking.js +67 -34
- package/dist/server-manager.d.ts +11 -11
- package/dist/server-manager.js +283 -166
- package/dist/session-recovery.js +113 -61
- package/dist/tool-approval.js +9 -3
- package/dist/tool-loader.js +154 -55
- package/dist/tool-metadata.d.ts +1 -1
- package/dist/tool-metadata.js +51 -23
- package/dist/tool-registrar.js +32 -10
- package/dist/tool-result-renderer.d.ts +3 -3
- package/dist/tool-result-renderer.js +65 -44
- package/dist/types.d.ts +2 -2
- package/dist/types.js +39 -18
- package/dist/ui-message-history.js +33 -13
- package/dist/ui-resource-handler.d.ts +3 -3
- package/dist/ui-resource-handler.js +37 -21
- package/dist/ui-server.js +101 -43
- package/dist/ui-session.js +66 -29
- package/dist/ui-stream-types.js +14 -4
- package/dist/ui-tool-visibility.js +12 -6
- package/dist/unix-socket-transport.d.ts +1 -2
- package/dist/unix-socket-transport.js +19 -12
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +60 -35
- package/package.json +29 -5
- package/skills/mcp-scripting/SKILL.md +6 -6
package/dist/session-recovery.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// - treat generic -32000/ConnectionClosed errors as session expiry
|
|
22
22
|
// - treat AbortError/cancellation as a session failure
|
|
23
23
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
24
|
-
import { SdkError, SdkHttpError, SdkErrorCode, ProtocolError, InsufficientScopeError, UnauthorizedError } from "@modelcontextprotocol/client";
|
|
24
|
+
import { SdkError, SdkHttpError, SdkErrorCode, ProtocolError, InsufficientScopeError, UnauthorizedError, } from "@modelcontextprotocol/client";
|
|
25
25
|
import { logger } from "./logger.js";
|
|
26
26
|
import { abortable, throwIfAborted } from "./abort.js";
|
|
27
27
|
import { isServerDisabled, isNonInteractiveOAuth } from "./types.js";
|
|
@@ -43,9 +43,12 @@ export async function trackToolCallOutcome(fn, signal) {
|
|
|
43
43
|
return await fn();
|
|
44
44
|
}
|
|
45
45
|
catch (error) {
|
|
46
|
-
if (outcome.pending &&
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
if (outcome.pending &&
|
|
47
|
+
error instanceof Error &&
|
|
48
|
+
(signal?.aborted ||
|
|
49
|
+
(error instanceof SdkError &&
|
|
50
|
+
(error.code === SdkErrorCode.RequestTimeout ||
|
|
51
|
+
error.code === SdkErrorCode.ConnectionClosed)))) {
|
|
49
52
|
// Abort reasons can be shared by calls that never reached dispatch.
|
|
50
53
|
const interrupted = new Error(error.message, { cause: error });
|
|
51
54
|
interruptedToolCalls.add(interrupted);
|
|
@@ -54,8 +57,9 @@ export async function trackToolCallOutcome(fn, signal) {
|
|
|
54
57
|
throw error;
|
|
55
58
|
}
|
|
56
59
|
finally {
|
|
57
|
-
for (const cleanup of outcome.cleanup)
|
|
60
|
+
for (const cleanup of outcome.cleanup) {
|
|
58
61
|
cleanup();
|
|
62
|
+
}
|
|
59
63
|
}
|
|
60
64
|
});
|
|
61
65
|
}
|
|
@@ -63,8 +67,9 @@ const toolSend = new AsyncLocalStorage();
|
|
|
63
67
|
/** Retain fetch/body-error origin without parsing or buffering the response. */
|
|
64
68
|
export const trackToolTransportFailure = async (input, init) => {
|
|
65
69
|
const pending = toolSend.getStore();
|
|
66
|
-
if (pending)
|
|
70
|
+
if (pending) {
|
|
67
71
|
pending.bodyFailure = undefined;
|
|
72
|
+
}
|
|
68
73
|
let toolPost = false;
|
|
69
74
|
if (pending && init?.method === "POST" && typeof init.body === "string") {
|
|
70
75
|
try {
|
|
@@ -73,18 +78,21 @@ export const trackToolTransportFailure = async (input, init) => {
|
|
|
73
78
|
}
|
|
74
79
|
catch { }
|
|
75
80
|
}
|
|
76
|
-
if (toolPost && pending?.outcome)
|
|
81
|
+
if (toolPost && pending?.outcome) {
|
|
77
82
|
pending.outcome.pending = true;
|
|
83
|
+
}
|
|
78
84
|
try {
|
|
79
85
|
const response = await fetch(input, init);
|
|
80
|
-
if (toolPost && !response.ok && response.status < 500 && pending?.outcome)
|
|
86
|
+
if (toolPost && !response.ok && response.status < 500 && pending?.outcome) {
|
|
81
87
|
pending.outcome.pending = false;
|
|
88
|
+
}
|
|
82
89
|
const mediaType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
83
90
|
const observeBody = response.ok
|
|
84
91
|
? mediaType === "text/event-stream" || (toolPost && mediaType === "application/json")
|
|
85
92
|
: toolPost && response.status >= 500;
|
|
86
|
-
if (!pending || !response.body || !observeBody)
|
|
93
|
+
if (!pending || !response.body || !observeBody) {
|
|
87
94
|
return response;
|
|
95
|
+
}
|
|
88
96
|
pending.streaming = response.ok && mediaType === "text/event-stream";
|
|
89
97
|
// The native end hook has no error argument. Only a failed body read proves loss.
|
|
90
98
|
const reader = response.body.getReader();
|
|
@@ -96,28 +104,33 @@ export const trackToolTransportFailure = async (input, init) => {
|
|
|
96
104
|
controller.close();
|
|
97
105
|
reader.releaseLock();
|
|
98
106
|
}
|
|
99
|
-
else
|
|
107
|
+
else {
|
|
100
108
|
controller.enqueue(value);
|
|
109
|
+
}
|
|
101
110
|
}
|
|
102
111
|
catch (error) {
|
|
103
112
|
// Native intentional closure suppresses onRequestStreamEnd.
|
|
104
|
-
if (init?.signal?.aborted)
|
|
113
|
+
if (init?.signal?.aborted) {
|
|
105
114
|
pending.finishSend();
|
|
115
|
+
}
|
|
106
116
|
else if (error instanceof Error) {
|
|
107
117
|
pending.bodyFailure = error;
|
|
108
|
-
if (!pending.streaming)
|
|
118
|
+
if (!pending.streaming) {
|
|
109
119
|
interruptedToolCalls.add(error);
|
|
120
|
+
}
|
|
110
121
|
}
|
|
111
122
|
controller.error(error);
|
|
112
123
|
reader.releaseLock();
|
|
113
124
|
}
|
|
114
125
|
},
|
|
115
|
-
cancel: reason => reader.cancel(reason).finally(() => reader.releaseLock()),
|
|
126
|
+
cancel: (reason) => reader.cancel(reason).finally(() => reader.releaseLock()),
|
|
116
127
|
}), response);
|
|
117
128
|
}
|
|
118
129
|
catch (error) {
|
|
119
|
-
if (error instanceof Error &&
|
|
120
|
-
|
|
130
|
+
if (error instanceof Error &&
|
|
131
|
+
!init?.signal?.aborted &&
|
|
132
|
+
init?.method === "POST" &&
|
|
133
|
+
new Headers(init.headers).get("Mcp-Method") === "tools/call") {
|
|
121
134
|
toolTransportFailures.add(error);
|
|
122
135
|
}
|
|
123
136
|
throw error;
|
|
@@ -128,68 +141,89 @@ export function trackToolTransport(transport, http = false) {
|
|
|
128
141
|
const requests = new Map();
|
|
129
142
|
const onmessage = transport.onmessage;
|
|
130
143
|
transport.onmessage = (message, extra) => {
|
|
131
|
-
const pending = ("id" in message && message.id !== undefined ? requests.get(message.id) : undefined) ??
|
|
132
|
-
|
|
144
|
+
const pending = ("id" in message && message.id !== undefined ? requests.get(message.id) : undefined) ??
|
|
145
|
+
toolSend.getStore();
|
|
146
|
+
if (pending &&
|
|
147
|
+
"id" in message &&
|
|
148
|
+
message.id === pending.id &&
|
|
149
|
+
("result" in message || "error" in message)) {
|
|
133
150
|
pending.responseReceived = true;
|
|
134
|
-
if (pending.outcome)
|
|
151
|
+
if (pending.outcome) {
|
|
135
152
|
pending.outcome.pending = false;
|
|
153
|
+
}
|
|
136
154
|
requests.delete(pending.id);
|
|
137
155
|
pending.finishSend();
|
|
138
156
|
}
|
|
139
157
|
onmessage?.(message, extra);
|
|
140
158
|
};
|
|
141
159
|
const onerror = transport.onerror;
|
|
142
|
-
transport.onerror = error => {
|
|
160
|
+
transport.onerror = (error) => {
|
|
143
161
|
const pending = toolSend.getStore();
|
|
144
162
|
// Reader failures are observed before the SDK reports them; earlier parser errors are not retryable.
|
|
145
|
-
if (pending && !pending.bodyFailure)
|
|
163
|
+
if (pending && !pending.bodyFailure) {
|
|
146
164
|
pending.otherFailure ??= error;
|
|
165
|
+
}
|
|
147
166
|
onerror?.(error);
|
|
148
167
|
};
|
|
149
168
|
const send = transport.send.bind(transport);
|
|
150
169
|
transport.send = async (message, options) => {
|
|
151
170
|
try {
|
|
152
171
|
if (!("method" in message && message.method === "tools/call" && "id" in message)) {
|
|
153
|
-
|
|
172
|
+
await toolSend.run(undefined, () => send(message, options));
|
|
173
|
+
return;
|
|
154
174
|
}
|
|
155
175
|
const pending = {
|
|
156
|
-
id: message.id,
|
|
157
|
-
|
|
176
|
+
id: message.id,
|
|
177
|
+
outcome: toolCallOutcome.getStore(),
|
|
178
|
+
streaming: false,
|
|
179
|
+
responseReceived: false,
|
|
180
|
+
bodyFailure: undefined,
|
|
181
|
+
otherFailure: undefined,
|
|
158
182
|
finishSend: () => { },
|
|
159
183
|
};
|
|
160
184
|
if (pending.outcome) {
|
|
161
185
|
requests.set(message.id, pending);
|
|
162
186
|
pending.outcome.cleanup.add(() => requests.delete(message.id));
|
|
163
|
-
if (!http)
|
|
187
|
+
if (!http) {
|
|
164
188
|
pending.outcome.pending = true;
|
|
189
|
+
}
|
|
165
190
|
}
|
|
166
191
|
try {
|
|
167
192
|
if (!options?.requestSignal) {
|
|
168
|
-
|
|
193
|
+
await toolSend.run(pending, () => send(message, options));
|
|
194
|
+
return;
|
|
169
195
|
}
|
|
170
196
|
await abortable(new Promise((resolve, reject) => {
|
|
171
197
|
pending.finishSend = resolve;
|
|
172
|
-
void toolSend
|
|
198
|
+
void toolSend
|
|
199
|
+
.run(pending, () => send(message, {
|
|
173
200
|
...options,
|
|
174
201
|
onRequestStreamEnd: () => {
|
|
175
202
|
options.onRequestStreamEnd?.();
|
|
176
203
|
const error = pending.otherFailure ?? pending.bodyFailure;
|
|
177
204
|
if (!pending.responseReceived && error) {
|
|
178
|
-
if (!pending.otherFailure)
|
|
205
|
+
if (!pending.otherFailure) {
|
|
179
206
|
toolTransportFailures.add(error);
|
|
207
|
+
}
|
|
180
208
|
reject(error);
|
|
181
209
|
}
|
|
182
|
-
else
|
|
210
|
+
else {
|
|
183
211
|
resolve();
|
|
212
|
+
}
|
|
184
213
|
},
|
|
185
|
-
}))
|
|
186
|
-
|
|
214
|
+
}))
|
|
215
|
+
.then(() => {
|
|
216
|
+
if (!pending.streaming) {
|
|
217
|
+
resolve();
|
|
218
|
+
}
|
|
219
|
+
}, reject);
|
|
187
220
|
}), options.requestSignal);
|
|
188
221
|
}
|
|
189
222
|
catch (error) {
|
|
190
223
|
// Local send failures are distinct from a sent request waiting for its reply.
|
|
191
|
-
if (!http && pending.outcome)
|
|
224
|
+
if (!http && pending.outcome) {
|
|
192
225
|
pending.outcome.pending = false;
|
|
226
|
+
}
|
|
193
227
|
throw error;
|
|
194
228
|
}
|
|
195
229
|
finally {
|
|
@@ -197,17 +231,21 @@ export function trackToolTransport(transport, http = false) {
|
|
|
197
231
|
}
|
|
198
232
|
}
|
|
199
233
|
catch (error) {
|
|
200
|
-
if ("method" in message &&
|
|
201
|
-
|
|
202
|
-
|
|
234
|
+
if ("method" in message &&
|
|
235
|
+
message.method === "tools/call" &&
|
|
236
|
+
error instanceof SdkHttpError &&
|
|
237
|
+
error.code === SdkErrorCode.ClientHttpNotImplemented &&
|
|
238
|
+
error.status >= 500 &&
|
|
239
|
+
error.status < 600) {
|
|
203
240
|
let errorBody = false;
|
|
204
241
|
try {
|
|
205
242
|
const body = JSON.parse(String(error.data.text));
|
|
206
243
|
errorBody = body?.jsonrpc === "2.0" && body.error !== undefined;
|
|
207
244
|
}
|
|
208
245
|
catch { }
|
|
209
|
-
if (!errorBody)
|
|
246
|
+
if (!errorBody) {
|
|
210
247
|
toolTransportFailures.add(error);
|
|
248
|
+
}
|
|
211
249
|
}
|
|
212
250
|
throw error;
|
|
213
251
|
}
|
|
@@ -229,17 +267,18 @@ const SERVER_NOT_INITIALIZED_MCP_MESSAGES = new Set([
|
|
|
229
267
|
"Bad Request: Server not initialized",
|
|
230
268
|
]);
|
|
231
269
|
export function isTerminatedSession(err, hadSessionId) {
|
|
232
|
-
if (!hadSessionId)
|
|
270
|
+
if (!hadSessionId) {
|
|
233
271
|
return false;
|
|
272
|
+
}
|
|
234
273
|
if (err instanceof SdkHttpError) {
|
|
235
|
-
return err.status === 404
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
274
|
+
return (err.status === 404 ||
|
|
275
|
+
(err.status === 400 &&
|
|
276
|
+
/"code"\s*:\s*-32000/.test(err.message) &&
|
|
277
|
+
/"message"\s*:\s*"Bad Request: Server not initialized"/.test(err.message)));
|
|
239
278
|
}
|
|
240
|
-
return err instanceof ProtocolError
|
|
241
|
-
|
|
242
|
-
|
|
279
|
+
return (err instanceof ProtocolError &&
|
|
280
|
+
err.code === -32000 &&
|
|
281
|
+
SERVER_NOT_INITIALIZED_MCP_MESSAGES.has(err.message));
|
|
243
282
|
}
|
|
244
283
|
function hasSessionId(connection) {
|
|
245
284
|
// Only StreamableHTTPClientTransport exposes `sessionId`; stdio/SSE
|
|
@@ -273,8 +312,9 @@ export async function withSessionRecovery(deps, serverName, fn) {
|
|
|
273
312
|
}
|
|
274
313
|
const reconnect = async (stale, error) => {
|
|
275
314
|
const definition = deps.config.mcpServers[serverName];
|
|
276
|
-
if (!definition)
|
|
315
|
+
if (!definition) {
|
|
277
316
|
throw error;
|
|
317
|
+
}
|
|
278
318
|
throwIfAborted(deps.signal);
|
|
279
319
|
let fresh = stale;
|
|
280
320
|
if (fresh.status !== "needs-auth") {
|
|
@@ -284,26 +324,32 @@ export async function withSessionRecovery(deps, serverName, fn) {
|
|
|
284
324
|
}
|
|
285
325
|
throwIfAborted(deps.signal);
|
|
286
326
|
if (fresh.status === "needs-auth" && deps.onNeedsAuth) {
|
|
287
|
-
fresh = await abortable(deps.onNeedsAuth(serverName, deps.signal), deps.signal) ?? fresh;
|
|
327
|
+
fresh = (await abortable(deps.onNeedsAuth(serverName, deps.signal), deps.signal)) ?? fresh;
|
|
288
328
|
throwIfAborted(deps.signal);
|
|
289
329
|
}
|
|
290
|
-
if (fresh.status === "needs-auth")
|
|
330
|
+
if (fresh.status === "needs-auth") {
|
|
291
331
|
throw new SessionRecoveryAuthRequiredError(serverName);
|
|
292
|
-
|
|
332
|
+
}
|
|
333
|
+
if (fresh.status !== "connected") {
|
|
293
334
|
throw error;
|
|
335
|
+
}
|
|
294
336
|
return fresh;
|
|
295
337
|
};
|
|
296
338
|
const isRecoverableAuth = (error, failed, activating = false) => {
|
|
297
339
|
const definition = deps.config.mcpServers[serverName];
|
|
298
|
-
if (!definition || isServerDisabled(definition) || !supportsOAuth(definition))
|
|
340
|
+
if (!definition || isServerDisabled(definition) || !supportsOAuth(definition)) {
|
|
299
341
|
return false;
|
|
342
|
+
}
|
|
300
343
|
if (error instanceof SdkHttpError) {
|
|
301
344
|
// A provider's exhausted native 401/M2M retry is terminal, not another consent cycle.
|
|
302
|
-
return failed.oauthProvider === false &&
|
|
345
|
+
return (failed.oauthProvider === false &&
|
|
346
|
+
error.status === 401 &&
|
|
347
|
+
error.code !== SdkErrorCode.ClientHttpAuthentication);
|
|
303
348
|
}
|
|
304
|
-
if (failed.oauthProvider && isNonInteractiveOAuth(definition.oauth))
|
|
349
|
+
if (failed.oauthProvider && isNonInteractiveOAuth(definition.oauth)) {
|
|
305
350
|
return false;
|
|
306
|
-
|
|
351
|
+
}
|
|
352
|
+
return (error instanceof InsufficientScopeError || (activating && error instanceof UnauthorizedError));
|
|
307
353
|
};
|
|
308
354
|
const terminalDispatch = async (fresh) => {
|
|
309
355
|
throwIfAborted(deps.signal);
|
|
@@ -312,8 +358,9 @@ export async function withSessionRecovery(deps, serverName, fn) {
|
|
|
312
358
|
}
|
|
313
359
|
catch (error) {
|
|
314
360
|
throwIfAborted(deps.signal);
|
|
315
|
-
if (isRecoverableAuth(error, fresh))
|
|
361
|
+
if (isRecoverableAuth(error, fresh)) {
|
|
316
362
|
throw new SessionRecoveryAuthRequiredError(serverName);
|
|
363
|
+
}
|
|
317
364
|
throw error;
|
|
318
365
|
}
|
|
319
366
|
};
|
|
@@ -333,18 +380,22 @@ export async function withSessionRecovery(deps, serverName, fn) {
|
|
|
333
380
|
}
|
|
334
381
|
catch (providerError) {
|
|
335
382
|
throwIfAborted(deps.signal);
|
|
336
|
-
if (!isRecoverableAuth(providerError, failed, true))
|
|
383
|
+
if (!isRecoverableAuth(providerError, failed, true)) {
|
|
337
384
|
throw providerError;
|
|
385
|
+
}
|
|
338
386
|
error = providerError;
|
|
339
387
|
}
|
|
340
388
|
}
|
|
341
389
|
}
|
|
342
|
-
if (isNonInteractiveOAuth(definition.oauth))
|
|
390
|
+
if (isNonInteractiveOAuth(definition.oauth)) {
|
|
343
391
|
throw error;
|
|
344
|
-
|
|
392
|
+
}
|
|
393
|
+
const fresh = await abortable(deps.onNeedsAuth?.(serverName, deps.signal, { connection: failed, error }) ??
|
|
394
|
+
Promise.resolve(undefined), deps.signal);
|
|
345
395
|
throwIfAborted(deps.signal);
|
|
346
|
-
if (!fresh || fresh === failed || fresh.status !== "connected")
|
|
396
|
+
if (!fresh || fresh === failed || fresh.status !== "connected") {
|
|
347
397
|
throw new SessionRecoveryAuthRequiredError(serverName);
|
|
398
|
+
}
|
|
348
399
|
return terminalDispatch(fresh);
|
|
349
400
|
};
|
|
350
401
|
// Nothing has been dispatched yet: wait for replacement/auth begun during UI preparation.
|
|
@@ -357,12 +408,13 @@ export async function withSessionRecovery(deps, serverName, fn) {
|
|
|
357
408
|
}
|
|
358
409
|
catch (err) {
|
|
359
410
|
throwIfAborted(deps.signal);
|
|
360
|
-
if (isRecoverableAuth(err, connection))
|
|
411
|
+
if (isRecoverableAuth(err, connection)) {
|
|
361
412
|
return recoverAuth(connection, err);
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
413
|
+
}
|
|
414
|
+
if (deps.retryOnTransportFailure === true &&
|
|
415
|
+
deps.config.mcpServers[serverName]?.retryOnTransportFailure === true &&
|
|
416
|
+
connection.client.getProtocolEra?.() === "modern" &&
|
|
417
|
+
isToolTransportFailure(err)) {
|
|
366
418
|
return terminalDispatch(connection);
|
|
367
419
|
}
|
|
368
420
|
if (!isTerminatedSession(err, hadSessionId)) {
|
package/dist/tool-approval.js
CHANGED
|
@@ -7,10 +7,12 @@ export function isToolCallApprovalRequired(config, serverName, toolMeta) {
|
|
|
7
7
|
const approval = definition?.approveTools !== undefined
|
|
8
8
|
? definition.approveTools
|
|
9
9
|
: config.settings?.approveTools;
|
|
10
|
-
if (approval === true)
|
|
10
|
+
if (approval === true) {
|
|
11
11
|
return true;
|
|
12
|
-
|
|
12
|
+
}
|
|
13
|
+
if (!Array.isArray(approval) || approval.length === 0) {
|
|
13
14
|
return false;
|
|
15
|
+
}
|
|
14
16
|
const prefix = resolveToolPrefix(definition, config.settings?.toolPrefix);
|
|
15
17
|
return matchesToolPattern(getToolNameCandidates(toolMeta.originalName, serverName, prefix), approval);
|
|
16
18
|
}
|
|
@@ -30,7 +32,11 @@ export async function ensureToolCallApproved(state, serverName, toolMeta, args,
|
|
|
30
32
|
const preview = sanitized.length > 500 ? `${sanitized.slice(0, 500)}...` : sanitized;
|
|
31
33
|
const title = `MCP: ${sanitizeTerminalText(serverName)} wants to run ${sanitizeTerminalText(toolMeta.originalName)}`;
|
|
32
34
|
const ownedSignal = combineAbortSignals(state.owner?.signal, signal);
|
|
33
|
-
const decision = await abortable(state.ui.select(`${title}\n\nArguments:\n${preview}`, [
|
|
35
|
+
const decision = await abortable(state.ui.select(`${title}\n\nArguments:\n${preview}`, [
|
|
36
|
+
"Allow once",
|
|
37
|
+
"Allow for session",
|
|
38
|
+
"Deny",
|
|
39
|
+
]), ownedSignal);
|
|
34
40
|
if (decision === "Allow once") {
|
|
35
41
|
return { ok: true };
|
|
36
42
|
}
|