Skip to content

Commit 062bcae

Browse files
authored
feat(mcp): add timeout parameter to wait_for_run_to_complete tool (#3035)
## Summary - Adds an optional `timeoutInSeconds` parameter (default 60s) to the `wait_for_run_to_complete` MCP tool - If the run doesn't complete within the timeout, returns the current run state instead of blocking indefinitely - Uses `AbortSignal.timeout()` combined with the existing MCP signal Fixes #3032
1 parent c2085e6 commit 062bcae

File tree

4 files changed

+50
-15
lines changed

4 files changed

+50
-15
lines changed

.changeset/mcp-wait-timeout.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
Add optional `timeoutInSeconds` parameter to the `wait_for_run_to_complete` MCP tool. Defaults to 60 seconds. If the run doesn't complete within the timeout, the current state of the run is returned instead of waiting indefinitely.

packages/cli-v3/src/mcp/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export const toolsMetadata = {
6868
name: "wait_for_run_to_complete",
6969
title: "Wait for Run to Complete",
7070
description:
71-
"Wait for a run to complete. The run ID is the ID of the run that was triggered. It starts with run_",
71+
"Wait for a run to complete. The run ID is the ID of the run that was triggered. It starts with run_. Has an optional timeoutInSeconds parameter (default 60s) - if the run doesn't complete within that time, the current state of the run will be returned.",
7272
},
7373
cancel_run: {
7474
name: "cancel_run",

packages/cli-v3/src/mcp/schemas.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,17 @@ export const CommonRunsInput = CommonProjectsInput.extend({
123123

124124
export type CommonRunsInput = z.output<typeof CommonRunsInput>;
125125

126+
export const WaitForRunInput = CommonRunsInput.extend({
127+
timeoutInSeconds: z
128+
.number()
129+
.describe(
130+
"The maximum time in seconds to wait for the run to complete. If the run doesn't complete within this time, the current state of the run will be returned. Defaults to 60 seconds."
131+
)
132+
.default(60),
133+
});
134+
135+
export type WaitForRunInput = z.output<typeof WaitForRunInput>;
136+
126137
export const GetRunDetailsInput = CommonRunsInput.extend({
127138
maxTraceLines: z
128139
.number()

packages/cli-v3/src/mcp/tools/runs.ts

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { AnyRunShape } from "@trigger.dev/core/v3";
22
import { toolsMetadata } from "../config.js";
33
import { formatRun, formatRunList, formatRunShape, formatRunTrace } from "../formatters.js";
4-
import { CommonRunsInput, GetRunDetailsInput, ListRunsInput } from "../schemas.js";
4+
import { CommonRunsInput, GetRunDetailsInput, ListRunsInput, WaitForRunInput } from "../schemas.js";
55
import { respondWithError, toolHandler } from "../utils.js";
66

77
export const getRunDetailsTool = {
@@ -65,8 +65,8 @@ export const waitForRunToCompleteTool = {
6565
name: toolsMetadata.wait_for_run_to_complete.name,
6666
title: toolsMetadata.wait_for_run_to_complete.title,
6767
description: toolsMetadata.wait_for_run_to_complete.description,
68-
inputSchema: CommonRunsInput.shape,
69-
handler: toolHandler(CommonRunsInput.shape, async (input, { ctx, signal }) => {
68+
inputSchema: WaitForRunInput.shape,
69+
handler: toolHandler(WaitForRunInput.shape, async (input, { ctx, signal }) => {
7070
ctx.logger?.log("calling wait_for_run_to_complete", { input });
7171

7272
if (ctx.options.devOnly && input.environment !== "dev") {
@@ -87,29 +87,48 @@ export const waitForRunToCompleteTool = {
8787
branch: input.branch,
8888
});
8989

90-
const runSubscription = apiClient.subscribeToRun(input.runId, { signal });
90+
const timeoutMs = input.timeoutInSeconds * 1000;
91+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
92+
const combinedSignal = signal
93+
? AbortSignal.any([signal, timeoutSignal])
94+
: timeoutSignal;
95+
96+
const runSubscription = apiClient.subscribeToRun(input.runId, { signal: combinedSignal });
9197
const readableStream = runSubscription.getReader();
9298

9399
let run: AnyRunShape | null = null;
94-
95-
while (true) {
96-
const { done, value } = await readableStream.read();
97-
if (done) {
98-
break;
100+
let timedOut = false;
101+
102+
try {
103+
while (true) {
104+
const { done, value } = await readableStream.read();
105+
if (done) {
106+
break;
107+
}
108+
run = value;
109+
110+
if (value.isCompleted) {
111+
break;
112+
}
99113
}
100-
run = value;
101-
102-
if (value.isCompleted) {
103-
break;
114+
} catch (error) {
115+
if (timeoutSignal.aborted) {
116+
timedOut = true;
117+
} else {
118+
throw error;
104119
}
105120
}
106121

107122
if (!run) {
108123
return respondWithError("Run not found");
109124
}
110125

126+
const prefix = timedOut
127+
? `Timed out after ${input.timeoutInSeconds}s. Returning current run state:\n\n`
128+
: "";
129+
111130
return {
112-
content: [{ type: "text", text: formatRunShape(run) }],
131+
content: [{ type: "text", text: prefix + formatRunShape(run) }],
113132
};
114133
}),
115134
};

0 commit comments

Comments
 (0)