diff --git a/docs/functions/mcp.ts b/docs/functions/mcp.ts index 3db922c1..bbd6c78c 100644 --- a/docs/functions/mcp.ts +++ b/docs/functions/mcp.ts @@ -107,27 +107,39 @@ async function loadManifest( return data.pages; } -function scorePage(page: ManifestPage, terms: string[]): number { +/** + * Matches a query term as a whole word, allowing a plural "s"/"es", so + * "capture" finds "captures" but "quick" does not match inside "QuickAdd". + */ +function wordPattern(term: string): RegExp { + const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return new RegExp(`(? 0) score += 8; + if (countMatches(headings, term) > 0) score += 4; + if (countMatches(description, term) > 0) score += 3; + score += Math.min(countMatches(text, term), 5); } return score; } -function snippetFor(page: ManifestPage, terms: string[]): string { +function snippetFor(page: ManifestPage, terms: RegExp[]): string { const text = page.text; const lower = text.toLowerCase(); for (const term of terms) { - const at = lower.indexOf(term); + const at = lower.search(term); if (at >= 0) { const start = Math.max(0, at - 120); const end = Math.min(text.length, at + 180); @@ -150,7 +162,7 @@ async function handleToolCall( if (name === "search_quickadd_docs") { const query = String(args.query ?? "").trim(); if (!query) return textResult(id, "Missing 'query' argument.", true); - const terms = query.toLowerCase().split(/\s+/).filter(Boolean); + const terms = query.toLowerCase().split(/\s+/).filter(Boolean).map(wordPattern); const pages = await loadManifest(request, env); const ranked = pages .map((page) => ({ page, score: scorePage(page, terms) }))