fix: resolve biome lint errors and warnings
This commit is contained in:
@@ -1,206 +1,219 @@
|
||||
import { fetchKijijiItems, fetchFacebookItems, fetchEbayItems } from "@marketplace-scrapers/core";
|
||||
import {
|
||||
fetchEbayItems,
|
||||
fetchFacebookItems,
|
||||
fetchKijijiItems,
|
||||
} from "@marketplace-scrapers/core";
|
||||
import { tools } from "./tools";
|
||||
|
||||
/**
|
||||
* Handle MCP JSON-RPC 2.0 protocol requests
|
||||
*/
|
||||
export async function handleMcpRequest(req: Request): Promise<Response> {
|
||||
try {
|
||||
const body = await req.json();
|
||||
try {
|
||||
const body = await req.json();
|
||||
|
||||
// Validate JSON-RPC 2.0 format
|
||||
if (!body.jsonrpc || body.jsonrpc !== "2.0" || !body.method) {
|
||||
return Response.json(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
error: { code: -32600, message: "Invalid Request" },
|
||||
id: body.id,
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
// Validate JSON-RPC 2.0 format
|
||||
if (!body.jsonrpc || body.jsonrpc !== "2.0" || !body.method) {
|
||||
return Response.json(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
error: { code: -32600, message: "Invalid Request" },
|
||||
id: body.id,
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const { method, params, id } = body;
|
||||
const { method, params, id } = body;
|
||||
|
||||
// Handle initialize method
|
||||
if (method === "initialize") {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
result: {
|
||||
protocolVersion: "2025-06-18",
|
||||
capabilities: {
|
||||
tools: {
|
||||
listChanged: true,
|
||||
},
|
||||
},
|
||||
serverInfo: {
|
||||
name: "marketplace-scrapers",
|
||||
version: "1.0.0",
|
||||
},
|
||||
instructions: "Use search_kijiji, search_facebook, or search_ebay tools to find listings across Canadian marketplaces",
|
||||
},
|
||||
});
|
||||
}
|
||||
// Handle initialize method
|
||||
if (method === "initialize") {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
result: {
|
||||
protocolVersion: "2025-06-18",
|
||||
capabilities: {
|
||||
tools: {
|
||||
listChanged: true,
|
||||
},
|
||||
},
|
||||
serverInfo: {
|
||||
name: "marketplace-scrapers",
|
||||
version: "1.0.0",
|
||||
},
|
||||
instructions:
|
||||
"Use search_kijiji, search_facebook, or search_ebay tools to find listings across Canadian marketplaces",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Handle tools/list method
|
||||
if (method === "tools/list") {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
result: {
|
||||
tools,
|
||||
},
|
||||
});
|
||||
}
|
||||
// Handle tools/list method
|
||||
if (method === "tools/list") {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
result: {
|
||||
tools,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Handle notifications (messages without id field should not get a response)
|
||||
if (!id) {
|
||||
// Notifications don't require a response
|
||||
if (method === "notifications/initialized") {
|
||||
// Client initialized successfully, no response needed
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
if (method === "notifications/progress") {
|
||||
// Progress notifications, no response needed
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
// Unknown notification - still no response for notifications
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
// Handle notifications (messages without id field should not get a response)
|
||||
if (!id) {
|
||||
// Notifications don't require a response
|
||||
if (method === "notifications/initialized") {
|
||||
// Client initialized successfully, no response needed
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
if (method === "notifications/progress") {
|
||||
// Progress notifications, no response needed
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
// Unknown notification - still no response for notifications
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
|
||||
// Handle tools/call method
|
||||
if (method === "tools/call") {
|
||||
const { name, arguments: args } = params || {};
|
||||
// Handle tools/call method
|
||||
if (method === "tools/call") {
|
||||
const { name, arguments: args } = params || {};
|
||||
|
||||
if (!name || !args) {
|
||||
return Response.json(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: { code: -32602, message: "Invalid params: name and arguments required" },
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
if (!name || !args) {
|
||||
return Response.json(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: {
|
||||
code: -32602,
|
||||
message: "Invalid params: name and arguments required",
|
||||
},
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Route tool calls to appropriate handlers
|
||||
try {
|
||||
let result;
|
||||
// Route tool calls to appropriate handlers
|
||||
try {
|
||||
let result: unknown;
|
||||
|
||||
if (name === "search_kijiji") {
|
||||
const query = args.query;
|
||||
if (!query) {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: { code: -32602, message: "query parameter is required" },
|
||||
});
|
||||
}
|
||||
const searchOptions = {
|
||||
location: args.location,
|
||||
category: args.category,
|
||||
keywords: args.keywords,
|
||||
sortBy: args.sortBy,
|
||||
sortOrder: args.sortOrder,
|
||||
maxPages: args.maxPages || 5,
|
||||
priceMin: args.priceMin,
|
||||
priceMax: args.priceMax,
|
||||
};
|
||||
const items = await fetchKijijiItems(
|
||||
query,
|
||||
1,
|
||||
"https://www.kijiji.ca",
|
||||
searchOptions,
|
||||
{}
|
||||
);
|
||||
result = items || [];
|
||||
} else if (name === "search_facebook") {
|
||||
const query = args.query;
|
||||
if (!query) {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: { code: -32602, message: "query parameter is required" },
|
||||
});
|
||||
}
|
||||
const items = await fetchFacebookItems(
|
||||
query,
|
||||
1,
|
||||
args.location || "toronto",
|
||||
args.maxItems || 25,
|
||||
args.cookiesSource,
|
||||
undefined
|
||||
);
|
||||
result = items || [];
|
||||
} else if (name === "search_ebay") {
|
||||
const query = args.query;
|
||||
if (!query) {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
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,
|
||||
});
|
||||
if (name === "search_kijiji") {
|
||||
const query = args.query;
|
||||
if (!query) {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: { code: -32602, message: "query parameter is required" },
|
||||
});
|
||||
}
|
||||
const searchOptions = {
|
||||
location: args.location,
|
||||
category: args.category,
|
||||
keywords: args.keywords,
|
||||
sortBy: args.sortBy,
|
||||
sortOrder: args.sortOrder,
|
||||
maxPages: args.maxPages || 5,
|
||||
priceMin: args.priceMin,
|
||||
priceMax: args.priceMax,
|
||||
};
|
||||
const items = await fetchKijijiItems(
|
||||
query,
|
||||
1,
|
||||
"https://www.kijiji.ca",
|
||||
searchOptions,
|
||||
{},
|
||||
);
|
||||
result = items || [];
|
||||
} else if (name === "search_facebook") {
|
||||
const query = args.query;
|
||||
if (!query) {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: { code: -32602, message: "query parameter is required" },
|
||||
});
|
||||
}
|
||||
const items = await fetchFacebookItems(
|
||||
query,
|
||||
1,
|
||||
args.location || "toronto",
|
||||
args.maxItems || 25,
|
||||
args.cookiesSource,
|
||||
undefined,
|
||||
);
|
||||
result = items || [];
|
||||
} else if (name === "search_ebay") {
|
||||
const query = args.query;
|
||||
if (!query) {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
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 results = args.maxItems ? items.slice(0, args.maxItems) : items;
|
||||
result = results || [];
|
||||
} else {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: { code: -32601, message: `Unknown tool: ${name}` },
|
||||
});
|
||||
}
|
||||
const results = args.maxItems ? items.slice(0, args.maxItems) : items;
|
||||
result = results || [];
|
||||
} else {
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: { code: -32601, message: `Unknown tool: ${name}` },
|
||||
});
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
result: {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: { code: -32603, message: `Tool execution failed: ${errorMessage}` },
|
||||
});
|
||||
}
|
||||
}
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
result: {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Unknown error";
|
||||
return Response.json({
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: {
|
||||
code: -32603,
|
||||
message: `Tool execution failed: ${errorMessage}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Method not found
|
||||
return Response.json(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: { code: -32601, message: `Method not found: ${method}` },
|
||||
},
|
||||
{ status: 404 }
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||
return Response.json(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
error: { code: -32700, message: `Parse error: ${errorMessage}` },
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
// Method not found
|
||||
return Response.json(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
error: { code: -32601, message: `Method not found: ${method}` },
|
||||
},
|
||||
{ status: 404 },
|
||||
);
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Unknown error";
|
||||
return Response.json(
|
||||
{
|
||||
jsonrpc: "2.0",
|
||||
error: { code: -32700, message: `Parse error: ${errorMessage}` },
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,25 @@
|
||||
*/
|
||||
|
||||
export const serverCard = {
|
||||
$schema: "https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json",
|
||||
version: "1.0",
|
||||
protocolVersion: "2025-06-18",
|
||||
serverInfo: {
|
||||
name: "marketplace-scrapers",
|
||||
title: "Marketplace Scrapers MCP Server",
|
||||
version: "1.0.0",
|
||||
},
|
||||
transport: {
|
||||
type: "streamable-http",
|
||||
endpoint: "/mcp",
|
||||
},
|
||||
capabilities: {
|
||||
tools: {
|
||||
listChanged: true,
|
||||
},
|
||||
},
|
||||
description: "Scrapes marketplace listings from Kijiji, Facebook Marketplace, and eBay",
|
||||
tools: "dynamic",
|
||||
$schema:
|
||||
"https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json",
|
||||
version: "1.0",
|
||||
protocolVersion: "2025-06-18",
|
||||
serverInfo: {
|
||||
name: "marketplace-scrapers",
|
||||
title: "Marketplace Scrapers MCP Server",
|
||||
version: "1.0.0",
|
||||
},
|
||||
transport: {
|
||||
type: "streamable-http",
|
||||
endpoint: "/mcp",
|
||||
},
|
||||
capabilities: {
|
||||
tools: {
|
||||
listChanged: true,
|
||||
},
|
||||
},
|
||||
description:
|
||||
"Scrapes marketplace listings from Kijiji, Facebook Marketplace, and eBay",
|
||||
tools: "dynamic",
|
||||
};
|
||||
|
||||
@@ -3,135 +3,138 @@
|
||||
*/
|
||||
|
||||
export const tools = [
|
||||
{
|
||||
name: "search_kijiji",
|
||||
description: "Search Kijiji marketplace for listings matching a query",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "Search query for Kijiji listings",
|
||||
},
|
||||
location: {
|
||||
type: "string",
|
||||
description: "Location name or ID (e.g., 'toronto', 'gta', 'ontario')",
|
||||
},
|
||||
category: {
|
||||
type: "string",
|
||||
description: "Category name or ID (e.g., 'computers', 'furniture', 'bikes')",
|
||||
},
|
||||
keywords: {
|
||||
type: "string",
|
||||
description: "Additional keywords to filter results",
|
||||
},
|
||||
sortBy: {
|
||||
type: "string",
|
||||
description: "Sort results by field",
|
||||
enum: ["relevancy", "date", "price", "distance"],
|
||||
default: "relevancy",
|
||||
},
|
||||
sortOrder: {
|
||||
type: "string",
|
||||
description: "Sort order",
|
||||
enum: ["asc", "desc"],
|
||||
default: "desc",
|
||||
},
|
||||
maxPages: {
|
||||
type: "number",
|
||||
description: "Maximum pages to fetch (~40 items per page)",
|
||||
default: 5,
|
||||
},
|
||||
priceMin: {
|
||||
type: "number",
|
||||
description: "Minimum price in cents",
|
||||
},
|
||||
priceMax: {
|
||||
type: "number",
|
||||
description: "Maximum price in cents",
|
||||
},
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "search_facebook",
|
||||
description: "Search Facebook Marketplace for listings matching a query",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "Search query for Facebook Marketplace listings",
|
||||
},
|
||||
location: {
|
||||
type: "string",
|
||||
description: "Location for search (e.g., 'toronto')",
|
||||
default: "toronto",
|
||||
},
|
||||
maxItems: {
|
||||
type: "number",
|
||||
description: "Maximum number of items to return",
|
||||
default: 5,
|
||||
},
|
||||
cookiesSource: {
|
||||
type: "string",
|
||||
description: "Optional Facebook session cookies source",
|
||||
},
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "search_ebay",
|
||||
description: "Search eBay for listings matching a query (default: Buy It Now only, Canada only)",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "Search query for eBay listings",
|
||||
},
|
||||
minPrice: {
|
||||
type: "number",
|
||||
description: "Minimum price filter",
|
||||
},
|
||||
maxPrice: {
|
||||
type: "number",
|
||||
description: "Maximum price filter",
|
||||
},
|
||||
strictMode: {
|
||||
type: "boolean",
|
||||
description: "Enable strict search mode",
|
||||
default: false,
|
||||
},
|
||||
exclusions: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "Terms to exclude from results",
|
||||
},
|
||||
keywords: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "Keywords to include in search",
|
||||
},
|
||||
buyItNowOnly: {
|
||||
type: "boolean",
|
||||
description: "Include only Buy It Now listings (exclude auctions)",
|
||||
default: true,
|
||||
},
|
||||
canadaOnly: {
|
||||
type: "boolean",
|
||||
description: "Include only Canadian sellers/listings",
|
||||
default: true,
|
||||
},
|
||||
maxItems: {
|
||||
type: "number",
|
||||
description: "Maximum number of items to return",
|
||||
default: 5,
|
||||
},
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "search_kijiji",
|
||||
description: "Search Kijiji marketplace for listings matching a query",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "Search query for Kijiji listings",
|
||||
},
|
||||
location: {
|
||||
type: "string",
|
||||
description:
|
||||
"Location name or ID (e.g., 'toronto', 'gta', 'ontario')",
|
||||
},
|
||||
category: {
|
||||
type: "string",
|
||||
description:
|
||||
"Category name or ID (e.g., 'computers', 'furniture', 'bikes')",
|
||||
},
|
||||
keywords: {
|
||||
type: "string",
|
||||
description: "Additional keywords to filter results",
|
||||
},
|
||||
sortBy: {
|
||||
type: "string",
|
||||
description: "Sort results by field",
|
||||
enum: ["relevancy", "date", "price", "distance"],
|
||||
default: "relevancy",
|
||||
},
|
||||
sortOrder: {
|
||||
type: "string",
|
||||
description: "Sort order",
|
||||
enum: ["asc", "desc"],
|
||||
default: "desc",
|
||||
},
|
||||
maxPages: {
|
||||
type: "number",
|
||||
description: "Maximum pages to fetch (~40 items per page)",
|
||||
default: 5,
|
||||
},
|
||||
priceMin: {
|
||||
type: "number",
|
||||
description: "Minimum price in cents",
|
||||
},
|
||||
priceMax: {
|
||||
type: "number",
|
||||
description: "Maximum price in cents",
|
||||
},
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "search_facebook",
|
||||
description: "Search Facebook Marketplace for listings matching a query",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "Search query for Facebook Marketplace listings",
|
||||
},
|
||||
location: {
|
||||
type: "string",
|
||||
description: "Location for search (e.g., 'toronto')",
|
||||
default: "toronto",
|
||||
},
|
||||
maxItems: {
|
||||
type: "number",
|
||||
description: "Maximum number of items to return",
|
||||
default: 5,
|
||||
},
|
||||
cookiesSource: {
|
||||
type: "string",
|
||||
description: "Optional Facebook session cookies source",
|
||||
},
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "search_ebay",
|
||||
description:
|
||||
"Search eBay for listings matching a query (default: Buy It Now only, Canada only)",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "Search query for eBay listings",
|
||||
},
|
||||
minPrice: {
|
||||
type: "number",
|
||||
description: "Minimum price filter",
|
||||
},
|
||||
maxPrice: {
|
||||
type: "number",
|
||||
description: "Maximum price filter",
|
||||
},
|
||||
strictMode: {
|
||||
type: "boolean",
|
||||
description: "Enable strict search mode",
|
||||
default: false,
|
||||
},
|
||||
exclusions: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "Terms to exclude from results",
|
||||
},
|
||||
keywords: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "Keywords to include in search",
|
||||
},
|
||||
buyItNowOnly: {
|
||||
type: "boolean",
|
||||
description: "Include only Buy It Now listings (exclude auctions)",
|
||||
default: true,
|
||||
},
|
||||
canadaOnly: {
|
||||
type: "boolean",
|
||||
description: "Include only Canadian sellers/listings",
|
||||
default: true,
|
||||
},
|
||||
maxItems: {
|
||||
type: "number",
|
||||
description: "Maximum number of items to return",
|
||||
default: 5,
|
||||
},
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user