Skip to main content
Glama

Roam Research

by 2b3pro
text-search.ts3.72 kB
import { q } from '@roam-research/roam-api-sdk'; import type { Graph } from '@roam-research/roam-api-sdk'; import { BaseSearchHandler, SearchResult, TextSearchParams } from './types.js'; import { SearchUtils } from './utils.js'; import { resolveRefs } from '../tools/helpers/refs.js'; export class TextSearchHandler extends BaseSearchHandler { constructor( graph: Graph, private params: TextSearchParams ) { super(graph); } async execute(): Promise<SearchResult> { const { text, page_title_uid, case_sensitive = false, limit = -1, offset = 0 } = this.params; // Get target page UID if provided for scoped search let targetPageUid: string | undefined; if (page_title_uid) { targetPageUid = await SearchUtils.findPageByTitleOrUid(this.graph, page_title_uid); } const searchTerms: string[] = []; if (case_sensitive) { searchTerms.push(text); } else { searchTerms.push(text); // Add capitalized version (e.g., "Hypnosis") searchTerms.push(text.charAt(0).toUpperCase() + text.slice(1)); // Add all caps version (e.g., "HYPNOSIS") searchTerms.push(text.toUpperCase()); // Add all lowercase version (e.g., "hypnosis") searchTerms.push(text.toLowerCase()); } const whereClauses = searchTerms.map(term => `[(clojure.string/includes? ?block-str "${term}")]`).join(' '); let queryStr: string; let queryParams: (string | number)[] = []; let queryLimit = limit === -1 ? '' : `:limit ${limit}`; let queryOffset = offset === 0 ? '' : `:offset ${offset}`; let queryOrder = `:order ?page-edit-time asc ?block-uid asc`; // Sort by page edit time, then block UID let baseQueryWhereClauses = ` [?b :block/string ?block-str] (or ${whereClauses}) [?b :block/uid ?block-uid] [?b :block/page ?p] [?p :node/title ?page-title] [?p :edit/time ?page-edit-time]`; // Fetch page edit time for sorting if (targetPageUid) { queryStr = `[:find ?block-uid ?block-str ?page-title :in $ ?page-uid ${queryLimit} ${queryOffset} ${queryOrder} :where ${baseQueryWhereClauses} [?p :block/uid ?page-uid]]`; queryParams = [targetPageUid]; } else { queryStr = `[:find ?block-uid ?block-str ?page-title :in $ ${queryLimit} ${queryOffset} ${queryOrder} :where ${baseQueryWhereClauses}]`; } const rawResults = await q(this.graph, queryStr, queryParams) as [string, string, string?][]; // Query to get total count without limit const countQueryStr = `[:find (count ?b) :in $ :where ${baseQueryWhereClauses.replace(/\[\?p :edit\/time \?page-edit-time\]/, '')}]`; // Remove edit time for count query const totalCountResults = await q(this.graph, countQueryStr, queryParams) as number[][]; const totalCount = totalCountResults[0] ? totalCountResults[0][0] : 0; // Resolve block references in content const resolvedResults = await Promise.all( rawResults.map(async ([uid, content, pageTitle]) => { const resolvedContent = await resolveRefs(this.graph, content); return [uid, resolvedContent, pageTitle] as [string, string, string?]; }) ); const searchDescription = `containing "${text}"`; const formattedResults = SearchUtils.formatSearchResults(resolvedResults, searchDescription, !targetPageUid); formattedResults.total_count = totalCount; return formattedResults; } }

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/2b3pro/roam-research-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server