Skip to content

Commit be64c3e

Browse files
author
t
committed
chore: move mcp cloudrun fix to PR #945, keep #933 dsh-plugin only
1 parent dd9efb6 commit be64c3e

2 files changed

Lines changed: 9 additions & 314 deletions

File tree

mcp/src/tools/cloudrun-process-log.test.ts

Lines changed: 0 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -169,133 +169,3 @@ describe("queryCloudRun getProcessLog handler", () => {
169169
expect(manager.cloudrun.getProcessLog).not.toHaveBeenCalled();
170170
});
171171
});
172-
173-
const CODING_BUILD_LOG_ERROR =
174-
"[DescribeCloudRunBuildLog] User not created or may not qcloud user, please login CODING and try again.";
175-
176-
describe("buildGetDeployLogCodingFallback next_step action union", () => {
177-
it("with runId: next_step and nextActions only use getProcessLog (never getDeployLog)", async () => {
178-
const { buildGetDeployLogCodingFallback } = await import("./cloudrun.js");
179-
const result = buildGetDeployLogCodingFallback({
180-
serverName: "svc-a",
181-
runId: "run-1",
182-
reason: "coding",
183-
});
184-
185-
expect(result.data.next_step.action).toBe("getProcessLog");
186-
expect(result.data.next_step.suggested_args.action).toBe("getProcessLog");
187-
expect(result.nextActions.map((a) => a.action)).toEqual(["getProcessLog"]);
188-
expect(result.nextActions[0]).toMatchObject({
189-
tool: "queryCloudRun",
190-
action: "getProcessLog",
191-
args: {
192-
action: "getProcessLog",
193-
detailServerName: "svc-a",
194-
runId: "run-1",
195-
},
196-
});
197-
});
198-
199-
it("without runId: next_step is getDeployRecords and nextActions stay on follow-up union", async () => {
200-
const { buildGetDeployLogCodingFallback } = await import("./cloudrun.js");
201-
const result = buildGetDeployLogCodingFallback({
202-
serverName: "svc-b",
203-
reason: "image_no_build",
204-
});
205-
206-
expect(result.data.next_step.action).toBe("getDeployRecords");
207-
expect(result.data.next_step.suggested_args.action).toBe("getDeployRecords");
208-
expect(result.nextActions.map((a) => a.action)).toEqual([
209-
"getDeployRecords",
210-
"getProcessLog",
211-
]);
212-
for (const next of result.nextActions) {
213-
expect(next.args.action).toBe(next.action);
214-
}
215-
});
216-
});
217-
218-
describe("queryCloudRun getDeployLog CODING fallback", () => {
219-
it("rewrites CODING getBuildLog failures to getProcessLog nextActions", async () => {
220-
const manager = makeManager({
221-
getBuildLog: vi.fn().mockRejectedValue(new Error(CODING_BUILD_LOG_ERROR)),
222-
});
223-
mockGetCloudBaseManager.mockReturnValue(manager);
224-
const { tools } = await createCloudRunTools();
225-
226-
const res = await tools.queryCloudRun.handler({
227-
action: "getDeployLog",
228-
detailServerName: "my-svc",
229-
});
230-
const parsed = parseToolResult(res);
231-
const payloadText = JSON.stringify(parsed);
232-
233-
expect(parsed.success).toBe(false);
234-
expect(parsed.error).toBe("CODING_BUILD_LOG_UNAVAILABLE");
235-
expect(parsed.message).toMatch(/getProcessLog/);
236-
expect(parsed.message).toMatch(/my-svc/);
237-
expect(parsed.message).toMatch(/run-latest/);
238-
expect(parsed.nextActions).toEqual(
239-
expect.arrayContaining([
240-
expect.objectContaining({
241-
tool: "queryCloudRun",
242-
action: "getProcessLog",
243-
args: expect.objectContaining({
244-
action: "getProcessLog",
245-
detailServerName: "my-svc",
246-
runId: "run-latest",
247-
}),
248-
}),
249-
]),
250-
);
251-
expect(parsed.data.next_step.action).toBe("getProcessLog");
252-
expect(payloadText).toMatch(/getProcessLog/);
253-
expect(payloadText).not.toBe(JSON.stringify({ success: false, error: CODING_BUILD_LOG_ERROR }));
254-
expect(manager.cloudrun.getBuildLog).toHaveBeenCalled();
255-
expect(manager.cloudrun.getProcessLog).not.toHaveBeenCalled();
256-
});
257-
258-
it("skips getBuildLog when latest BuildId is 0 (image deploy)", async () => {
259-
const manager = makeManager({
260-
getDeployRecords: vi.fn().mockResolvedValue({
261-
DeployRecords: [
262-
{
263-
DeployId: "d-image",
264-
Status: "normal",
265-
RunId: "run-image",
266-
BuildId: 0,
267-
},
268-
],
269-
}),
270-
});
271-
mockGetCloudBaseManager.mockReturnValue(manager);
272-
const { tools } = await createCloudRunTools();
273-
274-
const res = await tools.queryCloudRun.handler({
275-
action: "getDeployLog",
276-
detailServerName: "image-svc",
277-
});
278-
const parsed = parseToolResult(res);
279-
280-
expect(parsed.success).toBe(false);
281-
expect(parsed.error).toBe("NO_CODING_BUILD_FOR_IMAGE_DEPLOY");
282-
expect(parsed.nextActions[0].action).toBe("getProcessLog");
283-
expect(parsed.nextActions[0].args.runId).toBe("run-image");
284-
expect(manager.cloudrun.getBuildLog).not.toHaveBeenCalled();
285-
});
286-
287-
it("does not rewrite unrelated getBuildLog errors", async () => {
288-
const manager = makeManager({
289-
getBuildLog: vi.fn().mockRejectedValue(new Error("network timeout")),
290-
});
291-
mockGetCloudBaseManager.mockReturnValue(manager);
292-
const { tools } = await createCloudRunTools();
293-
294-
await expect(
295-
tools.queryCloudRun.handler({
296-
action: "getDeployLog",
297-
detailServerName: "my-svc",
298-
}),
299-
).rejects.toThrow("network timeout");
300-
});
301-
});

mcp/src/tools/cloudrun.ts

Lines changed: 9 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -302,27 +302,9 @@ export type CloudRunDeployRegistration = {
302302
waitMs: number;
303303
};
304304

305-
export type CloudRunDeployNextStepAction =
306-
| "getDeployLog"
307-
| "getProcessLog"
308-
| "getDeployRecords";
309-
310-
/**
311-
* Follow-up next_step after getDeployLog is unavailable (CODING login / image
312-
* deploy). Must never suggest getDeployLog again.
313-
*/
314-
export type CloudRunDeployFollowUpAction = "getProcessLog" | "getDeployRecords";
315-
316305
export type CloudRunDeployNextStep = {
317306
tool: "queryCloudRun";
318-
action: CloudRunDeployNextStepAction;
319-
suggested_args: Record<string, string | number>;
320-
note?: string;
321-
};
322-
323-
export type CloudRunDeployFollowUpNextStep = {
324-
tool: "queryCloudRun";
325-
action: CloudRunDeployFollowUpAction;
307+
action: "getDeployLog" | "getProcessLog" | "getDeployRecords";
326308
suggested_args: Record<string, string | number>;
327309
note?: string;
328310
};
@@ -339,110 +321,6 @@ export function isValidCloudRunRunId(value: unknown): value is string {
339321
return typeof value === "string" && value.trim().length > 0;
340322
}
341323

342-
/**
343-
* DescribeCloudRunBuildLog / getBuildLog fails when the Tencent Cloud account
344-
* has no CODING user. Agents should switch to getProcessLog (RunId) instead of
345-
* retrying getDeployLog.
346-
*/
347-
export function isCloudRunCodingBuildLogError(error: unknown): boolean {
348-
const message = error instanceof Error ? error.message : String(error);
349-
return /DescribeCloudRunBuildLog|please login CODING|login CODING|User not created or may not qcloud user/i.test(
350-
message,
351-
);
352-
}
353-
354-
export type CloudRunGetProcessLogNextAction = {
355-
tool: "queryCloudRun";
356-
action: CloudRunDeployFollowUpAction;
357-
args: {
358-
action: CloudRunDeployFollowUpAction;
359-
detailServerName: string;
360-
runId?: string;
361-
};
362-
};
363-
364-
export function buildGetDeployLogCodingFallback(options: {
365-
serverName: string;
366-
runId?: string;
367-
upstreamError?: string;
368-
reason: "coding" | "image_no_build";
369-
}): {
370-
success: false;
371-
error: string;
372-
message: string;
373-
nextActions: CloudRunGetProcessLogNextAction[];
374-
data: {
375-
runId?: string;
376-
next_step: CloudRunDeployFollowUpNextStep;
377-
upstreamError?: string;
378-
};
379-
} {
380-
const runId = isValidCloudRunRunId(options.runId) ? options.runId.trim() : undefined;
381-
// Narrow to follow-up actions only — never suggest getDeployLog again.
382-
const next_step: CloudRunDeployFollowUpNextStep = runId
383-
? {
384-
tool: "queryCloudRun",
385-
action: "getProcessLog",
386-
suggested_args: {
387-
action: "getProcessLog",
388-
detailServerName: options.serverName,
389-
runId,
390-
},
391-
note: "getDeployLog needs CODING / DescribeCloudRunBuildLog. Use getProcessLog for deploy-step and runtime logs.",
392-
}
393-
: {
394-
tool: "queryCloudRun",
395-
action: "getDeployRecords",
396-
suggested_args: {
397-
action: "getDeployRecords",
398-
detailServerName: options.serverName,
399-
},
400-
note: "Read latestDeploy.RunId from getDeployRecords, then queryCloudRun(action=\"getProcessLog\"). Skip retrying getDeployLog for CODING login errors.",
401-
};
402-
403-
const nextActions: CloudRunGetProcessLogNextAction[] = [
404-
{
405-
tool: "queryCloudRun",
406-
action: next_step.action,
407-
args: {
408-
action: next_step.action,
409-
detailServerName: options.serverName,
410-
...(runId ? { runId } : {}),
411-
},
412-
},
413-
];
414-
if (next_step.action === "getDeployRecords") {
415-
nextActions.push({
416-
tool: "queryCloudRun",
417-
action: "getProcessLog",
418-
args: {
419-
action: "getProcessLog",
420-
detailServerName: options.serverName,
421-
},
422-
});
423-
}
424-
425-
const message =
426-
options.reason === "image_no_build"
427-
? `Service '${options.serverName}' has no cloud source build (BuildId=0). Skip getDeployLog; use queryCloudRun(action="getProcessLog"${runId ? `, runId="${runId}"` : ""}) for deploy-step and runtime logs.`
428-
: `getDeployLog (DescribeCloudRunBuildLog) failed because this account is not a CODING user. Do not retry getDeployLog. Use queryCloudRun(action="getProcessLog", detailServerName="${options.serverName}"${runId ? `, runId="${runId}"` : ""}) — RunId comes from getDeployRecords/latestDeploy.`;
429-
430-
return {
431-
success: false,
432-
error:
433-
options.reason === "image_no_build"
434-
? "NO_CODING_BUILD_FOR_IMAGE_DEPLOY"
435-
: "CODING_BUILD_LOG_UNAVAILABLE",
436-
message,
437-
nextActions,
438-
data: {
439-
...(runId ? { runId } : {}),
440-
next_step,
441-
...(options.upstreamError ? { upstreamError: options.upstreamError } : {}),
442-
},
443-
};
444-
}
445-
446324
function extractServerManageTaskInfo(resp: unknown): {
447325
taskId?: number;
448326
taskStatus?: string;
@@ -1290,62 +1168,13 @@ export function registerCloudRunTools(server: ExtendedMcpServer) {
12901168
}
12911169

12921170
const buildId = input.buildId ?? latestDeploy.BuildId;
1293-
const latestRunId = isValidCloudRunRunId(latestDeploy.RunId)
1294-
? latestDeploy.RunId.trim()
1295-
: undefined;
1296-
1297-
// Image deploys typically have BuildId=0 and no CODING build.
1298-
if (!isValidCloudRunBuildId(buildId)) {
1299-
return {
1300-
content: [
1301-
{
1302-
type: "text",
1303-
text: JSON.stringify(
1304-
buildGetDeployLogCodingFallback({
1305-
serverName,
1306-
runId: latestRunId,
1307-
reason: "image_no_build",
1308-
}),
1309-
null,
1310-
2,
1311-
),
1312-
},
1313-
],
1314-
};
1315-
}
1316-
13171171
// Build log (CODING / DescribeCloudRunBuildLog). Meaningful only for
1318-
// cloud source builds. Accounts without a CODING user fail here —
1319-
// rewrite to getProcessLog instead of bubbling the raw English error.
1320-
let buildLogResult: unknown;
1321-
try {
1322-
buildLogResult = await cloudrunService.getBuildLog({
1323-
serverName,
1324-
buildId,
1325-
});
1326-
} catch (error) {
1327-
if (isCloudRunCodingBuildLogError(error)) {
1328-
const upstreamError = error instanceof Error ? error.message : String(error);
1329-
return {
1330-
content: [
1331-
{
1332-
type: "text",
1333-
text: JSON.stringify(
1334-
buildGetDeployLogCodingFallback({
1335-
serverName,
1336-
runId: latestRunId,
1337-
upstreamError,
1338-
reason: "coding",
1339-
}),
1340-
null,
1341-
2,
1342-
),
1343-
},
1344-
],
1345-
};
1346-
}
1347-
throw error;
1348-
}
1172+
// cloud source builds; image deploys have no build process. Accounts
1173+
// without a CODING user may fail here — use getProcessLog for runtime logs.
1174+
const buildLogResult: any = await cloudrunService.getBuildLog({
1175+
serverName,
1176+
buildId,
1177+
});
13491178

13501179
let processLogs: unknown[] = [];
13511180
let processLogsWarning: string | undefined;
@@ -1361,11 +1190,7 @@ export function registerCloudRunTools(server: ExtendedMcpServer) {
13611190
}
13621191
}
13631192

1364-
const buildLogRecord =
1365-
buildLogResult && typeof buildLogResult === "object"
1366-
? (buildLogResult as { Log?: { Text?: string } })
1367-
: undefined;
1368-
const buildLogText = typeof buildLogRecord?.Log?.Text === 'string' ? buildLogRecord.Log.Text : '';
1193+
const buildLogText = typeof buildLogResult?.Log?.Text === 'string' ? buildLogResult.Log.Text : '';
13691194
const processLogText = Array.isArray(processLogs) && processLogs.length > 0 ? normalizeProcessLogText(processLogs) : '';
13701195
const combinedLogText = [buildLogText, processLogText].filter(Boolean).join('\n');
13711196

@@ -1378,7 +1203,7 @@ export function registerCloudRunTools(server: ExtendedMcpServer) {
13781203
data: {
13791204
buildId,
13801205
deployRecord: latestDeploy,
1381-
buildLog: buildLogRecord?.Log || null,
1206+
buildLog: buildLogResult?.Log || null,
13821207
// Optional best-effort attach; prefer dedicated getProcessLog for runtime diagnosis
13831208
processLogs,
13841209
combinedLogText,

0 commit comments

Comments
 (0)