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:
2026-04-29 13:06:31 -04:00
parent 3ea6ee3938
commit 82e12283de
6 changed files with 39 additions and 11 deletions

View File

@@ -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 });
} }

View File

@@ -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) {

View File

@@ -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 () => {

View File

@@ -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;
} }

View File

@@ -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(

View File

@@ -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",