Skip to main content
Glama
camiloluvino

Roam Research MCP Server

by camiloluvino

roam_search_by_text

Search for text across Roam Research pages or within specific pages to find relevant blocks, with options for case sensitivity and pagination.

Instructions

Search for blocks containing specific text across all pages or within a specific page. This tool supports pagination via the limit and offset parameters.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
textYesThe text to search for
page_title_uidNoOptional: Title or UID of the page to search in (UID is preferred for accuracy). If not provided, searches across all pages.
case_sensitiveNoOptional: Whether the search should be case-sensitive. If false, it will search for the provided text, capitalized versions, and first word capitalized versions.
limitNoOptional: The maximum number of results to return. Defaults to 50. Use -1 for no limit, but be aware that very large results sets can impact performance.
offsetNoOptional: The number of results to skip before returning matches. Useful for pagination. Defaults to 0.

Implementation Reference

  • Core handler class TextSearchHandler that executes the roam_search_by_text tool logic using Datomic queries to find blocks matching the text (case-insensitive variations), scoped to optional page, with pagination, resolves block refs, and formats results.
    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; } }
  • JSON schema defining the input parameters and description for the 'roam_search_by_text' tool.
    [toolName(BASE_TOOL_NAMES.SEARCH_BY_TEXT)]: { name: toolName(BASE_TOOL_NAMES.SEARCH_BY_TEXT), description: 'Search for blocks containing specific text across all pages or within a specific page. This tool supports pagination via the `limit` and `offset` parameters.', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'The text to search for' }, page_title_uid: { type: 'string', description: 'Optional: Title or UID of the page to search in (UID is preferred for accuracy). If not provided, searches across all pages.' }, case_sensitive: { type: 'boolean', description: 'Optional: Whether the search should be case-sensitive. If false, it will search for the provided text, capitalized versions, and first word capitalized versions.', default: false }, limit: { type: 'integer', description: 'Optional: The maximum number of results to return. Defaults to 50. Use -1 for no limit, but be aware that very large results sets can impact performance.', default: 50 }, offset: { type: 'integer', description: 'Optional: The number of results to skip before returning matches. Useful for pagination. Defaults to 0.', default: 0 } }, required: ['text'] } },
  • Registration and dispatching logic in the MCP server that routes calls to 'roam_search_by_text' to the ToolHandlers.searchByText method.
    case BASE_TOOL_NAMES.SEARCH_BY_TEXT: { const params = request.params.arguments as { text: string; page_title_uid?: string; }; const result = await this.toolHandlers.searchByText(params); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; }
  • ToolHandlers class method that delegates 'searchByText' calls to SearchOperations.
    async searchByText(params: { text: string; page_title_uid?: string; }) { return this.searchOps.searchByText(params); }
  • SearchOperations class method that instantiates and executes TextSearchHandlerImpl for text search.
    async searchByText(params: TextSearchParams): Promise<SearchHandlerResult> { const handler = new TextSearchHandlerImpl(this.graph, params); return handler.execute(); }

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