Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ jobs:
working-directory: ./mcp-server
run: npm ci

- name: Build
working-directory: ./mcp-server
run: npm run build

- name: Run tests
working-directory: ./mcp-server
run: npm run test:run
16 changes: 15 additions & 1 deletion mcp-server/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## Unreleased (2026-04-20)

### Added

* MCP tool `currents-get-context` for `GET /context` (test failure context for AI debugging).

### Fixed

* `currents-list-affected-tests`: serialize `action_type` query params as `action_type[]` to match OpenAPI.
* `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.

## [2.2.5](https://github.com/currents-dev/currents-mcp/compare/v2.2.4...v2.2.5) (2026-02-05)

### Bug Fixes
Expand All @@ -10,7 +22,9 @@

### Documentation

* add comprehensive parity analysis documentation ([#47](https://github.com/currents-dev/currents-mcp/pull/47))## [2.2.4](https://github.com/currents-dev/currents-mcp/compare/v2.2.3...v2.2.4) (2026-01-27)
* add comprehensive parity analysis documentation ([#47](https://github.com/currents-dev/currents-mcp/pull/47))

## [2.2.4](https://github.com/currents-dev/currents-mcp/compare/v2.2.3...v2.2.4) (2026-01-27)

## [2.2.3](https://github.com/currents-dev/currents-mcp/compare/v2.2.1...v2.2.3) (2026-01-27)

Expand Down
10 changes: 9 additions & 1 deletion mcp-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { getTestsPerformanceTool } from "./tools/tests/get-tests-performance.js"
import { getTestSignatureTool } from "./tools/tests/get-tests-signature.js";
// Errors tools
import { getErrorsExplorerTool } from "./tools/errors/get-errors-explorer.js";
import { getContextTool } from "./tools/context/get-context.js";
// Webhooks tools
import { listWebhooksTool } from "./tools/webhooks/list-webhooks.js";
import { createWebhookTool } from "./tools/webhooks/create-webhook.js";
Expand Down Expand Up @@ -103,7 +104,7 @@ server.tool(

server.tool(
"currents-list-affected-tests",
"List tests affected by actions (quarantine, skip, tag) for a project within a date range. Returns aggregated data grouped by test signature. Supports filtering by action types, action ID, status, and search. Requires projectId, date_start, and date_end. (Preview endpoint: fields and path may change.)",
"List tests affected by actions (quarantine, skip, tag) for a project within a date range. Returns aggregated data grouped by test signature. Supports filtering by action types, action ID, status, and search. Requires projectId, date_start, and date_end.",
listAffectedTestsTool.schema,
listAffectedTestsTool.handler
);
Expand Down Expand Up @@ -239,6 +240,13 @@ server.tool(
getErrorsExplorerTool.handler
);

server.tool(
"currents-get-context",
"Retrieve structured test failure context for AI debugging (GET /context). Supports run-level (run_id only), instance-level (run_id + instance_id), or test-level (instance_id + test_id). Optional format json|md, detail default|compact|summary, and pagination limit/page for run/instance levels.",
getContextTool.schema,
getContextTool.handler
);

// Webhooks API tools
server.tool(
"currents-list-webhooks",
Expand Down
9 changes: 8 additions & 1 deletion mcp-server/src/lib/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,14 @@ export async function postApi<T, B>(path: string, body: B): Promise<T | null> {
logger.error(response);
return null;
}
return (await response.json()) as T;
if (response.status === 204 || response.headers.get("content-length") === "0") {
return {} as T;
}
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
return (await response.json()) as T;
}
return {} as T;
} catch (error: any) {
logger.error("Error making Currents POST request:", error.toString());
return null;
Expand Down
29 changes: 29 additions & 0 deletions mcp-server/src/tools/actions/list-affected-tests.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import * as request from "../../lib/request.js";
import { listAffectedTestsTool } from "./list-affected-tests.js";

vi.mock("../../lib/request.js");

describe("listAffectedTestsTool", () => {
beforeEach(() => {
vi.clearAllMocks();
});

it("serializes action_type as action_type[] per OpenAPI", async () => {
vi.spyOn(request, "fetchApi").mockResolvedValue({ status: "OK", data: [] });

await listAffectedTestsTool.handler({
projectId: "p1",
date_start: "2026-01-01",
date_end: "2026-01-02",
action_type: ["skip", "tag"],
});

expect(request.fetchApi).toHaveBeenCalledWith(
expect.stringContaining("action_type%5B%5D=skip")
);
expect(request.fetchApi).toHaveBeenCalledWith(
expect.stringContaining("action_type%5B%5D=tag")
);
});
});
2 changes: 1 addition & 1 deletion mcp-server/src/tools/actions/list-affected-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion mcp-server/src/tools/actions/update-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ const handler = async ({
}

const body: UpdateActionRequest = {};

if (name !== undefined) body.name = name;
if (description !== undefined) body.description = description;
if (action !== undefined) body.action = action;
Expand Down
59 changes: 59 additions & 0 deletions mcp-server/src/tools/context/get-context.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

vi.mock("../../lib/env.js", () => ({
CURRENTS_API_KEY: "k",
CURRENTS_API_URL: "https://api.test.com/v1",
}));

const { getContextTool } = await import("./get-context.js");

describe("getContextTool", () => {
beforeEach(() => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
headers: new Headers({ "content-type": "application/json" }),
json: async () => ({ status: "OK", data: { level: "run" } }),
})
);
});

afterEach(() => {
vi.unstubAllGlobals();
vi.clearAllMocks();
});

it("calls GET /context with query params for run-level", async () => {
await getContextTool.handler({
run_id: "run-1",
format: "json",
detail: "default",
limit: 10,
page: 0,
});

expect(fetch).toHaveBeenCalledWith(
"https://api.test.com/v1/context?run_id=run-1&format=json&detail=default&limit=10&page=0",
expect.objectContaining({
headers: expect.objectContaining({
Authorization: "Bearer k",
Accept: "application/json",
}),
})
);
});

it("rejects test_id without instance_id", async () => {
const result = await getContextTool.handler({
run_id: "run-1",
test_id: "t1",
} as any);

expect(result.content[0].type).toBe("text");
expect(String((result.content[0] as { text: string }).text)).toContain(
"Invalid parameters"
);
expect(fetch).not.toHaveBeenCalled();
});
});
Loading
Loading