roam_search_hierarchy
Search up or down the block hierarchy to find parent or child blocks. Specify a block UID to navigate its relational tree within a page or graph.
Instructions
Search for parent or child blocks in the block hierarchy. Can search up or down the hierarchy from a given block.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| parent_uid | No | Optional: UID of the block to find children of | |
| child_uid | No | Optional: UID of the block to find parents of | |
| page_title_uid | No | Optional: Title or UID of the page to search in (UID is preferred for accuracy). | |
| max_depth | No | Optional: How many levels deep to search (default: 1) | |
| graph | No | Target graph key from ROAM_GRAPHS config. Defaults to ROAM_DEFAULT_GRAPH. Only needed in multi-graph mode. | |
| write_key | No | Write confirmation key. Required for write operations on non-default graphs when write_key is configured. |
Implementation Reference
- src/search/hierarchy-search.ts:15-118 (handler)Core handler executing the hierarchy search logic. Uses Datomic Datalog queries with an ancestor rule to find descendants (given parent_uid) or ancestors (given child_uid) of a block. Optionally scoped to a page via page_title_uid. Returns matched blocks with resolved content and 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); } 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 = [ANCESTOR_RULE, 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 = [ANCESTOR_RULE, 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 = [ANCESTOR_RULE, 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 = [ANCESTOR_RULE, 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}` }; } } - src/search/hierarchy-search.ts:8-13 (schema)TypeScript interface defining the input parameters for the hierarchy search operation.
export interface HierarchySearchParams { parent_uid?: string; // Search for children of this block child_uid?: string; // Search for parents of this block page_title_uid?: string; max_depth?: number; // How many levels deep to search (default: 1) } - src/tools/schemas.ts:313-340 (schema)MCP tool schema definition for roam_search_hierarchy. Defines the input JSON Schema with parameters parent_uid, child_uid, page_title_uid, and max_depth.
roam_search_hierarchy: { name: 'roam_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: withMultiGraphParams({ 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 } }, - src/server/roam-server.ts:332-352 (registration)MCP server registration: case branch in the call-tool request handler that dispatches to toolHandlers.searchHierarchy(). Validates that exactly one of parent_uid or child_uid is provided.
case 'roam_search_hierarchy': { const params = cleanedArgs 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 toolHandlers.searchHierarchy(params); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } - src/tools/tool-handlers.ts:95-102 (helper)ToolHandlers class delegates searchHierarchy to the searchOps (SearchOperations) instance.
async searchHierarchy(params: { parent_uid?: string; child_uid?: string; page_title_uid?: string; max_depth?: number; }) { return this.searchOps.searchHierarchy(params); }