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 = ""
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It discloses the return format (dictionary with Book objects or None) but lacks critical behavioral details such as whether this is a read-only operation, error handling for invalid names, performance implications, or authentication needs. The description adds some value but is insufficient for a mutation-sensitive context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately sized and front-loaded: the first sentence states the purpose clearly, followed by structured Args and Returns sections. Every sentence adds value without redundancy, making it efficient and easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no annotations, no output schema, and low schema coverage, the description is moderately complete. It covers the basic purpose, parameter semantics, and return format, but gaps remain in behavioral transparency and usage guidelines. For a tool with one parameter and no complex outputs, it meets minimum viability but could be more comprehensive.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It adds meaning by explaining that 'document_names' is a list of strings used to search for documents in Readwise, clarifying the parameter's role beyond the bare schema. However, it does not detail constraints like name formatting or case sensitivity.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'find' and resource 'documents in Readwise', specifying it searches by 'a list of names'. It distinguishes from siblings that filter by IDs or other criteria, though not explicitly named. The purpose is specific but could be slightly more precise about differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage when searching by document names, as opposed to siblings that use IDs or filters, but does not explicitly state when to use this tool versus alternatives. No exclusions or prerequisites are mentioned, leaving some ambiguity for the agent.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

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

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