From 22eb65d4a2d5ae36d4c6c29a9aeb4f1d577646d6 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Wed, 29 Apr 2026 05:37:24 -0400 Subject: [PATCH] refactor: share mcp api calls --- packages/mcp-server/src/protocol/handler.ts | 103 +++++--------------- packages/mcp-server/test/protocol.test.ts | 27 +++++ 2 files changed, 54 insertions(+), 76 deletions(-) diff --git a/packages/mcp-server/src/protocol/handler.ts b/packages/mcp-server/src/protocol/handler.ts index 80150e4..c5f9a09 100644 --- a/packages/mcp-server/src/protocol/handler.ts +++ b/packages/mcp-server/src/protocol/handler.ts @@ -2,7 +2,30 @@ import { logger } from "../logger"; import { tools } from "./tools"; const API_BASE_URL = process.env.API_BASE_URL || "http://localhost:4005/api"; -const API_TIMEOUT = Number(process.env.API_TIMEOUT) || 180000; // 3 minutes default +const API_TIMEOUT = Number(process.env.API_TIMEOUT) || 180000; + +async function callMarketplaceApi( + marketplace: string, + params: URLSearchParams, +): Promise { + const url = `${API_BASE_URL}/${marketplace}?${params.toString()}`; + logger.log(`[MCP] Calling ${marketplace} API`); + const response = await Promise.race([ + fetch(url), + new Promise((_, reject) => + setTimeout( + () => reject(new Error(`Request timed out after ${API_TIMEOUT}ms`)), + API_TIMEOUT, + ), + ), + ]); + if (!response.ok) { + const errorText = await response.text(); + logger.error(`[MCP] ${marketplace} API error ${response.status}: ${errorText}`); + throw new Error(`API returned ${response.status}: ${errorText}`); + } + return response.json(); +} /** * Handle MCP JSON-RPC 2.0 protocol requests @@ -119,31 +142,7 @@ export async function handleMcpRequest(req: Request): Promise { if (args.unstableFilter !== undefined) params.append("unstableFilter", args.unstableFilter.toString()); - logger.log( - `[MCP] Calling Kijiji API: ${API_BASE_URL}/kijiji?${params.toString()}`, - ); - const response = await Promise.race([ - fetch(`${API_BASE_URL}/kijiji?${params.toString()}`), - new Promise((_, reject) => - setTimeout( - () => - reject(new Error(`Request timed out after ${API_TIMEOUT}ms`)), - API_TIMEOUT, - ), - ), - ]); - - if (!response.ok) { - const errorText = await response.text(); - logger.error( - `[MCP] Kijiji API error ${response.status}: ${errorText}`, - ); - throw new Error(`API returned ${response.status}: ${errorText}`); - } - result = await response.json(); - logger.log( - `[MCP] Kijiji returned ${Array.isArray(result) ? result.length : 0} items`, - ); + result = await callMarketplaceApi("kijiji", params); } else if (name === "search_facebook") { const query = args.query; if (!query) { @@ -160,31 +159,7 @@ export async function handleMcpRequest(req: Request): Promise { if (args.unstableFilter !== undefined) params.append("unstableFilter", args.unstableFilter.toString()); - logger.log( - `[MCP] Calling Facebook API: ${API_BASE_URL}/facebook?${params.toString()}`, - ); - const response = await Promise.race([ - fetch(`${API_BASE_URL}/facebook?${params.toString()}`), - new Promise((_, reject) => - setTimeout( - () => - reject(new Error(`Request timed out after ${API_TIMEOUT}ms`)), - API_TIMEOUT, - ), - ), - ]); - - if (!response.ok) { - const errorText = await response.text(); - logger.error( - `[MCP] Facebook API error ${response.status}: ${errorText}`, - ); - throw new Error(`API returned ${response.status}: ${errorText}`); - } - result = await response.json(); - logger.log( - `[MCP] Facebook returned ${Array.isArray(result) ? result.length : 0} items`, - ); + result = await callMarketplaceApi("facebook", params); } else if (name === "search_ebay") { const query = args.query; if (!query) { @@ -214,31 +189,7 @@ export async function handleMcpRequest(req: Request): Promise { if (args.unstableFilter !== undefined) params.append("unstableFilter", args.unstableFilter.toString()); - logger.log( - `[MCP] Calling eBay API: ${API_BASE_URL}/ebay?${params.toString()}`, - ); - const response = await Promise.race([ - fetch(`${API_BASE_URL}/ebay?${params.toString()}`), - new Promise((_, reject) => - setTimeout( - () => - reject(new Error(`Request timed out after ${API_TIMEOUT}ms`)), - API_TIMEOUT, - ), - ), - ]); - - if (!response.ok) { - const errorText = await response.text(); - logger.error( - `[MCP] eBay API error ${response.status}: ${errorText}`, - ); - throw new Error(`API returned ${response.status}: ${errorText}`); - } - result = await response.json(); - logger.log( - `[MCP] eBay returned ${Array.isArray(result) ? result.length : 0} items`, - ); + result = await callMarketplaceApi("ebay", params); } else { return Response.json({ jsonrpc: "2.0", diff --git a/packages/mcp-server/test/protocol.test.ts b/packages/mcp-server/test/protocol.test.ts index 36bf633..69bddcd 100644 --- a/packages/mcp-server/test/protocol.test.ts +++ b/packages/mcp-server/test/protocol.test.ts @@ -152,6 +152,33 @@ describe("MCP protocol unstableFilter", () => { expect(String(calledUrl)).toContain("unstableFilter=true"); }); + test("tools/call returns API JSON as text content", async () => { + global.fetch = mock(() => + Promise.resolve( + new Response(JSON.stringify([{ title: "item" }]), { status: 200 }), + ), + ) as unknown as typeof fetch; + + const response = await handleMcpRequest( + new Request("http://localhost", { + method: "POST", + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "tools/call", + params: { + name: "search_facebook", + arguments: { query: "laptop" }, + }, + }), + }), + ); + + const body = await response.json(); + expect(body.result.content[0].type).toBe("text"); + expect(JSON.parse(body.result.content[0].text)).toEqual([{ title: "item" }]); + }); + test("handler should forward unstableFilter=true for search_ebay", async () => { await handleMcpRequest( new Request("http://localhost", {