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