UNPKG

roam-research-mcp

Version:
151 lines (150 loc) • 6.81 kB
import * as fs from 'fs'; import * as path from 'path'; import { fileURLToPath } from 'url'; import { PageOperations } from './operations/pages.js'; import { BlockOperations } from './operations/blocks.js'; import { BlockRetrievalOperations } from './operations/block-retrieval.js'; import { SearchOperations } from './operations/search/index.js'; import { MemoryOperations } from './operations/memory.js'; import { TodoOperations } from './operations/todos.js'; import { OutlineOperations } from './operations/outline.js'; import { BatchOperations } from './operations/batch.js'; import { GuidelinesOperations, DEFAULT_GUIDELINES_PAGE } from './operations/guidelines.js'; import { TableOperations } from './operations/table.js'; import { DatomicSearchHandlerImpl } from './operations/search/handlers.js'; import { FullPageViewOperations } from './operations/full-page-view.js'; export class ToolHandlers { constructor(graph, memoriesTag = 'Memories', guidelinesPage = DEFAULT_GUIDELINES_PAGE) { this.graph = graph; this.cachedCheatsheet = null; this.pageOps = new PageOperations(graph); this.blockOps = new BlockOperations(graph); this.blockRetrievalOps = new BlockRetrievalOperations(graph); this.searchOps = new SearchOperations(graph); this.memoryOps = new MemoryOperations(graph, memoriesTag); this.todoOps = new TodoOperations(graph); this.outlineOps = new OutlineOperations(graph); this.batchOps = new BatchOperations(graph); this.tableOps = new TableOperations(graph); this.fullPageViewOps = new FullPageViewOperations(graph, this.pageOps); this.guidelinesOps = new GuidelinesOperations(graph, guidelinesPage); } // Per-graph agent conventions, read from a page in the graph. async getGuidelines() { return this.guidelinesOps.getGuidelines(); } // Page Operations async findPagesModifiedToday(limit = 50, offset = 0, sort_order = 'desc') { return this.pageOps.findPagesModifiedToday(limit, offset, sort_order); } async createPage(title, content) { return this.pageOps.createPage(title, content); } async fetchPageByTitle(title, format) { // The MCP read tool's markdown may be handed straight back to // roam_update_page_markdown, so it must be round-trippable. return this.pageOps.fetchPageByTitle(title, format, { escapeNewlines: true }); } async fetchPageFullView(title, children_depth, max_references) { return this.fullPageViewOps.fetchPageFullView(title, children_depth, max_references); } async getSubPages(prefix, filter_tag, include_content) { return this.fullPageViewOps.fetchSubPages(prefix, filter_tag, include_content); } // Block Operations async fetchBlock(block_uid, depth, include_ancestors) { return this.blockRetrievalOps.fetchBlock(block_uid, depth, include_ancestors); } async moveBlock(block_uid, parent_uid, order = 'last') { return this.blockOps.moveBlock(block_uid, parent_uid, order); } // Search Operations async searchByStatus(status, page_title_uid, include, exclude) { return this.searchOps.searchByStatus(status, page_title_uid, include, exclude); } async searchForTag(primary_tag, page_title_uid, near_tag) { return this.searchOps.searchForTag(primary_tag, page_title_uid, near_tag); } async searchBlockRefs(params) { return this.searchOps.searchBlockRefs(params); } async searchHierarchy(params) { return this.searchOps.searchHierarchy(params); } async searchByText(params) { return this.searchOps.searchByText(params); } async searchByDate(params) { return this.searchOps.searchByDate(params); } // Datomic query async executeDatomicQuery(params) { const handler = new DatomicSearchHandlerImpl(this.graph, params); return handler.execute(); } // Memory Operations async remember(memory, categories, heading, parent_uid, include_memories_tag) { return this.memoryOps.remember(memory, categories, heading, parent_uid, include_memories_tag); } async recall(sort_by = 'newest', filter_tag) { return this.memoryOps.recall(sort_by, filter_tag); } // Todo Operations async addTodos(todos) { return this.todoOps.addTodos(todos); } // Outline Operations async createOutline(outline, page_title_uid, block_text_uid, order) { return this.outlineOps.createOutline(outline, page_title_uid, block_text_uid, order); } async importMarkdown(content, page_uid, page_title, parent_uid, parent_string, order = 'first') { return this.outlineOps.importMarkdown(content, page_uid, page_title, parent_uid, parent_string, order); } // Batch Operations async processBatch(actions) { return this.batchOps.processBatch(actions); } // Table Operations async createTable(input) { return this.tableOps.createTable(input); } // Page Update with Diff async updatePageMarkdown(title, markdown, dryRun = false) { return this.pageOps.updatePageMarkdown(title, markdown, dryRun); } // Page Rename async renamePage(params) { return this.pageOps.renamePage(params); } async getRoamMarkdownCheatsheet() { if (this.cachedCheatsheet) { return this.cachedCheatsheet; } const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const cheatsheetPath = path.join(__dirname, '../Roam_Markdown_Cheatsheet.md'); try { let cheatsheetContent = await fs.promises.readFile(cheatsheetPath, 'utf-8'); const customInstructionsPath = process.env.CUSTOM_INSTRUCTIONS_PATH; if (customInstructionsPath) { try { // Check if file exists asynchronously await fs.promises.access(customInstructionsPath); const customInstructionsContent = await fs.promises.readFile(customInstructionsPath, 'utf-8'); cheatsheetContent += `\n\n${customInstructionsContent}`; } catch (error) { // File doesn't exist or is not readable, ignore custom instructions if (error.code !== 'ENOENT') { console.warn(`Could not read custom instructions file at ${customInstructionsPath}: ${error}`); } } } this.cachedCheatsheet = cheatsheetContent; return cheatsheetContent; } catch (error) { throw new Error(`Failed to read cheatsheet: ${error}`); } } }