search_local_docs
Search locally extracted Salesforce documentation in a SQLite database to find relevant sections based on your query.
Instructions
Search locally extracted Salesforce documentation in the SQLite database.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| maxResults | No |
Implementation Reference
- src/index.ts:196-231 (handler)The handler for the 'search_local_docs' tool call. It parses the query/maxResults using SearchDocsSchema, calls searchDocuments() from db.ts, and formats results as markdown. Also handles edge cases (empty DB, no matches).
if (name === "search_local_docs") { const { query, maxResults } = SearchDocsSchema.parse(args); const results = await searchDocuments(query, maxResults); if (results.length === 0) { const database = await getDatabase(); const countStmt = database.prepare('SELECT COUNT(*) FROM documents'); countStmt.step(); const rowCount = countStmt.get()[0] as number; countStmt.free(); if (rowCount === 0) { return { content: [{ type: "text", text: "No results found in the local database.\n\nNote: If this is a new installation, your local database is currently empty. You must run the `mass_extract_guide` tool on a Salesforce category URL first to index the documentation locally." }] }; } else { return { content: [{ type: "text", text: `No matching documentation found for "${query}". Try different or fewer keywords.` }] }; } } let output = `# Search Results for "${query}"\n\n`; for (const r of results) { output += `## [${r.title}](${r.url})\n*Category: ${r.category}* | *Score: ${(r.score * 100).toFixed(1)}%*\n\n`; output += `> ${r.matchContent.substring(0, 500)}...\n\n---\n`; } return { content: [{ type: "text", text: output }] }; } - src/db.ts:138-234 (helper)The searchDocuments() function performs the actual search in the SQLite database. It tokenizes the query, runs LIKE queries against chunked content, scores documents by term coverage and frequency, and returns the top results.
export async function searchDocuments(query: string, maxResults: number = 5) { const database = await getDatabase(); const queryLower = query.toLowerCase(); const searchTerms = queryLower.split(/\s+/).filter(w => w.length > 2); if (searchTerms.length === 0) return []; const likeConditions = searchTerms.map(t => 'c.content_lower LIKE ?').join(' OR '); const params = searchTerms.map(t => `%${t}%`); const sql = ` SELECT d.id, d.url, d.title, d.category, c.content, c.content_lower FROM chunks c JOIN documents d ON c.document_id = d.id WHERE (${likeConditions}) LIMIT 1000 `; const stmt = database.prepare(sql); stmt.bind(params); const rows: any[] = []; const columns = stmt.getColumnNames(); while (stmt.step()) { const rowData = stmt.get(); const row: any = {}; columns.forEach((col: string, idx: number) => row[col] = rowData[idx]); rows.push(row); } stmt.free(); // BUG-10 Fix: Group matching chunks by document URL so we can score the document globally const docsByUrl = new Map(); for (const row of rows) { if (!docsByUrl.has(row.url)) { docsByUrl.set(row.url, { url: row.url, title: row.title, category: row.category, chunks: [] }); } docsByUrl.get(row.url).chunks.push(row); } const scoredDocs = []; for (const doc of docsByUrl.values()) { let docHits = 0; let totalFreq = 0; // Evaluate each term against the combined content of all matched chunks for this doc const combinedLower = doc.chunks.map((c: any) => c.content_lower).join(' '); for (const term of searchTerms) { if (combinedLower.includes(term)) { docHits++; // Rough frequency count for tie-breaking totalFreq += (combinedLower.split(term).length - 1); } } const density = docHits / searchTerms.length; // Find best individual chunk to use as the snippet let bestChunk = doc.chunks[0]; let bestChunkHits = -1; for (const c of doc.chunks) { let cHits = 0; for (const term of searchTerms) { if (c.content_lower.includes(term)) cHits++; } if (cHits > bestChunkHits) { bestChunkHits = cHits; bestChunk = c; } } scoredDocs.push({ url: doc.url, title: doc.title, category: doc.category, matchContent: bestChunk.content, score: density, totalFreq }); } // Sort by term coverage hits first, then by raw frequency scoredDocs.sort((a, b) => { if (b.score !== a.score) return b.score - a.score; return b.totalFreq - a.totalFreq; }); return scoredDocs.slice(0, maxResults); } - src/index.ts:30-33 (schema)The Zod schema 'SearchDocsSchema' used to validate inputs for the search_local_docs tool: query (string, 1-500 chars) and maxResults (number, 1-20, default 5).
const SearchDocsSchema = z.object({ query: z.string().min(1).max(500), maxResults: z.number().int().min(1).max(20).optional().default(5) }); - src/index.ts:74-85 (registration)Registration of the 'search_local_docs' tool in the ListTools handler, including its name, description, and inputSchema (type, properties, required).
{ name: "search_local_docs", description: "Search locally extracted Salesforce documentation in the SQLite database.", inputSchema: { type: "object", properties: { query: { type: "string" }, maxResults: { type: "number" } }, required: ["query"] } },