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
| 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) |
Implementation Reference
- src/search/hierarchy-search.ts:14-126 (handler)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}` }; } }
- src/tools/schemas.ts:286-313 (schema)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 } },
- src/server/roam-server.ts:249-269 (registration)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) }], }; }
- src/tools/tool-handlers.ts:77-84 (registration)Tool handler wrapper method that delegates to SearchOperations.searchHierarchyasync searchHierarchy(params: { parent_uid?: string; child_uid?: string; page_title_uid?: string; max_depth?: number; }) { return this.searchOps.searchHierarchy(params); }
- src/tools/schemas.ts:17-17 (helper)Constant definition for the base tool name 'roam_search_hierarchy' used in schema and registration.SEARCH_HIERARCHY: 'roam_search_hierarchy',