Skip to main content
Glama
index.test.ts13 kB
import {describe, it, expect, beforeAll, afterAll} from 'vitest'; import {Client} from '@modelcontextprotocol/sdk/client/index.js'; import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js'; import type {TextContent} from '@modelcontextprotocol/sdk/types.js'; import {fileURLToPath} from 'url'; import {dirname, join} from 'path'; import {readFileSync} from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Read the expected version from package.json const projectPackageJson = JSON.parse( readFileSync(join(__dirname, '..', 'package.json'), 'utf-8') ) as {version: string}; const EXPECTED_VERSION = projectPackageJson.version; describe('PDFDancer MCP Server E2E Tests', () => { let client: Client; let transport: StdioClientTransport; beforeAll(async () => { // Spawn the server process const serverPath = join(__dirname, 'index.ts'); // Use npx for cross-platform compatibility (works on Windows and Unix) const isWindows = process.platform === 'win32'; const command = isWindows ? 'npx.cmd' : 'npx'; // Create transport and client transport = new StdioClientTransport({ command, args: ['tsx', serverPath], env: { ...process.env, PDFDANCER_DOCS_BASE_URL: process.env.PDFDANCER_DOCS_BASE_URL || 'https://docusaurus-cloudflare-search.michael-lahr-0b0.workers.dev/' } }); client = new Client( { name: 'pdfdancer-mcp-test-client', version: '1.0.0' }, { capabilities: {} } ); await client.connect(transport); }, 30000); afterAll(async () => { await client.close(); }); describe('Server Initialization', () => { it('should connect successfully', async () => { expect(client).toBeDefined(); }); it('should list all available tools', async () => { const tools = await client.listTools(); expect(tools.tools).toBeDefined(); expect(tools.tools.length).toBe(4); const toolNames = tools.tools.map(t => t.name); expect(toolNames).toContain('help'); expect(toolNames).toContain('version'); expect(toolNames).toContain('search-docs'); expect(toolNames).toContain('get-docs'); }); }); describe('help tool', () => { it('should return help documentation', async () => { const result = await client.callTool({ name: 'help', arguments: {} }); const content = result.content as TextContent[]; expect(content).toBeDefined(); expect(content.length).toBeGreaterThan(0); expect(content[0].type).toBe('text'); if (content[0].type === 'text') { expect(content[0].text).toContain('PDFDancer SDK Documentation'); expect(content[0].text).toContain('Available MCP Tools'); } expect(result.structuredContent).toBeDefined(); }); }); describe('version tool', () => { it('should return server version', async () => { const result = await client.callTool({ name: 'version', arguments: {} }); const content = result.content as TextContent[]; expect(content).toBeDefined(); expect(content.length).toBeGreaterThan(0); expect(content[0].type).toBe('text'); if (content[0].type === 'text') { expect(content[0].text).toContain('pdfdancer-mcp version:'); expect(content[0].text).toContain(EXPECTED_VERSION); } expect(result.structuredContent).toBeDefined(); expect(result.structuredContent).toHaveProperty('version'); }); }); describe('search-docs tool', () => { it('should search documentation with a simple query', async () => { const result = await client.callTool({ name: 'search-docs', arguments: { query: 'authentication' } }); const content = result.content as TextContent[]; expect(content).toBeDefined(); expect(content.length).toBeGreaterThan(0); expect(content[0].type).toBe('text'); if (content[0].type === 'text') { // May fail due to network issues, so check for either success or fetch error const text = content[0].text; const hasResults = text.includes('result(s) for "authentication"'); const hasNetworkError = text.includes('fetch failed') || text.includes('Request to'); expect(hasResults || hasNetworkError).toBe(true); } // Only check structured content if API call succeeded if (result.structuredContent) { const structured = result.structuredContent as { results: unknown[]; total: number; query: string; took: number; }; expect(structured).toHaveProperty('results'); expect(structured).toHaveProperty('total'); expect(structured).toHaveProperty('query'); expect(structured.query).toBe('authentication'); } }); it('should respect maxResults parameter', async () => { const result = await client.callTool({ name: 'search-docs', arguments: { query: 'PDF', maxResults: 3 } }); // Only check if API call succeeded if (result.structuredContent) { const structured = result.structuredContent as { results: unknown[]; }; expect(structured.results.length).toBeLessThanOrEqual(3); } else { // Network error is acceptable in tests const content = result.content as TextContent[]; expect(content[0].type).toBe('text'); } }); it('should handle queries with no results', async () => { const result = await client.callTool({ name: 'search-docs', arguments: { query: 'xyznonexistentterm123456' } }); const content = result.content as TextContent[]; expect(content[0].type).toBe('text'); if (content[0].type === 'text') { const text = content[0].text; const hasNoMatches = text.includes('No matches'); const hasNetworkError = text.includes('fetch failed') || text.includes('Request to'); expect(hasNoMatches || hasNetworkError).toBe(true); } }); it('should handle complex Lunr.js search syntax', async () => { const result = await client.callTool({ name: 'search-docs', arguments: { query: '+Java +images' } }); // Only check if API call succeeded if (result.structuredContent) { const structured = result.structuredContent as { query: string; }; expect(structured.query).toBe('+Java +images'); } else { // Network error is acceptable const content = result.content as TextContent[]; expect(content[0].type).toBe('text'); } }); }); describe('get-docs tool', () => { it('should retrieve documentation for a valid route', async () => { // Test with a known route that should exist const result = await client.callTool({ name: 'get-docs', arguments: { route: '/docs/intro' } }); const content = result.content as TextContent[]; expect(content).toBeDefined(); expect(content.length).toBeGreaterThan(0); // Only check structured content if API call succeeded if (result.structuredContent) { const structured = result.structuredContent as { route: string; content: string; }; expect(structured).toHaveProperty('route'); expect(structured).toHaveProperty('content'); expect(structured.route).toBe('/docs/intro'); } else { // Network error is acceptable expect(content[0].type).toBe('text'); } }); it('should require route to start with /', async () => { const result = await client.callTool({ name: 'get-docs', arguments: { route: 'invalid-route' } }); // MCP SDK returns errors in structured format const content = result.content as TextContent[]; expect(result.isError).toBe(true); expect(content[0].type).toBe('text'); if (content[0].type === 'text') { expect(content[0].text).toContain('route must start with /'); } }); }); describe('Error Handling', () => { it('should handle invalid tool names gracefully', async () => { const result = await client.callTool({ name: 'nonexistent-tool', arguments: {} }); // MCP SDK returns errors in structured format const content = result.content as TextContent[]; expect(result.isError).toBe(true); expect(content[0].type).toBe('text'); if (content[0].type === 'text') { expect(content[0].text).toContain('nonexistent-tool'); expect(content[0].text).toContain('not found'); } }); it('should handle missing required arguments', async () => { const result = await client.callTool({ name: 'search-docs', arguments: {} }); // MCP SDK returns errors in structured format const content = result.content as TextContent[]; expect(result.isError).toBe(true); expect(content[0].type).toBe('text'); if (content[0].type === 'text') { expect(content[0].text).toContain('query'); expect(content[0].text).toContain('Required'); } }); it('should handle invalid argument types', async () => { const result = await client.callTool({ name: 'search-docs', arguments: { query: 'test', maxResults: 'invalid' as unknown as number } }); // MCP SDK returns errors in structured format const content = result.content as TextContent[]; expect(result.isError).toBe(true); expect(content[0].type).toBe('text'); if (content[0].type === 'text') { expect(content[0].text).toContain('maxResults'); expect(content[0].text).toContain('number'); } }); }); describe('API Integration', () => { it('should successfully connect to the PDFDancer docs API', async () => { const result = await client.callTool({ name: 'search-docs', arguments: { query: 'PDF', maxResults: 1 } }); // Only check if API call succeeded if (result.structuredContent) { const structured = result.structuredContent as { took: number; }; // Should have a took field indicating API responded expect(structured).toHaveProperty('took'); expect(typeof structured.took).toBe('number'); } else { // Network error is acceptable in tests const content = result.content as TextContent[]; expect(content[0].type).toBe('text'); if (content[0].type === 'text') { const text = content[0].text; const hasNetworkError = text.includes('fetch failed') || text.includes('Request to'); expect(hasNetworkError).toBe(true); } } }); }); });

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/MenschMachine/pdfdancer-mcp'

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