list_docs
Retrieve documents from Dart AI by folder, title, or text content. Filter results and paginate with limit and offset parameters.
Instructions
List documents with optional filtering by folder, title_contains, text_contains. Supports pagination.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| folder | No | Filter by folder (dart_id or name) | |
| title_contains | No | Filter by title substring (case-insensitive) | |
| text_contains | No | Filter by text content substring (case-insensitive) | |
| limit | No | Max docs to return (default: 50, max: 500) | |
| offset | No | Pagination offset (default: 0) |
Implementation Reference
- src/tools/list_docs.ts:34-215 (handler)Main handler function for list_docs tool. Validates input (limit, offset, folder), resolves folder name to ID, calls DartClient.listDocs(), calculates pagination metadata, and returns ListDocsOutput.
export async function handleListDocs(input?: ListDocsInput): Promise<ListDocsOutput> { const DART_TOKEN = process.env.DART_TOKEN; if (!DART_TOKEN) { throw new DartAPIError( 'DART_TOKEN environment variable is required. Get your token from: https://app.dartai.com/?settings=account', 401 ); } // ============================================================================ // Step 1: Defensive input handling and validation // ============================================================================ const safeInput = input || {}; // Validate limit (default 50, max 500) let limit = safeInput.limit !== undefined ? safeInput.limit : 50; if (typeof limit !== 'number' || limit < 1 || limit > 500) { throw new ValidationError( `limit must be a number between 1 and 500 (received: ${limit})`, 'limit' ); } // Validate offset (default 0, non-negative) let offset = safeInput.offset !== undefined ? safeInput.offset : 0; if (typeof offset !== 'number' || offset < 0) { throw new ValidationError( `offset must be a non-negative number (received: ${offset})`, 'offset' ); } // ============================================================================ // Step 2: Validate folder reference (if provided) // ============================================================================ let resolvedFolder: string | undefined; if (safeInput.folder) { // Get config to validate folder reference let config: DartConfig; try { config = await handleGetConfig({ cache_bust: false }); } catch (error) { if (error instanceof DartAPIError) { throw new DartAPIError( `Failed to retrieve workspace config for folder validation: ${error.message}`, error.statusCode, error.response ); } throw error; } // Check if folder exists (by dart_id or name) if (!config.folders || config.folders.length === 0) { throw new ValidationError( 'No folders found in workspace configuration. Create a folder first in Dart AI.', 'folder' ); } const folder = findFolder(config.folders, safeInput.folder); if (!folder) { const folderNames = getFolderNames(config.folders); const availableFolders = folderNames.join(', '); throw new ValidationError( `Invalid folder: "${safeInput.folder}" not found in workspace. Available folders: ${availableFolders}`, 'folder', folderNames ); } resolvedFolder = folder.dart_id; } // ============================================================================ // Step 3: Call DartClient.listDocs() // ============================================================================ const client = new DartClient({ token: DART_TOKEN }); const apiInput: { folder?: string; title_contains?: string; text_contains?: string; limit: number; offset: number; } = { limit, offset, }; if (resolvedFolder) { apiInput.folder = resolvedFolder; } // Validate title_contains is non-empty string if provided if (safeInput.title_contains !== undefined) { if (typeof safeInput.title_contains !== 'string') { throw new ValidationError( 'title_contains must be a string', 'title_contains' ); } if (safeInput.title_contains.trim() !== '') { apiInput.title_contains = safeInput.title_contains; } // Silently ignore empty/whitespace-only strings } // Validate text_contains is non-empty string if provided if (safeInput.text_contains !== undefined) { if (typeof safeInput.text_contains !== 'string') { throw new ValidationError( 'text_contains must be a string', 'text_contains' ); } if (safeInput.text_contains.trim() !== '') { apiInput.text_contains = safeInput.text_contains; } // Silently ignore empty/whitespace-only strings } let apiResponse: { docs: DartAPIDoc[]; total: number }; try { apiResponse = await client.listDocs(apiInput); } catch (error) { if (error instanceof DartAPIError) { throw new DartAPIError( `Failed to list documents: ${error.message}`, error.statusCode, error.response ); } throw error; } // ============================================================================ // Step 4: Calculate pagination metadata // ============================================================================ const returnedCount = apiResponse.docs.length; const totalCount = apiResponse.total; const hasMore = (offset + returnedCount) < totalCount; const nextOffset = hasMore ? offset + returnedCount : null; // ============================================================================ // Step 5: Build filters_applied object for transparency // ============================================================================ const filtersApplied: Record<string, unknown> = { limit, offset, }; if (resolvedFolder) { filtersApplied.folder = resolvedFolder; } if (safeInput.title_contains) { filtersApplied.title_contains = safeInput.title_contains; } if (safeInput.text_contains) { filtersApplied.text_contains = safeInput.text_contains; } // ============================================================================ // Step 6: Return output // ============================================================================ return { docs: apiResponse.docs, total_count: totalCount, returned_count: returnedCount, has_more: hasMore, next_offset: nextOffset, filters_applied: filtersApplied, }; } // Import DartDoc type from types (avoiding circular dependency) import type { DartDoc as DartAPIDoc } from '../types/index.js'; - src/types/index.ts:525-531 (schema)ListDocsInput interface defining optional input params: folder, title_contains, text_contains, limit, offset.
export interface ListDocsInput { folder?: string; title_contains?: string; text_contains?: string; limit?: number; offset?: number; } - src/types/index.ts:533-540 (schema)ListDocsOutput interface defining return shape: docs array, total_count, returned_count, has_more, next_offset, filters_applied.
export interface ListDocsOutput { docs: DartDoc[]; total_count: number; returned_count: number; has_more: boolean; next_offset: number | null; filters_applied: Record<string, unknown>; } - src/index.ts:664-692 (registration)Tool registration in src/index.ts: json schema defining list_docs name, description, and inputSchema with folder, title_contains, text_contains, limit, offset.
{ name: 'list_docs', description: 'List documents with optional filtering by folder, title_contains, text_contains. Supports pagination.', inputSchema: { type: 'object', properties: { folder: { type: 'string', description: 'Filter by folder (dart_id or name)', }, title_contains: { type: 'string', description: 'Filter by title substring (case-insensitive)', }, text_contains: { type: 'string', description: 'Filter by text content substring (case-insensitive)', }, limit: { type: 'integer', description: 'Max docs to return (default: 50, max: 500)', }, offset: { type: 'integer', description: 'Pagination offset (default: 0)', }, }, }, }, - src/api/dartClient.ts:433-460 (handler)DartClient.listDocs() API method that builds query params and sends GET request to /docs/list endpoint, returning docs array and total count.
async listDocs(input?: { folder?: string; title_contains?: string; text_contains?: string; limit?: number; offset?: number; }): Promise<{ docs: DartDoc[]; total: number }> { const queryParams = new URLSearchParams(); if (input) { // Config returns folder names (strings), not IDs — use name-based param if (input.folder) queryParams.append('folder', input.folder); // API uses "title" and "text" (not "title_contains" / "text_contains") if (input.title_contains) queryParams.append('title', input.title_contains); if (input.text_contains) queryParams.append('text', input.text_contains); if (input.limit !== undefined) queryParams.append('limit', input.limit.toString()); if (input.offset !== undefined) queryParams.append('offset', input.offset.toString()); } const query = queryParams.toString(); const endpoint = query ? `/docs/list?${query}` : '/docs/list'; const response = await this.request<{ count: number; results: any[] }>('GET', endpoint); return { docs: response.results || [], total: response.count || 0, }; }