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
|
|
@ -142,7 +142,7 @@ describe("Config", () => {
|
|||
// V2 nests under `mcp.servers`, so it must not be misdetected and re-migrated.
|
||||
expect(ConfigMigrateV1.isV1({ mcp: { servers: { context7: { type: "local", command: ["npx"] } } } })).toBe(false)
|
||||
expect(ConfigMigrateV1.isV1({ mcp: {} })).toBe(false)
|
||||
expect(ConfigMigrateV1.isV1({ mcp: { timeout: { request: 1000 } } })).toBe(false)
|
||||
expect(ConfigMigrateV1.isV1({ mcp: { timeout: { execution: 1000 } } })).toBe(false)
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
@ -467,14 +467,14 @@ describe("Config", () => {
|
|||
},
|
||||
tool_output: { max_lines: 1000, max_bytes: 32768 },
|
||||
mcp: {
|
||||
timeout: { startup: 5000, request: 60000 },
|
||||
timeout: { startup: 5000, catalog: 60000, execution: 43200000 },
|
||||
servers: {
|
||||
local: {
|
||||
type: "local",
|
||||
command: ["node", "./mcp/server.js"],
|
||||
environment: { API_KEY: "secret" },
|
||||
disabled: false,
|
||||
timeout: { request: 10000 },
|
||||
timeout: { catalog: 10000 },
|
||||
},
|
||||
remote: {
|
||||
type: "remote",
|
||||
|
|
@ -552,14 +552,14 @@ describe("Config", () => {
|
|||
})
|
||||
expect(documents[0]?.info.tool_output).toEqual({ max_lines: 1000, max_bytes: 32768 })
|
||||
expect(documents[0]?.info.mcp).toEqual({
|
||||
timeout: { startup: 5000, request: 60000 },
|
||||
timeout: { startup: 5000, catalog: 60000, execution: 43200000 },
|
||||
servers: {
|
||||
local: {
|
||||
type: "local",
|
||||
command: ["node", "./mcp/server.js"],
|
||||
environment: { API_KEY: "secret" },
|
||||
disabled: false,
|
||||
timeout: { request: 10000 },
|
||||
timeout: { catalog: 10000 },
|
||||
},
|
||||
remote: {
|
||||
type: "remote",
|
||||
|
|
@ -792,19 +792,19 @@ describe("Config", () => {
|
|||
buffer: 10000,
|
||||
})
|
||||
expect(documents[0]?.info.mcp).toMatchObject({
|
||||
timeout: { request: 5000 },
|
||||
timeout: { catalog: 5000, execution: 5000 },
|
||||
servers: {
|
||||
local: {
|
||||
type: "local",
|
||||
command: ["node", "server.js"],
|
||||
disabled: true,
|
||||
timeout: { request: 10000 },
|
||||
timeout: { catalog: 10000, execution: 10000 },
|
||||
},
|
||||
remote: {
|
||||
type: "remote",
|
||||
url: "https://mcp.example.com",
|
||||
oauth: { client_id: "client", callback_port: 19876 },
|
||||
timeout: { request: 20000 },
|
||||
timeout: { catalog: 20000, execution: 20000 },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
26
packages/core/test/fixture/mcp-timeout.ts
Normal file
26
packages/core/test/fixture/mcp-timeout.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
GetPromptRequestSchema,
|
||||
ListPromptsRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js"
|
||||
|
||||
const server = new Server({ name: "timeout", version: "1.0.0" }, { capabilities: { prompts: {}, tools: {} } })
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
if (process.env.MCP_TIMEOUT_TARGET === "catalog") await Bun.sleep(100)
|
||||
return { tools: [{ name: "slow", inputSchema: { type: "object" } }] }
|
||||
})
|
||||
server.setRequestHandler(ListPromptsRequestSchema, () => Promise.resolve({ prompts: [{ name: "slow" }] }))
|
||||
server.setRequestHandler(CallToolRequestSchema, async () => {
|
||||
await Bun.sleep(100)
|
||||
return { content: [] }
|
||||
})
|
||||
server.setRequestHandler(GetPromptRequestSchema, async () => {
|
||||
await Bun.sleep(100)
|
||||
return { messages: [] }
|
||||
})
|
||||
|
||||
await server.connect(new StdioServerTransport())
|
||||
|
|
@ -177,6 +177,70 @@ test("retains output schemas across paginated MCP discovery", async () => {
|
|||
])
|
||||
})
|
||||
|
||||
test("applies the configured MCP catalog timeout", async () => {
|
||||
const result = Effect.runPromise(
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const connection = yield* MCPClient.connect(
|
||||
"catalog-timeout",
|
||||
new ConfigMCP.Local({
|
||||
type: "local",
|
||||
command: [process.execPath, path.join(import.meta.dir, "fixture/mcp-timeout.ts")],
|
||||
environment: { MCP_TIMEOUT_TARGET: "catalog" },
|
||||
timeout: new ConfigMCP.Timeout({ catalog: 10 }),
|
||||
}),
|
||||
import.meta.dir,
|
||||
)
|
||||
return yield* connection.tools()
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
await expect(result).rejects.toThrow("Request timed out")
|
||||
})
|
||||
|
||||
test("applies the configured MCP execution timeout", async () => {
|
||||
const result = Effect.runPromise(
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const connection = yield* MCPClient.connect(
|
||||
"execution-timeout",
|
||||
new ConfigMCP.Local({
|
||||
type: "local",
|
||||
command: [process.execPath, path.join(import.meta.dir, "fixture/mcp-timeout.ts")],
|
||||
timeout: new ConfigMCP.Timeout({ execution: 10 }),
|
||||
}),
|
||||
import.meta.dir,
|
||||
)
|
||||
return yield* connection.callTool({ name: "slow" })
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
await expect(result).rejects.toThrow("Request timed out")
|
||||
})
|
||||
|
||||
test("applies the configured MCP execution timeout to prompts", async () => {
|
||||
const result = Effect.runPromise(
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const connection = yield* MCPClient.connect(
|
||||
"prompt-timeout",
|
||||
new ConfigMCP.Local({
|
||||
type: "local",
|
||||
command: [process.execPath, path.join(import.meta.dir, "fixture/mcp-timeout.ts")],
|
||||
timeout: new ConfigMCP.Timeout({ execution: 10 }),
|
||||
}),
|
||||
import.meta.dir,
|
||||
)
|
||||
return yield* connection.prompt({ name: "slow" })
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
await expect(result).rejects.toThrow("Request timed out")
|
||||
})
|
||||
|
||||
it.effect("advertises MCP output schemas to Code Mode", () =>
|
||||
Effect.gen(function* () {
|
||||
const registry = yield* ToolRegistry.Service
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue