Roam Research MCP Server
Accede y gestiona tu grafo de Roam Research a través de su API.
Documentación

Servidor MCP + CLI de Roam Research
Introducción
Creé este proyecto para resolver un problema personal: quería gestionar mi grafo de Roam Research directamente desde Claude Code (y otros LLMs). Mientras construía el servidor del Model Context Protocol (MCP) para dar a los agentes de IA acceso a mis notas, me di cuenta de que las herramientas subyacentes eran lo bastante potentes como para sostenerse por sí solas.
Lo que empezó como un backend para agentes de IA evolucionó hasta convertirse en una CLI independiente con todas las funciones. Ahora puedes usar las mismas capacidades de API desde tu terminal—enviando contenido a Roam, buscando en tu grafo y gestionando tareas—sin necesidad de un LLM.
Tanto si quieres dar a Claude superpoderes sobre tu base de conocimiento como si solo quieres una CLI robusta para tus propios scripts, este proyecto te cubre.

Novedades en v4.0
En una línea: un bloque que contiene un salto de línea suave (Shift+Enter) ahora sobrevive a una reescritura de página. Lee una página, escríbela de nuevo y nada se mueve.
Hasta ahora, un bloque multilínea se representaba como dos líneas físicas, la segunda en la columna 0. Eso restablecía la línea base de sangría del analizador, por lo que cada bloque posterior se colapsaba hacia la raíz y roam_update_page_markdown generaba obedientemente los movimientos necesarios para que tu página real coincidiera. Leer una página y escribir una revisión, el propósito documentado de la herramienta, era suficiente para desencadenarlo. Los cuerpos de las llamadas y los bloques de código delimitados son exactamente los bloques que contienen saltos suaves.
- Los saltos suaves se representan como
⏎. Una página que contiene uno gana una línea de marcador<!-- roam:escaped-newlines -->inicial; consérvala si escribes el markdown de vuelta. Las páginas sin bloques multilínea se representan de forma idéntica en bytes a 3.x, sin marcador ni codificación. - Las barras invertidas nunca son especiales. El diseño anterior escapaba los saltos de línea como
\n, que también es un prefijo común en texto escrito:\nabla,\neq,C:\newdir. El centinela no necesita esa regla, por lo que todos esos se escriben exactamente como se escribieron, en todas partes. - Los viajes de ida y vuelta literales no son operaciones. La salida del renderizador enviada de vuelta sin cambios, incluido el encabezado del título, produce cero acciones. La CLI comparte la corrección:
roam getcanalizado enroam save --updatedeja la página como estaba. - Dos protecciones más en las reescrituras de página. El markdown no vacío que se analiza a cero bloques ahora se rechaza en lugar de eliminar todos los bloques de la página (el markdown genuinamente vacío aún borra una página, como se documenta). Y un primer bloque escrito a mano que resulta ser un H1 que repite el título de la página ya no se elimina en una actualización ordinaria.
- Las referencias vinculadas también se codifican.
roam_fetch_page_full_viewescapa los saltos suaves en bloques referentes y rutas de navegación, no solo en el contenido de la propia página. - Los clientes de navegador pasan la comprobación previa de CORS. El transporte HTTP ahora permite
MCP-Protocol-VersionyLast-Event-ID, ambos que un cliente debe enviar después de la inicialización. Los clientes que no son de navegador nunca se vieron afectados. - El SDK de MCP está fijado a la versión contra la que se ejecuta el conjunto de pruebas, por lo que una instalación nueva obtiene la superficie de protocolo que se probó en lugar de lo que npm sirva ese día.
Por qué una versión principal. Cuatro superficies de lectura devuelven bytes diferentes para cualquier página que contenga un bloque multilínea: roam_fetch_page_by_title (format: "markdown"), roam_fetch_page_full_view, roam_get_subpages y roam get. Si usas el servidor a través de un asistente de IA, no se requiere nada de ti. Un script que analice la salida markdown de páginas multilínea verá la nueva codificación. Si fijaste roam-research-mcp@3, conservas las correcciones de 3.2.0 y su limitación multilínea documentada hasta que vuelvas a fijar.
El detalle completo, incluidos los casos límite y cómo se verificó cada corrección contra el estado anterior, está en el registro de cambios.
En qué se diferencia del servidor MCP oficial de Roam
Roam Research envía su propio servidor MCP y CLI (@roam-research/roam-mcp). Es una buena herramienta, y este proyecto no intenta reemplazarla. Hablan con dos APIs diferentes de Roam, que es la diferencia de la que se deriva todo lo demás.
| Este proyecto | @roam-research/roam-mcp oficial | |
|---|---|---|
| Habla con | La API REST de backend de Roam (token de grafo + nombre de grafo) | La API HTTP local de Roam Desktop |
| Necesita Roam en ejecución | No — funciona sin interfaz | Sí, la aplicación de escritorio debe estar abierta (se abre mediante enlace profundo para iniciarla) |
| Dónde puede ejecutarse | En cualquier lugar: portátil, servidor, contenedor, CI | La máquina que ejecuta Roam Desktop |
| Demonio compartido | Sí — roam server ejecuta un demonio HTTP para cada cliente | stdio por cliente |
| Multi-grafo | Variable de entorno ROAM_GRAPHS, con protección write_key para grafos elegidos | ~/.roam-tools.json, un token por grafo |
| Grafos solo web | Funciona | Solo escritorio |
Usa el servidor oficial cuando quieras la ruta compatible de Roam, o necesites cosas que solo la aplicación en ejecución puede hacer: controlar la interfaz de escritorio (abrir una página, leer la selección actual, manejar la barra lateral), búsqueda semántica/por incrustaciones, sugerencias de enlaces, carga de archivos, comentarios o invocar herramientas que registren las extensiones de Roam.
Usa este cuando Roam no esté en ejecución o no esté instalado — un servidor, un contenedor, un trabajo cron, un paso de CI. O cuando quieras los extras que este proyecto ha desarrollado: una CLI independiente completa con canalización de stdin, un demonio HTTP compartido con autenticación bearer opcional, un diff inteligente de páginas que preserva los UID de bloque (y por lo tanto tus referencias de bloque), operaciones por lotes con marcadores de posición de UID para construir estructuras anidadas en una sola llamada y herramientas de memoria de agente.
Una omisión deliberada: no hay herramienta de eliminación de páginas aquí. Roam no tiene deshacer que pueda revertir una eliminación masiva por API. El servidor oficial sí ofrece delete_page; este proyecto adopta una línea más conservadora.
Interoperan
Los dos servidores comparten convenciones a propósito, por lo que ejecutar ambos no te cuesta nada:
[[roam/agent guidelines]]— ambos leen la misma página para tus convenciones. Escríbelas una vez; ambos las respetan. Consulta Pautas de agente.#.rm-hide/#.rm-private— ambos retienen bloques etiquetados del contenido dirigido a IA. Etiqueta una vez, oculto de ambos. Consulta Ocultar contenido de la IA.
CLI independiente: roam
La CLI roam te permite interactuar con tu grafo directamente desde la terminal. Admite canalización de entrada estándar (stdin) para todos los comandos de creación y recuperación de contenido, lo que la hace perfecta para flujos de trabajo de automatización.
Ejemplos rápidos
# Save a quick thought to your daily page
roam save "Idea: A CLI for Roam would be cool"
# Pipe content from a file to a new page
cat meeting_notes.md | roam save --title "Meeting: Project Alpha"
# Create a TODO item on today's daily page
echo "Buy milk" | roam save --todo
# Prepend to top of page (newest-first ordering)
roam save -p "Changelog" --order first "v2.18.0 release"
# Search your graph and pipe results to another tool
roam search "important" --json | jq .
# Search for pages by namespace prefix
roam search --namespace "Convention" # Finds all Convention/* pages
# Fetch a page by title
roam get "Roam Research"
# Fetch daily pages using any date format (auto-normalized)
roam get today # Today's daily page
roam get 2026-03-21 # ISO date → "March 21st, 2026"
roam get "03/21/2026" # US date → "March 21st, 2026"
roam get "March 21" # Named (assumes current year)
# Fetch a block with ancestors (parent chain to page root)
roam get abc123def -a # Block + children + ancestors
roam get abc123def -a -d 0 # Ancestors only, no children
# Fetch page by UID or Roam URL
roam get page abc123def
roam get page "https://roamresearch.com/#/app/my-graph/page/abc123def"
# Sort and group results
roam get --tag Project --sort created --group-by tag
# Find references (backlinks) to a page
roam refs "Project Alpha"
# Update a block (e.g., toggle TODO status)
roam update ((block-uid)) --todo
# Multi-graph: read from a specific graph
roam get "Page Title" -g work
# Multi-graph: write to a protected graph
roam save "Note" -g work --write-key "$ROAM_SYSTEM_WRITE_KEY"
Comandos disponibles: get, search, save, refs, update, batch, rename, status, server.
Ejecuta roam <command> --help para obtener detalles de cualquier comando.
Instalación
npm install -g roam-research-mcp
# The 'roam' command is now available globally
Herramientas del servidor MCP
El servidor MCP expone estas herramientas a los asistentes de IA (como Claude), lo que les permite leer, escribir y organizar tu grafo de Roam de forma inteligente.
Soporte multi-grafo: Todas las herramientas aceptan parámetros opcionales
graphywrite_key. Usagraphpara apuntar a un grafo específico de tu configuraciónROAM_GRAPHS, ywrite_keypara operaciones de escritura en grafos protegidos.
| Nombre de la herramienta | Descripción |
|---|---|
roam_fetch_page_by_title | Obtener el contenido de una página por título. |
roam_fetch_page_full_view | Obtener el contenido de una página más todas las referencias vinculadas con contexto de ruta de navegación e hijos. |
roam_fetch_block | Obtener un bloque por UID con hijos opcionales (profundidad) y/o ancestros (hasta la raíz de la página). |
roam_create_page | Crear nuevas páginas, opcionalmente con contenido mixto de texto y tablas. |
roam_update_page_markdown | Actualizar una página usando diff inteligente (preserva los UID de bloque). |
roam_get_subpages | Listar subpáginas bajo un prefijo de espacio de nombres (por ejemplo, "Proyecto/") con filtro de etiqueta opcional. |
roam_search_by_text | Búsqueda de texto completo en todo el grafo o dentro de páginas específicas. Admite búsqueda por prefijo de espacio de nombres para títulos de página. |
roam_search_block_refs | Encontrar bloques que referencian una página, etiqueta o UID de bloque. |
roam_search_by_status | Encontrar elementos TODO o DONE. |
roam_search_for_tag | Encontrar bloques que contienen etiquetas específicas (admite exclusión). |
roam_search_by_date | Encontrar bloques/páginas por fecha de creación o modificación. |
roam_find_pages_modified_today | Listar páginas modificadas desde la medianoche. |
roam_add_todo | Añadir elementos TODO a la página diaria de hoy. |
roam_create_table | Crear tablas de Roam con el formato correcto. |
roam_create_outline | Crear esquemas jerárquicos. |
roam_process_batch_actions | Ejecutar múltiples acciones de bajo nivel (crear, mover, actualizar, eliminar) en un solo lote. |
roam_move_block | Mover un bloque a un nuevo padre o posición. |
roam_remember / roam_recall | herramientas especializadas para la gestión de memoria de IA dentro de Roam. |
roam_datomic_query | Ejecutar consultas Datalog sin procesar para filtrado avanzado. |
roam_markdown_cheatsheet | Recuperar la referencia markdown con sabor de Roam. |
roam_get_guidelines | Recuperar las convenciones de agente definidas por el usuario de este grafo. |
Resultados estructurados de herramientas de escritura (v3.0.0+)
Las diez herramientas de escritura declaran un outputSchema y devuelven structuredContent — un objeto validado — junto con el texto habitual. Un cliente puede leer page_uid, uid_map o success directamente en lugar de buscar JSON dentro de una cadena, lo que hace que encadenar llamadas sea más fiable:
// roam_process_batch_actions
{ "success": true, "uid_map": { "parent1": "Xk7mN2pQ9" },
"validation_passed": true, "actions_attempted": 4 }
Tres cosas que vale la pena saber:
- No se quitó nada. El canal de texto no cambia, por lo que un cliente que ignore
structuredContentse comporta exactamente como antes. - Las herramientas de lectura deliberadamente no tienen ninguno. Ya serializan todo su resultado en el canal de texto, por lo que un esquema solo duplicaría la carga útil.
- Estos campos son solo aditivos. Algunos clientes validan respuestas en vivo contra una lista de herramientas en caché, por lo que un campo se añadirá o quedará obsoleto — nunca se renombrará ni eliminará fuera de una versión principal.
Actualización a 4.0.0: las lecturas markdown de una página que contiene un salto de línea suave (Shift+Enter) ahora representan ese salto como
⏎y llevan un marcador<!-- roam:escaped-newlines -->inicial, por lo que la página sobrevive intacta a una reescritura. Las páginas sin bloques multilínea son idénticas en bytes a 3.x. Los usuarios de asistentes de IA no necesitan hacer nada; los scripts que analizan la salida markdown de páginas multilínea ven la nueva codificación. Consulta el registro de cambios.
Actualización desde 2.x: se renombraron tres campos de resultado de escritura —
uid→page_uid(roam_create_page),created_uids→created_blocks(roam_create_outline,roam_import_markdown) ypreservedUids→preserved_uids(roam_update_page_markdown). Esto solo afecta al código que lee esos nombres; si usas el servidor a través de un asistente de IA, nada cambia. Consulta el registro de cambios para saber por qué.
Pautas de agente (por grafo)
roam_get_guidelines lee una página dentro del grafo — [[roam/agent guidelines]] por defecto — con tus propias convenciones: cómo etiquetas, cómo organizas las páginas en espacios de nombres, qué nunca debe hacer un agente. El servidor MCP oficial de Roam lee el mismo título de página, por lo que una página sirve para ambos.
Esto es distinto de CUSTOM_INSTRUCTIONS_PATH, y los dos se componen:
CUSTOM_INSTRUCTIONS_PATH | [[roam/agent guidelines]] | |
|---|---|---|
| Vive en | un archivo en disco | una página en el grafo |
| Alcance | a nivel de servidor, todos los grafos | por grafo |
| Para cambiarlo | edita el archivo, reinicia el servidor | edita la página |
| Responde | cómo escribir markdown de Roam | cómo este usuario quiere que se maneje este grafo |
Simplemente crea la página. Sin ninguna configuración, roam_get_guidelines lee [[roam/agent guidelines]] — el mismo título que lee el propio servidor de Roam, así que escribirlo una vez hace que ambos lo respeten. Crear una página con ese título namespaced exacto es la opción de participación; no se lee nada del grafo a menos que un agente llame explícitamente a la herramienta.
Si la página no existe, la herramienta devuelve exists: false en lugar de fallar, por lo que siempre es seguro llamarla.
También devuelve las reglas que no son tuyas para establecer
Junto con tus convenciones, cada respuesta de roam_get_guidelines lleva un campo roamSyntax: la breve lista de cosas que destruyen contenido — roam_update_page_markdown elimina cada bloque que tu markdown omite, vistas previas truncadas de structure escritas de vuelta como si fueran contenido, referencias a bloques reescritas como texto plano — más una advertencia de que las lecturas excluyen silenciosamente los subárboles de #.rm-hide, y los pocos lugares donde el markdown de Roam invierte el markdown estándar.
Dos razones por las que viaja aquí en lugar de en la hoja de referencia. Llega a cada cliente, incluido uno que nunca llama a roam_markdown_cheatsheet; y se devuelve incluso cuando un grafo no tiene página de pautas, que es exactamente el caso donde un agente tiene menos contexto. La estratificación es deliberada: tus convenciones ganan en estilo, roamSyntax gana en seguridad de datos. Ninguna convención puede hacer que una vista previa truncada esté completa.
La referencia completa de sintaxis — componentes, consultas, embeds, selección de herramientas — permanece en roam_markdown_cheatsheet. roamSyntax es ~800 tokens y está deliberadamente limitada.
Cada grafo puede apuntar a una página diferente, o desactivarla:
ROAM_GRAPHS='{
"personal": {"token": "...", "graph": "..."},
"work": {"token": "...", "graph": "...", "guidelinesPage": "work/agent rules"},
"private": {"token": "...", "graph": "...", "guidelinesPage": false}
}'
ROAM_GUIDELINES_PAGE='team/agent guidelines' # change the default for every graph
El orden de resolución es por grafo guidelinesPage → ROAM_GUIDELINES_PAGE → roam/agent guidelines. Arriba: personal usa la anulación de entorno, work usa su propia página, y private tiene las pautas completamente desactivadas. Solo un false explícito lo desactiva — un valor no establecido nunca lo hace.
Los resultados se almacenan en caché durante 30 segundos — una edición en la página surte efecto sin reiniciar. Una plantilla inicial vive en .roam/agent-guidelines.template.md.
Ten en cuenta que las pautas se leen a través de la ruta normal de páginas, por lo que los bloques etiquetados con #.rm-hide / #.rm-private también se les retienen — ver abajo.
Las lecturas de una página que contiene un salto de línea suave lo renderizan como ⏎ para que cada bloque
permanezca en una línea — una nueva línea sin escapar aterriza en la columna 0 y reasigna
todo lo que le sigue al escribir de vuelta. Tales cargas útiles llevan un marcador
<!-- roam:escaped-newlines --> inicial; consérvalo si escribes el markdown
de vuelta. El markdown que tú autoras nunca se decodifica: las barras invertidas no son especiales, y
solo ⏎ dentro de una carga útil marcada se interpreta. La salida de roam_get_guidelines
es prosa simple — sin centinela, sin marcador.
Ocultando contenido de la IA
Los bloques etiquetados con #.rm-hide o #.rm-private — y todo lo anidado debajo de ellos — se omiten del contenido que estas herramientas devuelven. Tanto la forma de hashtag (#.rm-hide, #[[.rm-hide]]) como la de enlace ([[.rm-hide]]) funcionan. .rm-private es la etiqueta existente de Roam "oculto de otros usuarios"; .rm-hide oculta de la IA específicamente.
Esto sigue la misma convención que el servidor MCP oficial de Roam, por lo que un bloque etiquetado para uno está oculto del otro.
Aplicado a: roam_fetch_page_by_title, roam_fetch_block, roam_fetch_page_full_view, roam_get_subpages, roam_search_by_text, roam_search_for_tag, roam_search_by_status, roam_search_block_refs, roam_search_hierarchy, roam_search_by_date.
Los bloques ocultos también se excluyen del diff de reescritura de página, que es lo que evita que sean eliminados por estar ausentes del markdown que el agente no podría haber escrito. roam_update_page_markdown (y roam save --update) reemplaza una página con lo que le des, eliminando lo que tu markdown omite — por lo que su línea base se poda con este mismo filtro, bajo la regla de que la línea base contra la que un diff elimina debe ser la misma página que al llamador se le permitió leer. Reporta preserved_hidden cuando protegió algo. El contenido se preserva; el orden exacto relativo a los hermanos visibles puede cambiar. Esto fue un error real de pérdida de datos antes de la corrección — ver el changelog.
Este es un filtro de conveniencia, no una garantía de seguridad. roam_datomic_query lee la base de datos directamente y deliberadamente no lo aplica, por lo que un agente capaz aún puede sacar a la superficie bloques ocultos a través de Datalog crudo. Trata estas etiquetas como "mantenlo fuera del camino de la IA", no como "mantenlo en secreto".
La coincidencia de etiquetas no distingue entre mayúsculas y minúsculas, y solo coinciden etiquetas exactas — #.rm-hidden y #.rm-highlight se dejan solas. El conjunto de UIDs ocultos se almacena en caché durante 30 segundos, por lo que un bloque etiquetado justo ahora puede permanecer visible hasta ese tiempo.
Configuración
Variables de Entorno
Modo de Grafo Único
Para un solo grafo de Roam, establece estas en tu entorno o en un archivo .env:
ROAM_API_TOKEN=your-api-token
ROAM_GRAPH_NAME=your-graph-name
Modo Multi-Grafo (v2.0+)
Conéctate a múltiples grafos de Roam desde una sola instancia de servidor:
ROAM_GRAPHS='{
"personal": {"token": "token-1", "graph": "personal-db", "memoriesTag": "#[[Personal Memories]]"},
"work": {"token": "token-2", "graph": "work-db", "protected": true, "memoriesTag": "#[[Work Memories]]"},
"research": {"token": "token-3", "graph": "research-db"}
}'
ROAM_DEFAULT_GRAPH=personal
ROAM_SYSTEM_WRITE_KEY=your-secret-key
Opciones de Configuración de Grafos:
| Propiedad | Requerida | Descripción |
|---|---|---|
token | Sí | Token de API de Roam para este grafo |
graph | Sí | Nombre del grafo/identificador de base de datos |
protected | No | Si true, las escrituras requieren confirmación de ROAM_SYSTEM_WRITE_KEY — excepto en el grafo predeterminado, ver abajo |
memoriesTag | No | Etiqueta para roam_remember/roam_recall (anula el predeterminado global) |
Dos tipos de control de acceso (y cómo difieren)
El servidor tiene dos candados independientes. Son fáciles de confundir porque ambos son "claves" — aquí está la versión simple (ambos son opcionales y desactivados por defecto):
Token portador — HTTP_AUTH_TOKEN | Clave de escritura — ROAM_SYSTEM_WRITE_KEY | |
|---|---|---|
| En una frase | La clave de la puerta principal | El pestillo de una caja fuerte dentro |
| Controla | Quién puede llegar al servidor en absoluto | Si se permite una escritura a un grafo protected |
| Cubre | Todo — lecturas y escrituras, todos los grafos | Solo escrituras, y solo a grafos marcados con protected |
| ¿Protege la lectura? | Sí | No |
| Cuándo lo necesitas | Solo si el servidor es alcanzable más allá de tu propia máquina (p. ej. -H 0.0.0.0) | Siempre que quieras una protección contra ediciones accidentales a grafos importantes |
| Cómo se envía | Cabecera HTTP: Authorization: Bearer <token> | Un argumento write_key en herramientas de escritura / comandos CLI |
Piensa en una casa: el token portador bloquea la puerta principal (mantiene a los extraños completamente fuera), y la clave de escritura bloquea una caja fuerte dentro (incluso alguien ya en la casa la necesita para cambiar lo que hay en la caja fuerte). En tu propia máquina vinculada a 127.0.0.1, la puerta principal da a una pared — no necesitas el token portador allí. La clave de escritura sigue siendo útil localmente como protección de "¿estás seguro?", porque Roam no tiene deshacer.
Entonces: para marcar un grafo como que necesita la clave de escritura, establece protected: true en él y configura ROAM_SYSTEM_WRITE_KEY; los llamadores luego pasan un write_key coincidente para cualquier escritura a ese grafo.
⚠️
protectedno hace nada en tu grafo predeterminado. Las escrituras a cualquier grafo queROAM_DEFAULT_GRAPHnombre siempre están permitidas, antes de queprotectedse consulte — la bandera protege los grafos que tienes que pedir por nombre, con el razonamiento de que alcanzar un grafo no predeterminado es el acto deliberado que vale la pena confirmar. Si quieres que un grafo esté protegido contra escritura, no debe ser tu predeterminado.
Opcional:
ROAM_MEMORIES_TAG: Etiqueta predeterminada pararoam_remember/roam_recall(respaldo cuandomemoriesTagpor grafo no está establecido).HTTP_STREAM_PORT: Puerto para el transporte HTTP Stream (predeterminado 8088). Solo modo--server— el modo stdio no abre ningún socket, por lo que esto se ignora allí.HTTP_STREAM_HOST: Host al que vincular el transporte HTTP (predeterminado127.0.0.1, solo loopback). Solo modo--server. Establece a0.0.0.0para exponer en la LAN, y estableceHTTP_AUTH_TOKENcuando lo hagas.HTTP_AUTH_TOKEN: Token portador opcional que bloquea todo el endpoint HTTP. Sin establecer = abierto (bien para loopback). Cuando se establece, cada solicitud MCP debe enviarAuthorization: Bearer <token>(GET /healthpermanece abierto). Úsalo siempre que te vincules más allá de127.0.0.1. Diferente deROAM_SYSTEM_WRITE_KEY— ver Dos tipos de control de acceso.
Ejecutando el Servidor
1. Modo Predeterminado (stdio) Mejor para integración local (p. ej., Claude Desktop, extensiones de IDE). El cliente MCP lanza el proceso por sesión y habla con él a través de stdin/stdout. No se abre ningún puerto — nada sobre MCP sobre stdio necesita uno.
Antes de 3.1.0 este modo también abría un listener HTTP, y lo vinculaba a cada interfaz. Si estabas usando ese endpoint, ejecuta un daemon
--serveren su lugar; ver abajo.
npx roam-research-mcp
2. Modo de Servidor Compartido (--server)
Mejor para un daemon único de larga duración, solo HTTP, que comparten múltiples clientes MCP — en lugar de que cada sesión genere su propio subproceso. Esto ahorra memoria y da a los clientes una URL estable.
HTTP_STREAM_PORT=8088 npx roam-research-mcp --server
O adminístralo a través del CLI roam, que añade inicio/parada/estado/logs:
roam server start # start the shared daemon in the background
roam server start -H 0.0.0.0 # expose on the LAN (no transport auth!)
roam server status # is it up? version, graphs, active sessions
roam server logs -f # follow the log
roam server stop # stop a CLI-started daemon
roam server status funciona sin importar cómo se lanzó el daemon (sondea /health), por lo que también reporta un daemon iniciado por una unidad LaunchAgent/systemd. El estado (pidfile + log) vive en ~/.roam/ (anula con ROAM_HOME).
Los dos modos son mutuamente excluyentes, y cada uno abre exactamente un transporte: el modo stdio habla stdio y no vincula nada, --server habla HTTP y no lee stdin. En modo --server el servidor:
- ejecuta solo HTTP (sin transporte stdio),
- vincula el exacto
HTTP_STREAM_PORTenHTTP_STREAM_HOSTy sale con código no cero si el puerto está ocupado (sin deriva silenciosa — un daemon compartido debe mantener una URL estable), - expone
GET /health→{"status":"ok", ...}para comprobaciones de liveness.
Apunta los clientes MCP a él con una configuración de transporte HTTP:
{
"mcpServers": {
"roam-research-mcp": {
"type": "http",
"url": "http://127.0.0.1:8088/mcp"
}
}
}
Las variables de entorno (tokens, grafos) viven con el proceso del servidor, no con la configuración del cliente.
Asegurando un servidor expuesto (dos capas):
Si te vinculas más allá del loopback (-H 0.0.0.0), añade el candado perimetral:
HTTP_AUTH_TOKEN=$(openssl rand -hex 32) roam server start -H 0.0.0.0
Los clientes luego envían el token como cabecera:
{
"mcpServers": {
"roam-research-mcp": {
"type": "http",
"url": "http://<host>:8088/mcp",
"headers": { "Authorization": "Bearer <token>" }
}
}
}
Mantén ambos — hacen trabajos diferentes (ver Dos tipos de control de acceso arriba): el token portador controla quién puede conectarse, la clave de escritura solo protege escrituras a grafos protegidos.
⚠️ La clave de escritura no es un sustituto del token portador. En un servidor expuesto sin
HTTP_AUTH_TOKEN, cualquiera en la red aún puede leer cada grafo (y escribir en los no protegidos). Para cualquier cosa más allá del loopback, estableceHTTP_AUTH_TOKEN.
Manteniéndolo en ejecución (LaunchAgent de macOS):
Crea ~/Library/LaunchAgents/com.example.roam-mcp.plist con RunAtLoad + KeepAlive, tus variables de entorno bajo EnvironmentVariables, y --server como la última entrada de ProgramArguments. Mantén StandardOutPath/StandardErrorPath en una ruta local (p. ej. ~/Library/Logs/), luego:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.roam-mcp.plist
curl -s http://127.0.0.1:8088/health # verify
3. Docker
docker run -p 8088:8088 --env-file .env roam-research-mcp --server
Configurando en LLMs
Claude Desktop / Cline:
Añade a tu archivo de configuración MCP (p. ej., ~/Library/Application Support/Claude/claude_desktop_config.json):
Fijando la versión.
npx -y roam-research-mcpobtiene la última versión cada vez que tu cliente inicia el servidor, por lo que una nueva versión principal llega sin previo aviso. Fija la versión principal para decidir tú mismo cuándo migrar:
argsObtienes ["-y", "roam-research-mcp"]Lo último, siempre — incluyendo la próxima versión principal ["-y", "roam-research-mcp@3"]Solo 3.x; las versiones principales requieren una edición aquí ["-y", "roam-research-mcp@3.0.0"]Exactamente esta compilación Fijar la versión principal es el valor predeterminado sensato: sigues recibiendo correcciones y nuevas herramientas, pero un cambio importante se convierte en algo que eliges adoptar. Los ejemplos a continuación permanecen sin fijar para coincidir con lo que la mayoría pega primero.
Gráfico único:
{
"mcpServers": {
"roam-research": {
"command": "npx",
"args": ["-y", "roam-research-mcp"],
"env": {
"ROAM_API_TOKEN": "your-token",
"ROAM_GRAPH_NAME": "your-graph"
}
}
}
}
Multi-gráfico:
{
"mcpServers": {
"roam-research": {
"command": "npx",
"args": ["-y", "roam-research-mcp"],
"env": {
"ROAM_GRAPHS": "{\"personal\":{\"token\":\"token-1\",\"graph\":\"personal-db\",\"memoriesTag\":\"#[[Memories]]\"},\"work\":{\"token\":\"token-2\",\"graph\":\"work-db\",\"protected\":true}}",
"ROAM_DEFAULT_GRAPH": "personal",
"ROAM_SYSTEM_WRITE_KEY": "your-secret-key"
}
}
}
}
Analizador de Bloques de Consulta (v2.11.0+)
Una utilidad para analizar y ejecutar bloques de consulta de Roam programáticamente. Convierte la sintaxis de {{[[query]]: ...}} en consultas Datalog.
Cláusulas Soportadas
| Cláusula | Sintaxis | Descripción |
|---|---|---|
| Referencia de página | [[page]] | Bloques que referencian una página |
| Referencia de bloque | ((uid)) | Bloques que referencian un bloque |
and | {and: [[a]] [[b]]} | Todas las condiciones deben coincidir |
or | {or: [[a]] [[b]]} | Cualquier condición coincide |
not | {not: [[tag]]} | Excluir coincidencias |
between | {between: [[date1]] [[date2]]} | Filtro de rango de fechas |
search | {search: text} | Búsqueda de texto completo |
daily notes | {daily notes: } | Solo páginas de notas diarias |
by | {by: [[User]]} | Creado o editado por usuario |
created by | {created by: [[User]]} | Creado por usuario |
edited by | {edited by: User} | Editado por usuario |
Fechas Relativas
La cláusula between admite fechas relativas: today, yesterday, last week, last month, this year, 7 days ago, 2 months ago, etc.
Uso
import { QueryExecutor } from 'roam-research-mcp/query';
const executor = new QueryExecutor(graph);
// Execute a query
const results = await executor.execute(
'{{[[query]]: "My Query" {and: [[Project]] {between: [[last month]] [[today]]}}}}'
);
// Parse without executing (for debugging)
const { name, query } = QueryParser.parseWithName(queryBlock);
Funciones de Utilidad
import { isQueryBlock, extractQueryBlocks } from 'roam-research-mcp/query';
// Detect if text is a query block
isQueryBlock('{{[[query]]: [[tag]]}}'); // true
// Extract all query blocks from a string
extractQueryBlocks(pageContent); // ['{{[[query]]: ...}}', ...]
Soporte
Si este proyecto te ayuda a gestionar tu base de conocimientos o a construir agentes interesantes, ¡considera invitarme a un café! Ayuda a mantener las actualizaciones.
Licencia
Licencia MIT - Creado por Ian Shen.
