roam_search_by_status
Search for TODO or DONE blocks across Roam Research pages using status filters and optional content terms to organize tasks.
Instructions
Search for blocks with a specific status (TODO/DONE) across all pages or within a specific page.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| status | Yes | Status to search for (TODO or DONE) | |
| page_title_uid | No | Optional: Title or UID of the page to search in (UID is preferred for accuracy). If not provided, searches across all pages. | |
| include | No | Optional: Comma-separated list of terms to filter results by inclusion (matches content or page title) | |
| exclude | No | Optional: Comma-separated list of terms to filter results by exclusion (matches content or page title) |
Implementation Reference
- src/search/status-search.ts:12-65 (handler)Core implementation of the status search logic using Datomic queries to find blocks containing '{{[[TODO]]}}' or '{{[[DONE]]}}' markers, with optional page scoping, block ref resolution, and result formatting.export class StatusSearchHandler extends BaseSearchHandler { constructor( graph: Graph, private params: StatusSearchParams ) { super(graph); } async execute(): Promise<SearchResult> { const { status, page_title_uid } = this.params; // Get target page UID if provided let targetPageUid: string | undefined; if (page_title_uid) { targetPageUid = await SearchUtils.findPageByTitleOrUid(this.graph, page_title_uid); } // Build query based on whether we're searching in a specific page let queryStr: string; let queryParams: any[]; if (targetPageUid) { queryStr = `[:find ?block-uid ?block-str :in $ ?status ?page-uid :where [?p :block/uid ?page-uid] [?b :block/page ?p] [?b :block/string ?block-str] [?b :block/uid ?block-uid] [(clojure.string/includes? ?block-str (str "{{[[" ?status "]]}}"))]]`; queryParams = [status, targetPageUid]; } else { queryStr = `[:find ?block-uid ?block-str ?page-title :in $ ?status :where [?b :block/string ?block-str] [?b :block/uid ?block-uid] [?b :block/page ?p] [?p :node/title ?page-title] [(clojure.string/includes? ?block-str (str "{{[[" ?status "]]}}"))]]`; queryParams = [status]; } const rawResults = await q(this.graph, queryStr, queryParams) as [string, string, string?][]; // Resolve block references in content const resolvedResults = await Promise.all( rawResults.map(async ([uid, content, pageTitle]) => { const resolvedContent = await resolveRefs(this.graph, content); return [uid, resolvedContent, pageTitle] as [string, string, string?]; }) ); return SearchUtils.formatSearchResults(resolvedResults, `with status ${status}`, !targetPageUid); } }
- src/tools/schemas.ts:242-268 (schema)Input schema definition, description, and tool name mapping for 'roam_search_by_status'.[toolName(BASE_TOOL_NAMES.SEARCH_BY_STATUS)]: { name: toolName(BASE_TOOL_NAMES.SEARCH_BY_STATUS), description: 'Search for blocks with a specific status (TODO/DONE) across all pages or within a specific page.', inputSchema: { type: 'object', properties: { status: { type: 'string', description: 'Status to search for (TODO or DONE)', enum: ['TODO', 'DONE'] }, page_title_uid: { type: 'string', description: 'Optional: Title or UID of the page to search in (UID is preferred for accuracy). If not provided, searches across all pages.' }, include: { type: 'string', description: 'Optional: Comma-separated list of terms to filter results by inclusion (matches content or page title)' }, exclude: { type: 'string', description: 'Optional: Comma-separated list of terms to filter results by exclusion (matches content or page title)' } }, required: ['status'] } },
- src/server/roam-server.ts:225-236 (registration)Tool registration in the MCP server request handler switch statement, dispatching to ToolHandlers.searchByStatus.case BASE_TOOL_NAMES.SEARCH_BY_STATUS: { const { status, page_title_uid, include, exclude } = request.params.arguments as { status: 'TODO' | 'DONE'; page_title_uid?: string; include?: string; exclude?: string; }; const result = await this.toolHandlers.searchByStatus(status, page_title_uid, include, exclude); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; }
- SearchOperations.searchByStatus method that instantiates the StatusSearchHandler, executes core search, and applies post-processing filters for include/exclude parameters.async searchByStatus( status: 'TODO' | 'DONE', page_title_uid?: string, include?: string, exclude?: string ): Promise<SearchHandlerResult> { const handler = new StatusSearchHandlerImpl(this.graph, { status, page_title_uid, }); const result = await handler.execute(); // Post-process results with include/exclude filters let matches = result.matches; if (include) { const includeTerms = include.split(',').map(term => term.trim()); matches = matches.filter((match: SearchResult) => { const matchContent = match.content; const matchTitle = match.page_title; const terms = includeTerms; return terms.some(term => matchContent.includes(term) || (matchTitle && matchTitle.includes(term)) ); }); } if (exclude) { const excludeTerms = exclude.split(',').map(term => term.trim()); matches = matches.filter((match: SearchResult) => { const matchContent = match.content; const matchTitle = match.page_title; const terms = excludeTerms; return !terms.some(term => matchContent.includes(term) || (matchTitle && matchTitle.includes(term)) ); }); } return { success: true, matches, message: `Found ${matches.length} block(s) with status ${status}${include ? ` including "${include}"` : ''}${exclude ? ` excluding "${exclude}"` : ''}` }; }
- src/tools/tool-handlers.ts:56-63 (helper)Delegation method in ToolHandlers class that routes the tool call to SearchOperations.async searchByStatus( status: 'TODO' | 'DONE', page_title_uid?: string, include?: string, exclude?: string ) { return this.searchOps.searchByStatus(status, page_title_uid, include, exclude); }