Skip to content
Draft
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
6 changes: 3 additions & 3 deletions src/tools/news.tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ export function registerNewsTools(server: McpServer): void {

Supports optional filters:
- beat: filter by beat slug — active beats: "aibtc-network", "bitcoin-macro", "quantum". Retired beats return 410 Gone.
- status: filter by signal status (e.g. "submitted", "approved", "rejected")
- status: filter by signal status (e.g. "submitted", "approved", "rejected"). Use "accepted" to include both approved and brief_included signals.
- agent: filter by BTC address of the correspondent
- tag: filter by tag slug
- since: ISO timestamp — only return signals newer than this
Expand All @@ -285,9 +285,9 @@ No authentication required.`,
.optional()
.describe("Filter by beat slug — active beats: aibtc-network, bitcoin-macro, quantum. Retired legacy slugs return 410 Gone."),
status: z
.enum(["submitted", "approved", "replaced", "rejected", "brief_included"])
.enum(["submitted", "approved", "replaced", "rejected", "brief_included", "accepted"])
.optional()
.describe("Filter by signal status (e.g. 'submitted' for pending review)"),
.describe("Filter by signal status; 'accepted' includes approved and brief_included signals"),
agent: z
.string()
.optional()
Expand Down
65 changes: 65 additions & 0 deletions tests/tools/news-list-signals.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { afterEach, describe, expect, it, vi } from "vitest";

import { registerNewsTools } from "../../src/tools/news.tools.js";

interface RegisteredTool {
config: {
inputSchema: Record<string, { safeParse: (value: unknown) => { success: boolean } }>;
};
handler: (args: Record<string, unknown>) => Promise<unknown>;
}

function createTrackingServer() {
const tools = new Map<string, RegisteredTool>();
const server = {
registerTool: vi.fn(
(
name: string,
config: RegisteredTool["config"],
handler: RegisteredTool["handler"]
) => {
tools.set(name, { config, handler });
}
),
};

return { server, tools };
}

describe("news_list_signals accepted filter", () => {
afterEach(() => {
vi.unstubAllGlobals();
});

it("exposes accepted as a valid read-only status and forwards it to the API", async () => {
const { server, tools } = createTrackingServer();
registerNewsTools(server as never);

const tool = tools.get("news_list_signals");
if (!tool) throw new Error("news_list_signals was not registered");

expect(tool.config.inputSchema.status.safeParse("accepted").success).toBe(true);

const fetchMock = vi.fn(async () => ({
ok: true,
status: 200,
text: async () => JSON.stringify({ signals: [], total: 0 }),
}));
vi.stubGlobal("fetch", fetchMock);

await tool.handler({
status: "accepted",
agent: "bc1qexample",
since: "2026-07-14T12:10:00Z",
limit: 50,
});

expect(fetchMock).toHaveBeenCalledOnce();
const [requestUrl] = fetchMock.mock.calls[0] as unknown as [string];
const url = new URL(requestUrl);
expect(url.searchParams.get("status")).toBe("accepted");
expect(url.searchParams.get("agent")).toBe("bc1qexample");
expect(url.searchParams.get("since")).toBe("2026-07-14T12:10:00Z");
expect(url.searchParams.get("limit")).toBe("50");
});
});