roamresearch-client-py
Roam Research client. Programmable. CLI. SDK. MCP.
For developers who automate. For LLMs that need graph access. Smart diff keeps UIDs intact. References survive. Minimal API calls.
Install
pip install roamresearch-client-py
# standalone
uv tool install roamresearch-client-py
Setup
export ROAM_API_TOKEN="your-token"
export ROAM_API_GRAPH="your-graph"
Or rr init creates ~/.config/roamresearch-client-py/config.toml:
[roam]
api_token = "your-token"
api_graph = "your-graph"
[mcp]
host = "127.0.0.1"
port = 9000
topic_node = ""
[batch]
size = 100
max_retries = 3
[logging]
level = "WARNING"
httpx_level = "WARNING"
Env vars take precedence.
CLI
Get
rr get "Page Title"
rr get "((block-uid))"
rr get "Page Title" --debug
Search
rr search "keyword"
rr search "term1" "term2"
rr search "term" --tag "#TODO"
rr search --tag "[[Project]]"
rr search "term" --page "Page" -i -n 50
Save
Create or update. Preserves UIDs.
rr save -t "Page" -f content.md
echo "# Hello" | rr save -t "Page"
Refs
rr refs "Page Title"
rr refs "block-uid"
Todos
rr todos
rr todos --done
rr todos --page "Work" -n 100
Query
rr q '[:find ?title :where [?e :node/title ?title]]'
rr q '[:find ?t :in $ ?p :where ...]' --args "Page"
MCP
rr mcp
rr mcp --port 9100
rr mcp --token <T> --graph <G>
Endpoints (default host 127.0.0.1, port 9000):
- Streamable HTTP:
http://127.0.0.1:9000/mcp - SSE:
- Event stream:
http://127.0.0.1:9000/sse - Client messages:
http://127.0.0.1:9000/messages
- Event stream:
Optional OAuth 2.0 (config-only; no users/DB):
- Protected Resource Metadata (RFC 9728):
http://127.0.0.1:9000/.well-known/oauth-protected-resourcehttp://127.0.0.1:9000/.well-known/oauth-protected-resource/mcphttp://127.0.0.1:9000/mcp/.well-known/oauth-protected-resource
- Authorization Server Metadata (RFC 8414):
http://127.0.0.1:9000/.well-known/oauth-authorization-serverhttp://127.0.0.1:9000/.well-known/oauth-authorization-server/mcphttp://127.0.0.1:9000/mcp/.well-known/oauth-authorization-server
- Token endpoint:
http://127.0.0.1:9000/oauth/token - Authorization endpoint (for
authorization_code+ PKCE):http://127.0.0.1:9000/authorize
Enable in ~/.config/roamresearch-client-py/config.toml:
[oauth]
enabled = true
require_auth = true
allow_access_token_query = false
signing_secret = "change-me-long-random"
[[oauth.clients]]
id = "claude"
secret = "" # empty for public client (authorization_code + PKCE)
scopes = ["mcp"]
redirect_uris = ["https://claude.ai/api/mcp/auth_callback"]
Notes:
- Disable/skip OAuth: set
[oauth].enabled = false(default). Nooauth.clientsneeded. oauth.clientsis a static allowlist of OAuth2 clients;secretis required forclient_credentialsand optional forauthorization_code(PKCE public clients).- Multiple clients are supported by adding multiple
[[oauth.clients]]sections.
Browser CORS / preflight:
- Some browser MCP clients will send
OPTIONS /ssepreflight requests. Configure allowed origins via:mcp.cors_allow_origins(comma-separated) ormcp.cors_allow_origin_regexmcp.cors_auto_allow_origin_from_host = true(default) allows same-origin requests based on the requestHost(recommended when behind nginx that setsHost/X-Forwarded-Proto).
SDK
Connect
from roamresearch_client_py import RoamClient
async with RoamClient() as client:
pass
async with RoamClient(api_token="...", graph="...") as client:
pass
Write
async with client.create_block("Root") as blk:
blk.write("Child 1")
blk.write("Child 2")
with blk:
blk.write("Grandchild")
blk.write("Child 3")
Read
page = await client.get_page_by_title("Page")
block = await client.get_block_by_uid("uid")
daily = await client.get_daily_page()
Search
results = await client.search_blocks(["python", "async"], limit=50)
todos = await client.search_by_tag("#TODO", limit=50)
refs = await client.find_references("block-uid")
refs = await client.find_page_references("Page Title")
todos = await client.search_todos(status="TODO", page_title="Work")
Update
await client.update_block_text("uid", "New text")
result = await client.update_page_markdown("Page", "## New\n- Item", dry_run=False)
# result['stats'] = {'creates': 0, 'updates': 2, 'moves': 0, 'deletes': 0}
# result['preserved_uids'] = ['uid1', 'uid2']
Query
result = await client.q('[:find ?title :where [?e :node/title ?title]]')
Batch
Atomic operations.
from roamresearch_client_py.client import (
create_page, create_block, update_block, remove_block, move_block
)
actions = [
create_page("New Page"),
create_block("Text", parent_uid="page-uid"),
update_block("uid", "Updated"),
move_block("uid", parent_uid="new-parent", order=0),
remove_block("old-uid"),
]
await client.batch_actions(actions)
MCP Tools
| Tool | Description |
|---|---|
save_markdown |
Create/update page |
get |
Fetch as markdown |
search |
Text + tag search |
query |
Raw Datalog |
find_references |
Block/page refs |
search_todos |
TODO/DONE items |
update_markdown |
Smart diff update |
{"title": "Notes", "markdown": "## Topic\n- Point"}
{"terms": ["python"], "tag": "TODO", "limit": 20}
{"identifier": "Page", "markdown": "## New", "dry_run": true}
Internals
Smart Diff — Match by content. Preserve UIDs. Detect moves. Minimize calls.
Markdown ↔ Roam — Bidirectional. Headings, lists, tables, code, inline.
Task Queue — SQLite. Background. Retry. JSONL logs.
Requirements
- Python 3.10+
- Roam API token
License
MIT
Metadata
Release files for roamresearch-client-py 0.4.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| roamresearch_client_py-0.4.1.tar.gz | 47.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| roamresearch_client_py-0.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 98.3 kB
Release files / roamresearch_client_py-0.4.1.tar.gz
| Download URL | roamresearch_client_py-0.4.1.tar.gz |
|---|---|
| Size | 47.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8d718141f988749318e250467fea5a02f65410888dd6e43ee41ba4a2dbf73e27
|
|
BLAKE2b-256 checksum How to use checksums |
284585d768dca113101113ea3f82b6029e419752d46e5dd677a190d2cc9a11d6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 6, 2026.
Transparency logRelease files / roamresearch_client_py-0.4.1-py3-none-any.whl
| Download URL | roamresearch_client_py-0.4.1-py3-none-any.whl |
|---|---|
| Size | 51.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
98739295d2d2b671d3190f6d467e098bc816843ca98c8154bff08092c5548de8
|
|
BLAKE2b-256 checksum How to use checksums |
61db0af8729e2cd9b3677af7618c25f991e246a669df1ebccb66eada39b767e4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 6, 2026.
Transparency log