@@ -148,6 +148,19 @@ describe("tool execution watchdog", () => {
148148 ) . toBe ( 100 ) ;
149149 } ) ;
150150
151+ test ( "non-positive or non-finite mcp.timeoutMs falls back to the default instead of a 1ms timeout" , ( ) => {
152+ const call = { id : "1" , name : "mcp__linear__get_issue" , arguments : { } } ;
153+ expect ( resolveToolExecutionTimeoutMs ( { mcpTimeoutMs : 0 } , call ) ) . toBe (
154+ DEFAULT_MCP_TOOL_TIMEOUT_MS ,
155+ ) ;
156+ expect ( resolveToolExecutionTimeoutMs ( { mcpTimeoutMs : - 5 } , call ) ) . toBe (
157+ DEFAULT_MCP_TOOL_TIMEOUT_MS ,
158+ ) ;
159+ expect ( resolveToolExecutionTimeoutMs ( { mcpTimeoutMs : NaN } , call ) ) . toBe (
160+ DEFAULT_MCP_TOOL_TIMEOUT_MS ,
161+ ) ;
162+ } ) ;
163+
151164 test ( "withTimeout dispose clears timer without leaving hung state" , async ( ) => {
152165 const parent = new AbortController ( ) ;
153166 const budget = withTimeout ( parent . signal , 50 ) ;
@@ -233,6 +246,23 @@ describe("tool execution watchdog", () => {
233246 10_000 ,
234247 ) ;
235248
249+ test (
250+ "mcp.timeoutMs: 0 does not instantly time out an mcp tool call (falls back to the default)" ,
251+ async ( ) => {
252+ const runner = createDynamicToolRunner (
253+ [ stringTool ( "mcp__linear__get_issue" , async ( ) => "ok" ) ] ,
254+ { mcpTimeoutMs : 0 } ,
255+ ) ;
256+ const result = await runner . run (
257+ { id : "1" , name : "mcp__linear__get_issue" , arguments : { } } ,
258+ new AbortController ( ) . signal ,
259+ ) ;
260+ expect ( result . isError ) . toBeUndefined ( ) ;
261+ expect ( result . content ) . toBe ( "ok" ) ;
262+ } ,
263+ 10_000 ,
264+ ) ;
265+
236266 test ( "parent cancel prefers execute salvage body over synthetic aborted" , async ( ) => {
237267 const parent = new AbortController ( ) ;
238268 const salvage = {
0 commit comments