reader_list_documents
List documents from your Readwise Reader library filtered by ID, location, category, tag, or update time. Retrieve specific documents, paginate results, or include full HTML content. Organize your reading list and knowledge repository.
Instructions
List documents from your Readwise Reader library.
Args:
id: Get a single document by ID (returns only that document)
location: Filter by folder location (new, later, shortlist, archive, feed)
category: Filter by category (article, email, rss, highlight, note, pdf, epub, tweet, video)
tag: Filter by tag keys (pass empty list [] for untagged documents)
updatedAfter: Filter by update time (ISO 8601 format, e.g., 2024-01-01T00:00:00Z)
limit: Number of results to return (1-100, default: 100)
withContent: Include full HTML content in response (default: false)
pageCursor: Pagination cursor for next page
Returns:
ListDocumentResponse with count, results, and nextPageCursorInput Schema
| Name | Required | Description | Default |
|---|---|---|---|
| id | No | ||
| location | No | ||
| category | No | ||
| tag | No | ||
| updatedAfter | No | ||
| limit | No | ||
| withContent | No | ||
| pageCursor | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| count | Yes | ||
| results | Yes | ||
| nextPageCursor | No |
Implementation Reference
- main.py:165-274 (handler)The main handler function for the 'reader_list_documents' MCP tool. It validates inputs (location, category, ISO 8601 datetime, limit), builds query parameters, calls the Readwise Reader API /list/ endpoint, parses the response into ReaderDocument objects, and returns a ListDocumentResponse.
async def reader_list_documents( id: Optional[str] = None, location: Optional[Literal["new", "later", "shortlist", "archive", "feed"]] = None, category: Optional[Literal["article", "email", "rss", "highlight", "note", "pdf", "epub", "tweet", "video"]] = None, tag: Optional[List[str]] = None, updatedAfter: Optional[str] = None, limit: Optional[int] = None, withContent: Optional[bool] = False, pageCursor: Optional[str] = None, ) -> ListDocumentResponse: """ List documents from your Readwise Reader library. Args: id: Get a single document by ID (returns only that document) location: Filter by folder location (new, later, shortlist, archive, feed) category: Filter by category (article, email, rss, highlight, note, pdf, epub, tweet, video) tag: Filter by tag keys (pass empty list [] for untagged documents) updatedAfter: Filter by update time (ISO 8601 format, e.g., 2024-01-01T00:00:00Z) limit: Number of results to return (1-100, default: 100) withContent: Include full HTML content in response (default: false) pageCursor: Pagination cursor for next page Returns: ListDocumentResponse with count, results, and nextPageCursor """ ctx = get_reader_context() logger.info( f"reader_list_documents: id={id}, location={location}, category={category}, " f"tag={tag}, updatedAfter={updatedAfter}, limit={limit}, withContent={withContent}, " f"pageCursor={pageCursor}" ) try: # Validate parameters if location: _validate_location(location) if category: _validate_category(category) if updatedAfter and not _validate_iso8601_datetime(updatedAfter): logger.warning( f"Invalid ISO 8601 datetime: {updatedAfter}. " "Expected format: YYYY-MM-DDTHH:MM:SSZ" ) updatedAfter = None if limit is not None and (limit < 1 or limit > 100): raise ValueError(f"Limit must be between 1 and 100. Got: {limit}") # Build query params params: Dict[str, Any] = {} if id: params["id"] = id if location: params["location"] = location if category: params["category"] = category if tag is not None: # Pass empty string for untagged, or multiple tag values if len(tag) == 0: params["tag"] = "" else: params["tag"] = tag if updatedAfter: params["updatedAfter"] = updatedAfter if limit: params["limit"] = limit if withContent: params["withHtmlContent"] = withContent if pageCursor: params["pageCursor"] = pageCursor # Make API request response = await ctx.client.get("/list/", params=params) response.raise_for_status() data = response.json() # Parse response try: results = [ ReaderDocument.from_dict(doc) for doc in data.get("results", []) ] list_response = ListDocumentResponse( count=data.get("count", 0), results=results, nextPageCursor=data.get("nextPageCursor"), ) except (TypeError, ValueError) as e: logger.error(f"Invalid API response format: {e}") raise ValueError( "Unexpected response format from Reader API. " "Expected 'count', 'results' fields." ) # Return ListDocumentResponse return list_response except httpx.HTTPStatusError as e: if e.response.status_code == 401: raise ValueError( "Authentication failed. Please check your READWISE_ACCESS_TOKEN. " "Get your token from: https://readwise.io/access_token" ) elif e.response.status_code == 429: raise _rate_limit_error(e.response) raise except ValueError as e: raise except Exception as e: logger.error(f"Error in reader_list_documents: {str(e)}") raise ValueError(f"Failed to list documents: {str(e)}") - main.py:156-163 (registration)Registers the 'reader_list_documents' tool with the MCP server using @mcp.tool decorator, including tool annotations (readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True).
@mcp.tool( name="reader_list_documents", annotations=ToolAnnotations( readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True, ), - models.py:6-79 (schema)ReaderDocument dataclass representing a document object from the Reader API. Used to parse individual results from the list endpoint.
@dataclass class ReaderDocument: """ Document object in Reader. """ # Required document identifiers id: str url: str # Document details title: str source_url: Optional[str] = None author: Optional[str] = None source: Optional[str] = None category: Optional[str] = None location: Optional[str] = None tags: Dict[str, Any] = field(default_factory=dict) site_name: Optional[str] = None word_count: Optional[int] = None notes: Optional[str] = None published_date: Optional[str] = None summary: Optional[str] = None html_content: Optional[str] = None image_url: Optional[str] = None parent_id: Optional[str] = None # Reading state reading_progress: float = 0.0 first_opened_at: Optional[datetime] = None last_opened_at: Optional[datetime] = None # Timestamps created_at: Optional[datetime] = None updated_at: Optional[datetime] = None saved_at: Optional[datetime] = None last_moved_at: Optional[datetime] = None @classmethod def from_dict(cls, doc: Dict[str, Any]) -> "ReaderDocument": """Create a ReaderDocument from a dictionary representation""" return cls( id=doc.get('id', ''), url=doc.get('url', ''), title=doc.get('title', 'Untitled'), source_url=doc.get('source_url'), author=doc.get('author'), source=doc.get('source'), category=doc.get('category'), location=doc.get('location'), tags=doc.get('tags', {}), site_name=doc.get('site_name'), word_count=doc.get('word_count'), notes=doc.get('notes'), published_date=doc.get('published_date'), summary=doc.get('summary'), html_content=doc.get('html_content'), image_url=doc.get('image_url'), parent_id=doc.get('parent_id'), reading_progress=doc.get('reading_progress', 0.0), first_opened_at=doc.get('first_opened_at'), last_opened_at=doc.get('last_opened_at'), created_at=doc.get('created_at'), updated_at=doc.get('updated_at'), saved_at=doc.get('saved_at'), last_moved_at=doc.get('last_moved_at') ) @dataclass class ListDocumentResponse: """Response of the document list API""" count: int results: List[ReaderDocument] nextPageCursor: Optional[str] = None - models.py:74-79 (schema)ListDocumentResponse dataclass representing the response of the document list API. Contains count, results (list of ReaderDocument), and optional nextPageCursor.
@dataclass class ListDocumentResponse: """Response of the document list API""" count: int results: List[ReaderDocument] nextPageCursor: Optional[str] = None - main.py:108-114 (helper)Helper function that validates the location parameter against a set of valid locations.
def _validate_location(location: str, valid_set: set = VALID_LOCATIONS) -> None: """Validate location parameter.""" if location not in valid_set: raise ValueError( f"Invalid location '{location}'. " f"Must be one of: {', '.join(sorted(valid_set))}" )