roam_recall
Retrieve and organize stored memories from Roam Research pages or tagged blocks, with options to filter by specific tags and sort by creation date.
Instructions
Retrieve all stored memories on page titled MEMORIES_TAG, or tagged block content with the same name. Returns a combined, deduplicated list of memories. Optionally filter blocks with a specific tag and sort by creation date.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| sort_by | No | Sort order for memories based on creation date | newest |
| filter_tag | No | Include only memories with a specific filter tag. For single word tags use format "tag", for multi-word tags use format "tag word" (without brackets) |
Implementation Reference
- src/tools/operations/memory.ts:104-205 (handler)Core implementation of the roam_recall tool handler. Queries the memories page and graph for blocks tagged with MEMORIES_TAG, sorts by creation time, resolves block references, deduplicates, optionally filters by tag, removes the memories tag prefix, and returns the list of memories.async recall(sort_by: 'newest' | 'oldest' = 'newest', filter_tag?: string): Promise<{ success: boolean; memories: string[] }> { // Get memories tag from environment var memoriesTag = process.env.MEMORIES_TAG; if (!memoriesTag) { memoriesTag = "Memories" } // Extract the tag text, removing any formatting const tagText = memoriesTag .replace(/^#/, '') // Remove leading # .replace(/^\[\[/, '').replace(/\]\]$/, ''); // Remove [[ and ]] try { // Get page blocks using query to access actual block content const ancestorRule = `[ [ (ancestor ?b ?a) [?a :block/children ?b] ] [ (ancestor ?b ?a) [?parent :block/children ?b] (ancestor ?parent ?a) ] ]`; // Query to find all blocks on the page const pageQuery = `[:find ?string ?time :in $ % ?title :where [?page :node/title ?title] [?block :block/string ?string] [?block :create/time ?time] (ancestor ?block ?page)]`; // Execute query const pageResults = await q(this.graph, pageQuery, [ancestorRule, tagText]) as [string, number][]; // Process page blocks with sorting let pageMemories = pageResults .sort(([_, aTime], [__, bTime]) => sort_by === 'newest' ? bTime - aTime : aTime - bTime ) .map(([content]) => content); // Get tagged blocks from across the graph const tagResults = await this.searchOps.searchForTag(tagText); // Process tagged blocks with sorting let taggedMemories = tagResults.matches .sort((a: SearchResult, b: SearchResult) => { const aTime = a.block_uid ? parseInt(a.block_uid.split('-')[0], 16) : 0; const bTime = b.block_uid ? parseInt(b.block_uid.split('-')[0], 16) : 0; return sort_by === 'newest' ? bTime - aTime : aTime - bTime; }) .map(match => match.content); // Resolve any block references in both sets const resolvedPageMemories = await Promise.all( pageMemories.map(async (content: string) => resolveRefs(this.graph, content)) ); const resolvedTaggedMemories = await Promise.all( taggedMemories.map(async (content: string) => resolveRefs(this.graph, content)) ); // Combine both sets and remove duplicates while preserving order let uniqueMemories = [ ...resolvedPageMemories, ...resolvedTaggedMemories ].filter((memory, index, self) => self.indexOf(memory) === index ); // Format filter tag with exact Roam tag syntax const filterTagFormatted = filter_tag ? (filter_tag.includes(' ') ? `#[[${filter_tag}]]` : `#${filter_tag}`) : null; // Filter by exact tag match if provided if (filterTagFormatted) { uniqueMemories = uniqueMemories.filter(memory => memory.includes(filterTagFormatted)); } // Format memories tag for removal and clean up memories tag const memoriesTagFormatted = tagText.includes(' ') || tagText.includes('/') ? `#[[${tagText}]]` : `#${tagText}`; uniqueMemories = uniqueMemories.map(memory => memory.replace(memoriesTagFormatted, '').trim()); // return { // success: true, // memories: [ // `memoriesTag = ${memoriesTag}`, // `filter_tag = ${filter_tag}`, // `filterTagFormatted = ${filterTagFormatted}`, // `memoriesTagFormatted = ${memoriesTagFormatted}`, // ] // } return { success: true, memories: uniqueMemories }; } catch (error: any) { throw new McpError( ErrorCode.InternalError, `Failed to recall memories: ${error.message}` ); } }
- src/tools/schemas.ts:435-453 (schema)Input schema definition for the roam_recall tool, including parameters for sorting and filtering memories.[toolName(BASE_TOOL_NAMES.RECALL)]: { name: toolName(BASE_TOOL_NAMES.RECALL), description: 'Retrieve all stored memories on page titled MEMORIES_TAG, or tagged block content with the same name. Returns a combined, deduplicated list of memories. Optionally filter blocks with a specific tag and sort by creation date.', inputSchema: { type: 'object', properties: { sort_by: { type: 'string', description: 'Sort order for memories based on creation date', enum: ['newest', 'oldest'], default: 'newest' }, filter_tag: { type: 'string', description: 'Include only memories with a specific filter tag. For single word tags use format "tag", for multi-word tags use format "tag word" (without brackets)' } } } },
- src/server/roam-server.ts:307-316 (registration)Registration of the roam_recall tool in the MCP server switch statement, dispatching to ToolHandlers.recall().case BASE_TOOL_NAMES.RECALL: { const { sort_by = 'newest', filter_tag } = request.params.arguments as { sort_by?: 'newest' | 'oldest'; filter_tag?: string; }; const result = await this.toolHandlers.recall(sort_by, filter_tag); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; }
- src/tools/tool-handlers.ts:114-116 (handler)Wrapper handler method in ToolHandlers class that delegates to MemoryOperations.recall()async recall(sort_by: 'newest' | 'oldest' = 'newest', filter_tag?: string) { return this.memoryOps.recall(sort_by, filter_tag); }
- src/tools/schemas.ts:23-23 (schema)Base tool name constant used for schema key and registration matching.RECALL: 'roam_recall',