Skip to main content
Glama

search_knowledge

Quickly locate technical details and related content across project documents using intelligent keyword search with result grouping. Ideal for researching before creating new content or verifying existing documentation.

Instructions

Search project knowledge documents for keywords with intelligent result grouping.

When to use this tool:

  • Finding information across multiple documents

  • Locating specific technical details

  • Discovering related content

  • Checking if topic is already documented

  • Researching before creating new content

Key features:

  • Case-insensitive full-text search

  • Searches document body, titles, and content

  • Groups results by document

  • Returns matching chapters with context

  • Space-separated keyword support

You should:

  1. Use specific keywords for better results

  2. Try multiple search terms if needed

  3. Search before creating new documents

  4. Use 2-3 word phrases for precision

  5. Review all results before concluding

  6. Consider variations of technical terms

  7. Check both titles and content matches

DO NOT use when:

  • Know exact document and chapter

  • Need complete document listing

  • Searching for project main content

Returns: {success: bool, total_documents: int, total_matches: int, results: [...], error?: str}

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
project_idYesThe project identifier
queryYesSpace-separated keywords to search for

Implementation Reference

  • Primary handler function for the search_knowledge tool. Handles input validation, project lookup, keyword parsing, delegates to searchDocumentsAsync, transforms results to match expected output format (total_documents, total_matches, results with document and chapters), and formats the MCP response with logging.
    async searchKnowledgeAsync(params: { project_id: z.infer<typeof secureProjectIdSchema>; query: z.infer<typeof secureSearchQuerySchema>; }): Promise<string> { const context = this.createContext('search_knowledge', params); try { const { project_id, query } = params; const projectInfo = await getProjectDirectoryAsync(this.storagePath, project_id); // Project doesn't exist - return empty results without creating ghost entry if (!projectInfo) { this.logSuccess('search_knowledge', { project_id }, context); return this.formatSuccessResponse({ total_documents: 0, total_matches: 0, results: [], }); } const [, projectPath] = projectInfo; // Parse search keywords const keywords = query .toLowerCase() .split(/\s+/) .filter((k) => k.length > 0); // Search documents - pass projectPath, not knowledgePath const results = await searchDocumentsAsync(projectPath, keywords.join(' ')); // Transform results for response - matching test expectations const transformedResults = results.map((result: SearchResult) => ({ document: result.file, chapters: result.matching_chapters.map((ch) => ({ title: ch.chapter || '(Document Introduction)', matches: ch.keywords_found.length, })), })); this.logSuccess('search_knowledge', { project_id }, context); return this.formatSuccessResponse({ total_documents: transformedResults.length, total_matches: results.reduce((sum, r) => sum + r.match_count, 0), results: transformedResults, }); } catch (error) { const mcpError = error instanceof MCPError ? error : new MCPError( MCPErrorCode.INTERNAL_ERROR, `Search failed: ${error instanceof Error ? error.message : String(error)}`, { project_id: params.project_id, query: params.query, traceId: context.traceId, } ); this.logError( 'search_knowledge', { project_id: params.project_id, query: params.query, }, mcpError, context ); return this.formatErrorResponse(mcpError, context); } }
  • Core search helper function called by the handler. Performs full-text keyword search across all knowledge documents in a project, parsing MD files, extracting chapters, finding matches with context, and grouping results by document and chapter.
    export async function searchDocumentsAsync( projectPath: string, query: string ): Promise<SearchResult[]> { if (!query) { return []; } const knowledgeDir = join(projectPath, 'knowledge'); try { await access(knowledgeDir); } catch { return []; } // Split query into individual keywords const keywords = query .split(/\s+/) .map((kw) => kw.trim().toLowerCase()) .filter((kw) => kw); if (keywords.length === 0) { return []; } // Structure to aggregate results by document const documentResults: Record< string, { metadata: DocumentMetadata; matching_chapters: Record< string, { chapter: string; keywords_found: Set<string>; match_context: Record<string, string[]>; chapter_summary: string; } >; } > = {}; // Search through all markdown files const files = await readdir(knowledgeDir); const mdFiles = files.filter((f) => f.endsWith('.md')); for (const mdFile of mdFiles) { try { const filePath = join(knowledgeDir, mdFile); const content = await readFile(filePath, 'utf8'); // Parse document const [metadata, body] = parseDocument(content); const chapters = parseChapters(body); // Search each keyword for (const keyword of keywords) { const bodyLower = body.toLowerCase(); // If no chapters, search entire body if (chapters.length === 0 && bodyLower.includes(keyword)) { const contexts: string[] = []; let startPos = 0; let index = bodyLower.indexOf(keyword, startPos); while (index !== -1) { // Extract context const ctxStart = Math.max(0, index - 50); const ctxEnd = Math.min(body.length, index + keyword.length + 50); let context = body.slice(ctxStart, ctxEnd).trim(); if (ctxStart > 0) context = '...' + context; if (ctxEnd < body.length) context = context + '...'; contexts.push(context); startPos = index + 1; index = bodyLower.indexOf(keyword, startPos); } if (contexts.length > 0) { // Initialize document entry if needed if (!(mdFile in documentResults)) { documentResults[mdFile] = { metadata, matching_chapters: {}, }; } // Add body-level match as a special chapter if (!('_document_body' in documentResults[mdFile].matching_chapters)) { documentResults[mdFile].matching_chapters['_document_body'] = { chapter: '', keywords_found: new Set(), match_context: {}, chapter_summary: '', }; } const chapterData = documentResults[mdFile].matching_chapters['_document_body']; chapterData.keywords_found.add(keyword); if (!(keyword in chapterData.match_context)) { chapterData.match_context[keyword] = []; } chapterData.match_context[keyword].push(...contexts); } } // Search in chapters for (const chapter of chapters) { if (chapter.content.toLowerCase().includes(keyword)) { const contexts: string[] = []; const chapterLower = chapter.content.toLowerCase(); let startPos = 0; let index = chapterLower.indexOf(keyword, startPos); while (index !== -1) { // Extract context const ctxStart = Math.max(0, index - 50); const ctxEnd = Math.min(chapter.content.length, index + keyword.length + 50); let context = chapter.content.slice(ctxStart, ctxEnd).trim(); if (ctxStart > 0) context = '...' + context; if (ctxEnd < chapter.content.length) context = context + '...'; contexts.push(context); startPos = index + 1; index = chapterLower.indexOf(keyword, startPos); } if (contexts.length > 0) { // Initialize document entry if needed if (!(mdFile in documentResults)) { documentResults[mdFile] = { metadata, matching_chapters: {}, }; } // Initialize chapter entry if needed if (!(chapter.title in documentResults[mdFile].matching_chapters)) { documentResults[mdFile].matching_chapters[chapter.title] = { chapter: chapter.title, keywords_found: new Set(), match_context: {}, chapter_summary: chapter.summary, }; } const chapterData = documentResults[mdFile].matching_chapters[chapter.title]; chapterData.keywords_found.add(keyword); if (!(keyword in chapterData.match_context)) { chapterData.match_context[keyword] = []; } chapterData.match_context[keyword].push(...contexts); } } } // If chapters exist, also search for pre-chapter content if (chapters.length > 0 && bodyLower.includes(keyword)) { // Get the content before the first chapter const firstChapterStart = body.indexOf('\n##'); if (firstChapterStart > 0) { const preChapterContent = body.slice(0, firstChapterStart); if (preChapterContent.toLowerCase().includes(keyword)) { const contexts: string[] = []; const contentLower = preChapterContent.toLowerCase(); let startPos = 0; let index = contentLower.indexOf(keyword, startPos); while (index !== -1) { // Extract context const ctxStart = Math.max(0, index - 50); const ctxEnd = Math.min(preChapterContent.length, index + keyword.length + 50); let context = preChapterContent.slice(ctxStart, ctxEnd).trim(); if (ctxStart > 0) context = '...' + context; if (ctxEnd < preChapterContent.length) context = context + '...'; contexts.push(context); startPos = index + 1; index = contentLower.indexOf(keyword, startPos); } if (contexts.length > 0) { // Initialize document entry if needed if (!(mdFile in documentResults)) { documentResults[mdFile] = { metadata, matching_chapters: {}, }; } // Add pre-chapter content as a special chapter if (!('_pre_chapter' in documentResults[mdFile].matching_chapters)) { documentResults[mdFile].matching_chapters['_pre_chapter'] = { chapter: '', keywords_found: new Set(), match_context: {}, chapter_summary: '', }; } const chapterData = documentResults[mdFile].matching_chapters['_pre_chapter']; chapterData.keywords_found.add(keyword); if (!(keyword in chapterData.match_context)) { chapterData.match_context[keyword] = []; } chapterData.match_context[keyword].push(...contexts); } } } } } } catch { // Skip files that can't be read continue; } } // Convert to document-centric format const results: SearchResult[] = []; for (const [filename, docData] of Object.entries(documentResults)) { // Convert matching chapters to array const matchingChapters: MatchingChapter[] = []; for (const [, chapterData] of Object.entries(docData.matching_chapters)) { matchingChapters.push({ chapter: chapterData.chapter, keywords_found: Array.from(chapterData.keywords_found).sort(), match_context: chapterData.match_context, chapter_summary: chapterData.chapter_summary, }); } results.push({ file: filename, match_count: matchingChapters.length, metadata: docData.metadata, matching_chapters: matchingChapters, }); } return results; }
  • MCP tool registration for 'search_knowledge', defining title, description, input schema (project_id, query), and handler that invokes SearchToolHandler.searchKnowledgeAsync and wraps response in MCP content format.
    server.registerTool( 'search_knowledge', { title: 'Search Knowledge', description: TOOL_DESCRIPTIONS.search_knowledge, inputSchema: { project_id: secureProjectIdSchema.describe('The project identifier'), query: secureSearchQuerySchema.describe('Space-separated keywords to search for'), }, }, async ({ project_id, query }) => { const result = await searchHandler.searchKnowledgeAsync({ project_id, query }); return { content: [ { type: 'text', text: result, }, ], }; } );
  • Detailed tool description and usage instructions for search_knowledge, including when to use, key features, best practices, return format, used in registration.
    search_knowledge: `Search project knowledge documents for keywords with intelligent result grouping. When to use this tool: - Finding information across multiple documents - Locating specific technical details - Discovering related content - Checking if topic is already documented - Researching before creating new content Key features: - Case-insensitive full-text search - Searches document body, titles, and content - Groups results by document - Returns matching chapters with context - Space-separated keyword support You should: 1. Use specific keywords for better results 2. Try multiple search terms if needed 3. Search before creating new documents 4. Use 2-3 word phrases for precision 5. Review all results before concluding 6. Consider variations of technical terms 7. Check both titles and content matches DO NOT use when: - Know exact document and chapter - Need complete document listing - Searching for project main content Returns: {success: bool, total_documents: int, total_matches: int, results: [...], error?: str}`,

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sven-borkert/knowledge-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server