Skip to main content
Glama

Readwise MCP Server

by IAmAlexander
web-client.html10.7 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Readwise MCP Web Client</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; color: #333; } h1 { color: #2c3e50; border-bottom: 2px solid #eee; padding-bottom: 10px; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; font-weight: bold; } input, select, textarea { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } button { background-color: #3498db; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background-color: #2980b9; } .card { border: 1px solid #ddd; border-radius: 4px; padding: 15px; margin-top: 20px; background-color: #f9f9f9; } .highlight { background-color: #fffde7; border-left: 3px solid #fdd835; padding: 10px; margin: 10px 0; } .response { white-space: pre-wrap; font-family: monospace; background-color: #f5f5f5; padding: 15px; overflow-x: auto; border-radius: 4px; margin-top: 20px; } .events-log { height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; background-color: #f5f5f5; font-family: monospace; font-size: 14px; margin-top: 20px; } .log-entry { margin-bottom: 5px; border-bottom: 1px solid #eee; padding-bottom: 5px; } .success { color: #27ae60; } .error { color: #e74c3c; } .info { color: #2980b9; } </style> </head> <body> <h1>Readwise MCP Web Client</h1> <div class="card"> <h2>Server Configuration</h2> <div class="form-group"> <label for="server-url">MCP Server URL:</label> <input type="text" id="server-url" value="http://localhost:3000" /> </div> <div class="form-group"> <label for="api-key">Readwise API Key:</label> <input type="password" id="api-key" placeholder="Enter your Readwise API key" /> </div> </div> <div class="card"> <h2>Get Books</h2> <div class="form-group"> <label for="books-page">Page:</label> <input type="number" id="books-page" value="1" min="1" /> </div> <div class="form-group"> <label for="books-page-size">Page Size:</label> <input type="number" id="books-page-size" value="10" min="1" max="100" /> </div> <button id="get-books-btn">Get Books</button> </div> <div class="card"> <h2>Get Highlights</h2> <div class="form-group"> <label for="book-id">Book ID:</label> <input type="text" id="book-id" placeholder="Enter a book ID" /> </div> <div class="form-group"> <label for="highlights-page">Page:</label> <input type="number" id="highlights-page" value="1" min="1" /> </div> <div class="form-group"> <label for="highlights-page-size">Page Size:</label> <input type="number" id="highlights-page-size" value="20" min="1" max="100" /> </div> <button id="get-highlights-btn">Get Highlights</button> </div> <div class="card"> <h2>Search Highlights</h2> <div class="form-group"> <label for="search-query">Search Query:</label> <input type="text" id="search-query" placeholder="Enter search terms" /> </div> <div class="form-group"> <label for="search-limit">Result Limit:</label> <input type="number" id="search-limit" value="10" min="1" max="100" /> </div> <button id="search-highlights-btn">Search Highlights</button> </div> <div class="card"> <h2>Analyze Highlights</h2> <div class="form-group"> <label for="analyze-book-id">Book ID:</label> <input type="text" id="analyze-book-id" placeholder="Enter a book ID" /> </div> <div class="form-group"> <label for="analyze-task">Task:</label> <select id="analyze-task"> <option value="analyze">Analyze</option> <option value="summarize">Summarize</option> <option value="connect">Find Connections</option> <option value="question">Generate Questions</option> </select> </div> <button id="analyze-highlights-btn">Analyze Highlights</button> </div> <h2>Events Log</h2> <div class="events-log" id="events-log"></div> <h2>Response</h2> <div class="response" id="response"></div> <script> // Utility for generating UUIDs function uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } // Add a log entry to the events log function logEvent(message, type = 'info') { const logElement = document.getElementById('events-log'); const entry = document.createElement('div'); entry.className = `log-entry ${type}`; entry.innerHTML = `[${new Date().toLocaleTimeString()}] ${message}`; logElement.appendChild(entry); logElement.scrollTop = logElement.scrollHeight; } // Display response function displayResponse(data) { document.getElementById('response').textContent = JSON.stringify(data, null, 2); } // Make an MCP request using SSE function makeMCPRequest(request) { const serverUrl = document.getElementById('server-url').value; const apiKey = document.getElementById('api-key').value; // Ensure request has a request_id if (!request.request_id) { request.request_id = uuidv4(); } logEvent(`Making ${request.type} request: ${request.name} (${request.request_id})`, 'info'); // Initialize EventSource for SSE let eventSource; // First make a POST request to initiate the MCP request fetch(`${serverUrl}/mcp`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify(request) }) .then(response => { if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } return response.json(); }) .then(data => { logEvent(`Request accepted: ${data.request_id}`, 'success'); // Now set up SSE to receive events const sseUrl = `${serverUrl}/sse?request_id=${request.request_id}`; eventSource = new EventSource(sseUrl); eventSource.addEventListener('request_received', function(e) { logEvent('Server received request', 'info'); }); eventSource.addEventListener('response', function(e) { const responseData = JSON.parse(e.data); logEvent('Received response', 'success'); displayResponse(responseData); }); eventSource.addEventListener('request_completed', function(e) { logEvent('Request completed', 'success'); // Close the EventSource connection eventSource.close(); }); eventSource.addEventListener('error', function(e) { logEvent('SSE connection error', 'error'); eventSource.close(); }); }) .catch(error => { logEvent(`Error: ${error.message}`, 'error'); }); } // Get Books document.getElementById('get-books-btn').addEventListener('click', function() { const page = parseInt(document.getElementById('books-page').value); const pageSize = parseInt(document.getElementById('books-page-size').value); const request = { type: 'tool_call', name: 'get_books', parameters: { page, page_size: pageSize }, request_id: `get-books-${Date.now()}` }; makeMCPRequest(request); }); // Get Highlights document.getElementById('get-highlights-btn').addEventListener('click', function() { const bookId = document.getElementById('book-id').value; const page = parseInt(document.getElementById('highlights-page').value); const pageSize = parseInt(document.getElementById('highlights-page-size').value); if (!bookId) { logEvent('Book ID is required', 'error'); return; } const request = { type: 'tool_call', name: 'get_highlights', parameters: { book_id: bookId, page, page_size: pageSize }, request_id: `get-highlights-${Date.now()}` }; makeMCPRequest(request); }); // Search Highlights document.getElementById('search-highlights-btn').addEventListener('click', function() { const query = document.getElementById('search-query').value; const limit = parseInt(document.getElementById('search-limit').value); if (!query) { logEvent('Search query is required', 'error'); return; } const request = { type: 'tool_call', name: 'search_highlights', parameters: { query, limit }, request_id: `search-highlights-${Date.now()}` }; makeMCPRequest(request); }); // Analyze Highlights document.getElementById('analyze-highlights-btn').addEventListener('click', function() { const bookId = document.getElementById('analyze-book-id').value; const task = document.getElementById('analyze-task').value; if (!bookId) { logEvent('Book ID is required', 'error'); return; } const request = { type: 'prompt_call', name: 'readwise_highlight', parameters: { book_id: bookId, task }, request_id: `analyze-highlights-${Date.now()}` }; makeMCPRequest(request); }); // Initialize document.addEventListener('DOMContentLoaded', function() { logEvent('Web client initialized', 'info'); logEvent('To use this client, start the Readwise MCP server with SSE transport:', 'info'); logEvent('npm run public', 'info'); }); </script> </body> </html>

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