@thoute/mcp
v0.7.1
Published
Local MCP server for a Thoute vault — pulls ciphertext over the Thoute API and decrypts client-side.
Readme
@thoute/mcp
A local Model Context Protocol server for your Thoute vault. It lets an MCP-aware client (Claude Desktop, Cursor, …) read and write your notes through natural tools: search, fetch, append to today's journal, and summarize recent activity.
How it works (and why it runs locally)
Your Thoute vault is end-to-end encrypted. The cloud only ever stores ciphertext. So this server runs on your machine: it authenticates to the Thoute API with an API key, pulls the encrypted vault, and decrypts it locally with your passphrase. Nothing but the one-time embedding-model download ever leaves your machine. Search (keyword and semantic) runs on-device over the decrypted content, using the same embedding model as the Thoute app.
Prerequisites
- Node.js 20 or newer.
- A Thoute API key (
thoute_pk_…) created in the app under Settings → Integrations. Give itread:vault(andwrite:vaultif you want the write tools). You can bind it to a single vault. - Your vault passphrase.
Install
Nothing to install — your MCP client runs it on demand with npx @thoute/mcp
(see below). First run downloads the package and caches it.
Configure your MCP client
Add an entry to your client's MCP config. For Claude Desktop that is claude_desktop_config.json:
{
"mcpServers": {
"thoute": {
"command": "npx",
"args": ["-y", "@thoute/mcp"],
"env": {
"THOUTE_API_KEY": "thoute_pk_xxxxxxxxxxxxxxxx",
"THOUTE_PASSPHRASE": "your vault passphrase",
"THOUTE_VAULT_ID": "vault-... (optional if you have one vault)",
"THOUTE_SERVER_URL": "https://app.thoute.com",
"THOUTE_MCP_ALLOW_WRITE": "0"
}
}
}
}Fill in the env values, then restart the client. (To pin a version, use
@thoute/[email protected].)
Environment variables
| Variable | Required | Default | Notes |
| --- | --- | --- | --- |
| THOUTE_API_KEY | yes | | Your thoute_pk_… key. |
| THOUTE_PASSPHRASE | yes | | Your vault passphrase (used locally to decrypt). |
| THOUTE_VAULT_ID | no | the only vault | Required if the key can reach more than one vault. |
| THOUTE_SERVER_URL | no | https://app.thoute.com | Point at a local server for development. |
| THOUTE_MCP_ALLOW_WRITE | no | 0 | Set to 1 to enable the write tools — create_block, update_block, append_to_journal, append_to_library, upload_attachment (also needs a write:vault key). |
Tools
Read (always available):
search_vault(query, limit?)— hybrid keyword + on-device semantic search.get_block(id)— a block with its page, parent, children, and tags.get_page(id)— a page rendered as a Markdown outline.list_today_journal()— today's daily journal page.summarize_recent(days?)— blocks created or updated recently, grouped by page.due_tasks(asOf?, withinDays?)— open tasks that are overdue or due/scheduled today, grouped (Overdue / Today) and sorted by priority.asOf(ISOYYYY-MM-DD) overrides "today";withinDayswidens the today window for future look-ahead (default0= today only).refresh_vault()— re-pull and decrypt from the server.
Write (only when THOUTE_MCP_ALLOW_WRITE=1 and the key has write:vault):
append_to_journal(content, type?, date?, status?, priority?, dueDate?, scheduledDate?, scheduledTime?)— append a top-level block to a daily journal page (defaults to today). Creates the journal page if it doesn't exist yet.append_to_library(content, type?, status?, priority?, dueDate?, scheduledDate?, scheduledTime?)— append a top-level block to the Library (the Home outline). Creates the Library page if it doesn't exist yet.create_block(content, parentId? | pageId?, type?, status?, priority?, dueDate?, scheduledDate?, scheduledTime?)— append a block under an existing block or page.update_block(id, content?, status?, priority?, dueDate?, scheduledDate?, scheduledTime?)— update a block's content and/or its scheduling fields (at least one is required).upload_attachment(filePath, parentId? | pageId?, content?)— encrypt and upload a local binary file (PDF, image, Office doc, audio, video, …) and create the typed attachment block that references it.filePathis an absolute path on the machine running the MCP server. Defaults to today's journal when no placement is given;pageId: "home"targets the Library,pageId: "journal-YYYY-MM-DD"a daily page. The file is end-to-end encrypted before upload; searchable text is extracted by the web app the first time the attachment is opened.
Block references
((blockId)) in a block's content is a live link to another block. Reads render it as “label” ((blockId)) — the label is there so the text is readable, but the token is the data. When editing, copy it through unchanged.
This matters because update_block replaces content wholesale and re-derives the block's outbound references from the text it receives. Writing back only the label removes the link from both the content and the reference index, and nothing in the result records what the id was. get_block prints the exact stored text under Raw content; edit against that form.
References to blocks that aren't loaded render as the bare ((blockId)) rather than a placeholder, for the same reason — an unresolved reference is usually just an un-hydrated page, not a dead link.
Scheduling fields
The scheduling fields make full task and agenda management possible over MCP: status (open, in-progress, done, archived, hold, blocked, delegated, dropped), priority (low, medium, high, urgent), dueDate / scheduledDate (ISO YYYY-MM-DD), and scheduledTime (24h HH:mm). Pass an empty string to any date or time to clear it. Setting status to done stamps completion (and moving off done clears it), matching the app.
Despite the names, the dates and times are not task-only — they're valid on any block type, and setting one never changes the type of an existing block. On create, when no explicit type is given, status / priority / dueDate infer type: "task" and a bare scheduledTime infers type: "event".
scheduledTime is what makes a block an agenda item:
create_block(pageId="journal-2026-08-03", type="event", content="QSOS",
scheduledDate="2026-08-03", scheduledTime="07:30")renders in the app as 7:30a QSOS with a leading time chip. Two conveniences match the app's own behavior: a time given on a journal page with no scheduledDate takes that day, and a timed root event on a journal page is placed in the day's time order rather than appended, so agenda items stay sorted however they arrive. (Timed events order only against other timed events; notes and untimed blocks stay where they are. Blocks created under a parentId are never reordered.)
append_to_journal, append_to_library, and upload_attachment auto-create their target page (and register it in the vault catalog) when it's missing, so you can write to a fresh day or an empty vault without opening the app first.
Notes
- The server loads a snapshot of the vault at startup and serves tools from memory. Call
refresh_vaultto pick up changes made elsewhere. - Semantic search downloads a ~22 MB model on first use and caches it (and your block vectors) under
~/.thoute/mcp/. If the model can't load (e.g. offline), search falls back to keyword-only. - v1 writes target existing pages only; creating pages (including today's journal) from the server is a planned follow-up.
Develop
npm run typecheck
npm test # crypto unit tests
# integration harnesses (need a local sync-server + Postgres):
DATABASE_URL=… THOUTE_SERVER_URL=http://localhost:3002 npx tsx scripts/it.ts # read path
DATABASE_URL=… THOUTE_SERVER_URL=http://localhost:3002 npx tsx scripts/it-tools.ts # MCP tools
DATABASE_URL=… THOUTE_SERVER_URL=http://localhost:3002 npx tsx scripts/it-write.ts # write tools
npx tsx scripts/it-embed.ts # semantic ranking (downloads the model)
npm run build # tsup → self-contained dist/index.js
MCP_USE_DIST=1 DATABASE_URL=… THOUTE_SERVER_URL=… npx tsx scripts/it-tools.ts # test the bundlePublishing (maintainers)
npm run build (tsup) bundles the server and the shared repo files it imports
into a single dist/index.js; only dist/ ships (see files in package.json).
Publishing uses Trusted Publishing (GitHub OIDC) — no stored npm token, and
npm attaches provenance automatically. The package is live on npm as
@thoute/mcp.
To cut a release: bump version in package.json, push a tag mcp-vX.Y.Z, and
the mcp-publish workflow publishes via
OIDC (the tag must match version). Add a "What's New" entry as needed.
The one-time bootstrap (already done): create the @thoute npm org/scope, do the
first publish locally (npm login && npm run build && npm publish — trusted
publishing requires the package to already exist), then on npmjs.com →
@thoute/mcp → Settings → Trusted Publisher → GitHub Actions register repo
jcromwell/thoute, workflow mcp-publish.yml.
