Skip to content

Commit c550bc0

Browse files
Parity: currents-mcp ↔ Currents API (#127)
* fix(mcp): align list runs and affected-tests queries with OpenAPI Serialize action_type with form explode; use status[] and completion_state[] for project runs; add pr_id. Add get-runs query test and update changelog. * fix(mcp): list runs status/completion_state as repeated OpenAPI keys --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent d497f64 commit c550bc0

5 files changed

Lines changed: 49 additions & 5 deletions

File tree

mcp-server/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99
### Fixed
1010

11-
* `currents-list-affected-tests`: serialize `action_type` query params as `action_type[]` to match OpenAPI.
11+
* `currents-list-affected-tests`: serialize `action_type` as repeated `action_type` query keys (OpenAPI `style: form`, `explode: true`).
12+
* `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.
13+
* MCP tools intentionally omit deprecated-only OpenAPI query parameters (for example `tag[]` on find run).
1214
* `currents-update-webhook`: require at least one updatable field so the HTTP request body is never empty (matches OpenAPI `requestBody.required: true`).
1315
* `postApi`: treat HTTP 201 and empty JSON bodies like other successful POST responses.
1416

mcp-server/src/tools/actions/list-affected-tests.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ describe("listAffectedTestsTool", () => {
99
vi.clearAllMocks();
1010
});
1111

12-
it("serializes action_type as action_type[] per OpenAPI", async () => {
12+
it("serializes action_type as repeated action_type per OpenAPI (form explode)", async () => {
1313
vi.spyOn(request, "fetchApi").mockResolvedValue({ status: "OK", data: [] });
1414

1515
await listAffectedTestsTool.handler({
@@ -20,10 +20,10 @@ describe("listAffectedTestsTool", () => {
2020
});
2121

2222
expect(request.fetchApi).toHaveBeenCalledWith(
23-
expect.stringContaining("action_type%5B%5D=skip")
23+
expect.stringContaining("action_type=skip")
2424
);
2525
expect(request.fetchApi).toHaveBeenCalledWith(
26-
expect.stringContaining("action_type%5B%5D=tag")
26+
expect.stringContaining("action_type=tag")
2727
);
2828
});
2929
});

mcp-server/src/tools/actions/list-affected-tests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const handler = async ({
7474
}
7575

7676
if (action_type && action_type.length > 0) {
77-
action_type.forEach((t) => queryParams.append("action_type[]", t));
77+
action_type.forEach((t) => queryParams.append("action_type", t));
7878
}
7979

8080
if (action_id) {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { beforeEach, describe, expect, it, vi } from "vitest";
2+
import * as request from "../../lib/request.js";
3+
import { getRunsTool } from "./get-runs.js";
4+
5+
vi.mock("../../lib/request.js");
6+
7+
describe("getRunsTool", () => {
8+
beforeEach(() => {
9+
vi.clearAllMocks();
10+
});
11+
12+
it("serializes list runs query per OpenAPI (repeated status, completion_state, pr_id)", async () => {
13+
vi.spyOn(request, "fetchApi").mockResolvedValue({ status: "OK", data: [] });
14+
15+
await getRunsTool.handler({
16+
projectId: "p1",
17+
status: ["PASSED", "FAILED"],
18+
completion_state: ["COMPLETE"],
19+
pr_id: "42",
20+
});
21+
22+
const url = vi.mocked(request.fetchApi).mock.calls[0][0] as string;
23+
expect(url).toContain("status=PASSED");
24+
expect(url).toContain("status=FAILED");
25+
expect(url).toContain("completion_state=COMPLETE");
26+
expect(url).toContain("pr_id=42");
27+
});
28+
});

mcp-server/src/tools/runs/get-runs.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ const zodSchema = z.object({
4242
.max(200)
4343
.optional()
4444
.describe("Search runs by ciBuildId or commit message. Case-insensitive."),
45+
pr_id: z
46+
.string()
47+
.min(1)
48+
.max(128)
49+
.regex(/^[!-~]+$/)
50+
.optional()
51+
.describe(
52+
"Filter runs by normalized pull request id (meta.pr.id). Printable ASCII only, max 128 characters."
53+
),
4554
authors: z
4655
.array(z.string())
4756
.optional()
@@ -73,6 +82,7 @@ const handler = async ({
7382
tags,
7483
tag_operator,
7584
search,
85+
pr_id,
7686
authors,
7787
status,
7888
completion_state,
@@ -106,6 +116,10 @@ const handler = async ({
106116
queryParams.append("search", search);
107117
}
108118

119+
if (pr_id) {
120+
queryParams.append("pr_id", pr_id);
121+
}
122+
109123
if (authors && authors.length > 0) {
110124
authors.forEach((a) => queryParams.append("authors[]", a));
111125
}

0 commit comments

Comments
 (0)