Skip to main content
Glama
camiloluvino

Roam Research MCP Server

by camiloluvino

roam_datomic_query

Execute custom Datomic queries on Roam Research graphs for advanced data retrieval with complex filtering, boolean logic, sorting, and proximity search capabilities.

Instructions

Execute a custom Datomic query on the Roam graph for advanced data retrieval beyond the available search tools. This provides direct access to Roam's query engine. Note: Roam graph is case-sensitive.

Optimal Use Cases for

  • Advanced Filtering (including Regex): Use for scenarios requiring complex filtering, including regex matching on results post-query, which Datalog does not natively support for all data types. It can fetch broader results for client-side post-processing.

  • Highly Complex Boolean Logic: Ideal for intricate combinations of "AND", "OR", and "NOT" conditions across multiple terms or attributes.

  • Arbitrary Sorting Criteria: The go-to for highly customized sorting needs beyond default options.

  • Proximity Search: For advanced search capabilities involving proximity, which are difficult to implement efficiently with simpler tools.

List of some of Roam's data model Namespaces and Attributes: ancestor (descendants), attrs (lookup), block (children, heading, open, order, page, parents, props, refs, string, text-align, uid), children (view-type), create (email, time), descendant (ancestors), edit (email, seen-by, time), entity (attrs), log (id), node (title), page (uid, title), refs (text). Predicates (clojure.string/includes?, clojure.string/starts-with?, clojure.string/ends-with?, <, >, <=, >=, =, not=, !=). Aggregates (distinct, count, sum, max, min, avg, limit). Tips: Use :block/parents for all ancestor levels, :block/children for direct descendants only; combine clojure.string for complex matching, use distinct to deduplicate, leverage Pull patterns for hierarchies, handle case-sensitivity carefully, and chain ancestry rules for multi-level queries.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesThe Datomic query to execute (in Datalog syntax). Example: `[:find ?block-string :where [?block :block/string ?block-string] (or [(clojure.string/includes? ?block-string "hypnosis")] [(clojure.string/includes? ?block-string "trance")] [(clojure.string/includes? ?block-string "suggestion")]) :limit 25]`
inputsNoOptional array of input parameters for the query
regexFilterNoOptional: A regex pattern to filter the results client-side after the Datomic query. Applied to JSON.stringify(result) or specific fields if regexTargetField is provided.
regexFlagsNoOptional: Flags for the regex filter (e.g., "i" for case-insensitive, "g" for global).
regexTargetFieldNoOptional: An array of field paths (e.g., ["block_string", "page_title"]) within each Datomic result object to apply the regex filter to. If not provided, the regex is applied to the stringified full result.

Implementation Reference

  • The core handler implementation for executing custom Datomic queries on the Roam graph, including optional client-side regex filtering on results.
    export class DatomicSearchHandler extends BaseSearchHandler { constructor( graph: Graph, private params: DatomicSearchParams ) { super(graph); } async execute(): Promise<SearchResult> { try { // Execute the datomic query using the Roam API let results = await q(this.graph, this.params.query, this.params.inputs || []) as unknown[]; if (this.params.regexFilter) { let regex: RegExp; try { regex = new RegExp(this.params.regexFilter, this.params.regexFlags); } catch (e) { return { success: false, matches: [], message: `Invalid regex filter provided: ${e instanceof Error ? e.message : String(e)}` }; } results = results.filter(result => { if (this.params.regexTargetField && this.params.regexTargetField.length > 0) { for (const field of this.params.regexTargetField) { // Access nested fields if path is provided (e.g., "prop.nested") const fieldPath = field.split('.'); let value: any = result; for (const part of fieldPath) { if (typeof value === 'object' && value !== null && part in value) { value = value[part]; } else { value = undefined; // Field not found break; } } if (typeof value === 'string' && regex.test(value)) { return true; } } return false; } else { // Default to stringifying the whole result if no target field is specified return regex.test(JSON.stringify(result)); } }); } return { success: true, matches: results.map(result => ({ content: JSON.stringify(result), block_uid: '', // Datomic queries may not always return block UIDs page_title: '' // Datomic queries may not always return page titles })), message: `Query executed successfully. Found ${results.length} results.` }; } catch (error) { return { success: false, matches: [], message: `Failed to execute query: ${error instanceof Error ? error.message : String(error)}` }; } } }
  • The tool schema defining the name, description, and input validation for roam_datomic_query.
    [toolName(BASE_TOOL_NAMES.DATOMIC_QUERY)]: { name: toolName(BASE_TOOL_NAMES.DATOMIC_QUERY), description: 'Execute a custom Datomic query on the Roam graph for advanced data retrieval beyond the available search tools. This provides direct access to Roam\'s query engine. Note: Roam graph is case-sensitive.\n\n__Optimal Use Cases for `roam_datomic_query`:__\n- __Advanced Filtering (including Regex):__ Use for scenarios requiring complex filtering, including regex matching on results post-query, which Datalog does not natively support for all data types. It can fetch broader results for client-side post-processing.\n- __Highly Complex Boolean Logic:__ Ideal for intricate combinations of "AND", "OR", and "NOT" conditions across multiple terms or attributes.\n- __Arbitrary Sorting Criteria:__ The go-to for highly customized sorting needs beyond default options.\n- __Proximity Search:__ For advanced search capabilities involving proximity, which are difficult to implement efficiently with simpler tools.\n\nList of some of Roam\'s data model Namespaces and Attributes: ancestor (descendants), attrs (lookup), block (children, heading, open, order, page, parents, props, refs, string, text-align, uid), children (view-type), create (email, time), descendant (ancestors), edit (email, seen-by, time), entity (attrs), log (id), node (title), page (uid, title), refs (text).\nPredicates (clojure.string/includes?, clojure.string/starts-with?, clojure.string/ends-with?, <, >, <=, >=, =, not=, !=).\nAggregates (distinct, count, sum, max, min, avg, limit).\nTips: Use :block/parents for all ancestor levels, :block/children for direct descendants only; combine clojure.string for complex matching, use distinct to deduplicate, leverage Pull patterns for hierarchies, handle case-sensitivity carefully, and chain ancestry rules for multi-level queries.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The Datomic query to execute (in Datalog syntax). Example: `[:find ?block-string :where [?block :block/string ?block-string] (or [(clojure.string/includes? ?block-string "hypnosis")] [(clojure.string/includes? ?block-string "trance")] [(clojure.string/includes? ?block-string "suggestion")]) :limit 25]`' }, inputs: { type: 'array', description: 'Optional array of input parameters for the query', items: { type: 'string' } }, regexFilter: { type: 'string', description: 'Optional: A regex pattern to filter the results client-side after the Datomic query. Applied to JSON.stringify(result) or specific fields if regexTargetField is provided.' }, regexFlags: { type: 'string', description: 'Optional: Flags for the regex filter (e.g., "i" for case-insensitive, "g" for global).', }, regexTargetField: { type: 'array', items: { type: 'string' }, description: 'Optional: An array of field paths (e.g., ["block_string", "page_title"]) within each Datomic result object to apply the regex filter to. If not provided, the regex is applied to the stringified full result.' } }, required: ['query'] } },
  • Registration and dispatch logic in the MCP server\'s tool call handler that routes calls to roam_datomic_query to the appropriate handler method.
    case BASE_TOOL_NAMES.DATOMIC_QUERY: { const { query, inputs } = request.params.arguments as { query: string; inputs?: unknown[]; }; const result = await this.toolHandlers.executeDatomicQuery({ query, inputs }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; }
  • Intermediate handler method in ToolHandlers class that instantiates and calls the DatomicSearchHandlerImpl.
    // Datomic query async executeDatomicQuery(params: { query: string; inputs?: unknown[] }) { const handler = new DatomicSearchHandlerImpl(this.graph, params); return handler.execute(); }
  • Definition of the base tool name constant used for handler routing and registration.
    DATOMIC_QUERY: 'roam_datomic_query',

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