# WorkflowyMCP

> An MCP server that uses Workflowy and Claude to manage Tasks & Information in Workflowy and create a Second Brain

- **Type:** MCP server
- **Install:** `agentstack add mcp-dromologue-workflowymcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [dromologue](https://agentstack.voostack.com/s/dromologue)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [dromologue](https://github.com/dromologue)
- **Source:** https://github.com/dromologue/workflowyMCP

## Install

```sh
agentstack add mcp-dromologue-workflowymcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Workflowy MCP Server

A Rust MCP server that gives Claude (Code or Desktop) read/write access
to your Workflowy graph, plus a generic template for turning that raw access
into a working second brain.

There are two ways to use it:

1. **Bare MCP server.** Wire the binary into your MCP host and call the 41
   tools directly. No templates, no opinions.
2. **Second brain.** Hand [`BOOTSTRAP.md`](BOOTSTRAP.md) to Claude. The
   assistant follows the script: builds the binary, wires the host, sets up
   your secondBrain directory at whatever path you choose (exposed to the
   server via `$SECONDBRAIN_DIR`), and installs the wflow skill that
   drives every subsequent session.

The methodology in option 2 is opinionated; the server itself is not. The
repo only ships generic templates — your node IDs, drafts, and session logs
live wherever `$SECONDBRAIN_DIR` points, never in this repo.

---

## Quick install

You need: Rust 1.75+ (`rustup install stable`), a Workflowy account with an
API key (Settings → API in Workflowy), and an MCP host (Claude Code
or Claude Desktop).

```bash
git clone https://github.com/dromologue/workflowyMCP.git ~/code/workflowy-mcp-server
cd ~/code/workflowy-mcp-server
cargo build --release
echo "WORKFLOWY_API_KEY=" > .env
```

The binary reads `.env` from the directory it's launched from. The
clone path above (`~/code/workflowy-mcp-server`) is the working
directory for the host's MCP launch — the host runs the binary from
there, so `.env` resolves correctly. **Recommended alternative:** put
the same key in your MCP host's `env` block (see the next section), so
the binary works regardless of where it's launched from.

Wire the resulting `target/release/workflowy-mcp-server` into your MCP host:

- **Claude Code:** `claude mcp add workflowy -- $(pwd)/target/release/workflowy-mcp-server`
- **Claude Desktop:** edit
  `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
  or `%APPDATA%\Claude\claude_desktop_config.json` (Windows). See
  [BOOTSTRAP.md](BOOTSTRAP.md) for the JSON shape.

Verify by calling the `workflowy_status` tool — the host should return
`status: "ok"`, `api_reachable: true`, `authenticated: true`. If
`authenticated: false`, your API key is wrong or not reaching the
binary; check the env-var section below.

That's it for plain MCP usage.

---

## Environment variables

The server reads four env vars at runtime. The repository ships no
machine-specific defaults: a path or node ID you don't set is a feature you
don't use. Set them in the `env` block of your MCP host config (Claude Code:
`~/.claude.json`; Claude Desktop: `claude_desktop_config.json`) and,
when you also use the `wflow-do` CLI from a shell, in your shell
profile (`~/.zshrc` or `~/.bashrc`).

| Variable | Required? | What it controls |
|----------|-----------|------------------|
| `WORKFLOWY_API_KEY` | Yes | Bearer token for the Workflowy API. |
| `SECONDBRAIN_DIR` | Optional | Absolute path to your operational secondBrain directory (drafts, session logs, briefs, memory). When set, the `review` tool's bucket-d session-log scan and the `wflow-do index` default output path read from `$SECONDBRAIN_DIR/session-logs/`. Unset or empty disables those features (graceful skip). |
| `WORKFLOWY_INDEX_PATH` | Optional | Absolute path to the persistent name-index JSON. Conventionally `$SECONDBRAIN_DIR/memory/name_index.json`. Unset or empty disables persistence — the index then lives only in memory for the lifetime of each process. |
| `WORKFLOWY_REVIEW_ROOT` | Optional | Default root node for the `review` and `audit_mirrors` tools when `root_id` is omitted (your review-anchor / "Distillations" node). No hardcoded fallback — if unset, those two tools require an explicit `root_id`. |

Example MCP host `env` block (Claude Code or Desktop):

```json
"env": {
  "WORKFLOWY_API_KEY": "",
  "SECONDBRAIN_DIR": "/absolute/path/to/secondBrain",
  "WORKFLOWY_INDEX_PATH": "/absolute/path/to/secondBrain/memory/name_index.json",
  "WORKFLOWY_REVIEW_ROOT": ""
}
```

Example shell profile (so the CLI agrees with the MCP server):

```bash
export SECONDBRAIN_DIR="/absolute/path/to/secondBrain"
export WORKFLOWY_INDEX_PATH="$SECONDBRAIN_DIR/memory/name_index.json"
```

Neither path needs to be inside the user's home directory — a Dropbox /
iCloud / Google Drive folder works as long as the host process can
read and write it. Paths with spaces are fine in the MCP `env` block
(JSON quoting handles it) and in the shell profile (the export line
quotes the value).

---

## Set up the second brain (recommended)

Hand [`BOOTSTRAP.md`](BOOTSTRAP.md) to Claude. The assistant runs through
six steps: build, wire the host, bootstrap your secondBrain directory at
the path you set in `$SECONDBRAIN_DIR`, populate your structural node IDs,
install the wflow skill, and (optionally) pre-warm the persistent name
index. After bootstrap, the assistant follows
[`templates/skills/wflow/SKILL.md`](templates/skills/wflow/SKILL.md) as the
operating manual.

The detailed long-form walkthrough — large-tree convergence,
troubleshooting — lives in [`docs/SETUP.md`](docs/SETUP.md).

---

## User-specific files (what you create)

The repo is generic. Everything specific to your Workflowy tree, your
intellectual frameworks, and your in-progress work lives outside it. These
are the files you populate (or accept the bundled defaults from the wflow
skill, then customise):

| File | Lives at | What it holds | Why it's external |
|------|----------|---------------|-------------------|
| `workflowy_node_links.md` | `$SECONDBRAIN_DIR/memory/` (canonical) and `~/.claude/skills/wflow/` (bundled fallback for surfaces that can't read `$SECONDBRAIN_DIR`) | Cached UUIDs for your structural Workflowy nodes (Inbox, Tasks, Reading List, Distillations, Themes, etc.) plus a **Triage Sources** table that defines which nodes Workflow 6 sweeps. Editing this list is how you add a new triage target — capture sub-node, Slack-saved-items mirror — without changing the skill. | The skill is portable; your tree isn't. The skill references this file so different users can have entirely different Workflowy structures. |
| `distillation_taxonomy.md` | `$SECONDBRAIN_DIR/memory/` (canonical) and `~/.claude/skills/wflow/` (bundled fallback) | Your distillation pillars (the top-level conceptual buckets you organise atomic notes into), themes (cross-cutting tags), inbound routing rules (which topic goes to which pillar), and the named frameworks you work with. | The skill describes the synthesis workflow generically and reads your actual taxonomy from this file. Pillars and frameworks are deeply personal; embedding one user's into the skill would force everyone else to inherit them. |
| `name_index.json` | `$WORKFLOWY_INDEX_PATH` (typically `$SECONDBRAIN_DIR/memory/name_index.json`) | Auto-managed by the MCP server. Persistent name index that turns Workflowy URL fragments and short-hashes into full UUIDs in O(1). Survives restarts; checkpoints every 30 s; refreshes via background walks every 30 minutes. | You don't write this file directly — the server maintains it. But it lives in your secondBrain so it's portable across your machines (e.g. via Google Drive / Dropbox / iCloud). |
| `drafts/`, `session-logs/`, `briefs/` | `$SECONDBRAIN_DIR/` | Your in-flight distillation work (`drafts/`), per-session audit trails (`session-logs/`), and external-facing handoff documents (`briefs/`). | The skill's end-of-session discipline writes to these locations. They're per-user by definition. |
| `tasks/todo.local.md` | inside this repo (gitignored) | Cross-session engineering follow-ups for the workflowy-mcp-server itself — issues you noticed, ideas to revisit, items the previous habit would have filed as Workflowy "system tasks." | Engineering todos belong with the code, not in your Workflowy outline; gitignored so each contributor's local list stays out of the tracked tree. |

**Precedence rule.** When the skill needs data from the two memory files,
it prefers the canonical at `$SECONDBRAIN_DIR/memory/.md`. If that
path isn't readable (e.g. a host with no filesystem access), it
falls back to the bundled copy at `~/.claude/skills/wflow/.md`. The
bundled copy is overwritten on each skill ZIP rebuild, so canonical edits
need to be re-bundled before they reach surfaces that depend on the
fallback.

---

## What ships in this repo

```
workflowyMCP/
├── BOOTSTRAP.md              ← LLM-facing install script (hand to Claude)
├── README.md                 ← this file
├── docs/SETUP.md             ← long-form bootstrap notes
├── specs/specification.md    ← authoritative behavioural spec
├── templates/
│   ├── secondbrain/          ← skeleton copied to $SECONDBRAIN_DIR
│   │   ├── README.md
│   │   ├── memory/workflowy_node_links.md     (template; user fills in)
│   │   ├── memory/distillation_taxonomy.md    (template; user fills in)
│   │   ├── drafts/  session-logs/  briefs/
│   └── skills/wflow/SKILL.md ← the operating manual the assistant follows
└── src/                       ← Rust MCP server source
```

User-specific data (node IDs, drafts, session logs, briefs) belongs at
whatever path you set via `$SECONDBRAIN_DIR`. The repo content stays
generic so the next person who clones it gets a clean starting point.

---

## Tool reference

The server exposes 41 tools. `node_id` accepts any of: full UUID (with or
without hyphens), 12-char URL-suffix short hash, or 8-char prefix.
`parent_id` (and any other parent-scoped argument) accepts `null` or
omission as "workspace root" across the whole tool surface — `create_node`,
`batch_create_nodes`, `insert_content`, `list_children`, and the rest
behave the same way.

| Category | Tools |
|----------|-------|
| Search & navigate | `node_at_path`, `resolve_link`, `search_nodes`, `find_node`, `get_node`, `list_children`, `tag_search`, `get_subtree`, `find_backlinks`, `path_of`, `find_by_tag_and_path`, `read_batch` |
| Create & edit | `create_node`, `batch_create_nodes`, `insert_content`, `smart_insert`, `convert_markdown`, `edit_node`, `move_node`, `reorder_nodes`, `delete_node`, `complete_node`, `duplicate_node`, `create_from_template`, `bulk_update`, `bulk_tag`, `transaction`, `export_subtree` |
| Mirror discipline | `create_mirror` (convention-based: duplicates the canonical's name into a new parent and writes `mirror_of:` to the new node's note), `audit_mirrors` |
| Todos & scheduling | `list_todos`, `list_upcoming`, `list_overdue`, `daily_review`, `since` |
| Project management | `get_project_summary`, `get_recent_changes` |
| Diagnostics & ops | `workflowy_status`, `health_check`, `cancel_all`, `build_name_index`, `review`, `get_recent_tool_calls` |

**Native task completion.** `complete_node(node_id)` toggles the
Workflowy `completed` boolean — the legacy `#done` tag-as-completion
workaround is deprecated for tasks. `bulk_update(operation: "complete"|"uncomplete", filter: …)`
toggles a filtered set in one call; `transaction` accepts the same ops
with rollback. Wire payload is `POST /nodes/{id}` with
`{"completed": true|false}`.

**Reordering siblings (`reorder_nodes`).** Workflowy's `move_node`
priority is *position-relative-to-siblings* and renormalises after every
call, so a naive forward `priority=0,1,2,…` loop fights itself when you
try to batch-reorder a set. `reorder_nodes(parent_id, node_ids[])`
takes the desired head-first order and walks it in **reverse**, issuing
`move_node` with `priority=0` per id — every move plants its node at
position 0, the previously-planted nodes shift one step right, and after
N moves the head of the parent's children is the requested sequence.
Side effect: ids not currently under `parent_id` are reparented as part
of the reorder (the primitive is built on `move_node`, not a sibling-
only assertion). Capped at 200 ids per call. Returns `Complete` or
`Partial { reason: cancelled | timeout }` with per-id `ok / error /
skipped` entries; partial outcomes are safe to re-issue with the full
list because each reverse-priority-0 move is idempotent. The
orchestration lives once in `crate::workflows::reorder_nodes_via_priority`
and is shared with `wflow-do reorder --parent  --node  --node `.

**Mirror creation (convention).** Workflowy's REST API does not expose
native mirror creation, so `create_mirror(canonical_node_id, target_parent_id)`
implements the documented `mirror_of:` / `canonical_of:` note convention
that `audit_mirrors` already understands: a new node is created under
the target parent with the same name as the canonical, and its
description carries `mirror_of: `. Edits to the
canonical do **not** propagate to the mirror — the link is structural
and human-curated, not live. Pass an optional `pillar` to write a
`canonical_of: ` marker to the canonical when it lacks one;
existing markers are never overwritten. Pass `dry_run=true` to preview
the resolved canonical, target_parent, and mirror name (verbatim copy of
the canonical's name) without writing — useful when batching multiple
mirror passes across a synthesis. The mirror's create + the optional
canonical edit (and the dry-run preview) run through the shared
[`crate::workflows`](src/workflows.rs) module, the same code path the
`wflow-do create-mirror [--dry-run]` CLI calls.

**`scope_resolved` diagnostic field.** Every tool that accepts an
`Option` for parent_id (`create_node`, `batch_create_nodes`,
`insert_content`, `create_mirror`, `list_children`, `find_node`,
`search_nodes`) returns a `scope_resolved` field naming what the server
actually targeted: `workspace_root` when the resolved scope was None
(caller passed null or omitted), or `scoped:` otherwise.
Read this field after every call where parent_id was null/omitted to
verify the server resolved to where you intended — pre-2026-05-09
callers had no way to audit this without inspecting the wire payload.

**`insert_content` payload cap.** The hard cap is 80 lines per call,
lowered from 200 on 2026-05-04 after the failure-report 2026-05-03
session observed ≥80-line payloads failing at the MCP transport layer
with no diagnostic. Above the cap, the call returns a typed error with
a chunking instruction; chunk to ≤80 lines and pass the previous
batch's `last_inserted_id` as the next call's `parent_id` to keep the
hierarchy stitched together.

**Truncation envelope.** Every walk-shaped tool that emits JSON includes
the same four fields when its 20 s walk budget fires:

```json
{
  "truncated": true,
  "truncation_limit": 10000,
  "truncation_reason": "timeout",
  "truncation_recovery_hint": "Call build_name_index(parent_id=...) … then re-issue with use_index=true …"
}
```

Read `truncation_reason` and `truncation_recovery_hint` on every walk
response. For name-based queries (`search_nodes`, `find_node`),
`use_index=true` answers in O(1) from the persistent name index without
burning the walk budget — populate it first with
`build_name_index(parent_id=)`. Index path is name-only;
description-content matching still needs a live walk.

For large workspaces, prefer `node_at_path` (path of names → UUID, ~1 second
on any tree size) and `resolve_link` (Workflowy URL + optional parent path
→ full node info) over `search_nodes`. They cost O(depth) API calls instead
of O(tree).

Conventions parsed from node text:

- Tags: `#inbox`, `#review`, `#urgent`
- Assignees: `@alice`, `@bob`
- Due dates: `due:2026-03-15`, `#due-2026-03-15`, or bare `2026-03-15`
  (priority order)

---

## Reliability properties

Every API-touching handler runs inside a uniform `run_handler` wrapper
that observes the server-wide cancel registry and applies a
kind-appropriate wall-clock deadline:

| Tool kind | Budget | Examples |
|-----------|--------|----------|
| Read | 30 s | `get_node`, `list_children` |
| Write | 15 s | `create_node`, `delete_node`, `edit_node` |
| Bulk | 180 s | `insert_content`, `transaction`, `bulk_update`, `path_of`, `node_at_path` |
| Walk | 20 s (internal) | `search_nodes`, `get_subtree`, `find_node` |

`cancel_all` interru

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [dromologue](https://github.com/dromologue)
- **Source:** [dromologue/workflowyMCP](https://github.com/dromologue/workflowyMCP)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-dromologue-workflowymcp
- Seller: https://agentstack.voostack.com/s/dromologue
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
