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