diff --git a/packages/core/src/mcp/client.ts b/packages/core/src/mcp/client.ts index fcd0814fb8c..04f50201c5f 100644 --- a/packages/core/src/mcp/client.ts +++ b/packages/core/src/mcp/client.ts @@ -32,6 +32,7 @@ import { InstallationVersion } from "../installation/version" const DEFAULT_STARTUP_TIMEOUT = 30_000 const DEFAULT_REQUEST_TIMEOUT = 30_000 +const ELICITATION_PROGRESS_INTERVAL = 10_000 type Transport = StdioClientTransport | StreamableHTTPClientTransport @@ -182,9 +183,31 @@ export const connect = Effect.fnUntraced(function* ( Promise.resolve({ roots: [{ uri: pathToFileURL(directory).href }] }), ) if (elicitation) { - client.setRequestHandler(ElicitRequestSchema, (request, extra) => - Effect.runPromise(elicitation.create({ server, params: request.params, signal: extra.signal })), - ) + client.setRequestHandler(ElicitRequestSchema, async (request, extra) => { + const progressToken = extra._meta?.progressToken + const started = Date.now() + // Human input can exceed the server's request timeout; progress keeps cooperative callers alive. + const sendProgress = () => { + if (progressToken === undefined) return + void extra + .sendNotification({ + method: "notifications/progress", + params: { + progressToken, + progress: Date.now() - started, + message: "Waiting for user response", + }, + }) + .catch(() => undefined) + } + const interval = progressToken === undefined ? undefined : setInterval(sendProgress, ELICITATION_PROGRESS_INTERVAL) + sendProgress() + try { + return await Effect.runPromise(elicitation.create({ server, params: request.params, signal: extra.signal })) + } finally { + if (interval !== undefined) clearInterval(interval) + } + }) client.setNotificationHandler(ElicitationCompleteNotificationSchema, (notification) => Effect.runPromise(elicitation.complete({ server, elicitationID: notification.params.elicitationId })), )