Logseq MCP Server
# Logseq MCP Server
**Turn your Logseq graph into memory and workspace for AI agents.** A
[Model Context Protocol](https://modelcontextprotocol.io) server for
[Logseq](https://logseq.com) with safety-scoped writes, an audit trail in your
daily journal, and verified queries exposed as tools. Built on **FastMCP** (the
high-level API of the official `mcp` package).
[](https://pypi.org/project/mcp-server-logseq/)
[](https://pypi.org/project/mcp-server-logseq/)
[](LICENSE)
<a href="https://glama.ai/mcp/servers/@dailydaniel/logseq-mcp">
<img width="380" height="200" src="https://glama.ai/mcp/servers/@dailydaniel/logseq-mcp/badge" alt="Logseq Server MCP server" />
</a>
> Targets the **file/Markdown ("OG") version** of Logseq — and plain-text files
> are part of why a graph makes good agent memory: git-syncable, greppable,
> durable, no lock-in. The newer DB (SQLite) version changed the underlying
> schema; some methods may behave differently there.
## Why
Agents need durable memory, and you already maintain one — your graph. The
missing piece is access you can trust: an agent should read broadly and write
usefully, but never touch what it shouldn't — and never do anything you can't
see. Three design choices make that possible:
- **Namespace-scoped writes.** Agents write only under their own prefix
(`byAgent/` by default), plus one deliberately narrow cross-namespace channel
that can change nothing but a task's `TODO/DOING/DONE` marker. Blacklisted
pages are hidden and redacted from every read.
- **An audit trail in your daily journal.** Every successful write appends a
line like `22:30 [[byAgent]] wrote [[byAgent/readingList/...]]` to today's
journal — reviewing your agents' work becomes part of a morning routine you
already have.
- **Verified queries as tools.** Ship known-good Datalog from config as named
tools (`query_week_plan`, …), so agents don't compose datascript by hand and
cheaper models stay reliable.
## How I use it
I run a small fleet of Claude Code agents with this server on an always-on Mac
mini, against my live personal graph:
- **Nightly research.** A link dropped into the reading list from the phone; at
night an agent claims it (`status:: researching`), reads the article — or
shallow-clones and reads the repo — writes a structured summary onto the page
and flips it to `read`.
- **Morning brief.** At 08:30 a small model assembles a one-page dashboard —
what was read overnight, week-plan progress, current NOW/DOING tasks — and
sends a single push notification.
- **One journal for everyone.** The human's tasks and the agents' audit lines
interleave in the same daily note:

The pages the researcher writes — properties, summary, relevance — link straight
into the rest of the graph:

```mermaid
flowchart LR
A[AI agents] -- MCP tools --> S[logseq-mcp]
S -- HTTP API --> L[Logseq graph]
S -. audit line per write .-> J[daily journal]
Y((you)) --> L
Y -- morning review --> J
```
## Requirements
- A running Logseq with the **local HTTP API server enabled**
(Settings → Features → *HTTP APIs server*, then start it from the 🔌 menu).
- An **authorization token** created in the HTTP API server settings.
## Usage
### Claude Code
Local (stdio), token from the environment:
```bash
claude mcp add logseq --scope user --env LOGSEQ_API_TOKEN=<YOUR_TOKEN> -- uvx mcp-server-logseq
```
Or point it at a remote instance over Streamable HTTP (how phone and remote
sessions reach a headless host — see [Transports](#transports)):
```bash
claude mcp add logseq --scope user --transport http http://<host>:8000/mcp \
--header "Authorization: Bearer <LOGSEQ_MCP_HTTP_TOKEN>"
```
### Claude Desktop
```json
{
"mcpServers": {
"logseq": {
"command": "uvx",
"args": ["mcp-server-logseq"],
"env": {
"LOGSEQ_API_TOKEN": "<YOUR_TOKEN>",
"LOGSEQ_API_URL": "http://127.0.0.1:12315"
}
}
}
}
```
## Configuration
| Source | Token | URL |
| --- | --- | --- |
| Environment | `LOGSEQ_API_TOKEN` | `LOGSEQ_API_URL` (default `http://localhost:12315`) |
| CLI flag | `--api-key` | `--url` |
The token is read from the environment or `--api-key`; it is never stored in
code. A `.env` file is supported (see `.env.example`).
### Config file (optional)
Behaviour beyond the defaults is set in a TOML file — path from
`LOGSEQ_MCP_CONFIG` (default `~/.config/logseq-mcp/config.toml`). Custom queries
live in EDN files next to it. **The server runs fine with no config file** (safe
read-mostly defaults); see [`examples/config.toml`](examples/config.toml) for a
full annotated example.
| Section | Key options |
| --- | --- |
| `[read]` | `resolve_depth` — how deep to expand `((block refs))` |
| `[write]` | `agent_write_prefix` (default `byAgent`), `allow_agents_write_any` |
| `[search]` | `files_path` — graph folder; set it to use the ripgrep backend |
| `[blacklist]` | `pages` — pages (and subpages) to hide and redact everywhere |
| `[tasks]` | `allow_status_change` — gate for `set_task_status` |
| `[audit_log]` | `enabled` — log writes to today's journal |
| `[queries.<name>]` | a named query: `file`/inline `query`, `register_as_tool`, … |
Secrets and the API URL stay in the environment, never in this file.
## Transports
By default the server runs over **stdio** (for Claude Desktop and other local
clients). A **Streamable HTTP** transport is also available for remote/networked
use (e.g. a phone client):
```bash
LOGSEQ_MCP_HTTP_TOKEN=<client-secret> \
mcp-server-logseq --transport streamable-http --host 0.0.0.0 --port 8000
# MCP endpoint: http://<host>:8000/mcp
```
Env vars: `LOGSEQ_MCP_TRANSPORT`, `LOGSEQ_MCP_HOST`, `LOGSEQ_MCP_PORT`,
`LOGSEQ_MCP_HTTP_TOKEN` (or `--http-token`).
### Authentication
The Streamable HTTP transport **requires** a bearer token: every request must
send `Authorization: Bearer <LOGSEQ_MCP_HTTP_TOKEN>`, or it gets `401`. The
server refuses to start in this mode without a token set. Note this is a
distinct secret from `LOGSEQ_API_TOKEN`:
| Secret | Direction |
| --- | --- |
| `LOGSEQ_API_TOKEN` | this server → Logseq |
| `LOGSEQ_MCP_HTTP_TOKEN` | client (phone) → this server |
> ⚠️ A bearer token over **plain HTTP** is only safe on an already-encrypted
> channel. Don't expose the raw port to the open internet. The easy path for a
> home/headless host is **Tailscale**: install it on the host and the client,
> and reach `http://<host>.<tailnet>.ts.net:8000/mcp` over the encrypted
> tunnel — no domains, nginx, or certificates. (`tailscale serve` can add TLS
> if you want `https://`.)
## Docker
Build once:
```bash
docker build -t logseq-mcp .
```
Quick try (ephemeral — `--rm` removes the container on stop):
```bash
docker run --rm -p 8000:8000 \
-e LOGSEQ_API_TOKEN=<logseq-token> \
-e LOGSEQ_MCP_HTTP_TOKEN=<client-secret> \
-e TZ=Europe/Moscow \
logseq-mcp
```
Persistent deploy (e.g. a headless Mac mini) — run once; `--restart` brings it
back after reboots:
```bash
docker run -d --name logseq-mcp --restart unless-stopped -p 8000:8000 \
-e LOGSEQ_API_TOKEN=<logseq-token> \
-e LOGSEQ_MCP_HTTP_TOKEN=<client-secret> \
-e TZ=Europe/Moscow \
-e LOGSEQ_MCP_CONFIG=/cfg/config.toml \
-v /path/to/config-dir:/cfg:ro \
-v "/path/to/your/graph:/graph:ro" \
logseq-mcp
```
- `-v .../config-dir:/cfg` — folder holding your `config.toml` (+ `queries/`,
`rules/`); set `files_path = "/graph"` in it to enable file search. Omit both
the mount and `LOGSEQ_MCP_CONFIG` to run on defaults.
- `-v .../graph:/graph` — your Logseq graph folder (read-only), for file search.
- `-e TZ=<zone>` — local time for audit-log timestamps (image bundles `tzdata`;
the clock is UTC otherwise).
The container serves Streamable HTTP on port 8000 and talks to a Logseq running
on the **host**. On Docker Desktop (macOS/Windows) the default
`LOGSEQ_API_URL=http://host.docker.internal:12315` already points at the host;
on Linux add `--add-host=host.docker.internal:host-gateway` (or set
`LOGSEQ_API_URL` to the host IP). Make sure Logseq's HTTP API server is running
and listening.
## Tools
All read output is normalized to a flat JSON shape and passed through the
blacklist. Reads resolve `((block refs))` non-lossily (the resolved block's
`uuid`/`status` is kept so you can act on it).
### Find
- **search** — full-text search over block content (`query`, `regex?`, `limit?`,
`case_sensitive?`, `exclude_journals?`). Uses ripgrep over `files_path` when
set, else a datascript content match.
- **find_tasks** — task blocks by `markers?`, `tag?`, `under_tag?` (descendant),
`page?`, `priority?`, `limit?`.
- **list_pages** — page names under a namespace `prefix?` (`depth?` limits levels).
Discovers a namespace's child pages, which are separate pages a parent's
`read_page` won't show. Structure only, not block content.
- **custom_query** — run a named query from the config (`name`, `inputs?`).
- **list_custom_queries** — list the configured queries.
- **datascript_query** — run a raw Datalog query (`query`, `inputs?`, `rules?`).
### Guide
- **get_logseq_guide** — returns the authoritative guide for querying/writing this
graph (verified Datalog gotchas: lowercase names, prefix descendants, marker and
journal-day types, tags vs refs, read/write scoping). A single source of truth
co-located with the server, so agents don't re-derive (and mis-derive) behaviour.
### Read
- **read_page** — a page as a normalized block tree (`page`, `depth?`).
- **read_block** — a block and its children (`uuid`, `depth?`).
### Write (agent namespace only)
- **write_note** — create/append/replace a page under `agent_write_prefix`
(`subpath`, `content?`, `mode?`, `properties?`).
- **set_page_properties** — set/remove page properties (`subpath`, `properties`;
a `null` value removes one).
- **edit_block** — replace one block's content (`uuid`, `old_content`,
`new_content`). Read-before-write is enforced: the edit is rejected unless
`old_content` matches the block's exact current content. Agent namespace only.
### Tasks
- **create_task** — create a task block in the agent namespace (`title`, `agent`,
`project?`, `marker?`, `priority?`, `tags?`, `plan_page?`, `blocks_on?`,
`on_page?`). The only way to create tasks — `write_note` rejects content that
starts with a task marker.
- **set_task_status** — change only a task's marker (`uuid`, `status`); gated by
`[tasks].allow_status_change`.
### Dynamic
- **query_<name>** — each config query with `register_as_tool = true` is
exposed as its own tool.
## Development
```bash
git clone https://github.com/dailydaniel/logseq-mcp.git
cd logseq-mcp
cp .env.example .env # fill in LOGSEQ_API_TOKEN
uv sync
uv run mcp-server-logseq
```
Inspect with the MCP Inspector:
```bash
npx @modelcontextprotocol/inspector uv --directory . run mcp-server-logseq
```
## License
MIT
TDQS
Scored across 10 tools
The tools have clear distinct purposes for core operations like creating pages, editing blocks, and retrieving content, but there is notable overlap between retrieval tools. Specifically, logseq_get_page and logseq_get_page_content both retrieve page details, and logseq_get_current_page_content and logseq_get_editing_block_content focus on current workspace content, which could cause confusion in selection. Descriptions help differentiate, but the boundaries are not perfectly distinct.
All tool names follow a consistent snake_case pattern with a 'logseq_' prefix, using clear verb_noun combinations such as create_page, edit_block, and get_all_pages. This uniformity makes the tools predictable and easy to parse, with no deviations in style or structure across the set.
With 10 tools, the count is well-scoped for a Logseq server, covering essential operations like page creation, editing, and content retrieval. Each tool serves a specific function without redundancy, fitting within the typical 3-15 range for such a domain, and no tools feel unnecessary or missing for basic interactions.
The tool set provides good coverage for core Logseq workflows, including CRUD-like operations for pages and blocks (create, get, edit, insert). However, there are minor gaps, such as no explicit tools for deleting pages or blocks, updating block content beyond editing mode, or managing properties post-creation, which agents might need to work around using existing tools.