Roam Research Python client with built-in CLI and MCP server support.
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file roamresearch_client_py-0.4.1.tar.gz.
File metadata
- Download URL: roamresearch_client_py-0.4.1.tar.gz
- Upload date:
- Size: 47.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d718141f988749318e250467fea5a02f65410888dd6e43ee41ba4a2dbf73e27
|
|
| MD5 |
847b2c82d78e61fd2e1f99603d77ccda
|
|
| BLAKE2b-256 |
284585d768dca113101113ea3f82b6029e419752d46e5dd677a190d2cc9a11d6
|
Provenance
The following attestation bundles were made for roamresearch_client_py-0.4.1.tar.gz:
Publisher:
release-from-pr-comment.yml on Leechael/roamresearch-client-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
roamresearch_client_py-0.4.1.tar.gz -
Subject digest:
8d718141f988749318e250467fea5a02f65410888dd6e43ee41ba4a2dbf73e27 - Sigstore transparency entry: 797507603
- Sigstore integration time:
-
Permalink:
Leechael/roamresearch-client-py@3d802df1cafe9274f7b45b6c4bd9f512b074da07 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Leechael
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-from-pr-comment.yml@3d802df1cafe9274f7b45b6c4bd9f512b074da07 -
Trigger Event:
issue_comment
-
Statement type:
File details
Details for the file roamresearch_client_py-0.4.1-py3-none-any.whl.
File metadata
- Download URL: roamresearch_client_py-0.4.1-py3-none-any.whl
- Upload date:
- Size: 51.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98739295d2d2b671d3190f6d467e098bc816843ca98c8154bff08092c5548de8
|
|
| MD5 |
8b8ab0790d9d379897d34d8809e14e07
|
|
| BLAKE2b-256 |
61db0af8729e2cd9b3677af7618c25f991e246a669df1ebccb66eada39b767e4
|
Provenance
The following attestation bundles were made for roamresearch_client_py-0.4.1-py3-none-any.whl:
Publisher:
release-from-pr-comment.yml on Leechael/roamresearch-client-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
roamresearch_client_py-0.4.1-py3-none-any.whl -
Subject digest:
98739295d2d2b671d3190f6d467e098bc816843ca98c8154bff08092c5548de8 - Sigstore transparency entry: 797507605
- Sigstore integration time:
-
Permalink:
Leechael/roamresearch-client-py@3d802df1cafe9274f7b45b6c4bd9f512b074da07 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Leechael
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-from-pr-comment.yml@3d802df1cafe9274f7b45b6c4bd9f512b074da07 -
Trigger Event:
issue_comment
-
Statement type: