Skip to main content
Glama
edricgsh

Readwise Reader MCP Server

by edricgsh

readwise_list_documents

Retrieve documents from Readwise Reader with filtering options for location, category, tags, and date ranges to organize your saved content.

Instructions

List documents from Readwise Reader with optional filtering

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
idNoFilter by specific document ID
updatedAfterNoFilter documents updated after this date (ISO 8601)
addedAfterNoFilter documents added after this date (ISO 8601). Note: This will fetch all documents first and then filter client-side.
locationNoFilter by document location
categoryNoFilter by document category
tagNoFilter by tag name
pageCursorNoPage cursor for pagination
withHtmlContentNo⚠️ PERFORMANCE WARNING: Include HTML content in the response. This significantly slows down the API. Only use when explicitly requested by the user or when raw HTML is specifically needed for the task.
withFullContentNo⚠️ PERFORMANCE WARNING: Include full converted text content in the response. This significantly slows down the API as it fetches and processes each document's content. Only use when explicitly requested by the user or when document content is specifically needed for analysis/reading. Default: false for performance.

Implementation Reference

  • The handleListDocuments function implements the core logic for the readwise_list_documents tool. It initializes the Readwise client, handles filtering (including client-side for addedAfter), pagination, fetches documents, optionally converts content to text or includes HTML, and formats the response.
    export async function handleListDocuments(args: any) {
      const client = initializeClient();
      const params = args as ListDocumentsParams;
      
      // If withFullContent is true, we also need HTML content
      if (params.withFullContent === true) {
        params.withHtmlContent = true;
      }
      
      let response;
      let clientSideFiltered = false;
      
      // If addedAfter is specified, we need to fetch all documents and filter client-side
      if (params.addedAfter) {
        clientSideFiltered = true;
        const addedAfterDate = new Date(params.addedAfter);
        
        // Create params without addedAfter for the API call
        const apiParams = { ...params };
        delete apiParams.addedAfter;
        
        // Fetch all documents if no other pagination is specified
        if (!apiParams.pageCursor && !apiParams.limit) {
          const allDocuments: any[] = [];
          let nextPageCursor: string | undefined;
          
          do {
            const fetchParams = { ...apiParams };
            if (nextPageCursor) {
              fetchParams.pageCursor = nextPageCursor;
            }
            
            const pageResponse = await client.listDocuments(fetchParams);
            allDocuments.push(...pageResponse.data.results);
            nextPageCursor = pageResponse.data.nextPageCursor;
          } while (nextPageCursor);
          
          // Filter documents by addedAfter date
          const filteredDocuments = allDocuments.filter(doc => {
            if (!doc.saved_at) return false;
            const savedDate = new Date(doc.saved_at);
            return savedDate > addedAfterDate;
          });
          
          response = {
            data: {
              count: filteredDocuments.length,
              nextPageCursor: undefined,
              results: filteredDocuments
            },
            messages: []
          };
        } else {
          // If pagination is specified, just do a regular API call and filter the current page
          response = await client.listDocuments(apiParams);
          const filteredDocuments = response.data.results.filter(doc => {
            if (!doc.saved_at) return false;
            const savedDate = new Date(doc.saved_at);
            return savedDate > addedAfterDate;
          });
          
          response.data.results = filteredDocuments;
          response.data.count = filteredDocuments.length;
        }
      } else {
        response = await client.listDocuments(params);
      }
    
      // Convert content to LLM-friendly text for documents only if withFullContent is explicitly true
      const shouldIncludeContent = params.withFullContent === true; // Default to false for performance
      const documentsWithText = await Promise.all(
        response.data.results.map(async (doc) => {
          let content = '';
          if (shouldIncludeContent) {
            // Try to use HTML content first (from Readwise), fallback to URL fetching
            if (doc.html_content) {
              // Use HTML content from Readwise for non-jina content types
              const shouldUseJina = !doc.category || doc.category === 'article' || doc.category === 'pdf';
              if (shouldUseJina) {
                const urlToConvert = doc.source_url || doc.url;
                if (urlToConvert) {
                  content = await convertUrlToText(urlToConvert, doc.category);
                }
              } else {
                content = extractTextFromHtml(doc.html_content);
              }
            } else {
              // Fallback to URL fetching if no HTML content available
              const urlToConvert = doc.source_url || doc.url;
              if (urlToConvert) {
                content = await convertUrlToText(urlToConvert, doc.category);
              }
            }
          }
          
          const result: any = {
            id: doc.id,
            url: doc.url,
            title: doc.title,
            author: doc.author,
            source: doc.source,
            category: doc.category,
            location: doc.location,
            tags: doc.tags,
            site_name: doc.site_name,
            word_count: doc.word_count,
            created_at: doc.created_at,
            updated_at: doc.updated_at,
            published_date: doc.published_date,
            summary: doc.summary,
            image_url: doc.image_url,
            source_url: doc.source_url,
            notes: doc.notes,
            parent_id: doc.parent_id,
            reading_progress: doc.reading_progress,
            first_opened_at: doc.first_opened_at,
            last_opened_at: doc.last_opened_at,
            saved_at: doc.saved_at,
            last_moved_at: doc.last_moved_at,
          };
          
          if (shouldIncludeContent) {
            result.content = content; // LLM-friendly text content instead of raw HTML
          }
          
          if (params.withHtmlContent && doc.html_content) {
            result.html_content = doc.html_content;
          }
          
          return result;
        })
      );
    
      let responseText = JSON.stringify({
        count: response.data.count,
        nextPageCursor: response.data.nextPageCursor,
        documents: documentsWithText
      }, null, 2);
      
      let allMessages = response.messages || [];
      
      // Add message about client-side filtering if it was performed
      if (clientSideFiltered) {
        allMessages.push({
          type: 'info',
          content: 'Documents were filtered client-side based on the addedAfter date. All documents were fetched from the API first, then filtered by their saved_at date.'
        });
      }
      
      if (allMessages.length > 0) {
        responseText += '\n\nMessages:\n' + allMessages.map(msg => `${msg.type.toUpperCase()}: ${msg.content}`).join('\n');
      }
    
      return {
        content: [
          {
            type: 'text',
            text: responseText,
          },
        ],
      };
    }
  • Defines the tool metadata including name, description, and detailed inputSchema with parameters like addedAfter, withFullContent, etc., for the readwise_list_documents tool.
    {
      name: 'readwise_list_documents',
      description: 'List documents from Readwise Reader with optional filtering',
      inputSchema: {
        type: 'object',
        properties: {
          id: {
            type: 'string',
            description: 'Filter by specific document ID',
          },
          updatedAfter: {
            type: 'string',
            description: 'Filter documents updated after this date (ISO 8601)',
          },
          addedAfter: {
            type: 'string',
            description: 'Filter documents added after this date (ISO 8601). Note: This will fetch all documents first and then filter client-side.',
          },
          location: {
            type: 'string',
            enum: ['new', 'later', 'shortlist', 'archive', 'feed'],
            description: 'Filter by document location',
          },
          category: {
            type: 'string',
            enum: ['article', 'book', 'tweet', 'pdf', 'email', 'youtube', 'podcast'],
            description: 'Filter by document category',
          },
          tag: {
            type: 'string',
            description: 'Filter by tag name',
          },
          pageCursor: {
            type: 'string',
            description: 'Page cursor for pagination',
          },
          withHtmlContent: {
            type: 'boolean',
            description: '⚠️ PERFORMANCE WARNING: Include HTML content in the response. This significantly slows down the API. Only use when explicitly requested by the user or when raw HTML is specifically needed for the task.',
          },
          withFullContent: {
            type: 'boolean',
            description: '⚠️ PERFORMANCE WARNING: Include full converted text content in the response. This significantly slows down the API as it fetches and processes each document\'s content. Only use when explicitly requested by the user or when document content is specifically needed for analysis/reading. Default: false for performance.',
          },
        },
        additionalProperties: false,
      },
    },
  • Registers the 'readwise_list_documents' tool name in the switch statement, dispatching to the handleListDocuments function.
    case 'readwise_list_documents':
      return handleListDocuments(args);
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions 'optional filtering' but fails to describe key behaviors such as pagination handling, rate limits, authentication requirements, or what the response format looks like. This is inadequate for a tool with 9 parameters and no output schema.

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 a single, efficient sentence that states the core functionality without unnecessary words. It's appropriately sized and front-loaded, making it easy for an agent to parse quickly.

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

Completeness2/5

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

Given the complexity (9 parameters, no annotations, no output schema), the description is incomplete. It doesn't explain the return format, pagination behavior, or error handling, which are critical for an agent to use this tool effectively. The schema handles parameters well, but overall context is lacking.

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

Parameters3/5

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

The input schema has 100% description coverage, so the schema already documents all parameters thoroughly. The description adds no additional meaning beyond 'optional filtering', which is implied by the schema. This meets the baseline of 3 when schema coverage is high.

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 action ('List') and resource ('documents from Readwise Reader'), making the purpose understandable. However, it doesn't differentiate this tool from sibling tools like 'readwise_list_tags' or 'readwise_topic_search' beyond mentioning documents specifically, which is why it doesn't reach a 5.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives like 'readwise_topic_search' or 'readwise_save_document'. It mentions optional filtering but doesn't specify scenarios or exclusions, leaving the agent without context for tool selection among siblings.

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/edricgsh/Readwise-Reader-MCP'

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