MCP integration (Claude Code and Codex)
@takazudo/zudotext-mcp is the official local stdio Model Context Protocol server for one encrypted zudo-text cloud workspace. In this checkout, Claude Code or Codex can call 20 direct document/history/change/asset tools and one optional cloud-agent tool without driving the app UI.
This is the shipped way to edit workspace documents with a local coding agent. It is separate from the desktop app's local Terminal frame: the shell does not configure MCP or provide cloud workspace access on its own. There is no public generic remote MCP endpoint and no per-published-site MCP endpoint. The optional restricted Kanban endpoint described below is unreleased and requires a configured Worker.
The server package is public on npm while its source repository remains private. Users do not need repository access or GitHub credentials to install it.
This page is the user workflow: setup, registration, and day-to-day operation. Local Agent Authoring is the design and safety authority. The @takazudo/zudotext-mcp package page is the distribution, configuration, exact-inventory, and release-verification reference. Agent task recipes is the task-shaped companion to this page: what to type for a given job, which tools it should trigger, and the caveat attached to each one.
Install
These npm instructions install the released generic authoring package. The restricted Kanban profile requires the reviewed checkout or a verified local tarball; npm 0.1.1 does not contain it. Its setup is separate in Kanban agent connections.
Choose one setup path. The recommended Settings-generated command installs and registers the server in one pass; do not precede it with the manual global install. Use the manual path when you need to assemble the package, token, and harness registration yourself. Both paths require Node.js 24 or newer.
Recommended: run the Settings-generated command
Open Settings → Local AI Agent:
Pick Claude Code or Codex.
Choose an absolute password-file path (recommended) or the
/prompt. For a password file, create it yourself with a trusted editor, write only the workspace password, and rundev/ tty chmod 600 /before continuing. The Settings screen never reads, displays, copies, or stores the password.absolute/ path/ to/ workspace- password Leave the encryption-salt override off; it is a debugging escape hatch, not normal configuration.
Choose Create token & config.
Copy the one-time config before discarding it or closing Settings. Paste it into a trusted local shell and run it as one executable command block. It installs and verifies the exact npm release, registers the selected harness with the generated environment, and ends with
mcp list.
The / option means the external harness process prompts through its controlling terminal while keeping stdin available for MCP framing.
This flow mints a PAT bound to the current workspace with exactly documents:read, documents:write, assets:read, and assets:write. The raw token appears only in the one-time config reveal. Closing or discarding the reveal drops the UI's copy but does not revoke the token; revoke an unused one under Settings → Access Tokens.
Manual: install the npm package
The package is a public npm-registry publication. Replace <VERSION> with the version shown on the package release reference:
ZUDOTEXT_MCP_VERSION="<VERSION>"
npm install --global "@takazudo/zudotext-mcp@${ZUDOTEXT_MCP_VERSION}"
zudotext-mcp --versionThe verification command must print the selected version.
Mint a scoped token
For the manual path, create a PAT under Settings → Access Tokens. Its settled least-privilege default is documents:read, documents:write, assets:read, and assets:write. Add agent:invoke only for ask_zudo_agent, or choose mutually exclusive Full access only for compatibility work that truly needs the wider server surface. Bind an MCP PAT to one workspace and set an expiry.
The server API still interprets an omitted scopes field as full for pre-scoped-token compatibility. The current UI does not silently choose that default.
Add it to Claude Code or Codex
If you ran the generated command from Settings → Local AI Agent, registration is already complete and you can skip these manual examples. Use them when you minted a PAT in the generic token screen or are assembling configuration by hand.
Before running either password-file example, create the absolute-path file with a trusted editor, write only the workspace password, and run chmod 600 /.
claude mcp add zudotext \
--env ZUDOTEXT_SERVER_URL=https://your-sync-server.example.com \
--env ZUDOTEXT_PAT=your_personal_access_token \
--env ZUDOTEXT_WORKSPACE_ID=your-workspace-id \
--env ZUDOTEXT_WORKSPACE_PASSWORD_FILE=/absolute/path/to/workspace-password \
-- zudotext-mcp
claude mcp listThe Codex command has the same shape:
codex mcp add zudotext \
--env ZUDOTEXT_SERVER_URL=https://your-sync-server.example.com \
--env ZUDOTEXT_PAT=your_personal_access_token \
--env ZUDOTEXT_WORKSPACE_ID=your-workspace-id \
--env ZUDOTEXT_WORKSPACE_PASSWORD_FILE=/absolute/path/to/workspace-password \
-- zudotext-mcp
codex mcp listThen ask the harness to call list_notes. Installing the official skill gives it the complete safety policy and exact tool inventory.
Configuration reference
| Variable | Required | Meaning |
|---|---|---|
ZUDOTEXT_SERVER_URL | yes | Sync-server origin. |
ZUDOTEXT_PAT | yes | Workspace-bound PAT with the required narrow scopes. |
ZUDOTEXT_WORKSPACE_ID | yes | The one workspace this process exposes. |
ZUDOTEXT_WORKSPACE_PASSWORD_FILE | one password source | Recommended absolute path to a file you create, containing only the workspace password. Protect it with chmod 600. |
ZUDOTEXT_WORKSPACE_PASSWORD | one password source | Inline password; convenient but visible to environment/config readers. |
| neither password variable | one password source | Prompt once from the MCP process's controlling terminal through /, without echo. Stdin remains the MCP protocol channel. |
ZUDOTEXT_WORKSPACE_SALT | no | Hex debugging override. Normally the server returns metadata, including for an assets:read PAT on a free account. |
ZUDOTEXT_AGENT_URL | no | Agent-server origin for ask_zudo_agent; defaults to the operator-run service. |
ZUDOTEXT_MCP_LOG_LEVEL | no | debug, info, warn, error, or silent; default info, stderr only. |
If both password variables are set, the inline environment value wins over the file. The password stays in process memory so an expired document key session can be reopened; the process never writes it to disk.
Install the official authoring skill
After completing either setup path, install the behaviorally equivalent policy skill for the harness you use:
# User scope
zudotext-mcp-install-skill --harness claude-code
zudotext-mcp-install-skill --harness codex
# Repository scope; --project requires an absolute path
zudotext-mcp-install-skill --harness claude-code --project "$PWD"
zudotext-mcp-install-skill --harness codex --project "$PWD"The user destinations are $CLAUDE_ (falling back to ~/) and $HOME/. Repository installs use . and ..
After installing a newer npm release, replace and byte-check the skills explicitly:
zudotext-mcp-install-skill --harness claude-code --update
zudotext-mcp-install-skill --harness claude-code --check
zudotext-mcp-install-skill --harness codex --update
zudotext-mcp-install-skill --harness codex --checkWithout --update, an existing skill is never overwritten. An owner may edit the installed destructive-confirmation defaults; such an intentional edit makes the byte-exact --check fail, and --update replaces it. Back up and reapply custom policy around an update. Claude Code explicitly invokes /; Codex uses $zudotext-authoring or the / selector.
Do not install these external-agent skills under .zudotext/skills/. That is the separate flat-file namespace for zudo-text's in-app inline AI.
Tool tour
The default authoring registry contains exactly 21 tools.
Documents and changes
| Tool | Contract |
|---|---|
list_notes(prefix?, limit?, cursor?) | List workspace-relative paths and current versions, with pagination. archives/ is an ordinary prefix and needs no special tool. |
read_note(path, maxBytes?, noteId?, expectedFileId?) | Read a UTF-8 window plus version. Default 1 MiB, maximum 8 MiB; truncated and total size remain explicit. |
search_notes(query, mode?, prefix?, limit?) | Regex search in files, content, or count mode. |
write_note(path, content, expectedVersion?, idempotencyKey?, noteId?, expectedLocationRevision?, expectedFileId?) | Omit the version only for create; pass the current version to replace one whole document. |
stash_log(project, title, content, noteId, idempotencyKey, createdAt) | Create a dated, collision-safe Markdown work log under logs/. |
delete_note(path, expectedVersion, idempotencyKey?, noteId?, expectedLocationRevision?, expectedFileId?) | Soft-delete the current document while keeping its tombstone and version history. |
move_note(fromPath, toPath, expectedVersion, idempotencyKey?, noteId?, expectedLocationRevision?, expectedFileId?) | Atomically move the stable document identity and preserve its history. |
what_changed(afterCursor?, limit?) | Poll ordered document upserts/deletes after a cursor. |
what_changed has a process-local implicit cursor. Successful implicit calls advance it, including intermediate pages; explicit afterCursor calls do not. It resets to zero when the MCP process restarts. The cursor is document-only: asset mutations never appear.
One search scans at most 2,000 candidate paths and reads content from at most 240 documents. Scope with prefix; a truncated response is incomplete, not evidence that no later match exists.
Authoring sessions and document history
| Tool | Contract |
|---|---|
begin_authoring_session(label) | Create the explicit document checkpoint that marks an agent undo boundary. |
list_note_versions(path, noteId?, expectedFileId?) | List versions of the stable document currently at a path, including history before a move. |
read_note_version(path, versionId, noteId?, expectedFileId?) | Read one complete historical document version. |
restore_note_version(path, versionId, expectedVersion, idempotencyKey, noteId?, expectedLocationRevision?, expectedFileId?) | Restore one version as a new head with an optimistic precondition. |
list_checkpoints() | List named document change-log cursors. |
get_checkpoint_summary(checkpointId, fromCursor?) | Project document changes ending at a checkpoint. |
get_checkpoint_restore_manifest(checkpointId) | Return the document work order for a client-orchestrated rollback. |
Call begin_authoring_session before the first document mutation when an undo boundary matters. A checkpoint is not a workspace snapshot. There is no one-call or atomic rollback: the client fetches the manifest, compares its expectedHeadCursor with a fresh explicit walk: call what_changed(afterCursor: 0), keep passing each response's changeCursor as the next afterCursor until hasMore is false, then compare the final cursor. It applies normal restore/delete/noop work and stops on conflict while reporting possible partial progress. A same-fileId old-path restore plus new-path delete is a move-back. HISTORY_COMPACTED remains distinguishable from empty history. The sync server's nightly version-retention job does not touch the changes log or compaction_floor, so it does not change HISTORY_COMPACTED semantics; it only thins unpinned version rows and queues their unreferenced R2 blobs for delayed reclaim.
Versions, checkpoints, summaries, restore manifests, and what_changed cover documents only.
Assets
| Tool | Contract |
|---|---|
list_assets() | List decrypted asset paths, sizes, and modification times. |
get_asset_usage() | Report plan, quota, used/add-on bytes, and soft-lock state. |
download_asset(path, destinationPath) | Decrypt into a new absolute local file; never overwrite a destination. |
upload_asset(sourcePath, path, idempotencyKey) | Encrypt and create from an explicit regular, non-symlink local file up to 25 MiB. |
create_asset_folder(path, idempotencyKey) | Create an encrypted empty-folder marker without replacing an existing path. |
Asset plaintext bytes and base64 never enter MCP text or structured results. Transfers cross an explicit local-path boundary: confirm the source before an upload and the new destination before a download. Check usage before material uploads; quota or soft lock can reject creation, while an over-quota account may still list and download.
Assets have no versions, checkpoints, history restore, document cursor, or undo. The narrow family deliberately has no move, rename, delete, recursive delete, replace, or delete-and-recreate tool; do not emulate one. Interactive asset moves/renames and document moves do not rewrite Markdown references.
Cloud agent
| Tool | Contract |
|---|---|
ask_zudo_agent(message, conversationId?) | Delegate semantic, open-ended, or multi-step work to the same cloud assistant used by the app. |
Direct tools spend zero AI turns. Every admitted ask_zudo_agent call spends one of the user's shared 100 AI turns per UTC day at admission, even if that turn later fails. Assistant-panel and inline-AI calls share the pool. All Automation API routes also share 120 requests per user per minute; cloud-agent internal calls consume that same budget, so avoid bursts and honor typed retry timing. One bounded internal query_kanban call can consume up to 85 of those requests.
Calls without conversationId share one process-local default conversation; passing an id pins a thread. Successful propose_edit activity returns validated editPreviews. A preview is not applied automatically: review its candidate newContent, then use write_note before expiry with baseVersion as expectedVersion and a fresh idempotency key. create_note is the deliberate direct-write exception: it commits a new note create-only and fails if the selected path already exists.
Write conflicts and retries
Canonical notes have a permanent noteId UUID and display number; their paths can change. After read_note, retain the returned noteIdentity.noteId, noteIdentity.locationRevision, and body version. Supply these as noteId, expectedLocationRevision, and expectedVersion for update, delete, move, or restore, together with a caller-stable idempotencyKey. Do not substitute a newer version or the current occupant of an old path when retrying. Locate the same UUID and review the new body before authorizing a different edit. Reads and history reads accept noteId to reject a reused path.
Creating a Note Tray note requires a newly minted UUID in noteId and a stable idempotencyKey; omit expectedVersion. Keep the UUID, key, destination and exact body across an uncertain response or process restart. The server assigns the permanent display number. An ordinary root/internal document stays unregistered: its read returns a separate fileId, passed as expectedFileId with the captured version for mutations. Never pass an ordinary file ID as a note UUID or supply both identity fields. A workspace that still needs enrollment must be opened in the current app; there is no path-only creation fallback.
stash_log additionally requires the captured createdAt ISO UTC timestamp, noteId, and idempotencyKey. Reuse the entire tuple and original content on retry. It changes a filename suffix only after a definite collision; uncertain responses retain the original attempt. Read/write/stash results expose identity metadata in structuredContent as well as readable text.
Whole-document writes use optimistic concurrency. On VERSION_CONFLICT, read the returned current-version details, re-read the document, reconcile with the owner's intent, and use a new precondition. If the payload or precondition changes, use a fresh idempotency key; the old key belongs only to an exact retry of the old fingerprint. A conflict writes nothing.
write_note,delete_note, andmove_noterequire a caller-stable key for canonical notes. For ordinary documents, a key can be omitted for a single request; supply one to replay an uncertain operation across calls or restart.restore_note_version,upload_asset, andcreate_asset_folderrequire a caller-stable key. Reuse it only for the exact same payload.stash_logavoids overwrites by choosing the next free dated filename.begin_authoring_sessionandask_zudo_agentare not idempotent.
The log-stash recipe
stash_log(project, title, content, noteId, idempotencyKey, createdAt) replaces a machine-local agent-logs/ directory with synced workspace documents. Logs land directly under logs/:
logs/YYYYMMDD-<project>-<title>.mdThe project is retained in both the filename and frontmatter. A collision takes the next -2, -3, … suffix and never overwrites an existing document. Pin logs/ as a Note Tray to browse the direct children in the app.
Resource safety
The official skills apply these defaults to direct and cloud-agent-proposed whole-document writes:
Ordinary notes and
archives/documents are writable.Preserve existing YAML frontmatter byte-for-byte unless the owner explicitly asks to change a field; malformed raw edits can desynchronize indexed data.
Directive round-tripping is unverified. Preserve directive syntax unless the owner explicitly accepts the risk.
Use the restricted Kanban tools below for approved card mutations, together with the matching captured-base UI writer. Keep generic whole-board writes read-only. Todo, timeline, mindmap, spreadsheet, and slide have separate persistence paths whose agent-write safety is unverified.
Treat
.zudotext.settings.json, frameset state, and pins as read-only. They are whole-document last-writer-wins state, and malformed settings may silently revert to defaults.
By default the packaged skill asks for owner confirmation immediately before a whole-note replacement, delete, move, version restore, checkpoint manifest application, or cloud-agent preview application. That policy is owner-editable in the installed skill.
Optional active-note pointer
On desktop, the off-by-default active-note pointer can help a local tool orient to the note currently visible in the app. It writes only path, workspace, pane/view, selection coordinates, and timestamp — never note content — to ~/.
The app refreshes it every 10 seconds and on note/pane/view changes; readers treat data older than 30 seconds as stale. Disable, workspace switch, no active workspace note, and clean shutdown remove it, while a crash can leave a stale file. It is a strictly read-only sidecar, not an MCP command channel, and token creation never enables it.
Security notes
Document keys reach bounded server memory. The local process derives raw keys with PBKDF2-SHA-256 at 600,000 iterations and opens a key session held in Durable Object memory for at most 24 hours. It is never persisted. A server restart or Durable Object eviction can end it sooner. Normal close and process signals start best-effort deletion; abnormal death relies on expiry.
Asset keys and plaintext remain local. Non-extractable asset keys stay in the stdio process.
/receives opaque path tokens and encrypted envelopes only; an asset-only process opens no document key session.api/ assets Credentials remain local. Protect the PAT, MCP config, password, and password file like SSH credentials. Workspace binding prevents redirecting a bound PAT to another workspace.
Logs are protocol-safe. Known credentials, key material, and session ids are redacted; stdout is MCP-only and logs use stderr.
Cloud-agent history is a separate disclosure. Tool results and edit proposals can persist as plaintext excerpts in the durable conversation record. Retiring a conversation makes it unlisted and unreachable but does not immediately erase Flue's append-only storage.
Verification status
Each release is checked by building, packing, installing into a temporary prefix, verifying the installed version and exact 21-tool inventory, inspecting the tarball, and byte-checking both packaged skills. See the package reference for the durable release contract. Authenticated deployment behavior still requires operator verification with a real workspace, bound PAT, and password source; missing credentials mean unverified, never a pass.
See also
Local Agent Authoring — canonical architecture and safety boundary.
Personal Access Tokens — UI defaults, scopes, workspace binding, and revocation.
Automation API — document wire contract, key sessions, limits, and typed failures.
Assets — interactive filer and narrow agent boundary.
Flue Agent Platform — cloud-agent ingress, edit safety, quota, and retention.
@takazudo/zudotext-mcp — package/release and installer reference.
Restricted Kanban connections (unreleased)
This capability requires a build from this source change and a configured matching Worker. It is not included in the currently published npm version shown above. A package release and deployment require separate authorization.
The default authoring profile has 21 tools and uses its workspace-bound authoring PAT, workspace ID, and password. It does not register Kanban tools. The separate ZUDOTEXT_MCP_PROFILE=kanban profile exposes exactly 8 tools listed below. It requires its own server-restricted Kanban credential, an active grant for selected boards, and an explicit temporary unlock in the first-party connection screen. A document/asset/agent PAT cannot call these routes; adding Kanban scopes to that PAT does not provision a grant. Use separate MCP client entries and credentials when both profiles are needed.
list_kanban_boards: approved boards only.get_kanban_board: ordered column IDs withcolumnLabels, observed revision and bounded cards; follownextOffsetuntil null.get_kanban_card: stable card identity or a revision-bound file reference.create_kanban_card: create a card and membership atomically.update_kanban_card: explicit title/body/labels/priority/due only; preserve other fields.move_kanban_card: required observed source column and revision; conditional claim has one winner.archive_kanban_card: archive with membership atomically.restore_kanban_card: restore into an existing column.
get_kanban_board returns ordered stable IDs in columns and an effective columnLabels map from each ID to its display label (configured label, or the ID as fallback). Match the user's intended label to this map, then send the returned ID in create/move/restore column and the observed ID in move expectedColumn. Never send a display label as an ID or assume labels are unique; resolve an ambiguous match before writing.
Every mutation requires the exact expectedRevision from a fresh read and a stable idempotencyKey. Retry a lost response with the identical request; a changed payload under the same key fails. On VERSION_CONFLICT, reread and reconsider intent. Never attach a fresh revision to a stale body. Reads never repair identities; missing identities use {fileId,version} until an explicit mutation initializes the target. Unknown format/source failures make zero writes.
Configure the restricted profile with ZUDOTEXT_SERVER_URL and an explicitly provisioned Kanban-only ZUDOTEXT_PAT. It does not read or prompt for a workspace password, create legacy key sessions, or register generic note, search, history, asset, local-path or AI inference tools. Keep credentials in the client/host secret facility, outside prompts and recipes. For Codex Cloud, a domain-scoped network secret can authorize the HTTPS adapter at POST / with the same JSON arguments; arbitrary hosted custom MCP availability is not assumed.
The first-party connection screen owns login, selected boards, and a separate explicit temporary unlock. Connections start locked. WORKSPACE_LOCKED or KEY_SESSION_EXPIRED means return there to unlock, not retry OAuth. Refresh never creates or extends the memory-only lease (at most 24 hours; eviction may end it sooner). Workspace-wide keys are temporarily held by the server; board restrictions are server authorization, not cryptographic board isolation. A dedicated agent workspace narrows key custody. Direct board editing is independent of built-in Workspace AI consent and never grants agent:invoke.
Remote HTTPS MCP exposes exactly these eight tools at /, with explicit OAuth deployment configuration and the same canonical audience intentionally accepted by the eight REST routes. Actual ChatGPT, Codex CLI, Claude Code and Codex Cloud account acceptance remains a separate live check; SDK/protocol tests do not establish those clients' readiness. Generic TODO, mindmap, timeline, schedule-query and asset operations retain their existing gaps.
Restricted client setup and acceptance
Kanban agent connections covers source-build setup for ChatGPT, Codex CLI/IDE, Claude Code and hosted environments, the separate optional tunnel, and outstanding live-client/native checks. The current npm 0.1.1 package does not contain this feature; the checkout's unchanged version is not release evidence. The portable routine in Agent task recipes (MCP) reads, conditionally claims, reports actual results and completes work without implying a scheduler.
Restricted TODO connections (unreleased)
The separate ZUDOTEXT_MCP_PROFILE=todo TODO profile exposes exactly 7 tools listed below, for existing zudo-text TODO documents (type: todo frontmatter, ## TODO / ## DONE sections, <!-- id: … --> item identities). It needs its own server-restricted TODO credential: a grant for exact selected documents (todo:read / todo:write), an explicit temporary unlock, and a TODO-only ZUDOTEXT_PAT. A Kanban credential cannot call these tools and a TODO credential cannot call Kanban, generic note, search, history, checkpoint, asset or agent routes. Moving, deleting or recreating an approved document ends the grant for it. The unlock carries workspace-wide keys; the per-document limit is server authorization, not separate document encryption.
list_todo_documents(): approved TODO documents only, with title and revision.get_todo_document(documentId, offset?, limit?): items (id, title, body, done, section, index, notify) and the whole-document revision; follownextOffsetuntil null.create_todo_item(documentId, expectedRevision, idempotencyKey, title, body?): append an open item with a new persisted identity.update_todo_item(documentId, expectedRevision, idempotencyKey, itemId, title?, body?): explicit title/body replacement only.set_todo_item_done(documentId, expectedRevision, idempotencyKey, itemId, done): complete or reopen; repeating the current state is a no-op.reorder_todo_item(documentId, expectedRevision, idempotencyKey, itemId, toIndex): move within the item's section.initialize_todo_item_ids(documentId, expectedRevision, idempotencyKey): append identities to items that have none.
Every mutation edits only the targeted item's bytes and keeps frontmatter, notifications, extra sections and every other item byte-for-byte; content the tool cannot edit that way is refused with zero writes (UNSUPPORTED_FORMAT), as are duplicate identities. Reads never normalize or assign identity: an item with id: null is reachable only after initialize_todo_item_ids. Retry a lost response with the identical request; a changed payload under the same key fails. On VERSION_CONFLICT (the whole document changed), reread and reconsider intent. There is no delete or archive tool.
Remote HTTPS MCP serves the same 7 tools at /, and the seven REST routes at / intentionally accept that same canonical audience. A remote client discovers it from / and authorizes on the same issuer with resource set to that URL and todo:read (add todo:write only when edits are needed). The resource alone selects the TODO family: a Kanban token, a TODO token on Kanban, a mixed kanban:*/todo:* request and a resource-less TODO request are all refused. Consent selects exact TODO documents, is read-only unless edit permission is chosen, and connects locked; unlock it in Settings → External Connections. Refresh can only narrow scopes and never unlocks, extends the lease or changes the documents. Actual client account acceptance remains a separate live check.
External cloud-agent previews retain workspaceId, target, baseVersion, baseContent, and operation/admission metadata. A reviewed generic write_note must use that original target and body version; metadata does not grant external clients access to built-in Assistant admission or Apply routes. Missing or foreign-workspace preview identity is rejected.
Search results retain structuredContent.documents: the path, file ID, body version and optional canonical note identity captured for each matched body. Carry the matched noteId (or ordinary fileId as expectedFileId) into subsequent reads/history. A moved or reused path must never select a different result silently.