Skip to content

Commit c5bd3a5

Browse files
committed
fix: include recent MCP target IDs in issue reports
1 parent add8d45 commit c5bd3a5

10 files changed

Lines changed: 349 additions & 31 deletions

File tree

‎apps/builder/app/services/github-issue-report.server.test.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ const report: IssueReportInput = {
4646
},
4747
],
4848
},
49+
recentTarget: {
50+
tool: "screenshot",
51+
entityIds: [{ field: "pageId", id: "page-456" }],
52+
},
4953
session: {
5054
staleNamespaces: ["styles"],
5155
missingNamespaces: ["instances"],
@@ -82,6 +86,8 @@ describe("GitHub issue reports", () => {
8286
expect(body).toContain("- Client: Codex 1.2.3");
8387
expect(body).toContain("- CLI: 1.2.3");
8488
expect(body).toContain("- Project ID: `project-123`");
89+
expect(body).toContain("- Last targeted tool: `screenshot`");
90+
expect(body).toContain("- Target pageId: `page-456`");
8591
expect(body).toContain("- pageId: `page-123`");
8692
expect(body).toContain("- instanceId: `instance-123`");
8793
expect(body).toContain("- Node.js: 22.14.0");

‎apps/builder/app/services/github-issue-report.server.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ const formatRuntime = (runtime: IssueReportInput["runtime"]) =>
3232
...(runtime?.projectId === undefined
3333
? []
3434
: [`- Project ID: \`${runtime.projectId}\``]),
35+
...(runtime?.recentTarget === undefined
36+
? []
37+
: [
38+
`- Last targeted tool: \`${runtime.recentTarget.tool}\``,
39+
...runtime.recentTarget.entityIds.map(
40+
({ field, id }) => `- Target ${field}: \`${id}\``
41+
),
42+
]),
3543
...(runtime === undefined
3644
? []
3745
: [

‎packages/cli/src/commands/mcp.test.ts‎

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ import {
2424
} from "@webstudio-is/project-build/state";
2525
import { publicApiOperations } from "@webstudio-is/protocol";
2626
import { contentEngineLimits } from "@webstudio-is/content-engine/limits";
27-
import { ROOT_INSTANCE_ID } from "@webstudio-is/sdk";
27+
import { ROOT_INSTANCE_ID, type Pages } from "@webstudio-is/sdk";
2828
import { updatePersistedMcpCheckpoint } from "./mcp-checkpoint";
2929
import {
3030
createCliProjectSessionStorage,
3131
createIssueReportFailureTracker,
32+
createIssueReportTargetTracker,
3233
} from "../project-session";
3334
import {
3435
__testing__,
@@ -40,6 +41,7 @@ import {
4041
const {
4142
getMcpDownloadAsset,
4243
getIssueReportFailureFile,
44+
getIssueReportPageId,
4345
persistIssueReportFailure,
4446
restoreIssueReportFailure,
4547
assertSingleOpCallToolSupported,
@@ -511,6 +513,69 @@ test("persists sanitized issue-report failure diagnostics for the next CLI proce
511513
});
512514
});
513515

516+
test("resolves a screenshot route to a page ID without retaining its URL", () => {
517+
const pages = {
518+
homePageId: "home",
519+
rootFolderId: "root",
520+
pages: new Map([
521+
["home", { id: "home", path: "" }],
522+
["blog", { id: "blog", path: "/blog/:slug" }],
523+
]),
524+
folders: new Map(),
525+
} as unknown as Pages;
526+
expect(
527+
getIssueReportPageId(
528+
"screenshot",
529+
{ path: "/blog/private-post?authToken=secret" },
530+
pages
531+
)
532+
).toBe("blog");
533+
expect(
534+
getIssueReportPageId(
535+
"screenshot",
536+
{ path: "https://private.example/" },
537+
pages
538+
)
539+
).toBeUndefined();
540+
expect(
541+
getIssueReportPageId(
542+
"screenshot",
543+
{ path: "/blog/private-post", url: "https://private.example/" },
544+
pages
545+
)
546+
).toBeUndefined();
547+
expect(
548+
getIssueReportPageId("list-pages", { path: "/blog/private-post" }, pages)
549+
).toBeUndefined();
550+
});
551+
552+
test("restores a successful tool target across CLI processes without private input", async () => {
553+
const projectRoot = await mkdtemp(
554+
path.join(tmpdir(), "webstudio-mcp-target-report-")
555+
);
556+
tempDirs.push(projectRoot);
557+
const filePath = getIssueReportFailureFile(projectRoot, "project-1");
558+
const firstTarget = createIssueReportTargetTracker();
559+
firstTarget.record(
560+
"screenshot",
561+
{ path: "/blog/private-post?authToken=secret" },
562+
"page-1"
563+
);
564+
await persistIssueReportFailure(filePath, undefined, firstTarget.snapshot());
565+
const restoredTarget = createIssueReportTargetTracker();
566+
await restoreIssueReportFailure(
567+
filePath,
568+
createIssueReportFailureTracker(),
569+
restoredTarget
570+
);
571+
expect(restoredTarget.get()).toEqual({
572+
tool: "screenshot",
573+
entityIds: [{ field: "pageId", id: "page-1" }],
574+
});
575+
expect(await readFile(filePath, "utf8")).not.toContain("private-post");
576+
expect(await readFile(filePath, "utf8")).not.toContain("authToken");
577+
});
578+
514579
test("preserves a destructive confirmation failure reason without the token or plan", async () => {
515580
const projectRoot = await mkdtemp(
516581
path.join(tmpdir(), "webstudio-mcp-confirmation-report-")

0 commit comments

Comments
 (0)