Skip to main content
Glama
camiloluvino

Roam Research MCP Server

by camiloluvino

roam_fetch_block_with_children

Retrieve a Roam Research block with its hierarchical child structure to specified depth, returning nested UID, text, order, and children data for knowledge graph analysis.

Instructions

Fetch a block by its UID along with its hierarchical children down to a specified depth. Returns a nested object structure containing the block's UID, text, order, and an array of its children.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
block_uidYesThe UID of the block to fetch.
depthNoOptional: The number of levels deep to fetch children. Defaults to 4.

Implementation Reference

  • Implements the core logic to fetch a Roam block by UID and its children recursively up to a specified depth using Datomic queries on the Roam graph.
    async fetchBlockWithChildren(block_uid_raw: string, depth: number = 4): Promise<RoamBlock | null> {
      if (!block_uid_raw) {
        throw new McpError(ErrorCode.InvalidRequest, 'block_uid is required.');
      }
    
      const block_uid = block_uid_raw.replace(/^\(\((.*)\)\)$/, '$1');
    
      const fetchChildren = async (parentUids: string[], currentDepth: number): Promise<Record<string, RoamBlock[]>> => {
        if (currentDepth >= depth || parentUids.length === 0) {
          return {};
        }
    
        const childrenQuery = `[:find ?parentUid ?childUid ?childString ?childOrder ?childHeading
                                :in $ [?parentUid ...]
                                :where [?parent :block/uid ?parentUid]
                                       [?parent :block/children ?child]
                                       [?child :block/uid ?childUid]
                                       [?child :block/string ?childString]
                                       [?child :block/order ?childOrder]
                                       [(get-else $ ?child :block/heading 0) ?childHeading]]`;
    
        const childrenResults = await q(this.graph, childrenQuery, [parentUids]) as [string, string, string, number, number | null][];
    
        const childrenByParent: Record<string, RoamBlock[]> = {};
        const allChildUids: string[] = [];
    
        for (const [parentUid, childUid, childString, childOrder, childHeading] of childrenResults) {
          if (!childrenByParent[parentUid]) {
            childrenByParent[parentUid] = [];
          }
          childrenByParent[parentUid].push({
            uid: childUid,
            string: childString,
            order: childOrder,
            heading: childHeading || undefined,
            children: [],
          });
          allChildUids.push(childUid);
        }
    
        const grandChildren = await fetchChildren(allChildUids, currentDepth + 1);
    
        for (const parentUid in childrenByParent) {
          for (const child of childrenByParent[parentUid]) {
            child.children = grandChildren[child.uid] || [];
          }
          childrenByParent[parentUid].sort((a, b) => a.order - b.order);
        }
    
        return childrenByParent;
      };
    
      try {
        const rootBlockQuery = `[:find ?string ?order ?heading
                                 :in $ ?blockUid
                                 :where [?b :block/uid ?blockUid]
                                        [?b :block/string ?string]
                                        [?b :block/order ?order]
                                        [(get-else $ ?b :block/heading 0) ?heading]]`;
        const rootBlockResult = await q(this.graph, rootBlockQuery, [block_uid]) as [string, number, number | null] | null;
    
        if (!rootBlockResult) {
          return null;
        }
    
        const [rootString, rootOrder, rootHeading] = rootBlockResult;
        const childrenMap = await fetchChildren([block_uid], 0);
    
        return {
          uid: block_uid,
          string: rootString,
          order: rootOrder,
          heading: rootHeading || undefined,
          children: childrenMap[block_uid] || [],
        };
      } catch (error) {
        throw new McpError(
          ErrorCode.InternalError,
          `Failed to fetch block with children: ${error instanceof Error ? error.message : String(error)}`
        );
      }
    }
  • Defines the input schema, description, and name for the 'roam_fetch_block_with_children' tool.
    [toolName(BASE_TOOL_NAMES.FETCH_BLOCK_WITH_CHILDREN)]: {
      name: toolName(BASE_TOOL_NAMES.FETCH_BLOCK_WITH_CHILDREN),
      description: 'Fetch a block by its UID along with its hierarchical children down to a specified depth. Returns a nested object structure containing the block\'s UID, text, order, and an array of its children.',
      inputSchema: {
        type: 'object',
        properties: {
          block_uid: {
            type: 'string',
            description: 'The UID of the block to fetch.'
          },
          depth: {
            type: 'integer',
            description: 'Optional: The number of levels deep to fetch children. Defaults to 4.',
            minimum: 0,
            maximum: 10
          }
        },
        required: ['block_uid']
      },
    },
  • Registers the tool by handling the CallToolRequest in the MCP server, dispatching to ToolHandlers.fetchBlockWithChildren based on the base tool name.
    case BASE_TOOL_NAMES.FETCH_BLOCK_WITH_CHILDREN: {
      const { block_uid, depth } = request.params.arguments as {
        block_uid: string;
        depth?: number;
      };
      const result = await this.toolHandlers.fetchBlockWithChildren(block_uid, depth);
      return {
        content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
      };
    }

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