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
| Name | Required | Description | Default |
|---|---|---|---|
| searchTerms | Yes | List 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, }, },
- src/readwise-client.ts:212-263 (helper)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; } }
- src/handlers/index.ts:9-32 (registration)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 }; });