find_readwise_documents_by_names
Search for specific documents in Readwise using document names. Returns matching Book objects or indicates when documents are not found.
Instructions
Find documents in Readwise by a list of names.
Args:
document_names (List[str]): The names of the documents to search for in Readwise.
Returns:
Dict[str, Optional[Book]]: A dictionary where keys are the requested document names
and values are the corresponding Book objects if found, or None otherwise.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| document_names | Yes |
Implementation Reference
- server.py:34-58 (handler)The tool handler function registered with @mcp.tool(). It logs the search and delegates to get_documents_by_names helper, returning the result dictionary.@mcp.tool() async def find_readwise_documents_by_names( document_names: List[str], ) -> Dict[str, Optional[Book]]: """Find documents in Readwise by a list of names. Args: document_names (List[str]): The names of the documents to search for in Readwise. Returns: Dict[str, Optional[Book]]: A dictionary where keys are the requested document names and values are the corresponding Book objects if found, or None otherwise. """ logging.info(f"*** Searching for documents: {', '.join(document_names)}") docs_dict = await get_documents_by_names(READWISE_API_KEY, document_names) found_count = sum(1 for doc in docs_dict.values() if doc is not None) logging.info(f"*** Found {found_count}/{len(document_names)} documents.") for name, doc in docs_dict.items(): if doc is None: logging.info(f"*** - '{name}': Not found") return docs_dict
- Helper function that implements the core logic: paginates through Readwise /books/ API, matches document titles case-insensitively, constructs Book objects, and returns dict with matches or None.async def get_documents_by_names( readwise_api_key: str, document_names: List[str], document_category: str = "" ) -> Dict[str, Optional[Book]]: """Get documents (aka books) from Readwise by a list of names""" # Create a mapping from lowercase name to original name for lookup name_map = {name.lower(): name for name in document_names} # Initialize results with None for all requested original names results: Dict[str, Optional[Book]] = {name: None for name in document_names} found_names_lower = set() params = {"page_size": PAGE_SIZE} if document_category: try: params["category"] = to_book_category(document_category).value except ValueError as e: raise ValueError(f"Invalid category: {document_category}. {str(e)}") url = f"{READWISE_API_URL}/books/" first_request = True logging.debug(f"Searching for documents in {url} with params: {params}") while True: # Pass params only on the first request. Subsequent requests use the 'next' URL. current_params = params if first_request else None response = await get_data(readwise_api_key, url, current_params) first_request = False logging.info(f"Response: {response}") books_json = response["results"] for book_json in books_json: book_title_lower = book_json["title"].lower() # Check if this book title matches one of the requested names (case-insensitive) # and we haven't found it yet. if book_title_lower in name_map and book_title_lower not in found_names_lower: original_name = name_map[book_title_lower] logging.info(f"Found document: {book_json} (requested as {original_name})") results[original_name] = Book(**book_json) found_names_lower.add(book_title_lower) # Check if all requested documents have been found if len(found_names_lower) == len(document_names): logging.info("Found all requested documents.") break url = response.get("next", None) if not url: logging.info("No more pages to fetch from Readwise API.") break logging.info( f"Fetched page, found {len(found_names_lower)}/{len(document_names)} requested documents so far. Next url: {url is not None}" ) await asyncio.sleep(DEFAULT_SLEEP_BETWEEN_REQUESTS_IN_SECONDS) # Small delay between requests # Log any names that were not found not_found_names = [name for name, book in results.items() if book is None] if not_found_names: logging.warning(f"Could not find the following documents: {', '.join(not_found_names)}") return results
- readwise_mcp/types/book.py:34-51 (schema)Pydantic BaseModel schema for Book objects returned by the tool.class Book(BaseModel): """Represents a document from Readwise API.""" id: int title: str author: str category: str source: str num_highlights: int last_highlight_at: datetime updated: datetime cover_image_url: HttpUrl highlights_url: str source_url: Optional[HttpUrl] = None asin: Optional[str] = None tags: List[str | Tag] = [] document_note: str = ""