Skip to main content
Glama
if001

org-roam-mcp

by if001
README.md
# org-roam-mcp

A deliberately thin MCP server that exposes a small, agent-oriented Org / Org-roam knowledge interface backed by Emacs.

The main design goal is to **reuse Org and Org-roam instead of reimplementing them in the MCP server**. The TypeScript process does not parse Org syntax, manage Org IDs, maintain backlinks, or implement a parallel knowledge database. It owns only transport and process lifecycle. Emacs owns the knowledge semantics.

This project is aimed at a shared knowledge base where:

- a human uses Org-roam normally from Emacs;
- Codex, Claude, or another MCP-capable agent uses the same `.org` files as durable memory;
- the agent runs against its own headless/named Emacs daemon, so its buffer operations do not switch the user's GUI Emacs windows;
- Org-roam SQLite databases are separate per Emacs process while the `.org` files remain the source of truth.

## Architecture

```text
Codex / Claude / other agent
            │
            │ MCP Streamable HTTP or MCP stdio
            ▼
┌──────────────────────────────┐
│ Thin TypeScript MCP server   │
│                              │
│ - MCP transport              │
│ - Emacs lifecycle            │
│ - capability discovery       │
│ - call forwarding            │
└──────────────┬───────────────┘
               │ emacsclient RPC
               ▼
┌──────────────────────────────┐
│ Agent-only Emacs daemon      │
│                              │
│ user's Emacs configuration   │
│ org / org-roam               │
│ org-agent-mcp registry       │
└──────────────┬───────────────┘
               │
               ▼
          shared *.org
          /          \
         /            \
 GUI Emacs DB       Agent DB
```

The MCP server and agent Emacs daemon share a lifecycle. Starting the MCP server starts a named Emacs daemon; stopping the MCP server terminates the daemon it created.

## Why an Emacs backend?

An agent could read and write `.org` files directly, but doing so would duplicate or bypass behavior already provided by Emacs and Org-roam:

- Org IDs and node identity;
- Org-roam database synchronization;
- backlinks and graph queries;
- user-specific `org-directory` / `org-roam-directory` configuration;
- file tags and Org syntax handling;
- custom node creation conventions.

The MCP boundary therefore exposes **semantic capabilities** rather than generic file operations or arbitrary Emacs commands.

## Requirements

- Emacs and `emacsclient`
- Org (bundled with Emacs)
- Org-roam for the bundled knowledge-base capabilities
- Node.js 22+

The TypeScript implementation targets the MCP TypeScript SDK v2 (`@modelcontextprotocol/server`).

## Install

```bash
npm install
npm run build
```

## Run

Recommended for sandboxed agents such as Codex: run the MCP server on the host
as Streamable HTTP, then point the agent at the URL. This keeps Emacs daemon and
`emacsclient` outside the agent sandbox while preserving an agent-only Emacs
process.

```bash
node --env-file ./.env dist/http-server.js
```

The default HTTP endpoint is:

```text
http://127.0.0.1:3333/mcp
```

For stdio MCP clients that can start local processes without sandbox socket
restrictions:

```bash
node --env-file ./.env dist/server.js
```

Useful environment variables:

```bash
ORG_MCP_EMACS=emacs
ORG_MCP_EMACSCLIENT=emacsclient
ORG_MCP_EMACS_SERVER_NAME_PREFIX=org-mcp
ORG_MCP_BOOTSTRAP=/absolute/path/to/elisp/org-agent-mcp.el
ORG_MCP_ROAM_DB=/tmp/my-agent-roam.db
ORG_MCP_HTTP_HOST=127.0.0.1
ORG_MCP_HTTP_PORT=3333
ORG_MCP_HTTP_PATH=/mcp
ORG_MCP_HTTP_TOKEN=
```

The daemon loads the user's normal Emacs init before the MCP bootstrap, allowing existing Org and Org-roam configuration to be reused.

## MCP host configuration

After building the project, point Codex at the host HTTP endpoint:

```toml
[mcp_servers.org_mcp]
url = "http://127.0.0.1:3333/mcp"
```

For stdio MCP hosts, point the agent's MCP host at the generated server:

```json
{
  "command": "node",
  "args": ["/absolute/path/to/org-roam-mcp/dist/server.js"]
}
```

The TypeScript server does **not** contain a hard-coded list of Org tools. On startup it asks the Emacs-side registry for the currently exported capabilities, converts their JSON Schemas into MCP tools, and forwards calls back to that daemon.

This means capabilities can be added or replaced in Emacs Lisp without changing the TypeScript transport.

## Agent skill

The repository includes an optional skill at:

```text
skills/org-roam-knowledge/SKILL.md
```

Install that skill in the location used by your agent (for example, your Codex or Claude skills directory) and enable this MCP server.

The MCP server defines **what operations are available**. The skill defines **how an agent should use the knowledge base**:

- search before creating;
- prefer updating/consolidating existing knowledge;
- create nodes only for durable concepts;
- save reusable knowledge rather than transcripts or temporary logs;
- use meaningful links and sparse tags;
- preserve contradictions and evolving conclusions instead of silently overwriting them;
- avoid direct file edits when an MCP capability exists.

Keeping these concerns separate is intentional:

```text
Skill                   MCP / Emacs
-------------------     ------------------------
when to remember        how to search Org-roam
what is durable         how to create a node
node granularity        how to update a heading
linking policy          how to create an ID link
knowledge hygiene       how to sync Org-roam DB
```

You can replace or modify the skill without changing the server, and you can extend the server capabilities without rewriting the note-taking policy.

## Default capabilities

The default surface is intentionally limited to a Roam-like agent knowledge base. It is not a general Org task-management or Emacs remote-control server. The current practical surface has 14 capabilities: 10 knowledge operations plus 4 read-only graph diagnostics.

| Capability | Purpose |
|---|---|
| `org_roam_search_nodes` | Find nodes by title, alias, ref, or tag |
| `org_roam_search_content` | Search note bodies and return compact snippets |
| `org_roam_get_node` | Read a note and its metadata |
| `org_roam_backlinks` | Find nodes linking to a node |
| `org_roam_forward_links` | Traverse links from a node |
| `org_roam_create_node` | Create a durable knowledge node |
| `org_roam_append_node` | Append a small unstructured addition |
| `org_roam_append_to_heading` | Add knowledge under a meaningful Org heading |
| `org_roam_add_link` | Connect nodes with an Org ID link |
| `org_roam_set_tags` | Replace file-level tags for broad classification |
| `org_roam_graph_stats` | Inspect graph size, connectedness, orphan ratio, and degree summary |
| `org_roam_find_orphans` | Find nodes with no graph connections |
| `org_roam_find_hubs` | Find high-degree knowledge hubs |
| `org_roam_find_bridges` | Find articulation nodes connecting graph regions |

A typical agent workflow is:

```text
search_nodes / search_content
        ↓
get_node
        ↓
backlinks / forward_links if useful
        ↓
existing knowledge fits?
   ┌────┴────┐
  yes        no
   │          │
append to   create_node
heading       │
   └────┬─────┘
        ↓
add_link / set_tags when useful
        ↓
graph diagnostics periodically, not on every write
```

## Implementation lessons borrowed from claude-orgmode

This project does **not** depend on `majorgreys/claude-orgmode` and does not include its Vulpea, attachment, task-management, or diagnostic surface. However, its mature Elisp implementation informed several internal choices:

- operations are keyed by stable Org node IDs rather than titles when mutating data;
- reading and appending operate on the body owned by a node/heading rather than blindly replacing or appending to the whole file;
- child headings are preserved when updating a parent section;
- non-displayed buffers (`find-file-noselect`) are used for edits;
- writes are followed by Org-roam DB synchronization.

These ideas improve safety without expanding the public MCP surface. The implementation remains independent and Org-roam-only.

## Graph analysis

The four graph capabilities are intentionally **read-only diagnostics**. They operate on the node/link relationships already indexed by Org-roam and do not build a second graph database. Repeated links between the same pair are collapsed for structural metrics.

- `org_roam_graph_stats` gives a cheap overview of graph health.
- `org_roam_find_orphans` surfaces isolated notes that may be forgotten or intentionally standalone.
- `org_roam_find_hubs` identifies high-degree entry points for broad topics.
- `org_roam_find_bridges` uses articulation-point analysis to find notes connecting otherwise separable graph regions.

An orphan, hub, or bridge is a **signal**, not an instruction. Agents should not create links, split notes, or merge notes solely to improve graph metrics. Semantic usefulness remains the criterion for edits.

### Deliberately not included

The default surface does not expose:

- Agenda / TODO / schedule / deadline / clock operations;
- refile or archive workflows;
- node deletion or arbitrary file movement;
- arbitrary Elisp evaluation;
- buffer/window control;
- generic file read/write tools;
- arbitrary interactive Org commands.

These exclusions keep the server focused on shared durable knowledge and avoid turning it into a broad Emacs remote-control interface.

## Capability discovery and extension

Capabilities live in an Emacs-side registry. For example:

```elisp
(defun my-org-agent-count-nodes (_args)
  `((count . ,(length (org-roam-node-list)))))

(org-agent-mcp-register
 "org_roam_count_nodes"
 #'my-org-agent-count-nodes
 :title "Count Org-roam nodes"
 :description "Count nodes in the configured Org-roam directory."
 :input-schema '((type . "object") (properties . nil))
 :annotations '((readOnlyHint . t)
                (destructiveHint . :json-false)
                (idempotentHint . t)
                (openWorldHint . :json-false)))
```

Restart the MCP server. The capability is discovered automatically and appears as an MCP tool without a TypeScript change.

This is **capability discovery**, not automatic export of every interactive Emacs command. Only explicitly registered, agent-safe operations are visible.

## Customize node creation

The bundled `org_roam_create_node` uses a conservative, noninteractive fallback. It deliberately avoids attempting to drive arbitrary interactive capture templates in a headless daemon.

To reuse your own filename/header/capture convention, replace the Emacs-side function:

```elisp
(setq org-agent-mcp-create-node-function
      (lambda (title content tags)
        ;; Use your own Org/Org-roam configuration here.
        ;; Return the resulting Org ID.
        ...))
```

This is the intended extension boundary: MCP transport stays unchanged while Emacs owns Org behavior.

## Shared files and separate databases

The human's GUI Emacs and the agent daemon operate on the same `.org` files, but the agent daemon should normally use its own Org-roam SQLite database:

```text
                    shared .org files
                     /           \
                    /             \
              GUI Emacs       Agent Emacs
                  │                │
             normal DB         private DB
```

The `.org` files are the source of truth. This avoids coupling the two Emacs processes to one SQLite connection/state while still exposing the same knowledge graph content.

## Design constraints

- `.org` files are shared and remain the source of truth.
- The agent daemon uses a separate Org-roam SQLite DB by default.
- The MCP process never parses or rewrites Org syntax itself.
- Only registered capabilities are visible to the agent.
- Capability inputs are explicit JSON Schemas.
- Interactive Emacs commands are not automatically exported.
- Tool calls use non-display buffer operations / direct Org-roam APIs and do not switch the user's GUI windows.
- Knowledge-management policy belongs in the skill rather than the transport layer.

## Current limitations

The project intentionally does not yet solve:

- simultaneous writes from multiple agent daemon instances;
- distributed locking between GUI Emacs and agent Emacs;
- automatic live refresh of MCP tools after registry mutation;
- arbitrary interactive `org-capture` / `org-roam-capture` prompts;
- fine-grained per-path authorization.

For one agent MCP process plus one GUI Emacs sharing a note directory, keeping these concerns explicit makes the implementation substantially smaller than a general Emacs MCP server.

## Suggested first use

1. Configure Org-roam normally in Emacs.
2. Build this server.
3. Configure your agent to start `dist/server.js` as an MCP server.
4. Install `skills/org-roam-knowledge/SKILL.md` for the agent.
5. Ask the agent to search an existing topic before writing anything.
6. Ask it to save one durable finding and verify the resulting node/link from your normal GUI Emacs.

Start with conservative autonomous note-taking. Once the quality of retrieval and consolidation is acceptable, broaden when the agent saves knowledge automatically rather than expanding the MCP tool surface first.