find_readwise_documents_by_names
Search for specific documents in Readwise using their names. Returns matching Book objects or null values for each requested name.
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 main tool handler function decorated with @mcp.tool(), registering the tool and implementing the logic by calling the helper get_documents_by_names.@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
- Core helper function that performs the API calls to Readwise to find and return documents by matching names case-insensitively.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 model defining the structure of a Book (document) object 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 = ""
- readwise_mcp/types/book.py:14-33 (schema)Enum for valid document categories used in filtering.class BookCategory(str, Enum): """Enumeration of valid book categories in Readwise.""" BOOKS = "books" ARTICLES = "articles" TWEETS = "tweets" SUPPLEMENTALS = "supplementals" PODCASTS = "podcasts" @classmethod def get_valid_values(cls) -> Set[str]: """Returns a set of all valid category string values.""" return {category.value for category in cls} @classmethod def is_valid_category(cls, category_str: str) -> bool: """Check if the given string is a valid book category.""" return category_str in cls.get_valid_values()