Skip to main content
Glama
install.package.test.ts10.1 kB
import {describe, it, expect, beforeAll, afterAll} from 'vitest'; import {execSync} from 'child_process'; import {mkdtempSync, rmSync, writeFileSync, existsSync, readdirSync, readFileSync} from 'fs'; import {tmpdir} from 'os'; import {join, resolve} from 'path'; import {Client} from '@modelcontextprotocol/sdk/client/index.js'; import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js'; // Read the expected version from package.json const projectPackageJson = JSON.parse( readFileSync(join(process.cwd(), 'package.json'), 'utf-8') ) as {version: string}; const EXPECTED_VERSION = projectPackageJson.version; // Check if mcptools is available function getMcptoolsPath(): string | null { try { // Use platform-specific command to find mcptools if (process.platform === 'win32') { // On Windows, where.exe returns Windows native paths return execSync('where.exe mcptools', {encoding: 'utf-8'}).split('\n')[0].trim(); } else { return execSync('which mcptools', {encoding: 'utf-8'}).trim(); } } catch { try { const gopath = execSync('go env GOPATH', {encoding: 'utf-8'}).trim(); const mcptoolsPath = join(gopath, 'bin', process.platform === 'win32' ? 'mcptools.exe' : 'mcptools'); if (existsSync(mcptoolsPath)) { return mcptoolsPath; } } catch { // Go not installed } return null; } } describe('NPM Package Installation Tests', () => { let testDir: string; let tarballPath: string; let projectRoot: string; beforeAll(() => { projectRoot = process.cwd(); // Create a temporary directory for testing testDir = mkdtempSync(join(tmpdir(), 'pdfdancer-mcp-test-')); // Build the package console.log('Building package...'); execSync('npm run build', { cwd: projectRoot, stdio: 'inherit' }); // Pack the package console.log('Packing package...'); const packOutput = execSync('npm pack', { cwd: projectRoot, encoding: 'utf-8' }); // Get the tarball filename from output and create absolute path const tarballFilename = packOutput.trim().split('\n').pop() || ''; tarballPath = resolve(projectRoot, tarballFilename); console.log(`Created tarball: ${tarballPath}`); }, 120000); // 2 minute timeout for build afterAll(async () => { // Cleanup test directory with retry for Windows file locking issues if (testDir) { let retries = 5; let lastError: Error | null = null; while (retries > 0) { try { rmSync(testDir, {recursive: true, force: true}); break; } catch (error) { lastError = error as Error; retries--; if (retries > 0) { // Wait a bit before retrying (Windows needs time to release file handles) await new Promise(resolve => setTimeout(resolve, 500)); } } } if (retries === 0 && lastError) { console.warn(`Failed to cleanup test directory after 5 retries: ${lastError.message}`); } } }); it('should successfully build the package', () => { expect(tarballPath).toBeTruthy(); expect(tarballPath).toMatch(/\.tgz$/); }); it('should be installable from tarball', () => { console.log(`Installing ${tarballPath} in ${testDir}...`); // Initialize a package.json in test directory writeFileSync( join(testDir, 'package.json'), JSON.stringify({name: 'test-install', version: '1.0.0'}, null, 2) ); // Install the package from tarball using absolute path execSync(`npm install "${tarballPath}"`, { cwd: testDir, stdio: 'inherit' }); // Verify installation succeeded const packageJson = JSON.parse( execSync('npm list --json', { cwd: testDir, encoding: 'utf-8' }) ); expect(packageJson.dependencies).toHaveProperty('@pdfdancer/pdfdancer-mcp'); }, 60000); it('should have executable binary in node_modules/.bin', () => { const binPath = join(testDir, 'node_modules', '.bin', 'pdfdancer-mcp'); // Check if binary exists (on Unix) or .cmd exists (on Windows) const existsUnix = existsSync(binPath); const existsWin = existsSync(`${binPath}.cmd`); expect(existsUnix || existsWin).toBe(true); }); it('should run via npx and respond to MCP protocol', async () => { const binPath = join(testDir, 'node_modules', '@pdfdancer', 'pdfdancer-mcp', 'dist', 'index.js'); // Create MCP client const transport = new StdioClientTransport({ command: 'node', args: [binPath], env: { ...process.env, PDFDANCER_DOCS_BASE_URL: 'https://docusaurus-cloudflare-search.michael-lahr-0b0.workers.dev/' } }); const client = new Client( { name: 'package-test-client', version: '1.0.0' }, { capabilities: {} } ); try { await client.connect(transport); // Test that we can list tools const tools = await client.listTools(); expect(tools.tools).toBeDefined(); expect(tools.tools.length).toBe(4); // Test version tool const result = await client.callTool({ name: 'version', arguments: {} }); expect(result.content).toBeDefined(); expect(Array.isArray(result.content)).toBe(true); await client.close(); // Give the process time to fully close on Windows await new Promise(resolve => setTimeout(resolve, 100)); } catch (error) { await client.close(); // Give the process time to fully close on Windows await new Promise(resolve => setTimeout(resolve, 100)); throw error; } }, 30000); it('should have correct package.json metadata', () => { const pkgJsonPath = join(testDir, 'node_modules', '@pdfdancer', 'pdfdancer-mcp', 'package.json'); const pkgJsonContent = execSync(`cat "${pkgJsonPath}"`, {encoding: 'utf-8'}); const installedPkgJson = JSON.parse(pkgJsonContent); // Verify package name expect(installedPkgJson.name).toBe('@pdfdancer/pdfdancer-mcp'); // Verify version expect(installedPkgJson.version).toBe(EXPECTED_VERSION); // Verify bin entry exists expect(installedPkgJson.bin).toBeDefined(); expect(installedPkgJson.bin).toBe('./dist/index.js'); // Verify main entry expect(installedPkgJson.main).toBe('./dist/index.js'); // Verify type is module expect(installedPkgJson.type).toBe('module'); // Verify engines expect(installedPkgJson.engines).toBeDefined(); expect(installedPkgJson.engines.node).toBe('>=18.17'); }); it('should only include dist folder in published package', () => { const installedDir = join(testDir, 'node_modules', '@pdfdancer', 'pdfdancer-mcp'); const installedFiles = readdirSync(installedDir); // Should have dist folder expect(installedFiles).toContain('dist'); // Should have package.json expect(installedFiles).toContain('package.json'); // Should NOT have src folder (not in files array) expect(installedFiles).not.toContain('src'); // Should NOT have node_modules expect(installedFiles).not.toContain('node_modules'); }); it('should work with mcptools CLI (third-party MCP client)', () => { const mcptoolsPath = getMcptoolsPath(); expect(mcptoolsPath).toBeTruthy(); // Fail if mcptools is not found // Use npx --no-install to use the already-installed package from the earlier test // This avoids the timeout from npx trying to extract and install the tarball const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx'; // Test version command const versionResult = execSync( `${mcptoolsPath} call version "${npxCommand}" --no-install @pdfdancer/pdfdancer-mcp`, { encoding: 'utf-8', cwd: testDir } ); expect(versionResult).toContain('pdfdancer-mcp version:'); expect(versionResult).toContain(EXPECTED_VERSION); // Test search-docs command with JSON output // Note: Windows cmd.exe doesn't recognize single quotes as string delimiters const jsonParam = process.platform === 'win32' ? `"{\\\"query\\\":\\\"page\\\"}"` // Windows: use double quotes with escaping : `'{"query":"page"}'`; // Unix: use single quotes const searchResult = execSync( `${mcptoolsPath} call search-docs -p ${jsonParam} "${npxCommand}" --no-install @pdfdancer/pdfdancer-mcp`, { encoding: 'utf-8', cwd: testDir } ); // Should contain search results or network error const hasResults = searchResult.includes('result(s) for "page"'); const hasNetworkError = searchResult.includes('fetch failed') || searchResult.includes('Request to'); expect(hasResults || hasNetworkError).toBe(true); // If results exist, verify structure if (hasResults) { expect(searchResult).toContain('Raw search response'); expect(searchResult).toContain('"query": "page"'); } }, 60000); });

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