search-backbone
Search Backbone.js documentation chapters for specific text and retrieve relevant chapter links with matching excerpts.
Instructions
Busca texto en los capítulos Markdown y devuelve enlaces a los capítulos con coincidencias.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Texto a buscar | |
| caseSensitive | No | Distinguir mayúsculas/minúsculas | |
| maxExcerpts | No | Número de fragmentos por capítulo |
Implementation Reference
- src/server.ts:76-102 (handler)The main handler function for the 'search-backbone' tool. It takes input arguments, calls searchResources from mcp-server, and formats search results into MCP content with introductory text and resource links to matching chapters.async (args) => { const query = args.query; const caseSensitive = args.caseSensitive; const maxExcerpts = args.maxExcerpts; const results = searchResources(query, { caseSensitive, maxExcerpts }); if (!results.length) { return { content: [ { type: "text", text: `Sin coincidencias para "${query}".` } ] }; } const content: Array<any> = []; content.push({ type: "text", text: `Coincidencias: ${results.length} capítulos para "${query}".` }); for (const r of results) { content.push({ type: "resource_link", uri: r.uri, name: `Capítulo ${String(r.chapter).padStart(2, '0')} — ${r.title}`, mimeType: r.mimeType, description: r.excerpts[0] ?? undefined, }); } return { content }; }
- src/server.ts:67-75 (schema)Zod-based input schema defining the parameters for the 'search-backbone' tool: required query string, optional case-sensitive flag, and max excerpts.{ title: "Buscar en capítulos Backbone", description: "Busca texto en los capítulos Markdown y devuelve enlaces a los capítulos con coincidencias.", inputSchema: { query: z.string().min(2, 'La consulta debe tener al menos 2 caracteres').describe('Texto a buscar'), caseSensitive: z.boolean().optional().describe('Distinguir mayúsculas/minúsculas'), maxExcerpts: z.number().int().min(1).max(5).optional().describe('Número de fragmentos por capítulo'), } },
- src/server.ts:65-103 (registration)MCP server registration of the 'search-backbone' tool, including name, schema, and handler function.server.registerTool( "search-backbone", { title: "Buscar en capítulos Backbone", description: "Busca texto en los capítulos Markdown y devuelve enlaces a los capítulos con coincidencias.", inputSchema: { query: z.string().min(2, 'La consulta debe tener al menos 2 caracteres').describe('Texto a buscar'), caseSensitive: z.boolean().optional().describe('Distinguir mayúsculas/minúsculas'), maxExcerpts: z.number().int().min(1).max(5).optional().describe('Número de fragmentos por capítulo'), } }, async (args) => { const query = args.query; const caseSensitive = args.caseSensitive; const maxExcerpts = args.maxExcerpts; const results = searchResources(query, { caseSensitive, maxExcerpts }); if (!results.length) { return { content: [ { type: "text", text: `Sin coincidencias para "${query}".` } ] }; } const content: Array<any> = []; content.push({ type: "text", text: `Coincidencias: ${results.length} capítulos para "${query}".` }); for (const r of results) { content.push({ type: "resource_link", uri: r.uri, name: `Capítulo ${String(r.chapter).padStart(2, '0')} — ${r.title}`, mimeType: r.mimeType, description: r.excerpts[0] ?? undefined, }); } return { content }; } );
- src/mcp-server.ts:49-103 (helper)Core search utility function that loads resources, performs regex-based text search on chapter contents, extracts contextual excerpts, counts matches per chapter, and returns sorted list of SearchMatch objects used by the tool handler.export const searchResources = ( query: string, opts?: { caseSensitive?: boolean; maxExcerpts?: number } ): SearchMatch[] => { const q = (query ?? '').trim(); if (!q) return []; loadResources(); const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const flags = opts?.caseSensitive ? 'g' : 'gi'; const regex = new RegExp(escapeRegExp(q), flags); const maxExcerpts = Math.max(1, Math.min(10, opts?.maxExcerpts ?? 3)); const matches: SearchMatch[] = []; for (const r of cachedResources) { const text = r.content.text ?? ''; if (!text) continue; let occurrences = 0; const excerpts: string[] = []; let m: RegExpExecArray | null; while ((m = regex.exec(text)) !== null) { occurrences++; if (excerpts.length < maxExcerpts) { const start = Math.max(0, m.index - 60); const end = Math.min(text.length, m.index + m[0].length + 60); const snippet = `${start > 0 ? '…' : ''}${text .slice(start, end) .replace(/\s+/g, ' ') .trim()}${end < text.length ? '…' : ''}`; excerpts.push(snippet); } if (regex.lastIndex === m.index) regex.lastIndex++; // evitar bucles con coincidencias vacías } if (occurrences > 0) { const chapter = r.metadata?.chapter ?? 0; const title = r.metadata?.title ?? `Capítulo ${chapter}`; const uri = `backbone://chapter/${String(chapter).padStart(2, '0')}`; matches.push({ chapter, title, uri, mimeType: r.content.mimeType, occurrences, excerpts, }); } } return matches.sort((a, b) => b.occurrences - a.occurrences); }