From 62ddfd0daa67634c7946a92d9ab30ab4c37c4602 Mon Sep 17 00:00:00 2001 From: patchwork-coder <304654813+patchwork-coder@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:14:41 -0600 Subject: [PATCH] Add accepted signal filter to news tool --- src/tools/news.tools.ts | 6 +-- tests/tools/news-list-signals.test.ts | 65 +++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 tests/tools/news-list-signals.test.ts diff --git a/src/tools/news.tools.ts b/src/tools/news.tools.ts index b8ffdec0..f67bd2dd 100644 --- a/src/tools/news.tools.ts +++ b/src/tools/news.tools.ts @@ -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 @@ -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() diff --git a/tests/tools/news-list-signals.test.ts b/tests/tools/news-list-signals.test.ts new file mode 100644 index 00000000..8edf0243 --- /dev/null +++ b/tests/tools/news-list-signals.test.ts @@ -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 { success: boolean } }>; + }; + handler: (args: Record) => Promise; +} + +function createTrackingServer() { + const tools = new Map(); + 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"); + }); +});