From af57a8928e63be10ec3f75854963c4ce315c3ce7 Mon Sep 17 00:00:00 2001 From: sparkysolutions10-pixelmy Date: Mon, 19 Oct 2026 22:33:31 +0530 Subject: [PATCH] fix(mcp): return empty search results as successful collection instead of error (#414) --- examples/mcp-server/README.md | 7 ++++--- src/mcp/server.ts | 9 +++++---- tests/mcp.test.ts | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/examples/mcp-server/README.md b/examples/mcp-server/README.md index bada952f..a60fd24e 100644 --- a/examples/mcp-server/README.md +++ b/examples/mcp-server/README.md @@ -103,9 +103,10 @@ JSON): ### What each tool does - `bazaar_search` — searches a Bazaar node's discovery endpoint. Takes `url` - (base URL of the Bazaar API) and an optional `query`. Returns the catalog of - discoverable resources, or a structured error such as `ERR_NO_RESULTS` or - `ERR_SERVICE_UNREACHABLE`. + (base URL of the Bazaar API) and an optional `query`. Returns the collection of + discoverable resources and count (or `{ resources: [], count: 0 }` when no matching + resources exist, distinguishing empty matches from unreachable services), or a structured + error such as `ERR_SERVICE_UNREACHABLE` or `ERR_URL_BLOCKED`. - `paid_call` — makes a paid API call to a Bazaar resource, handling the x402 402 payment loop automatically. Takes `url` (full URL of the resource) and an optional `method` (default `GET`). Returns the resource body, or a structured diff --git a/src/mcp/server.ts b/src/mcp/server.ts index 8a6cd7de..b1c171bc 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -225,12 +225,13 @@ export class BazaarMcpServer { const hasResources = (d: unknown): d is { resources: unknown[] } => typeof d === 'object' && d !== null && 'resources' in d && Array.isArray((d as Record).resources); - if ((Array.isArray(data) && data.length === 0) || (hasResources(data) && data.resources.length === 0)) { - return this.createErrorResponse('ERR_NO_RESULTS', 'No resources found'); - } + const rawList = Array.isArray(data) ? data : (hasResources(data) ? data.resources : []); + const resultPayload = typeof data === 'object' && data !== null && !Array.isArray(data) + ? { ...data, resources: rawList, count: rawList.length } + : { resources: rawList, count: rawList.length }; return { - content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], + content: [{ type: 'text', text: JSON.stringify(resultPayload, null, 2) }], }; } catch (e: any) { if (e instanceof SsrfError) { diff --git a/tests/mcp.test.ts b/tests/mcp.test.ts index e6ee7007..85c4ac74 100644 --- a/tests/mcp.test.ts +++ b/tests/mcp.test.ts @@ -217,6 +217,37 @@ describe('BazaarMcpServer bazaar_search SSRF guard (#407)', () => { expect(response.isError).toBeFalsy(); expect(JSON.parse(response.content[0].text).resources[0].name).toBe('x'); }); + + it('returns success with empty collection when bazaar_search finds no resources (#414)', async () => { + const emptyFetchImpl = async () => + new Response(JSON.stringify({ resources: [] }), { status: 200, headers: { 'content-type': 'application/json' } }); + const server = new BazaarMcpServer({ + fetchWithPayment: vi.fn(), + bazaarFetch: { allowedHosts: [], ...publicLookup, fetchImpl: emptyFetchImpl }, + }); + const handler = captureHandler(server); + const response = await handler({ params: { name: 'bazaar_search', arguments: { url: 'https://ok.example', query: 'nonexistent' } } }); + expect(response.isError).toBeFalsy(); + const payload = JSON.parse(response.content[0].text); + expect(payload.resources).toEqual([]); + expect(payload.count).toBe(0); + }); + + it('returns success with empty collection when endpoint returns empty array (#414)', async () => { + const emptyArrayFetchImpl = async () => + new Response(JSON.stringify([]), { status: 200, headers: { 'content-type': 'application/json' } }); + + const server = new BazaarMcpServer({ + fetchWithPayment: vi.fn(), + bazaarFetch: { allowedHosts: [], ...publicLookup, fetchImpl: emptyArrayFetchImpl }, + }); + const handler = captureHandler(server); + const response = await handler({ params: { name: 'bazaar_search', arguments: { url: 'https://ok.example' } } }); + expect(response.isError).toBeFalsy(); + const payload = JSON.parse(response.content[0].text); + expect(payload.resources).toEqual([]); + expect(payload.count).toBe(0); + }); }); describe('BazaarMcpServer argument validation (#411)', () => { @@ -483,3 +514,4 @@ describe('BazaarMcpServer bazaar_search upstream failures (#349)', () => { expect(body.reason).toContain('not valid JSON'); }); }); +