refactor: share mcp api calls
This commit is contained in:
@@ -2,7 +2,30 @@ import { logger } from "../logger";
|
|||||||
import { tools } from "./tools";
|
import { tools } from "./tools";
|
||||||
|
|
||||||
const API_BASE_URL = process.env.API_BASE_URL || "http://localhost:4005/api";
|
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
|
* Handle MCP JSON-RPC 2.0 protocol requests
|
||||||
@@ -119,31 +142,7 @@ export async function handleMcpRequest(req: Request): Promise<Response> {
|
|||||||
if (args.unstableFilter !== undefined)
|
if (args.unstableFilter !== undefined)
|
||||||
params.append("unstableFilter", args.unstableFilter.toString());
|
params.append("unstableFilter", args.unstableFilter.toString());
|
||||||
|
|
||||||
logger.log(
|
result = await callMarketplaceApi("kijiji", params);
|
||||||
`[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`,
|
|
||||||
);
|
|
||||||
} else if (name === "search_facebook") {
|
} else if (name === "search_facebook") {
|
||||||
const query = args.query;
|
const query = args.query;
|
||||||
if (!query) {
|
if (!query) {
|
||||||
@@ -160,31 +159,7 @@ export async function handleMcpRequest(req: Request): Promise<Response> {
|
|||||||
if (args.unstableFilter !== undefined)
|
if (args.unstableFilter !== undefined)
|
||||||
params.append("unstableFilter", args.unstableFilter.toString());
|
params.append("unstableFilter", args.unstableFilter.toString());
|
||||||
|
|
||||||
logger.log(
|
result = await callMarketplaceApi("facebook", params);
|
||||||
`[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`,
|
|
||||||
);
|
|
||||||
} else if (name === "search_ebay") {
|
} else if (name === "search_ebay") {
|
||||||
const query = args.query;
|
const query = args.query;
|
||||||
if (!query) {
|
if (!query) {
|
||||||
@@ -214,31 +189,7 @@ export async function handleMcpRequest(req: Request): Promise<Response> {
|
|||||||
if (args.unstableFilter !== undefined)
|
if (args.unstableFilter !== undefined)
|
||||||
params.append("unstableFilter", args.unstableFilter.toString());
|
params.append("unstableFilter", args.unstableFilter.toString());
|
||||||
|
|
||||||
logger.log(
|
result = await callMarketplaceApi("ebay", params);
|
||||||
`[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`,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return Response.json({
|
return Response.json({
|
||||||
jsonrpc: "2.0",
|
jsonrpc: "2.0",
|
||||||
|
|||||||
@@ -152,6 +152,33 @@ describe("MCP protocol unstableFilter", () => {
|
|||||||
expect(String(calledUrl)).toContain("unstableFilter=true");
|
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 () => {
|
test("handler should forward unstableFilter=true for search_ebay", async () => {
|
||||||
await handleMcpRequest(
|
await handleMcpRequest(
|
||||||
new Request("http://localhost", {
|
new Request("http://localhost", {
|
||||||
|
|||||||
Reference in New Issue
Block a user