Skip to main content
Glama
camiloluvino

Roam Research MCP Server

by camiloluvino

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
NameRequiredDescriptionDefault
statusYesStatus to search for (TODO or DONE)
page_title_uidNoOptional: Title or UID of the page to search in (UID is preferred for accuracy). If not provided, searches across all pages.
includeNoOptional: Comma-separated list of terms to filter results by inclusion (matches content or page title)
excludeNoOptional: Comma-separated list of terms to filter results by exclusion (matches content or page title)

Implementation Reference

  • 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);
      }
    }
  • 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']
      }
    },
  • 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}"` : ''}`
      };
    }
  • 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);
    }

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/camiloluvino/roamMCP'

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