From 46f7680ac22a2b2ae626d3e9bc12c7b781dc8338 Mon Sep 17 00:00:00 2001 From: Aleksei Kharitonov <91592026+AlexRixten@users.noreply.github.com> Date: Mon, 3 Aug 2026 18:38:17 +0200 Subject: [PATCH] fix: set isError on failed query-docs results --- packages/mcp/src/index.ts | 4 +++ packages/mcp/src/lib/api.ts | 4 +-- packages/mcp/src/lib/types.ts | 6 ++++ packages/mcp/test/api.test.ts | 58 +++++++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 packages/mcp/test/api.test.ts diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts index b63ff3657..71ab62d18 100644 --- a/packages/mcp/src/index.ts +++ b/packages/mcp/src/index.ts @@ -303,6 +303,10 @@ Do not call this tool more than 3 times per question.`, text: response.data, }, ], + // Flag failures at the protocol level. Without this an unset `isError` + // defaults to success, so a caller that branches on `isError` treats an + // error message (invalid ID, library not found) as documentation. + ...(response.isError ? { isError: true } : {}), }; } ); diff --git a/packages/mcp/src/lib/api.ts b/packages/mcp/src/lib/api.ts index 6bcd4502d..15356f8cd 100644 --- a/packages/mcp/src/lib/api.ts +++ b/packages/mcp/src/lib/api.ts @@ -157,7 +157,7 @@ export async function fetchLibraryContext( if (!response.ok) { const errorMessage = await parseErrorResponse(response, context.apiKey); console.error(errorMessage); - return { data: errorMessage }; + return { data: errorMessage, isError: true }; } const text = await response.text(); @@ -170,6 +170,6 @@ export async function fetchLibraryContext( } catch (error) { const errorMessage = `Error fetching library context. Please try again later. ${error}`; console.error(errorMessage); - return { data: errorMessage }; + return { data: errorMessage, isError: true }; } } diff --git a/packages/mcp/src/lib/types.ts b/packages/mcp/src/lib/types.ts index 3b5a024f0..4407e1ae3 100644 --- a/packages/mcp/src/lib/types.ts +++ b/packages/mcp/src/lib/types.ts @@ -30,6 +30,12 @@ export type ContextRequest = { export type ContextResponse = { data: string; + /** + * True when `data` is an error message rather than documentation, so the + * caller can surface it as a tool error (`isError: true`) instead of letting + * it default to a successful result. + */ + isError?: boolean; }; export interface ClientContext { diff --git a/packages/mcp/test/api.test.ts b/packages/mcp/test/api.test.ts new file mode 100644 index 000000000..5abe74e34 --- /dev/null +++ b/packages/mcp/test/api.test.ts @@ -0,0 +1,58 @@ +import { afterEach, describe, expect, test, vi } from "vitest"; +import { fetchLibraryContext } from "../src/lib/api.js"; + +// fetchLibraryContext calls global fetch; stub it per case. +afterEach(() => { + vi.unstubAllGlobals(); +}); + +function stubFetch(response: Partial & { ok: boolean }) { + // fetchLibraryContext reads response.headers (auth-prompt signal), so every + // stub needs a real Headers object. + const full = { headers: new Headers(), ...response } as Response; + vi.stubGlobal( + "fetch", + vi.fn(async () => full) + ); +} + +describe("fetchLibraryContext", () => { + test("flags a non-ok API response as an error", async () => { + // A failed lookup (e.g. 404 for a nonexistent library) must be marked so + // the tool result can set isError:true instead of defaulting to success. + stubFetch({ + ok: false, + status: 404, + json: async () => ({ message: "Library not found." }), + }); + + const result = await fetchLibraryContext({ query: "q", libraryId: "/no/such-lib" }); + expect(result.isError).toBe(true); + expect(result.data).toBe("Library not found."); + }); + + test("flags a network failure as an error", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => { + throw new Error("network down"); + }) + ); + + const result = await fetchLibraryContext({ query: "q", libraryId: "/vercel/next.js" }); + expect(result.isError).toBe(true); + expect(result.data).toContain("Error fetching library context"); + }); + + test("does not flag a successful documentation response", async () => { + stubFetch({ + ok: true, + status: 200, + text: async () => "# Some real documentation", + }); + + const result = await fetchLibraryContext({ query: "q", libraryId: "/vercel/next.js" }); + expect(result.isError).toBeUndefined(); + expect(result.data).toBe("# Some real documentation"); + }); +});