Skip to main content
Glama

Readwise MCP Server

by IAmAlexander
simple-readwise-server.js7.85 kB
// Simple Readwise MCP server in JavaScript // This avoids TypeScript compilation issues while preserving core functionality import { Server } from '@modelcontextprotocol/sdk/server'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio'; import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse'; import express from 'express'; import cors from 'cors'; import bodyParser from 'body-parser'; import { createServer } from 'http'; // Global error handling process.on('unhandledRejection', (error) => { console.error("UNHANDLED REJECTION:"); console.error(error); process.exit(1); }); /** * Get port from environment or command line arguments */ function getPort() { // Check command line args first (--port=XXXX) const portArg = process.argv.find(arg => arg.startsWith('--port=')); if (portArg) { const port = parseInt(portArg.split('=')[1], 10); if (!isNaN(port)) return port; } // Then check environment variable if (process.env.MCP_PORT) { const port = parseInt(process.env.MCP_PORT, 10); if (!isNaN(port)) return port; } // Default ports based on transport const isMCPInspector = process.env.MCP_INSPECTOR === 'true' || process.argv.includes('--mcp-inspector'); return isMCPInspector ? 3000 : 3001; } /** * Main function to run the server */ async function main() { console.error("Starting Simple Readwise MCP Server"); try { // Determine transport type const isMCPInspector = process.env.MCP_INSPECTOR === 'true' || process.argv.includes('--mcp-inspector'); const transportType = isMCPInspector ? 'sse' : 'stdio'; const port = getPort(); console.error(`Using ${transportType} transport on port ${port}`); // Create server console.error("Creating server..."); const server = new Server({ name: "simple-readwise-mcp", version: "1.0.0" }, { capabilities: { tools: { "echo": true } } }); console.error("Server created"); // Create Express app for HTTP server (needed for SSE transport) const app = express(); app.use(cors()); app.use(bodyParser.json()); const httpServer = createServer(app); // Set up health endpoint app.get('/health', (req, res) => { res.json({ status: 'ok', uptime: process.uptime(), transport: transportType, port: port }); }); // Set up direct request handling console.error("Setting up request handling..."); server._onRequest = async (method, params, context) => { console.error(`Received request: ${method}`, params); // Handle echo tool if (method === "mcp/call_tool" && params.name === "echo") { console.error("Executing echo tool", params); try { const message = params.parameters?.message || "No message provided"; // Add timeout protection for the echo tool const result = await Promise.race([ Promise.resolve({ content: [{ type: "text", text: `Echo: ${message}` }] }), new Promise((_, reject) => setTimeout(() => reject(new Error('Echo tool execution timed out')), 5000) ) ]); console.error(`Echo tool completed with result: ${JSON.stringify(result)}`); return result; } catch (error) { console.error(`Error in echo tool: ${error.message}`); // Follow MCP error format with isError flag return { isError: true, content: [{ type: "text", text: `Error: ${error.message}` }] }; } } // Let server handle other requests return null; }; // Start HTTP server for SSE transport console.error(`Starting HTTP server on port ${port}...`); await new Promise(resolve => { httpServer.listen(port, () => { console.error(`HTTP server started on port ${port}`); resolve(); }); }); // Set up the appropriate transport if (transportType === 'stdio') { setupStdioTransport(server); } else { setupSSETransport(server, app); } console.error("Server running. Press Ctrl+C to stop"); } catch (error) { console.error("ERROR IN MAIN:", error); process.exit(1); } } /** * Set up stdio transport */ function setupStdioTransport(server) { console.error("Setting up stdio transport..."); try { const transport = new StdioServerTransport(); server.connect(transport) .then(() => { console.error("Stdio transport connected successfully"); }) .catch(error => { console.error("Error connecting stdio transport:", error); }); } catch (error) { console.error("Error setting up stdio transport:", error); throw error; } } /** * Set up SSE transport and endpoints */ function setupSSETransport(server, app) { console.error("Setting up SSE transport..."); // Set up SSE endpoint app.get('/sse', (req, res) => { console.error("New SSE connection"); // Set SSE headers res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.setHeader('Access-Control-Allow-Origin', '*'); res.flushHeaders(); try { // Create transport const transport = new SSEServerTransport('/messages', res); // Connect transport to server server.connect(transport) .then(() => { console.error("SSE transport connected successfully"); // Keep-alive interval const keepAliveInterval = setInterval(() => { if (!res.writableEnded) { res.write('event: ping\ndata: {}\n\n'); } else { clearInterval(keepAliveInterval); } }, 30000); // Handle client disconnect req.on('close', () => { console.error("Client disconnected"); clearInterval(keepAliveInterval); transport.close().catch(err => { console.error("Error closing transport:", err); }); }); }) .catch(error => { console.error("Error connecting SSE transport:", error); if (!res.writableEnded) { res.write(`event: error\ndata: ${JSON.stringify({ error: 'Internal server error' })}\n\n`); res.end(); } }); } catch (error) { console.error("Error in SSE endpoint:", error); if (!res.writableEnded) { res.write(`event: error\ndata: ${JSON.stringify({ error: 'Internal server error' })}\n\n`); res.end(); } } }); // Set up messages endpoint for SSE app.post('/messages', (req, res) => { console.error("Received POST message"); // Handle the message through SSE transport try { // Find active transport const transport = server._transport; if (!transport || !transport.handlePostMessage) { console.error("No active SSE transport"); res.status(500).json({ error: 'No active SSE transport' }); return; } // Pass message to transport transport.handlePostMessage(req, res); } catch (error) { console.error("Error handling message:", error); res.status(500).json({ error: 'Internal server error' }); } }); console.error("SSE transport setup complete"); } // Start the application main().catch(error => { console.error("FATAL ERROR:", 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