Skip to main content
Glama

PDF Reader MCP Server

by sylphxltd
loader.test.ts6.87 kB
import fs from 'node:fs/promises'; import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs'; import { describe, expect, it, vi } from 'vitest'; import { loadPdfDocument } from '../../src/pdf/loader.js'; import * as pathUtils from '../../src/utils/pathUtils.js'; vi.mock('node:fs/promises'); vi.mock('pdfjs-dist/legacy/build/pdf.mjs'); vi.mock('../../src/utils/pathUtils.js', async () => { const actual = await vi.importActual('../../src/utils/pathUtils.js'); return { ...actual, resolvePath: vi.fn(), }; }); describe('loader', () => { describe('loadPdfDocument', () => { it('should load PDF from local file path', async () => { const mockBuffer = Buffer.from('fake pdf content'); const mockDocument = { numPages: 5 }; vi.mocked(pathUtils.resolvePath).mockReturnValue('/safe/path/test.pdf'); vi.mocked(fs.readFile).mockResolvedValue(mockBuffer); vi.mocked(pdfjsLib.getDocument).mockReturnValue({ promise: Promise.resolve(mockDocument as unknown as pdfjsLib.PDFDocumentProxy), } as pdfjsLib.PDFDocumentLoadingTask); const result = await loadPdfDocument({ path: 'test.pdf' }, 'test.pdf'); expect(result).toBe(mockDocument); expect(pathUtils.resolvePath).toHaveBeenCalledWith('test.pdf'); expect(fs.readFile).toHaveBeenCalledWith('/safe/path/test.pdf'); }); it('should load PDF from URL', async () => { const mockDocument = { numPages: 3 }; vi.mocked(pdfjsLib.getDocument).mockReturnValue({ promise: Promise.resolve(mockDocument as unknown as pdfjsLib.PDFDocumentProxy), } as pdfjsLib.PDFDocumentLoadingTask); const result = await loadPdfDocument( { url: 'https://example.com/test.pdf' }, 'https://example.com/test.pdf' ); expect(result).toBe(mockDocument); expect(pdfjsLib.getDocument).toHaveBeenCalledWith({ url: 'https://example.com/test.pdf', }); }); it('should throw McpError when neither path nor url provided', async () => { await expect(loadPdfDocument({}, 'unknown')).rejects.toThrow(McpError); await expect(loadPdfDocument({}, 'unknown')).rejects.toThrow( "Source unknown missing 'path' or 'url'." ); }); it('should handle file not found error (ENOENT)', async () => { const enoentError = Object.assign(new Error('File not found'), { code: 'ENOENT' }); vi.mocked(pathUtils.resolvePath).mockReturnValue('/safe/path/missing.pdf'); vi.mocked(fs.readFile).mockRejectedValue(enoentError); await expect(loadPdfDocument({ path: 'missing.pdf' }, 'missing.pdf')).rejects.toThrow( McpError ); await expect(loadPdfDocument({ path: 'missing.pdf' }, 'missing.pdf')).rejects.toThrow( "File not found at 'missing.pdf'." ); }); it('should handle generic file read errors', async () => { vi.mocked(pathUtils.resolvePath).mockReturnValue('/safe/path/error.pdf'); vi.mocked(fs.readFile).mockRejectedValue(new Error('Permission denied')); await expect(loadPdfDocument({ path: 'error.pdf' }, 'error.pdf')).rejects.toThrow(McpError); await expect(loadPdfDocument({ path: 'error.pdf' }, 'error.pdf')).rejects.toThrow( 'Failed to prepare PDF source error.pdf. Reason: Permission denied' ); }); it('should handle non-Error exceptions during file read', async () => { vi.mocked(pathUtils.resolvePath).mockReturnValue('/safe/path/test.pdf'); vi.mocked(fs.readFile).mockRejectedValue('String error'); await expect(loadPdfDocument({ path: 'test.pdf' }, 'test.pdf')).rejects.toThrow( 'Failed to prepare PDF source test.pdf. Reason: String error' ); }); it('should handle PDF.js loading errors', async () => { const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); const mockBuffer = Buffer.from('fake pdf'); vi.mocked(pathUtils.resolvePath).mockReturnValue('/safe/path/bad.pdf'); vi.mocked(fs.readFile).mockResolvedValue(mockBuffer); vi.mocked(pdfjsLib.getDocument).mockReturnValue({ promise: Promise.reject(new Error('Invalid PDF')), } as pdfjsLib.PDFDocumentLoadingTask); await expect(loadPdfDocument({ path: 'bad.pdf' }, 'bad.pdf')).rejects.toThrow(McpError); await expect(loadPdfDocument({ path: 'bad.pdf' }, 'bad.pdf')).rejects.toThrow( 'Failed to load PDF document from bad.pdf. Reason: Invalid PDF' ); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('PDF.js loading error for bad.pdf'), expect.any(Error) ); consoleErrorSpy.mockRestore(); }); it('should handle non-Error PDF.js loading exceptions', async () => { const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); vi.mocked(pdfjsLib.getDocument).mockReturnValue({ promise: Promise.reject('Unknown error'), } as pdfjsLib.PDFDocumentLoadingTask); await expect( loadPdfDocument({ url: 'https://example.com/bad.pdf' }, 'https://example.com/bad.pdf') ).rejects.toThrow('Failed to load PDF document from https://example.com/bad.pdf'); consoleErrorSpy.mockRestore(); }); it('should propagate McpError from resolvePath', async () => { const mcpError = new McpError(ErrorCode.InvalidRequest, 'Path validation failed'); vi.mocked(pathUtils.resolvePath).mockImplementation(() => { throw mcpError; }); await expect(loadPdfDocument({ path: 'test.pdf' }, 'test.pdf')).rejects.toThrow(mcpError); }); it('should use fallback message when PDF.js error message is empty', async () => { const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); vi.mocked(pdfjsLib.getDocument).mockReturnValue({ promise: Promise.reject(new Error('')), } as pdfjsLib.PDFDocumentLoadingTask); await expect( loadPdfDocument({ url: 'https://example.com/bad.pdf' }, 'https://example.com/bad.pdf') ).rejects.toThrow('Unknown loading error'); consoleErrorSpy.mockRestore(); }); it('should handle non-Error exception during file read with cause undefined', async () => { vi.mocked(pathUtils.resolvePath).mockReturnValue('/safe/path/test.pdf'); const nonErrorObject = { code: 'SOME_ERROR' }; vi.mocked(fs.readFile).mockRejectedValue(nonErrorObject); try { await loadPdfDocument({ path: 'test.pdf' }, 'test.pdf'); expect.fail('Should have thrown'); } catch (error) { expect(error).toBeInstanceOf(McpError); // Verify that cause is undefined when error is not an Error instance expect((error as McpError).cause).toBeUndefined(); } }); }); });

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/sylphxltd/pdf-reader-mcp'

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