Skip to content

Commit 0bfc016

Browse files
committed
Improve the deploy experience
1 parent 96609ec commit 0bfc016

File tree

5 files changed

+113
-7
lines changed

5 files changed

+113
-7
lines changed

packages/cli-v3/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
"socket.io-client": "4.7.5",
133133
"source-map-support": "0.5.21",
134134
"std-env": "^3.7.0",
135+
"strip-ansi": "^7.1.0",
135136
"supports-color": "^10.0.0",
136137
"tiny-invariant": "^1.2.0",
137138
"tinyexec": "^0.3.1",

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export const toolsMetadata = {
7474
name: "deploy",
7575
title: "Deploy",
7676
description:
77-
"Deploy a project. Use this tool when you need to deploy a project. This will trigger a deployment for the project.",
77+
"Deploy a project. Use this tool when you need to deploy a project. This will trigger a deployment for the project. This is a long running operation and including a progress token will allow you to display the progress to the user.",
7878
},
7979
list_deploys: {
8080
name: "list_deploys",

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import { getPackageJson, tryResolveTriggerPackageVersion } from "../../commands/
88
import { VERSION } from "../../version.js";
99
import { resolveSync as esmResolve } from "mlly";
1010
import { fileURLToPath } from "node:url";
11+
import stripAnsi from "strip-ansi";
1112

1213
export const deployTool = {
1314
name: toolsMetadata.deploy.name,
1415
title: toolsMetadata.deploy.title,
1516
description: toolsMetadata.deploy.description,
1617
inputSchema: DeployInput.shape,
17-
handler: toolHandler(DeployInput.shape, async (input, { ctx }) => {
18+
handler: toolHandler(DeployInput.shape, async (input, { ctx, createProgressTracker, _meta }) => {
1819
ctx.logger?.log("calling deploy", { input });
1920

2021
if (ctx.options.devOnly) {
@@ -59,23 +60,43 @@ export const deployTool = {
5960
nodePath,
6061
cliPath,
6162
args,
63+
meta: _meta,
6264
});
6365

66+
const progressTracker = createProgressTracker(100);
67+
await progressTracker.updateProgress(
68+
5,
69+
`Starting deploy to ${input.environment}${input.branch ? ` on branch ${input.branch}` : ""}`
70+
);
71+
6472
const deployProcess = x(nodePath, [cliPath, ...args], {
6573
nodeOptions: {
6674
cwd: cwd.cwd,
6775
env: {
6876
TRIGGER_MCP_SERVER: "1",
77+
CI: "true",
6978
},
7079
},
7180
});
7281

7382
const logs = [];
7483

7584
for await (const line of deployProcess) {
76-
logs.push(line);
85+
const lineWithoutAnsi = stripAnsi(line);
86+
87+
const buildingVersion = lineWithoutAnsi.match(/Building version (\d+\.\d+)/);
88+
89+
if (buildingVersion) {
90+
await progressTracker.incrementProgress(1, `Building version ${buildingVersion[1]}`);
91+
} else {
92+
await progressTracker.incrementProgress(1);
93+
}
94+
95+
logs.push(stripAnsi(line));
7796
}
7897

98+
await progressTracker.complete("Deploy complete");
99+
79100
ctx.logger?.log("deploy deployProcess", {
80101
logs,
81102
});

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

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
1+
import type { CallToolResult, ServerNotification } from "@modelcontextprotocol/sdk/types.js";
22
import z from "zod";
33
import { ToolMeta } from "./types.js";
44
import { loadConfig } from "../config.js";
@@ -37,9 +37,16 @@ function enumerateError(error: unknown) {
3737
return newError;
3838
}
3939

40+
export type ToolHandlerMeta = ToolMeta & {
41+
createProgressTracker: (total: number) => ProgressTracker;
42+
};
43+
4044
export function toolHandler<TInputShape extends z.ZodRawShape>(
4145
shape: TInputShape,
42-
handler: (input: z.output<z.ZodObject<TInputShape>>, meta: ToolMeta) => Promise<CallToolResult>
46+
handler: (
47+
input: z.output<z.ZodObject<TInputShape>>,
48+
meta: ToolHandlerMeta
49+
) => Promise<CallToolResult>
4350
) {
4451
return async (input: unknown, extra: ToolMeta) => {
4552
const parsedInput = z.object(shape).safeParse(input);
@@ -48,6 +55,81 @@ export function toolHandler<TInputShape extends z.ZodRawShape>(
4855
return respondWithError(parsedInput.error);
4956
}
5057

51-
return handler(parsedInput.data, extra);
58+
function createProgressTracker(total: number) {
59+
return new ProgressTracker(total, extra.sendNotification, extra._meta?.progressToken);
60+
}
61+
62+
return handler(parsedInput.data, { ...extra, createProgressTracker });
5263
};
5364
}
65+
66+
class ProgressTracker {
67+
private progress: number = 0;
68+
private progressToken: string | number | undefined;
69+
private total: number;
70+
private message: string;
71+
private sendNotification: (notification: ServerNotification) => Promise<void>;
72+
73+
constructor(
74+
total: number,
75+
sendNotification: (notification: ServerNotification) => Promise<void>,
76+
progressToken?: string | number
77+
) {
78+
this.message = "";
79+
this.progressToken = progressToken;
80+
this.progress = 0;
81+
this.total = total;
82+
this.sendNotification = sendNotification;
83+
}
84+
85+
async updateProgress(progress: number, message?: string) {
86+
this.progress = progress;
87+
88+
if (message) {
89+
this.message = message;
90+
}
91+
92+
await this.#sendNotification(progress, this.message);
93+
}
94+
95+
async incrementProgress(increment: number, message?: string) {
96+
this.progress += increment;
97+
98+
// make sure the progress is never greater than the total
99+
this.progress = Math.min(this.progress, this.total);
100+
101+
if (message) {
102+
this.message = message;
103+
}
104+
105+
await this.#sendNotification(this.progress, this.message);
106+
}
107+
108+
async complete(message?: string) {
109+
this.progress = this.total;
110+
if (message) {
111+
this.message = message;
112+
}
113+
await this.#sendNotification(this.progress, this.message);
114+
}
115+
116+
getProgress() {
117+
return this.progress;
118+
}
119+
120+
async #sendNotification(progress: number, message: string) {
121+
if (!this.progressToken) {
122+
return;
123+
}
124+
125+
await this.sendNotification({
126+
method: "notifications/progress",
127+
params: {
128+
progress,
129+
total: this.total,
130+
message: this.message,
131+
progressToken: this.progressToken,
132+
},
133+
});
134+
}
135+
}

pnpm-lock.yaml

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)