From eb6705df0f0e397d94802aa5a48a10d122097031 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Fri, 23 Jan 2026 13:10:45 -0500 Subject: [PATCH] feat: add 60-second timeouts to MCP request handlers for reliability --- package.json | 2 +- packages/mcp-server/src/protocol/handler.ts | 72 ++++++++++++++------- scripts/start.sh | 4 +- 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 9d88cb9..d71f855 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build:mcp": "bun build ./packages/mcp-server/src/index.ts --target=bun --outdir=./dist/mcp --minify", "build:all": "bun run build:api && bun run build:mcp", "build": "bun run clean && bun run build:all", - "start": "./start.sh" + "start": "./scripts/start.sh" }, "private": true, "type": "module", diff --git a/packages/mcp-server/src/protocol/handler.ts b/packages/mcp-server/src/protocol/handler.ts index e9525b2..0e8d0dd 100644 --- a/packages/mcp-server/src/protocol/handler.ts +++ b/packages/mcp-server/src/protocol/handler.ts @@ -115,13 +115,21 @@ export async function handleMcpRequest(req: Request): Promise { priceMin: args.priceMin, priceMax: args.priceMax, }; - const items = await fetchKijijiItems( - query, - 1, - "https://www.kijiji.ca", - searchOptions, - {}, - ); + const items = await Promise.race([ + fetchKijijiItems( + query, + 1, + "https://www.kijiji.ca", + searchOptions, + {}, + ), + new Promise((_, reject) => + setTimeout( + () => reject(new Error("Request timed out after 60 seconds")), + 60000, + ), + ), + ]); result = items || []; } else if (name === "search_facebook") { const query = args.query; @@ -132,14 +140,22 @@ export async function handleMcpRequest(req: Request): Promise { error: { code: -32602, message: "query parameter is required" }, }); } - const items = await fetchFacebookItems( - query, - 1, - args.location || "toronto", - args.maxItems || 25, - args.cookiesSource, - undefined, - ); + const items = await Promise.race([ + fetchFacebookItems( + query, + 1, + args.location || "toronto", + args.maxItems || 25, + args.cookiesSource, + undefined, + ), + new Promise((_, reject) => + setTimeout( + () => reject(new Error("Request timed out after 60 seconds")), + 60000, + ), + ), + ]); result = items || []; } else if (name === "search_ebay") { const query = args.query; @@ -150,15 +166,23 @@ export async function handleMcpRequest(req: Request): Promise { error: { code: -32602, message: "query parameter is required" }, }); } - const items = await fetchEbayItems(query, 1, { - minPrice: args.minPrice, - maxPrice: args.maxPrice, - strictMode: args.strictMode || false, - exclusions: args.exclusions || [], - keywords: args.keywords || [query], - buyItNowOnly: args.buyItNowOnly !== false, - canadaOnly: args.canadaOnly !== false, - }); + const items = await Promise.race([ + fetchEbayItems(query, 1, { + minPrice: args.minPrice, + maxPrice: args.maxPrice, + strictMode: args.strictMode || false, + exclusions: args.exclusions || [], + keywords: args.keywords || [query], + buyItNowOnly: args.buyItNowOnly !== false, + canadaOnly: args.canadaOnly !== false, + }), + new Promise((_, reject) => + setTimeout( + () => reject(new Error("Request timed out after 60 seconds")), + 60000, + ), + ), + ]); const results = args.maxItems ? items.slice(0, args.maxItems) : items; result = results || []; diff --git a/scripts/start.sh b/scripts/start.sh index cdb40e2..b98ae51 100755 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -6,7 +6,7 @@ trap 'echo "Received shutdown signal, stopping services..."; kill -TERM $API_PID # Start API Server in background echo "Starting API Server on port ${API_PORT:-4005}..." -bun ../dist/api/index.js & +bun dist/api/index.js & API_PID=$! # Give API server a moment to initialize @@ -14,7 +14,7 @@ sleep 1 # Start MCP Server in background echo "Starting MCP Server on port ${API_PORT:-4006}..." -bun ../dist/mcp/index.js & +bun dist/mcp/index.js & MCP_PID=$! echo "Both services started successfully"