Skip to main content
Glama
edricgsh

Readwise Reader MCP Server

by edricgsh

readwise_topic_search

Search documents in Readwise Reader by topic using regex matching on titles, summaries, notes, and tags to find relevant content.

Instructions

Search documents in Readwise Reader by topic using regex matching on title, summary, notes, and tags

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
searchTermsYesList of search terms to match against document content (case-insensitive regex matching)

Implementation Reference

  • MCP tool handler that extracts search terms from args, calls client.searchDocumentsByTopic, formats results as JSON, appends messages if any, and returns text content response.
    export async function handleTopicSearch(args: any) { const client = initializeClient(); const { searchTerms } = args as { searchTerms: string[] }; const response = await client.searchDocumentsByTopic(searchTerms); const searchResults = { searchTerms, totalMatches: response.data.length, documents: response.data.map((doc: any) => ({ id: doc.id, url: doc.url, title: doc.title, author: doc.author, source: doc.source, category: doc.category, location: doc.location, tags: doc.tags, site_name: doc.site_name, word_count: doc.word_count, created_at: doc.created_at, updated_at: doc.updated_at, published_date: doc.published_date, summary: doc.summary, image_url: doc.image_url, source_url: doc.source_url, notes: doc.notes, reading_progress: doc.reading_progress, first_opened_at: doc.first_opened_at, last_opened_at: doc.last_opened_at, saved_at: doc.saved_at, last_moved_at: doc.last_moved_at, })) }; let responseText = JSON.stringify(searchResults, null, 2); if (response.messages && response.messages.length > 0) { responseText += '\n\nMessages:\n' + response.messages.map(msg => `${msg.type.toUpperCase()}: ${msg.content}`).join('\n'); } return { content: [ { type: 'text', text: responseText, }, ], }; }
  • Tool schema definition with name, description, and inputSchema specifying searchTerms array.
    { name: 'readwise_topic_search', description: 'Search documents in Readwise Reader by topic using regex matching on title, summary, notes, and tags', inputSchema: { type: 'object', properties: { searchTerms: { type: 'array', items: { type: 'string' }, description: 'List of search terms to match against document content (case-insensitive regex matching)', minItems: 1, }, }, required: ['searchTerms'], additionalProperties: false, }, },
  • Core search logic in ReadwiseClient: paginates all documents, filters client-side using case-insensitive regex on title/summary/notes/tags for any matching search term.
    async searchDocumentsByTopic(searchTerms: string[]): Promise<APIResponse<ReadwiseDocument[]>> { try { // Fetch all documents without full content for performance const allDocuments: ReadwiseDocument[] = []; let nextPageCursor: string | undefined; do { const params: ListDocumentsParams = { withFullContent: false, withHtmlContent: false, }; if (nextPageCursor) { params.pageCursor = nextPageCursor; } const response = await this.listDocuments(params); allDocuments.push(...response.data.results); nextPageCursor = response.data.nextPageCursor; } while (nextPageCursor); // Create regex patterns from search terms (case-insensitive) const regexPatterns = searchTerms.map(term => new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i') ); // Filter documents that match any of the search terms const matchingDocuments = allDocuments.filter(doc => { // Extract searchable text fields const searchableFields = [ doc.title || '', doc.summary || '', doc.notes || '', // Handle tags - they can be string array or object Array.isArray(doc.tags) ? doc.tags.join(' ') : '', ]; const searchableText = searchableFields.join(' ').toLowerCase(); // Check if any regex pattern matches return regexPatterns.some(pattern => pattern.test(searchableText)); }); return this.createResponse(matchingDocuments); } catch (error) { if (error instanceof Error && error.message.startsWith('RATE_LIMIT:')) { const seconds = parseInt(error.message.split(':')[1], 10); throw new Error(`Rate limit exceeded. Too many requests. Please retry after ${seconds} seconds.`); } throw error; } }
  • Tool dispatcher function with switch case registering/routing readwise_topic_search to its handler.
    export async function handleToolCall(name: string, args: any) { switch (name) { case 'readwise_save_document': return handleSaveDocument(args); case 'readwise_list_documents': return handleListDocuments(args); case 'readwise_update_document': return handleUpdateDocument(args); case 'readwise_delete_document': return handleDeleteDocument(args); case 'readwise_list_tags': return handleListTags(args); case 'readwise_topic_search': return handleTopicSearch(args); default: throw new Error(`Unknown tool: ${name}`); } }
  • src/index.ts:24-26 (registration)
    MCP server registration of the tools list (including readwise_topic_search schema) for listTools requests.
    server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; });

Latest Blog Posts

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/edricgsh/Readwise-Reader-MCP'

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