fix(core): clarify MCP timeout budgets (#35626)
This commit is contained in:
parent
984430c97d
commit
bfdbf43ef8
7 changed files with 120 additions and 25 deletions
|
|
@ -7,8 +7,11 @@ export class Timeout extends Schema.Class<Timeout>("ConfigV2.MCP.Timeout")({
|
|||
startup: PositiveInt.pipe(Schema.optional).annotate({
|
||||
description: "Maximum time in milliseconds to establish and initialize the MCP server.",
|
||||
}),
|
||||
request: PositiveInt.pipe(Schema.optional).annotate({
|
||||
description: "Maximum time in milliseconds to wait for MCP catalog/list requests after initialization.",
|
||||
catalog: PositiveInt.pipe(Schema.optional).annotate({
|
||||
description: "Maximum time in milliseconds to wait for MCP discovery requests such as tools/list and prompts/list.",
|
||||
}),
|
||||
execution: PositiveInt.pipe(Schema.optional).annotate({
|
||||
description: "Maximum time in milliseconds to wait for MCP tool and prompt execution.",
|
||||
}),
|
||||
}) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ import { ConfigMCP } from "../config/mcp"
|
|||
import { InstallationVersion } from "../installation/version"
|
||||
|
||||
const DEFAULT_STARTUP_TIMEOUT = 30_000
|
||||
const DEFAULT_REQUEST_TIMEOUT = 30_000
|
||||
const DEFAULT_CATALOG_TIMEOUT = 30_000
|
||||
const DEFAULT_EXECUTION_TIMEOUT = 12 * 60 * 60 * 1_000 // 12 hours
|
||||
|
||||
type Transport = StdioClientTransport | StreamableHTTPClientTransport
|
||||
|
||||
|
|
@ -206,7 +207,8 @@ export const connect = Effect.fnUntraced(function* (
|
|||
Effect.ignore,
|
||||
),
|
||||
)
|
||||
const requestTimeout = config.timeout?.request ?? DEFAULT_REQUEST_TIMEOUT
|
||||
const catalogTimeout = config.timeout?.catalog ?? DEFAULT_CATALOG_TIMEOUT
|
||||
const executionTimeout = config.timeout?.execution ?? DEFAULT_EXECUTION_TIMEOUT
|
||||
return {
|
||||
instructions: client.getInstructions()?.trim() || undefined,
|
||||
tools: () =>
|
||||
|
|
@ -218,11 +220,11 @@ export const connect = Effect.fnUntraced(function* (
|
|||
async (cursor) => {
|
||||
const params = cursor === undefined ? undefined : { cursor }
|
||||
try {
|
||||
return await client.listTools(params, { timeout: requestTimeout })
|
||||
return await client.listTools(params, { timeout: catalogTimeout })
|
||||
} catch (error) {
|
||||
if (!(error instanceof Error) || !isOutputSchemaError(error)) throw error
|
||||
return client.request({ method: "tools/list", params }, TolerantListToolsResult, {
|
||||
timeout: requestTimeout,
|
||||
timeout: catalogTimeout,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
|
@ -248,7 +250,7 @@ export const connect = Effect.fnUntraced(function* (
|
|||
async (cursor) => {
|
||||
const params = cursor === undefined ? undefined : { cursor }
|
||||
return client.request({ method: "prompts/list", params }, TolerantListPromptsResult, {
|
||||
timeout: requestTimeout,
|
||||
timeout: catalogTimeout,
|
||||
})
|
||||
},
|
||||
(result) => result.prompts,
|
||||
|
|
@ -273,7 +275,7 @@ export const connect = Effect.fnUntraced(function* (
|
|||
client.request(
|
||||
{ method: "prompts/get", params: { name: input.name, arguments: input.args ?? {} } },
|
||||
GetPromptResultSchema,
|
||||
{ signal },
|
||||
{ signal, timeout: executionTimeout },
|
||||
),
|
||||
catch: (error) => (error instanceof Error ? error : new Error(String(error))),
|
||||
}).pipe(
|
||||
|
|
@ -287,8 +289,8 @@ export const connect = Effect.fnUntraced(function* (
|
|||
client.callTool(
|
||||
{ name: input.name, arguments: input.args ?? {} },
|
||||
CallToolResultSchema,
|
||||
// Keep progress tokens available without imposing a client timeout on tool execution.
|
||||
{ signal, resetTimeoutOnProgress: true, onprogress: () => {} },
|
||||
// Keep progress tokens available while enforcing a hard wall-clock execution timeout.
|
||||
{ signal, timeout: executionTimeout, onprogress: () => {} },
|
||||
),
|
||||
catch: (error) => (error instanceof Error ? error : new Error(String(error))),
|
||||
}).pipe(
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ function mcp(info: typeof ConfigV1.Info.Type) {
|
|||
)
|
||||
const timeout = info.experimental?.mcp_timeout
|
||||
if (!timeout && !Object.keys(servers).length) return undefined
|
||||
return { timeout: timeout === undefined ? undefined : { request: timeout }, servers }
|
||||
return { timeout: timeout === undefined ? undefined : { catalog: timeout, execution: timeout }, servers }
|
||||
}
|
||||
|
||||
function migrateMcp(info: ConfigMCPV1.Info) {
|
||||
|
|
@ -187,7 +187,7 @@ function migrateMcp(info: ConfigMCPV1.Info) {
|
|||
cwd: info.cwd,
|
||||
environment: info.environment,
|
||||
disabled,
|
||||
timeout: info.timeout === undefined ? undefined : { request: info.timeout },
|
||||
timeout: info.timeout === undefined ? undefined : { catalog: info.timeout, execution: info.timeout },
|
||||
}
|
||||
return {
|
||||
type: info.type,
|
||||
|
|
@ -201,7 +201,7 @@ function migrateMcp(info: ConfigMCPV1.Info) {
|
|||
redirect_uri: info.oauth.redirectUri,
|
||||
},
|
||||
disabled,
|
||||
timeout: info.timeout === undefined ? undefined : { request: info.timeout },
|
||||
timeout: info.timeout === undefined ? undefined : { catalog: info.timeout, execution: info.timeout },
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue