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) }],
      };
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses that the tool returns a nested object structure with specific fields (UID, text, order, children array), which adds context beyond the input schema. However, it doesn't mention potential errors (e.g., invalid UID), performance aspects, or authentication needs, leaving some behavioral traits unclear.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, well-structured sentence that front-loads the purpose and key details (fetching block with children, depth specification, return structure). Every part earns its place without waste, making it highly efficient and easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity (fetching hierarchical data), no annotations, and no output schema, the description is fairly complete. It explains the return structure (nested object with fields), which compensates for the lack of output schema. However, it could improve by mentioning error handling or example usage, but it's sufficient for basic understanding.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents both parameters (block_uid and depth) with descriptions and constraints. The description adds value by explaining that depth controls 'the number of levels deep to fetch children,' but this is largely redundant with the schema. Baseline 3 is appropriate as the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Fetch a block by its UID'), the resource ('block'), and the scope ('along with its hierarchical children down to a specified depth'). It distinguishes itself from siblings like roam_fetch_page_by_title (which fetches pages, not blocks with children) and roam_search_block_refs (which searches for references, not fetches hierarchical structure).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage by specifying 'fetch a block by its UID along with its hierarchical children,' suggesting it's for retrieving nested block structures. However, it doesn't explicitly state when to use this tool versus alternatives like roam_fetch_page_by_title (for pages) or roam_search_hierarchy (which might search but not fetch full nested data). No exclusions or prerequisites are mentioned.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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