docs: surface Kijiji AND-matching behavior in tool, API, and MCP responses
Kijiji zero-result queries (e.g. 'macbook air m1 apple silicon') are confusing because the failure mode is non-obvious. Surface the root cause everywhere the caller can see it: - MCP tool description warns about AND-matching and gives a concrete before/after example - API 404 body includes the actionable hint via emptySearchResponse(hint) - Core scraper logs the built URL and tip on page-1 zero results - MCP handler unwraps the API message field so the hint reaches the LLM
This commit is contained in:
@@ -31,9 +31,9 @@ export function parseNonNegativeIntegerParam(
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emptySearchResponse(): Response {
|
export function emptySearchResponse(hint?: string): Response {
|
||||||
return Response.json(
|
const message = hint
|
||||||
{ message: "Search didn't return any results!" },
|
? `Search didn't return any results! ${hint}`
|
||||||
{ status: 404 },
|
: "Search didn't return any results!";
|
||||||
);
|
return Response.json({ message }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,10 @@ export async function kijijiRoute(req: Request): Promise<Response> {
|
|||||||
{ hideUnstableResults: true },
|
{ hideUnstableResults: true },
|
||||||
);
|
);
|
||||||
if (items.results.length === 0 && items.unstableResults.length === 0) {
|
if (items.results.length === 0 && items.unstableResults.length === 0) {
|
||||||
return emptySearchResponse();
|
return emptySearchResponse(
|
||||||
|
`Kijiji matches ALL words in the query against listing titles. ` +
|
||||||
|
`Try a shorter or more common query (e.g. "macbook air m1" instead of "macbook air m1 apple silicon").`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Response.json(items, { status: 200 });
|
return Response.json(items, { status: 200 });
|
||||||
}
|
}
|
||||||
@@ -86,7 +89,10 @@ export async function kijijiRoute(req: Request): Promise<Response> {
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
if (!items || items.length === 0) {
|
if (!items || items.length === 0) {
|
||||||
return emptySearchResponse();
|
return emptySearchResponse(
|
||||||
|
`Kijiji matches ALL words in the query against listing titles. ` +
|
||||||
|
`Try a shorter or more common query (e.g. "macbook air m1" instead of "macbook air m1 apple silicon").`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Response.json(items, { status: 200 });
|
return Response.json(items, { status: 200 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -394,7 +394,8 @@ describe("API routes", () => {
|
|||||||
|
|
||||||
expect(response.status).toBe(404);
|
expect(response.status).toBe(404);
|
||||||
const body = await response.json();
|
const body = await response.json();
|
||||||
expect(body.message).toBe("Search didn't return any results!");
|
expect(body.message).toStartWith("Search didn't return any results!");
|
||||||
|
expect(body.message).toContain("Kijiji matches ALL words");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("ebayRoute forwards maxItems to core in default mode", async () => {
|
test("ebayRoute forwards maxItems to core in default mode", async () => {
|
||||||
|
|||||||
@@ -893,7 +893,17 @@ export default async function fetchKijijiItems(
|
|||||||
|
|
||||||
const searchResults = parseSearch(searchHtml, BASE_URL);
|
const searchResults = parseSearch(searchHtml, BASE_URL);
|
||||||
if (searchResults.length === 0) {
|
if (searchResults.length === 0) {
|
||||||
logger.log(`No more results found on page ${page}. Stopping pagination.`);
|
if (page === 1) {
|
||||||
|
logger.log(
|
||||||
|
`No results found on page 1. The search URL was: ${searchUrl}\n` +
|
||||||
|
`Tip: Kijiji matches ALL words in the query against listing titles. ` +
|
||||||
|
`Try a shorter or more common query (e.g. "macbook air m1" instead of "macbook air m1 apple silicon").`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.log(
|
||||||
|
`No more results found on page ${page}. Stopping pagination.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,14 @@ export async function handleMcpRequest(req: Request): Promise<Response> {
|
|||||||
logger.error(
|
logger.error(
|
||||||
`[MCP] Kijiji API error ${response.status}: ${errorText}`,
|
`[MCP] Kijiji API error ${response.status}: ${errorText}`,
|
||||||
);
|
);
|
||||||
throw new Error(`API returned ${response.status}: ${errorText}`);
|
let errorMessage = `API returned ${response.status}: ${errorText}`;
|
||||||
|
try {
|
||||||
|
const errorJson = JSON.parse(errorText) as { message?: string };
|
||||||
|
if (errorJson.message) errorMessage = errorJson.message;
|
||||||
|
} catch {
|
||||||
|
// not JSON — use raw text
|
||||||
|
}
|
||||||
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
result = await response.json();
|
result = await response.json();
|
||||||
logger.log(
|
logger.log(
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ export const tools = [
|
|||||||
properties: {
|
properties: {
|
||||||
query: {
|
query: {
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "Search query for Kijiji listings",
|
description:
|
||||||
|
"Search query for Kijiji listings. " +
|
||||||
|
"Kijiji requires ALL words to appear in the listing title — keep queries short and use terms sellers actually write. " +
|
||||||
|
"Avoid marketing/brand phrases sellers don't use (e.g. use 'macbook air m1' not 'macbook air m1 apple silicon'). " +
|
||||||
|
"If the search returns no results, try a shorter or more common query.",
|
||||||
},
|
},
|
||||||
location: {
|
location: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
Reference in New Issue
Block a user