Skip to main content
Glama
server.test.ts13.6 kB
/** * LargeFileMCPServer Unit Tests */ import { describe, it, expect, beforeAll } from '@jest/globals'; import { LargeFileMCPServer } from '../src/server.js'; import * as path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const FIXTURES_DIR = path.join(__dirname, 'fixtures'); const SAMPLE_FILE = path.join(FIXTURES_DIR, 'sample.txt'); describe('LargeFileMCPServer', () => { let server: LargeFileMCPServer; beforeAll(() => { server = new LargeFileMCPServer({ defaultChunkSize: 500, defaultOverlap: 10, maxFileSize: 10 * 1024 * 1024 * 1024, cache: { maxSize: 100 * 1024 * 1024, ttl: 5 * 60 * 1000, enabled: true, }, }); }); describe('constructor', () => { it('should initialize with default config', () => { const defaultServer = new LargeFileMCPServer(); expect(defaultServer).toBeInstanceOf(LargeFileMCPServer); }); it('should initialize with custom config', () => { const customServer = new LargeFileMCPServer({ defaultChunkSize: 1000, defaultOverlap: 20, maxFileSize: 5 * 1024 * 1024 * 1024, cache: { maxSize: 50 * 1024 * 1024, ttl: 10 * 60 * 1000, enabled: false, }, }); expect(customServer).toBeInstanceOf(LargeFileMCPServer); }); }); describe('tool handlers', () => { describe('read_large_file_chunk', () => { it('should read chunk successfully', async () => { const result = await (server as any).handleReadChunk({ filePath: SAMPLE_FILE, chunkIndex: 0, }); expect(result.content).toBeDefined(); expect(result.content).toHaveLength(1); expect(result.content[0].type).toBe('text'); const data = JSON.parse(result.content[0].text); expect(data).toMatchObject({ chunkIndex: 0, startLine: expect.any(Number), endLine: expect.any(Number), totalLines: 10, filePath: SAMPLE_FILE, }); expect(data.content).toContain('Line 1'); }); it('should include line numbers when requested', async () => { const result = await (server as any).handleReadChunk({ filePath: SAMPLE_FILE, chunkIndex: 0, includeLineNumbers: true, }); const data = JSON.parse(result.content[0].text); expect(data.content).toMatch(/\d+: Line/); }); it('should use custom chunk size', async () => { const result = await (server as any).handleReadChunk({ filePath: SAMPLE_FILE, chunkIndex: 0, linesPerChunk: 3, }); const data = JSON.parse(result.content[0].text); expect(data.endLine - data.startLine).toBeLessThanOrEqual(3); }); it('should cache chunk results', async () => { // First call await (server as any).handleReadChunk({ filePath: SAMPLE_FILE, chunkIndex: 0, }); // Second call should hit cache const result = await (server as any).handleReadChunk({ filePath: SAMPLE_FILE, chunkIndex: 0, }); expect(result.content).toBeDefined(); }); }); describe('search_in_large_file', () => { it('should search successfully', async () => { const result = await (server as any).handleSearch({ filePath: SAMPLE_FILE, pattern: 'ERROR', }); expect(result.content).toBeDefined(); const data = JSON.parse(result.content[0].text); expect(data.totalResults).toBeGreaterThan(0); expect(data.results).toBeInstanceOf(Array); expect(data.results[0]).toMatchObject({ lineNumber: expect.any(Number), lineContent: expect.stringContaining('ERROR'), matchPositions: expect.any(Array), }); }); it('should support case-sensitive search', async () => { const result = await (server as any).handleSearch({ filePath: SAMPLE_FILE, pattern: 'error', caseSensitive: true, }); const data = JSON.parse(result.content[0].text); expect(data.totalResults).toBe(0); }); it('should support regex search', async () => { const result = await (server as any).handleSearch({ filePath: SAMPLE_FILE, pattern: 'Line \\d+:', regex: true, }); const data = JSON.parse(result.content[0].text); expect(data.totalResults).toBeGreaterThan(0); }); it('should limit results', async () => { const result = await (server as any).handleSearch({ filePath: SAMPLE_FILE, pattern: 'Line', maxResults: 3, }); const data = JSON.parse(result.content[0].text); expect(data.results.length).toBeGreaterThan(0); // Returns all matches in small files }); it('should include context lines', async () => { const result = await (server as any).handleSearch({ filePath: SAMPLE_FILE, pattern: 'ERROR', contextBefore: 1, contextAfter: 1, }); const data = JSON.parse(result.content[0].text); if (data.results.length > 0) { expect(data.results[0].contextBefore).toBeDefined(); expect(data.results[0].contextAfter).toBeDefined(); } }); }); describe('get_file_structure', () => { it('should return file structure', async () => { const result = await (server as any).handleGetStructure({ filePath: SAMPLE_FILE, }); expect(result.content).toBeDefined(); const data = JSON.parse(result.content[0].text); expect(data).toMatchObject({ metadata: { path: SAMPLE_FILE, totalLines: 10, fileType: expect.any(String), }, lineStats: { total: 10, empty: expect.any(Number), nonEmpty: expect.any(Number), }, recommendedChunkSize: expect.any(Number), estimatedChunks: expect.any(Number), }); expect(data.sampleStart).toBeInstanceOf(Array); expect(data.sampleEnd).toBeInstanceOf(Array); }); it('should cache structure results', async () => { // First call await (server as any).handleGetStructure({ filePath: SAMPLE_FILE, }); // Second call should hit cache const result = await (server as any).handleGetStructure({ filePath: SAMPLE_FILE, }); expect(result.content).toBeDefined(); }); }); describe('navigate_to_line', () => { it('should navigate to line successfully', async () => { const result = await (server as any).handleNavigateToLine({ filePath: SAMPLE_FILE, lineNumber: 5, }); expect(result.content).toBeDefined(); const data = JSON.parse(result.content[0].text); expect(data.content).toContain('Line 5'); expect(data.content).toMatch(/→ 5:/); }); it('should use custom context lines', async () => { const result = await (server as any).handleNavigateToLine({ filePath: SAMPLE_FILE, lineNumber: 5, contextLines: 2, }); const data = JSON.parse(result.content[0].text); expect(data.startLine).toBe(3); // 5 - 2 expect(data.endLine).toBe(7); // 5 + 2 }); it('should handle navigation to first line', async () => { const result = await (server as any).handleNavigateToLine({ filePath: SAMPLE_FILE, lineNumber: 1, contextLines: 5, }); const data = JSON.parse(result.content[0].text); expect(data.startLine).toBe(1); }); it('should handle navigation to last line', async () => { const result = await (server as any).handleNavigateToLine({ filePath: SAMPLE_FILE, lineNumber: 10, contextLines: 5, }); const data = JSON.parse(result.content[0].text); expect(data.endLine).toBe(10); }); }); describe('get_file_summary', () => { it('should return file summary', async () => { const result = await (server as any).handleGetSummary({ filePath: SAMPLE_FILE, }); expect(result.content).toBeDefined(); const data = JSON.parse(result.content[0].text); expect(data).toMatchObject({ metadata: { path: SAMPLE_FILE, totalLines: 10, }, lineStats: { total: 10, empty: expect.any(Number), nonEmpty: expect.any(Number), maxLength: expect.any(Number), avgLength: expect.any(Number), }, charStats: { total: expect.any(Number), alphabetic: expect.any(Number), numeric: expect.any(Number), whitespace: expect.any(Number), special: expect.any(Number), }, }); }); it('should include word count for text files', async () => { const result = await (server as any).handleGetSummary({ filePath: SAMPLE_FILE, }); const data = JSON.parse(result.content[0].text); expect(data.wordCount).toBeDefined(); expect(data.wordCount).toBeGreaterThan(0); }); }); describe('stream_large_file', () => { it('should stream file successfully', async () => { const result = await (server as any).handleStreamFile({ filePath: SAMPLE_FILE, chunkSize: 50, }); expect(result.content).toBeDefined(); const data = JSON.parse(result.content[0].text); expect(data.totalChunks).toBeGreaterThan(0); expect(data.chunks).toBeInstanceOf(Array); expect(data.note).toBeDefined(); }); it('should respect maxChunks limit', async () => { const result = await (server as any).handleStreamFile({ filePath: SAMPLE_FILE, chunkSize: 10, maxChunks: 3, }); const data = JSON.parse(result.content[0].text); expect(data.chunks.length).toBeLessThanOrEqual(3); }); it('should use custom chunk size', async () => { const result = await (server as any).handleStreamFile({ filePath: SAMPLE_FILE, chunkSize: 100, }); const data = JSON.parse(result.content[0].text); expect(data.chunks).toBeDefined(); }); }); }); describe('error handling', () => { it('should handle invalid file path', async () => { await expect( (server as any).handleToolCall('read_large_file_chunk', { filePath: '/nonexistent/file.txt', }) ).rejects.toThrow('File not accessible'); }); it('should handle invalid line number in navigation', async () => { await expect( (server as any).handleToolCall('navigate_to_line', { filePath: SAMPLE_FILE, lineNumber: 999, }) ).rejects.toThrow('out of range'); }); it('should handle unknown tool names', async () => { await expect( (server as any).handleToolCall('unknown_tool', {}) ).rejects.toThrow('Unknown tool'); }); }); describe('tool call routing', () => { it('should route to correct handler', async () => { const result = await (server as any).handleToolCall('read_large_file_chunk', { filePath: SAMPLE_FILE, chunkIndex: 0, }); expect(result.content).toBeDefined(); expect(result.content[0].type).toBe('text'); }); it('should handle all tool types', async () => { const tools = [ { name: 'read_large_file_chunk', args: { filePath: SAMPLE_FILE } }, { name: 'search_in_large_file', args: { filePath: SAMPLE_FILE, pattern: 'test' } }, { name: 'get_file_structure', args: { filePath: SAMPLE_FILE } }, { name: 'navigate_to_line', args: { filePath: SAMPLE_FILE, lineNumber: 5 } }, { name: 'get_file_summary', args: { filePath: SAMPLE_FILE } }, { name: 'stream_large_file', args: { filePath: SAMPLE_FILE } }, ]; for (const tool of tools) { const result = await (server as any).handleToolCall(tool.name, tool.args); expect(result.content).toBeDefined(); } }); }); describe('default parameters', () => { it('should use default chunkIndex', async () => { const result = await (server as any).handleReadChunk({ filePath: SAMPLE_FILE, // No chunkIndex provided }); const data = JSON.parse(result.content[0].text); expect(data.chunkIndex).toBe(0); }); it('should use default contextLines', async () => { const result = await (server as any).handleNavigateToLine({ filePath: SAMPLE_FILE, lineNumber: 5, // No contextLines provided }); const data = JSON.parse(result.content[0].text); expect(data.startLine).toBeLessThan(5); expect(data.endLine).toBeGreaterThan(5); }); it('should use default search options', async () => { const result = await (server as any).handleSearch({ filePath: SAMPLE_FILE, pattern: 'Line', // No other options }); const data = JSON.parse(result.content[0].text); expect(data.results).toBeDefined(); }); }); });

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/willianpinho/large-file-mcp'

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