MCP
MCP
outl ships an MCP (Model Context Protocol) server as a subcommand of the same binary you already have: outl mcp serve.
Claude Desktop, Cursor, Zed, and anything else that speaks MCP can reach the workspace through it — no extra install, no daemon, no parallel codebase.
Every tool you see in the host’s tools panel maps 1:1 to a CLI subcommand.
The wiring lives in docs/cli.md; this page is just about plugging the server into a host.
Wiring it up
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"outl": {
"command": "outl",
"args": ["--workspace", "/Users/avelino/notes", "mcp", "serve"]
}
}
}
--workspace (short -w) is the global flag every subcommand honours; mcp serve targets whichever workspace it points at.
Restart Claude Desktop.
The outl tools and resources show up under the server name; calling any tool is exactly equivalent to running the matching CLI command with --json.
Cursor / Zed / other MCP hosts
The shape is the same. Any host that lets you register an MCP server with a command + args wants:
command: outl
args: ["--workspace", "<absolute path to workspace>", "mcp", "serve"]
Run outl mcp serve --help to see all flags.
From a script (smoke test)
printf '%s\n%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| outl --workspace ~/notes mcp serve
If both lines come back with valid JSON-RPC responses, the server is healthy.
What the host sees
Tools
Every CLI subcommand documented in docs/cli.md is registered as an MCP tool.
Names are outl_<command>_<verb> (e.g.
outl_page_get, outl_block_append, outl_daily_today, outl_search, outl_query).
Input schema mirrors the CLI flags; the response is the JSON envelope’s data field wrapped in MCP’s content shape.
Destructive tools (outl_page_delete, outl_block_delete) require confirm: true in the input.
Without it they return a recoverable CONFIRM_REQUIRED error and the workspace is untouched.
For multi-block authoring, prefer the composite tools over a chain of single-op calls:
outl_block_append_tree— append a root block + its recursive children in one shot.outl_page_create— accepts an optionalcontentforest so a brand-new page lands with its full outline in a single call.outl_batch— apply a sequence of{op, args}writes in one workspace session. Stops on first error and reportsfailed_at/appliedso the caller can pick up the suffix that never ran. Supported ops cover every other write tool. Seedocs/cli.md→ Batch for the payload shape.
Resources
URIs the host can attach as context without an explicit tool call.
Almost all of these are read-only; the one exception (outl://daily/today) lazily materialises today’s journal on first access, the same way outl daily today does in the CLI.
| URI | Type | Body |
|---|---|---|
outl://workspace/info | application/json | path, actor id, counts, ops |
outl://daily/today | text/markdown | today’s journal projection (creates on first read) |
outl://page/{slug} | text/markdown | page projection (template URI) |
Useful pattern: tell Claude Desktop “you are the assistant for my second brain” and attach outl://daily/today so it sees the day’s context without having to call a tool.
Prompts
Slash-style shortcuts the host renders in the prompt picker:
| Prompt | Arguments | What it does |
|---|---|---|
outl-summarize-day | date? (ISO) | pulls daily, asks for a summary |
outl-blog-from-block | block_id | expands a block into a blog draft |
Prompts are nice-to-have.
Same surface works through tools (outl_daily_today
- a free-form prompt) — they’re just keyboard shortcuts.
Architecture in one paragraph
outl mcp serve is a 200-line stdio loop on top of the same Rust handlers the CLI subcommands call.
There is no outl-mcp crate, no parallel logic, no JSON-RPC framework dependency (we speak the protocol directly).
Every new feature lands once, as a function in outl-actions, and is exposed in both surfaces — see crates/outl-cli/CLAUDE.md for the exact “add a new tool” walkthrough.
Troubleshooting
The server starts but the host shows zero tools. Check stderr (outl mcp serve --workspace … 2> /tmp/outl-mcp.log and tail it).
Almost always it’s a permission error reading the workspace path.
workspace at … is locked by another outl process. As of 0.5.1 the workspace lock (.outl/.lock) is shared, so this error is no longer raised on simple co-tenancy: TUI + MCP server + a subprocess CLI all coexist.
The remaining contention point is the per-actor write lock at ops/.lock-<actor>.
The opener tries the config actor first and, if taken, mints an ActorId::new() ephemeral and locks that one instead — so the second outl mcp serve of the day usually just writes to a fresh ops-<ephemeral>.jsonl without telling you.
You only see a hard error when both the config actor AND the ephemeral path can’t be acquired (e.g. a stale .lock-<actor> left by a killed process, or a filesystem that doesn’t support flock).
Recovery: delete the dangling ops/.lock-* files for processes that are no longer running, or move the workspace off the unsupported filesystem.
Tool calls return INTERNAL errors. Run the same command on the CLI (outl <command> --json) — same code path, same error, easier to read.
If CLI works and MCP doesn’t, file a bug.
Path quoting on macOS. If the workspace path has spaces, the JSON in claude_desktop_config.json must escape them.
Use a path without spaces (~/notes, ~/Documents/outl) — easier than fighting JSON escaping in two layers.
What’s NOT exposed over MCP
By design:
outl init,outl serve,outl reconcile— interactive or long-running, wrong shape for a tool call.outl import logseq|roam— one-time migration, not a workspace op.outl mcp serveitself — the host already booted you.
These stay CLI-only. Run them from a terminal when you need them.