Skip to main content
Glama

Readwise MCP Server

by IAmAlexander
test-inspector.js12.3 kB
#!/usr/bin/env node import { spawn } from 'child_process'; import { setTimeout } from 'timers/promises'; import chalk from 'chalk'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; import { execSync } from 'child_process'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); class InspectorTestRunner { serverProcess; inspectorProcess; testCases; results = { passed: [], failed: [] }; constructor() { // Define test cases this.testCases = [ { name: 'List Tools', command: 'list tools', validate: (output) => { const tools = [ 'get_highlights', 'get_books', 'get_documents', 'search_highlights' ]; return tools.every(tool => output.includes(tool)); } }, { name: 'List Prompts', command: 'list prompts', validate: (output) => { const prompts = [ 'readwise_highlight', 'readwise_search' ]; return prompts.every(prompt => output.includes(prompt)); } }, { name: 'Get Books', command: 'tool get_books --parameters {"page":1,"page_size":5}', validate: (output) => { try { const response = JSON.parse(output); return Array.isArray(response.books) && response.books.length <= 5; } catch { return false; } } }, { name: 'Get Highlights', command: 'tool get_highlights --parameters {"page":1,"page_size":5}', validate: (output) => { try { const response = JSON.parse(output); return Array.isArray(response.highlights) && response.highlights.length <= 5; } catch { return false; } } }, { name: 'Search Highlights', command: 'tool search_highlights --parameters {"query":"test"}', validate: (output) => { try { const response = JSON.parse(output); return Array.isArray(response.results); } catch { return false; } } }, { name: 'Get Documents', command: 'tool get_documents --parameters {"page":1,"page_size":5}', validate: (output) => { try { const response = JSON.parse(output); return Array.isArray(response.documents) && response.documents.length <= 5; } catch { return false; } } }, { name: 'Readwise Highlight Prompt', command: 'prompt readwise_highlight --parameters {"highlight_id":1}', validate: (output) => { return output.includes('highlight') || output.includes('error'); } }, { name: 'Readwise Search Prompt', command: 'prompt readwise_search --parameters {"query":"test"}', validate: (output) => { return output.includes('results') || output.includes('error'); } } ]; } async startServer() { return new Promise((resolve, reject) => { console.log('Starting server process...'); const serverPath = join(__dirname, '..', 'dist', 'index.js'); this.serverProcess = spawn('node', [serverPath], { env: { ...process.env, TRANSPORT: 'stdio' } }); let serverStarted = false; let serverError = ''; // Wait for server to be ready const checkStartup = (data) => { const message = data.toString(); console.log('Server output:', message.trim()); if (message.includes('Server started on port')) { serverStarted = true; resolve(); } }; this.serverProcess.stdout?.on('data', checkStartup); this.serverProcess.stderr?.on('data', (data) => { const message = data.toString(); console.error(chalk.red(`Server error: ${message.trim()}`)); serverError += message; checkStartup(data); }); this.serverProcess.on('error', (error) => { console.error(chalk.red('Server process error:', error)); reject(error); }); this.serverProcess.on('exit', (code) => { if (!serverStarted) { console.error(chalk.red(`Server process exited with code ${code}`)); reject(new Error(`Server failed to start: ${serverError}`)); } }); // Set a timeout setTimeout(10000).then(() => { if (!serverStarted) { reject(new Error('Server startup timeout')); } }); }); } async startInspector() { return new Promise((resolve, reject) => { console.log('Starting inspector process...'); // Kill any existing inspector processes try { execSync('pkill -f "@modelcontextprotocol/inspector"'); console.log('Killed existing inspector processes'); } catch (error) { // Ignore errors if no processes were found } // Wait a moment for ports to be released setTimeout(1000).then(() => { // Start the inspector with environment variables this.inspectorProcess = spawn('npx', ['@modelcontextprotocol/inspector'], { stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, PORT: '3003', CLIENT_PORT: '5175' } }); let inspectorStarted = false; let inspectorError = ''; // Wait for inspector to be ready const checkStartup = (data) => { const message = data.toString(); console.log('Inspector output:', message.trim()); if (message.includes('Connected') || message.includes('Starting inspector...')) { inspectorStarted = true; resolve(); } }; this.inspectorProcess.stdout?.on('data', checkStartup); this.inspectorProcess.stderr?.on('data', (data) => { const message = data.toString(); console.error(chalk.red(`Inspector error: ${message.trim()}`)); inspectorError += message; checkStartup(data); }); this.inspectorProcess.on('error', (error) => { console.error(chalk.red('Inspector process error:', error)); reject(error); }); this.inspectorProcess.on('exit', (code) => { if (!inspectorStarted) { console.error(chalk.red(`Inspector process exited with code ${code}`)); reject(new Error(`Inspector failed to start: ${inspectorError}`)); } }); // Set a timeout setTimeout(10000).then(() => { if (!inspectorStarted) { reject(new Error('Inspector startup timeout')); } }); }); }); } async runCommand(command) { return new Promise((resolve, reject) => { if (!this.inspectorProcess?.stdin || !this.inspectorProcess?.stdout) { reject(new Error('Inspector not running')); return; } let output = ''; const outputHandler = (data) => { output += data.toString(); if (output.includes('\n> ')) { this.inspectorProcess?.stdout?.removeListener('data', outputHandler); resolve(output.trim()); } }; this.inspectorProcess.stdout.on('data', outputHandler); this.inspectorProcess.stdin.write(command + '\n'); // Set a timeout setTimeout(10000).then(() => { this.inspectorProcess?.stdout?.removeListener('data', outputHandler); reject(new Error('Command timeout')); }); }); } async cleanup() { console.log('Cleaning up processes...'); if (this.inspectorProcess) { this.inspectorProcess.kill(); console.log('Killed inspector process'); } if (this.serverProcess) { this.serverProcess.kill(); console.log('Killed server process'); } // Kill any remaining inspector processes try { execSync('pkill -f "@modelcontextprotocol/inspector"'); console.log('Killed any remaining inspector processes'); } catch (error) { // Ignore errors if no processes were found } } logResult(testName, passed, output) { const status = passed ? chalk.green('✓ PASS') : chalk.red('✗ FAIL'); console.log(`${status} ${testName}`); if (!passed && output) { console.log(chalk.gray(output)); } if (passed) { this.results.passed.push(testName); } else { this.results.failed.push(testName); } } async runTests() { try { console.log(chalk.blue('Starting server...')); await this.startServer(); console.log(chalk.blue('Starting inspector...')); await this.startInspector(); console.log(chalk.blue('\nRunning tests...\n')); for (const test of this.testCases) { try { const output = await this.runCommand(test.command); const passed = test.validate ? test.validate(output) : test.expectedOutput ? (test.expectedOutput instanceof RegExp ? test.expectedOutput.test(output) : output.includes(test.expectedOutput)) : true; this.logResult(test.name, passed, passed ? undefined : output); } catch (error) { this.logResult(test.name, false, String(error)); } } console.log('\nTest Summary:'); console.log(chalk.green(`Passed: ${this.results.passed.length}`)); console.log(chalk.red(`Failed: ${this.results.failed.length}`)); return this.results.failed.length === 0; } catch (error) { console.error(chalk.red('\nTest run failed:'), error); return false; } finally { await this.cleanup(); } } } // Run tests const runner = new InspectorTestRunner(); runner.runTests() .then(success => { process.exit(success ? 0 : 1); }) .catch(error => { console.error(chalk.red('Fatal error:'), error); process.exit(1); }); //# sourceMappingURL=test-inspector.js.map

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/IAmAlexander/readwise-mcp'

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