Skip to main content
Glama

Knowledge MCP Server

by sven-borkert
05-search.test.tsโ€ข15.9 kB
#!/usr/bin/env tsx import { homedir } from 'os'; import { resolve, join } from 'path'; import { MCPTestClient } from '../utils/test-client.js'; import { TestRunner, assertSuccess, assertEqual, setupTestEnvironment, cleanupTestEnvironment, generateTestProjectId, setCurrentRunner, } from '../utils/test-helpers.js'; // Test configuration const TEST_STORAGE_PATH = resolve(join(homedir(), '.knowledge-mcp-test-search')); const SERVER_PATH = resolve('./dist/knowledge-mcp/index.js'); // Create test documents for searching async function setupSearchTestData(client: MCPTestClient, projectId: string): Promise<void> { // Document 1: TypeScript guide await client.callToolAndParse('create_knowledge_file', { project_id: projectId, filename: 'typescript-guide', title: 'TypeScript Programming Guide', introduction: 'A comprehensive guide to TypeScript programming language.', keywords: ['typescript', 'programming', 'javascript', 'types'], chapters: [ { title: 'Getting Started', content: 'Learn the basics of TypeScript and how to get started with programming in this statically typed language.', }, { title: 'Type System', content: 'Understanding the TypeScript type system is crucial for effective programming. Types provide safety and documentation.', }, { title: 'Advanced Features', content: 'TypeScript offers many advanced features for programming including generics, decorators, and more.', }, ], }); // Document 2: JavaScript best practices await client.callToolAndParse('create_knowledge_file', { project_id: projectId, filename: 'javascript-best-practices', title: 'JavaScript Best Practices', introduction: 'Essential best practices for JavaScript development.', keywords: ['javascript', 'best-practices', 'coding', 'standards'], chapters: [ { title: 'Code Organization', content: 'Organizing your JavaScript code effectively is important for maintainability.', }, { title: 'Performance Tips', content: 'JavaScript performance can be improved by following these tips and avoiding common pitfalls.', }, ], }); // Document 3: API development await client.callToolAndParse('create_knowledge_file', { project_id: projectId, filename: 'api-development', title: 'API Development Guide', introduction: 'Building robust APIs with modern technologies.', keywords: ['api', 'rest', 'graphql', 'development'], chapters: [ { title: 'REST APIs', content: 'REST APIs are a popular choice for web development. Learn the principles and best practices.', }, { title: 'GraphQL', content: 'GraphQL provides a flexible alternative to REST for API development.', }, { title: 'Authentication', content: 'Secure your APIs with proper authentication and authorization mechanisms including JWT tokens.', }, ], }); // Document 4: Testing guide await client.callToolAndParse('create_knowledge_file', { project_id: projectId, filename: 'testing-guide', title: 'Software Testing Guide', introduction: 'Comprehensive guide to software testing methodologies.', keywords: ['testing', 'unit', 'integration', 'quality'], chapters: [ { title: 'Unit Testing', content: 'Unit testing is a fundamental practice in software development. Learn how to write effective unit tests.', }, { title: 'Integration Testing', content: 'Integration testing ensures that different components work together properly.', }, { title: 'Test Driven Development', content: 'TDD is a development methodology where you write tests before implementing features.', }, ], }); } async function main(): Promise<void> { const runner = new TestRunner(); const client = new MCPTestClient({ serverPath: SERVER_PATH, storagePath: TEST_STORAGE_PATH, }); // Setup setupTestEnvironment(TEST_STORAGE_PATH); await client.connect(); client.setTestRunner(runner); try { runner.setSuite('Search Operations', 'Search & Discovery'); runner.enableDetailedTracking(); setCurrentRunner(runner); await runner.runTest('search_knowledge: Single keyword match', async () => { const projectId = generateTestProjectId('search-single'); await setupSearchTestData(client, projectId); const result = await client.callToolAndParse('search_knowledge', { project_id: projectId, query: 'typescript', }); assertSuccess(result); assertEqual(result.total_documents, 1); const results = result.results as any[]; assertEqual(results[0].document, 'typescript-guide.md'); // Check that it found matches in the document assertEqual(results[0].chapters.length > 0, true); }); await runner.runTest('search_knowledge: Multiple keyword search', async () => { const projectId = generateTestProjectId('search-multiple'); await setupSearchTestData(client, projectId); const result = await client.callToolAndParse('search_knowledge', { project_id: projectId, query: 'testing unit', }); assertSuccess(result); const results = result.results as any[]; assertEqual(results[0].document, 'testing-guide.md'); // Should have found matches in chapters const totalMatches = (results[0].chapters as any[]).reduce( (sum: number, ch: any) => sum + (ch.matches as number), 0 ); if (totalMatches < 2) { throw new Error('Did not find matches for both keywords'); } }); await runner.runTest('search_knowledge: Case insensitive search', async () => { const projectId = generateTestProjectId('search-case'); await setupSearchTestData(client, projectId); // Search with different cases const searches = ['API', 'api', 'Api', 'aPi']; for (const query of searches) { const result = await client.callToolAndParse('search_knowledge', { project_id: projectId, query: query, }); assertSuccess(result); assertEqual(result.total_documents, 1, `Failed for query: ${query}`); } }); await runner.runTest('search_knowledge: Search in chapter content', async () => { const projectId = generateTestProjectId('search-content'); await setupSearchTestData(client, projectId); const result = await client.callToolAndParse('search_knowledge', { project_id: projectId, query: 'JWT', }); assertSuccess(result); assertEqual(result.total_documents, 1); const results = result.results as any[]; assertEqual(results[0].document, 'api-development.md'); // Should find match in Authentication chapter const authChapter = (results[0].chapters as any[]).find( (ch: any) => ch.title === 'Authentication' ); if (!authChapter) { throw new Error('Did not find match in Authentication chapter'); } assertEqual(authChapter.matches, 1); }); await runner.runTest('search_knowledge: No matches found', async () => { const projectId = generateTestProjectId('search-nomatch'); await setupSearchTestData(client, projectId); const result = await client.callToolAndParse('search_knowledge', { project_id: projectId, query: 'nonexistent xyz123 impossible', }); assertSuccess(result); assertEqual(result.total_documents, 0); assertEqual(result.total_matches, 0); }); await runner.runTest('search_knowledge: Search across multiple documents', async () => { const projectId = generateTestProjectId('search-cross'); await setupSearchTestData(client, projectId); // Add a document that mentions testing AND typescript await client.callToolAndParse('create_knowledge_file', { project_id: projectId, filename: 'typescript-testing', title: 'Testing TypeScript Applications', introduction: 'How to test TypeScript code effectively.', keywords: ['typescript', 'testing', 'jest'], chapters: [ { title: 'Setup', content: 'Configure Jest for TypeScript testing with proper type checking.', }, ], }); const result = await client.callToolAndParse('search_knowledge', { project_id: projectId, query: 'typescript testing', }); assertSuccess(result); // Should find at least 2 documents if ((result.total_documents as number) < 2) { throw new Error(`Expected at least 2 documents, got ${result.total_documents as number}`); } }); await runner.runTest('search_knowledge: Match context extraction', async () => { const projectId = generateTestProjectId('search-context'); // Create document with specific content to test context extraction await client.callToolAndParse('create_knowledge_file', { project_id: projectId, filename: 'context-test', title: 'Context Test Document', introduction: 'Testing context extraction in search.', keywords: ['context', 'test'], chapters: [ { title: 'Long Chapter', content: `This is the beginning of a long chapter. The word MARKER appears here in the middle of the content. And this is the end of the chapter content with more text.`, }, ], }); const result = await client.callToolAndParse('search_knowledge', { project_id: projectId, query: 'marker', }); assertSuccess(result); const results = result.results as any[]; // The transformed results don't include match context // Just verify the match was found assertEqual(results[0].document, 'context-test.md'); assertEqual(results[0].chapters[0].matches, 1); }); await runner.runTest('search_knowledge: Keywords vs content priority', async () => { const projectId = generateTestProjectId('search-priority'); // Create two documents - both with the search term in content await client.callToolAndParse('create_knowledge_file', { project_id: projectId, filename: 'keyword-match', title: 'Keyword Match Doc', introduction: 'This doc mentions priority in the introduction.', keywords: ['priority', 'search', 'keyword'], chapters: [{ title: 'Chapter', content: 'Regular content without the search term.' }], }); await client.callToolAndParse('create_knowledge_file', { project_id: projectId, filename: 'content-match', title: 'Content Match Doc', introduction: 'This doc has the search term in content.', keywords: ['other', 'terms'], chapters: [ { title: 'Chapter', content: 'This content mentions priority multiple times. Priority is important.', }, ], }); const result = await client.callToolAndParse('search_knowledge', { project_id: projectId, query: 'priority', }); assertSuccess(result); assertEqual(result.total_documents, 2); // Both should be found const fileNames = (result.results as any[]).map((r) => r.document as string); if (!fileNames.includes('keyword-match.md') || !fileNames.includes('content-match.md')) { throw new Error('Did not find both keyword and content matches'); } }); await runner.runTest('search_knowledge: Empty project', async () => { const projectId = generateTestProjectId('search-empty'); // Create project but no knowledge files await client.callToolAndParse('update_project_main', { project_id: projectId, content: '# Empty Project\n\nNo knowledge files here.', }); const result = await client.callToolAndParse('search_knowledge', { project_id: projectId, query: 'anything', }); assertSuccess(result); assertEqual(result.total_documents, 0); assertEqual(result.total_matches, 0); }); await runner.runTest('search_knowledge: Special characters in query', async () => { const projectId = generateTestProjectId('search-special'); await client.callToolAndParse('create_knowledge_file', { project_id: projectId, filename: 'special-chars', title: 'Special Characters Test', introduction: 'Testing special characters.', keywords: ['special'], chapters: [ { title: 'Symbols', content: 'Content with C++ and C# programming languages, also Node.js framework.', }, ], }); // Test various special character searches const searches = ['C++', 'C#', 'Node.js']; for (const query of searches) { const result = await client.callToolAndParse('search_knowledge', { project_id: projectId, query: query, }); assertSuccess(result); if (result.total_matches === 0) { throw new Error(`Failed to find "${query}" in content`); } } }); await runner.runTest('search_knowledge: Chapter summary in results', async () => { const projectId = generateTestProjectId('search-summary'); // Create document with chapter that has a summary await client.callToolAndParse('create_knowledge_file', { project_id: projectId, filename: 'summary-test', title: 'Summary Test', introduction: 'Testing chapter summaries.', keywords: ['summary'], chapters: [ { title: 'Chapter with Summary', content: 'This chapter has content.', }, ], }); // Update chapter with summary await client.callToolAndParse('update_chapter', { project_id: projectId, filename: 'summary-test.md', chapter_title: 'Chapter with Summary', new_content: 'This chapter has content about summaries.', new_summary: 'This is a custom chapter summary for search results.', }); const result = await client.callToolAndParse('search_knowledge', { project_id: projectId, query: 'summaries', }); assertSuccess(result); // The transformed results don't include chapter summaries // Just verify the match was found const results = result.results as any[]; assertEqual(results[0].document, 'summary-test.md'); assertEqual(results[0].chapters[0].title, 'Chapter with Summary'); assertEqual(results[0].chapters[0].matches, 1); }); } finally { await client.disconnect(); cleanupTestEnvironment(TEST_STORAGE_PATH); // Export detailed results for HTML reporting const detailedSuiteResult = runner.getDetailedSuiteResult(); if (process.env.EXPORT_DETAILED_RESULTS) { const { writeFileSync, mkdirSync } = await import('fs'); const path = await import('path'); const resultsDir = path.resolve('./test-results/detailed-suites'); mkdirSync(resultsDir, { recursive: true }); const resultPath = path.resolve(resultsDir, '05-search.test.json'); writeFileSync(resultPath, JSON.stringify(detailedSuiteResult, null, 2)); } runner.printSummary(); runner.exitWithResults(); } } // Run tests if this file is executed directly if (import.meta.url === `file://${process.argv[1]}`) { main().catch((error: Error) => { console.error('๐Ÿ’ฅ Test execution failed:', error); process.exit(1); }); }

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