Skip to main content
Glama

Readwise MCP Server

by IAmAlexander
index.ts6.5 kB
#!/usr/bin/env node // Runtime imports - need .js extension import { ReadwiseMCPServer } from './server.js'; import { Server as MCPServer } from '@modelcontextprotocol/sdk/server/index.js'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'; // Type-only imports - no .js extension import type { TransportType } from './types/index.js'; import type { ValidationResult } from './types/validation.js'; // Local imports with implementation - need .js extension import { ReadwiseClient } from './api/client.js'; import { ReadwiseAPI } from './api/readwise-api.js'; import { SafeLogger } from './utils/safe-logger.js'; import { LogLevel } from './utils/logger-interface.js'; import { getConfig, saveConfig } from './utils/config.js'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import readline from 'readline'; import { stdin as input, stdout as output } from 'process'; /** * Main entry point for the Readwise MCP server */ async function main() { const argv = yargs(hideBin(process.argv)) .option('port', { alias: 'p', description: 'Port to listen on', type: 'number', default: process.env.PORT ? parseInt(process.env.PORT) : 3001 }) .option('transport', { alias: 't', description: 'Transport type (stdio or sse)', choices: ['stdio', 'sse'] as TransportType[], default: 'stdio' as TransportType }) .option('debug', { alias: 'd', description: 'Enable debug logging', type: 'boolean', default: false }) .option('api-key', { alias: 'k', description: 'Readwise API key', type: 'string' }) .option('setup', { alias: 's', description: 'Run setup wizard', type: 'boolean', default: false }) .help() .argv as { port: number; transport: TransportType; debug: boolean; 'api-key'?: string; setup: boolean; }; // Create logger const logger = new SafeLogger({ level: argv.debug ? LogLevel.DEBUG : LogLevel.INFO, transport: argv.transport === 'sse' ? console.log : console.error, timestamps: true, colors: true, }); logger.info('Starting Readwise MCP server'); logger.debug('Command line arguments:', argv as any); try { // Run setup wizard if requested if (argv.setup) { const apiKey = await runSetupWizard(); logger.info('Setup complete'); return; } // Load config logger.debug('Loading configuration...'); const config = getConfig(); logger.debug('Configuration loaded'); // Get API key from command-line args or config const apiKey = argv['api-key'] || config.readwiseApiKey; if (!apiKey) { logger.error('No API key provided. Please provide an API key using the --api-key flag or run the setup wizard with --setup'); process.exit(1); } logger.debug('API key validated'); logger.info('Initializing server...'); // Start the server const server = new ReadwiseMCPServer( apiKey, argv.port, logger, argv.transport ); logger.info('Starting server...'); await server.start(); logger.info('Server started successfully'); // Handle shutdown gracefully const shutdown = async () => { logger.info('Shutting down...'); await server.stop(); process.exit(0); }; process.on('SIGINT', shutdown); process.on('SIGTERM', shutdown); // Handle unhandled errors process.on('uncaughtException', (error) => { logger.error('Uncaught exception', error); shutdown(); }); process.on('unhandledRejection', (reason) => { logger.error('Unhandled promise rejection', reason as any); shutdown(); }); } catch (error) { logger.error('Error starting server', error as any); process.exit(1); } } /** * Run the setup wizard to configure the API key */ async function runSetupWizard(): Promise<string> { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const question = (prompt: string): Promise<string> => { return new Promise((resolve) => { rl.question(prompt, (answer) => { resolve(answer); }); }); }; console.log('Readwise MCP Server Setup Wizard'); console.log('-------------------------------'); console.log(''); console.log('This wizard will help you set up your Readwise MCP server.'); console.log('You will need your Readwise API key to continue.'); console.log(''); console.log('You can find your API key at https://readwise.io/access_token'); console.log(''); let apiKey = await question('Enter your Readwise API key: '); while (!apiKey) { console.log('API key is required.'); apiKey = await question('Enter your Readwise API key: '); } try { // Test the API key by making a request to the Readwise API console.log('Validating API key...'); const client = new ReadwiseClient({ apiKey }); const api = new ReadwiseAPI(client); try { // Attempt to fetch a single book to verify API key works await api.getBooks({ page: 1, page_size: 1 }); console.log('API key validated successfully.'); } catch (error) { if (error instanceof Error && error.message.includes('401')) { console.error('Error: Invalid API key. Please check your API key and try again.'); rl.close(); return runSetupWizard(); // Restart the setup wizard } console.warn('Warning: Could not validate API key due to API connection issue.'); const proceed = await question('Do you want to save this API key anyway? (y/n): '); if (proceed.toLowerCase() !== 'y') { rl.close(); return runSetupWizard(); // Restart the setup wizard } } // Save the API key to the config with secure permissions saveConfig({ readwiseApiKey: apiKey }); console.log(''); console.log('Configuration saved successfully.'); console.log('You can now start the server using:'); console.log(' readwise-mcp'); console.log(''); } catch (error) { console.error('Error saving configuration:', error instanceof Error ? error.message : String(error)); } finally { rl.close(); } return apiKey; } // Start the server main().catch((error) => { console.error('Error:', error instanceof Error ? error.message : String(error)); process.exit(1); });

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