Skip to main content
Glama

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
NameRequiredDescriptionDefault
document_namesYes

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
  • 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 = ""

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/kiseki-technologies/kiseki-labs-readwise-mcp'

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