Skip to main content
Glama
camiloluvino

Roam Research MCP Server

by camiloluvino

roam_search_hierarchy

Navigate Roam Research block hierarchies by searching for parent or child blocks from a given starting point, enabling structured exploration of connected knowledge.

Instructions

Search for parent or child blocks in the block hierarchy. Can search up or down the hierarchy from a given block.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
parent_uidNoOptional: UID of the block to find children of
child_uidNoOptional: UID of the block to find parents of
page_title_uidNoOptional: Title or UID of the page to search in (UID is preferred for accuracy).
max_depthNoOptional: How many levels deep to search (default: 1)

Implementation Reference

  • Core implementation of the roam_search_hierarchy tool. Executes Datomic queries to retrieve either descendants (children hierarchy) of a parent_uid or ancestors (parent hierarchy) of a child_uid, optionally limited to a specific page. Resolves block references in content and includes depth information.
    export class HierarchySearchHandler extends BaseSearchHandler {
      constructor(
        graph: Graph,
        private params: HierarchySearchParams
      ) {
        super(graph);
      }
    
      async execute(): Promise<SearchResult> {
        const { parent_uid, child_uid, page_title_uid, max_depth = 1 } = this.params;
    
        if (!parent_uid && !child_uid) {
          return {
            success: false,
            matches: [],
            message: 'Either parent_uid or child_uid must be provided'
          };
        }
    
        // Get target page UID if provided
        let targetPageUid: string | undefined;
        if (page_title_uid) {
          targetPageUid = await SearchUtils.findPageByTitleOrUid(this.graph, page_title_uid);
        }
    
        // Define ancestor rule for recursive traversal
        const ancestorRule = `[
          [ (ancestor ?child ?parent) 
              [?parent :block/children ?child] ]
          [ (ancestor ?child ?a) 
              [?parent :block/children ?child] 
              (ancestor ?parent ?a) ]
        ]`;
    
        let queryStr: string;
        let queryParams: any[];
    
        if (parent_uid) {
          // Search for all descendants using ancestor rule
          if (targetPageUid) {
            queryStr = `[:find ?block-uid ?block-str ?depth
                        :in $ % ?parent-uid ?page-uid
                        :where [?p :block/uid ?page-uid]
                               [?parent :block/uid ?parent-uid]
                               (ancestor ?b ?parent)
                               [?b :block/string ?block-str]
                               [?b :block/uid ?block-uid]
                               [?b :block/page ?p]
                               [(get-else $ ?b :block/path-length 1) ?depth]]`;
            queryParams = [ancestorRule, parent_uid, targetPageUid];
          } else {
            queryStr = `[:find ?block-uid ?block-str ?page-title ?depth
                        :in $ % ?parent-uid
                        :where [?parent :block/uid ?parent-uid]
                               (ancestor ?b ?parent)
                               [?b :block/string ?block-str]
                               [?b :block/uid ?block-uid]
                               [?b :block/page ?p]
                               [?p :node/title ?page-title]
                               [(get-else $ ?b :block/path-length 1) ?depth]]`;
            queryParams = [ancestorRule, parent_uid];
          }
        } else {
          // Search for ancestors using the same rule
          if (targetPageUid) {
            queryStr = `[:find ?block-uid ?block-str ?depth
                        :in $ % ?child-uid ?page-uid
                        :where [?p :block/uid ?page-uid]
                               [?child :block/uid ?child-uid]
                               (ancestor ?child ?b)
                               [?b :block/string ?block-str]
                               [?b :block/uid ?block-uid]
                               [?b :block/page ?p]
                               [(get-else $ ?b :block/path-length 1) ?depth]]`;
            queryParams = [ancestorRule, child_uid, targetPageUid];
          } else {
            queryStr = `[:find ?block-uid ?block-str ?page-title ?depth
                        :in $ % ?child-uid
                        :where [?child :block/uid ?child-uid]
                               (ancestor ?child ?b)
                               [?b :block/string ?block-str]
                               [?b :block/uid ?block-uid]
                               [?b :block/page ?p]
                               [?p :node/title ?page-title]
                               [(get-else $ ?b :block/path-length 1) ?depth]]`;
            queryParams = [ancestorRule, child_uid];
          }
        }
    
        const rawResults = await q(this.graph, queryStr, queryParams) as [string, string, string?, number?][];
        
        // Resolve block references and format results to include depth information
        const matches = await Promise.all(rawResults.map(async ([uid, content, pageTitle, depth]) => {
          const resolvedContent = await resolveRefs(this.graph, content);
          return {
            block_uid: uid,
            content: resolvedContent,
            depth: depth || 1,
            ...(pageTitle && { page_title: pageTitle })
          };
        }));
    
        const searchDescription = parent_uid
          ? `descendants of block ${parent_uid}`
          : `ancestors of block ${child_uid}`;
    
        return {
          success: true,
          matches,
          message: `Found ${matches.length} block(s) as ${searchDescription}`
        };
      }
    }
  • Input schema definition for the roam_search_hierarchy tool, including parameters for searching hierarchy up or down from a block.
    [toolName(BASE_TOOL_NAMES.SEARCH_HIERARCHY)]: {
      name: toolName(BASE_TOOL_NAMES.SEARCH_HIERARCHY),
      description: 'Search for parent or child blocks in the block hierarchy. Can search up or down the hierarchy from a given block.',
      inputSchema: {
        type: 'object',
        properties: {
          parent_uid: {
            type: 'string',
            description: 'Optional: UID of the block to find children of'
          },
          child_uid: {
            type: 'string',
            description: 'Optional: UID of the block to find parents of'
          },
          page_title_uid: {
            type: 'string',
            description: 'Optional: Title or UID of the page to search in (UID is preferred for accuracy).'
          },
          max_depth: {
            type: 'integer',
            description: 'Optional: How many levels deep to search (default: 1)',
            minimum: 1,
            maximum: 10
          }
        }
        // Note: Validation for either parent_uid or child_uid is handled in the server code
      }
    },
  • MCP tool registration and dispatching for roam_search_hierarchy in the server request handler, including input validation.
    case BASE_TOOL_NAMES.SEARCH_HIERARCHY: {
      const params = request.params.arguments as {
        parent_uid?: string;
        child_uid?: string;
        page_title_uid?: string;
        max_depth?: number;
      };
    
      // Validate that either parent_uid or child_uid is provided, but not both
      if ((!params.parent_uid && !params.child_uid) || (params.parent_uid && params.child_uid)) {
        throw new McpError(
          ErrorCode.InvalidRequest,
          'Either parent_uid or child_uid must be provided, but not both'
        );
      }
    
      const result = await this.toolHandlers.searchHierarchy(params);
      return {
        content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
      };
    }
  • Tool handler wrapper method that delegates to SearchOperations.searchHierarchy
    async searchHierarchy(params: {
      parent_uid?: string;
      child_uid?: string;
      page_title_uid?: string;
      max_depth?: number;
    }) {
      return this.searchOps.searchHierarchy(params);
    }
  • Constant definition for the base tool name 'roam_search_hierarchy' used in schema and registration.
    SEARCH_HIERARCHY: 'roam_search_hierarchy',

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