refactor: share mcp api calls
This commit is contained in:
@@ -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<unknown> {
|
||||
const url = `${API_BASE_URL}/${marketplace}?${params.toString()}`;
|
||||
logger.log(`[MCP] Calling ${marketplace} API`);
|
||||
const response = await Promise.race([
|
||||
fetch(url),
|
||||
new Promise<Response>((_, 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<Response> {
|
||||
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<Response>((_, 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<Response> {
|
||||
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<Response>((_, 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<Response> {
|
||||
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<Response>((_, 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",
|
||||
|
||||
@@ -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", {
|
||||
|
||||
Reference in New Issue
Block a user