Skip to main content

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

Optional OAuth 2.0 (config-only; no users/DB):

  • Protected Resource Metadata (RFC 9728):
    • http://127.0.0.1:9000/.well-known/oauth-protected-resource
    • http://127.0.0.1:9000/.well-known/oauth-protected-resource/mcp
    • http://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-server
    • http://127.0.0.1:9000/.well-known/oauth-authorization-server/mcp
    • http://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). No oauth.clients needed.
  • oauth.clients is a static allowlist of OAuth2 clients; secret is required for client_credentials and optional for authorization_code (PKCE public clients).
  • Multiple clients are supported by adding multiple [[oauth.clients]] sections.

Browser CORS / preflight:

  • Some browser MCP clients will send OPTIONS /sse preflight requests. Configure allowed origins via:
    • mcp.cors_allow_origins (comma-separated) or mcp.cors_allow_origin_regex
    • mcp.cors_auto_allow_origin_from_host = true (default) allows same-origin requests based on the request Host (recommended when behind nginx that sets Host/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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

roamresearch_client_py-0.3.5.tar.gz (47.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

roamresearch_client_py-0.3.5-py3-none-any.whl (51.0 kB view details)

Uploaded Python 3

File details

Details for the file roamresearch_client_py-0.3.5.tar.gz.

File metadata

  • Download URL: roamresearch_client_py-0.3.5.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

Hashes for roamresearch_client_py-0.3.5.tar.gz
Algorithm Hash digest
SHA256 5f86d7b51e378c734d9d4953d470d06e9ef9e495abfe649030ef367fc83be671
MD5 86767fba4909532db78593eb26b2a0cd
BLAKE2b-256 187c351c2ce539d54e915b6c49fc4875f1f91f29f0d6bb8dd4d3d83ec4711ff6

See more details on using hashes here.

Provenance

The following attestation bundles were made for roamresearch_client_py-0.3.5.tar.gz:

Publisher: release-from-pr-comment.yml on Leechael/roamresearch-client-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file roamresearch_client_py-0.3.5-py3-none-any.whl.

File metadata

File hashes

Hashes for roamresearch_client_py-0.3.5-py3-none-any.whl
Algorithm Hash digest
SHA256 f97169d9fec4466271b8f9b278746ce5cedb2a761b4ca1023528106d2e7e138f
MD5 14445a0f5120cb99c2c9ac9c8e0908e2
BLAKE2b-256 d168222a598d9f6a5accb89122949a6346e7bdd5034efdb4d409631d9c6a3153

See more details on using hashes here.

Provenance

The following attestation bundles were made for roamresearch_client_py-0.3.5-py3-none-any.whl:

Publisher: release-from-pr-comment.yml on Leechael/roamresearch-client-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page