Skip to main content
Glama

Readwise MCP Server

by IAmAlexander
highlight-prompt.ts6.41 kB
import { BaseMCPPrompt } from '../mcp/registry/base-prompt.js'; import { ReadwiseAPI } from '../api/readwise-api.js'; import { GetHighlightsParams } from '../types/index.js'; import { ValidationResult, validateNumberRange, validateAllowedValues, combineValidationResults } from '../types/validation.js'; import type { Logger } from '../utils/logger-interface.js'; import type { MCPResponse } from '../mcp/types.js'; /** * Parameters for the ReadwiseHighlightPrompt */ export interface ReadwiseHighlightPromptParams extends GetHighlightsParams { /** * Optional context to include in the prompt */ context?: string; /** * Optional task for the model to perform with the highlights */ task?: 'summarize' | 'analyze' | 'connect' | 'question'; } /** * Prompt for retrieving and analyzing highlights in Readwise */ export class ReadwiseHighlightPrompt extends BaseMCPPrompt<ReadwiseHighlightPromptParams, MCPResponse> { /** * The name of the prompt */ readonly name = 'readwise_highlight'; /** * The description of the prompt */ readonly description = 'Retrieves and analyzes highlights from Readwise'; /** * The parameters for the prompt */ readonly parameters = { type: 'object', properties: { book_id: { type: 'string', description: 'The ID of the book to get highlights from' }, page: { type: 'number', description: 'The page number of results to get' }, page_size: { type: 'number', description: 'The number of results per page (max 100)' }, search: { type: 'string', description: 'Search term to filter highlights' }, context: { type: 'string', description: 'Additional context to include in the prompt' }, task: { type: 'string', enum: ['summarize', 'analyze', 'connect', 'question'], description: 'The task to perform with the highlights' } }, required: [] }; /** * Create a new ReadwiseHighlightPrompt * @param api - The ReadwiseAPI instance to use * @param logger - The logger instance */ constructor(private api: ReadwiseAPI, logger: Logger) { super(logger); } /** * Validate the parameters * @param params - The parameters to validate * @returns Validation result */ validate(params: ReadwiseHighlightPromptParams): ValidationResult { const validations = [ validateNumberRange(params, 'page', 1, undefined, 'Page must be a positive number'), validateNumberRange(params, 'page_size', 1, 100, 'Page size must be a number between 1 and 100'), validateAllowedValues(params, 'task', ['summarize', 'analyze', 'connect', 'question'], 'Task must be one of: summarize, analyze, connect, question') ]; return combineValidationResults(validations); } /** * Execute the prompt */ async execute(params: ReadwiseHighlightPromptParams): Promise<MCPResponse> { this.logger.debug('Executing ReadwiseHighlightPrompt', { params } as any); try { // Fetch highlights from Readwise API const highlightsResponse = await this.api.getHighlights({ book_id: params.book_id, page: params.page || 1, page_size: params.page_size || 20, search: params.search }); if (!highlightsResponse.results || highlightsResponse.results.length === 0) { this.logger.warn('No highlights found for the given parameters', { params } as any); throw new Error('No highlights found. Try different parameters or check your Readwise library.'); } // Format highlights as structured content const highlights = highlightsResponse.results.map(highlight => { return `"${highlight.text}"${highlight.note ? ` - Note: ${highlight.note}` : ''}`; }).join('\n\n'); // Build the message content based on task let messageContent = ''; const task = params.task || 'summarize'; switch (task) { case 'summarize': messageContent = `Here are some highlights from your Readwise library:\n\n${highlights}\n\nPlease provide a summary of these highlights.`; break; case 'analyze': messageContent = `Here are some highlights from your Readwise library:\n\n${highlights}\n\nPlease analyze these highlights and identify key themes and insights.`; break; case 'connect': messageContent = `Here are some highlights from your Readwise library:\n\n${highlights}\n\nPlease identify connections between these highlights and suggest related topics to explore.`; break; case 'question': messageContent = `Here are some highlights from your Readwise library:\n\n${highlights}\n\nPlease generate thoughtful questions based on these highlights to deepen understanding.`; break; default: messageContent = `Here are some highlights from your Readwise library:\n\n${highlights}`; } // Include custom context if provided if (params.context) { messageContent = `${params.context}\n\n${messageContent}`; } this.logger.debug('Successfully generated prompt from highlights', { highlightCount: highlightsResponse.results.length, task } as any); // Return MCPResponse return { content: [ { type: 'text', text: messageContent } ] }; } catch (error) { // Log the error details this.logger.error('Error executing ReadwiseHighlightPrompt', error as any); if (error instanceof Error) { if (error.message.includes('No highlights found')) { throw error; } if (error.message.includes('401')) { throw new Error('Authentication failed. Please check your Readwise API key.'); } else if (error.message.includes('429')) { throw new Error('Rate limit exceeded. Please try again later.'); } else if (error.message.includes('5')) { throw new Error('Readwise service error. Please try again later.'); } throw new Error(`Failed to process highlights: ${error.message}`); } throw new Error('An unexpected error occurred while processing highlights.'); } } }

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