diff --git a/mcp-server/CHANGELOG.md b/mcp-server/CHANGELOG.md index 4e75f25..471ff8b 100644 --- a/mcp-server/CHANGELOG.md +++ b/mcp-server/CHANGELOG.md @@ -8,7 +8,9 @@ ### Fixed -* `currents-list-affected-tests`: serialize `action_type` query params as `action_type[]` to match OpenAPI. +* `currents-list-affected-tests`: serialize `action_type` as repeated `action_type` query keys (OpenAPI `style: form`, `explode: true`). +* `currents-get-runs`: serialize `status` and `completion_state` as repeated query keys (OpenAPI `style: form`, `explode: true`); add optional `pr_id` query param per OpenAPI list runs. +* MCP tools intentionally omit deprecated-only OpenAPI query parameters (for example `tag[]` on find run). * `currents-update-webhook`: require at least one updatable field so the HTTP request body is never empty (matches OpenAPI `requestBody.required: true`). * `postApi`: treat HTTP 201 and empty JSON bodies like other successful POST responses. diff --git a/mcp-server/src/tools/actions/list-affected-tests.test.ts b/mcp-server/src/tools/actions/list-affected-tests.test.ts index df963db..460b90f 100644 --- a/mcp-server/src/tools/actions/list-affected-tests.test.ts +++ b/mcp-server/src/tools/actions/list-affected-tests.test.ts @@ -9,7 +9,7 @@ describe("listAffectedTestsTool", () => { vi.clearAllMocks(); }); - it("serializes action_type as action_type[] per OpenAPI", async () => { + it("serializes action_type as repeated action_type per OpenAPI (form explode)", async () => { vi.spyOn(request, "fetchApi").mockResolvedValue({ status: "OK", data: [] }); await listAffectedTestsTool.handler({ @@ -20,10 +20,10 @@ describe("listAffectedTestsTool", () => { }); expect(request.fetchApi).toHaveBeenCalledWith( - expect.stringContaining("action_type%5B%5D=skip") + expect.stringContaining("action_type=skip") ); expect(request.fetchApi).toHaveBeenCalledWith( - expect.stringContaining("action_type%5B%5D=tag") + expect.stringContaining("action_type=tag") ); }); }); diff --git a/mcp-server/src/tools/actions/list-affected-tests.ts b/mcp-server/src/tools/actions/list-affected-tests.ts index c438242..fabd3bd 100644 --- a/mcp-server/src/tools/actions/list-affected-tests.ts +++ b/mcp-server/src/tools/actions/list-affected-tests.ts @@ -74,7 +74,7 @@ const handler = async ({ } if (action_type && action_type.length > 0) { - action_type.forEach((t) => queryParams.append("action_type[]", t)); + action_type.forEach((t) => queryParams.append("action_type", t)); } if (action_id) { diff --git a/mcp-server/src/tools/runs/get-runs.test.ts b/mcp-server/src/tools/runs/get-runs.test.ts new file mode 100644 index 0000000..2675f2f --- /dev/null +++ b/mcp-server/src/tools/runs/get-runs.test.ts @@ -0,0 +1,28 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import * as request from "../../lib/request.js"; +import { getRunsTool } from "./get-runs.js"; + +vi.mock("../../lib/request.js"); + +describe("getRunsTool", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("serializes list runs query per OpenAPI (repeated status, completion_state, pr_id)", async () => { + vi.spyOn(request, "fetchApi").mockResolvedValue({ status: "OK", data: [] }); + + await getRunsTool.handler({ + projectId: "p1", + status: ["PASSED", "FAILED"], + completion_state: ["COMPLETE"], + pr_id: "42", + }); + + const url = vi.mocked(request.fetchApi).mock.calls[0][0] as string; + expect(url).toContain("status=PASSED"); + expect(url).toContain("status=FAILED"); + expect(url).toContain("completion_state=COMPLETE"); + expect(url).toContain("pr_id=42"); + }); +}); diff --git a/mcp-server/src/tools/runs/get-runs.ts b/mcp-server/src/tools/runs/get-runs.ts index fbb2f56..12502be 100644 --- a/mcp-server/src/tools/runs/get-runs.ts +++ b/mcp-server/src/tools/runs/get-runs.ts @@ -42,6 +42,15 @@ const zodSchema = z.object({ .max(200) .optional() .describe("Search runs by ciBuildId or commit message. Case-insensitive."), + pr_id: z + .string() + .min(1) + .max(128) + .regex(/^[!-~]+$/) + .optional() + .describe( + "Filter runs by normalized pull request id (meta.pr.id). Printable ASCII only, max 128 characters." + ), authors: z .array(z.string()) .optional() @@ -73,6 +82,7 @@ const handler = async ({ tags, tag_operator, search, + pr_id, authors, status, completion_state, @@ -106,6 +116,10 @@ const handler = async ({ queryParams.append("search", search); } + if (pr_id) { + queryParams.append("pr_id", pr_id); + } + if (authors && authors.length > 0) { authors.forEach((a) => queryParams.append("authors[]", a)); }