Your editor, readable by your AI
From v0.1.118, Notepatra ships notepatra-mcp — a Model Context Protocol server that lets Claude Desktop, Claude Code, OpenAI Codex, the OpenAI Agents SDK, and any spec-compliant MCP client see what you have open, search your workspace, run read-only SQL over your data files, read your Git status and Noter notes, and — only with your explicit per-action approval — edit, save, and create notes. Everything runs over stdio and a local socket. Nothing leaves your machine.
notepatra-mcp-windows-x64.zip) plus a one-click notepatra-mcp.mcpb Claude Desktop bundle as release assets. Everything on this page describes v0.1.129.
v0.1.125, v0.1.126 and v0.1.127 were all MCP-only releases, driven by testing this server against a real editor rather than by new features. v0.1.125 fixed a credential leak in
search_project, a goto_line that reported success from the wrong line, and a sidecar that hung on every mistyped argument. A retest then found that the credential fix covered only one of four read paths — see what changed in v0.1.127 below.
What it is
MCP (the Model Context Protocol) is the open standard AI assistants use to talk to tools. Notepatra's implementation is a small standalone binary, notepatra-mcp, that sits between your AI client and the running editor:
AI client (Claude Desktop, Claude Code, Codex, ...)
│ stdio · JSON-RPC 2.0 (MCP)
▼
notepatra-mcp ──local socket──▶ Notepatra (the running editor)
- Transport: a stdio JSON-RPC 2.0 MCP server. It speaks MCP protocol revision
2025-06-18and also accepts2025-03-26and2024-11-05from older clients. - Editor connection: pass
--socketand the server talks to the running editor over a dedicated per-user local socket (a Unix domain socket; the editor opens it on launch). Without--socket, the server runs against a built-in mock editor, so you can exercise the protocol without Notepatra running. - Capabilities: tools (48 of them), resources (your open tabs and Noter notes), and prompts (three ready-made ones).
- Safety: reads never need permission; every write shows an approval card inside the editor window and does nothing until you click Approve. Details below.
In practice: ask your assistant "what do I have open?", "find where the config is parsed", "diff these two tabs", "format this JSON and put it in a new tab" — and it drives the actual editor on your screen.
The tool surface — 49 tools, three tiers
notepatra-mcp exposes 49 tools, grouped in three tiers by what they can touch. Read tools observe. Act tools drive visible, non-destructive editor actions. Write tools change buffer contents or disk — and every one of those is gated behind a human approval card.
Tier 1 — Read (24 tools, no approval needed)
| Tool | What it does |
|---|---|
list_open_tabs | List the open tabs with id, index, title, file path, and modified state. The id is stable; the index is not. |
read_tab | Read the text of one open tab, selected by tab_id, index, or title. Takes max_bytes and reports truncated + total_chars, so a large buffer can be paged instead of swallowing your whole context. |
get_selection | Get the currently selected text, with the tab it belongs to. |
get_status | Editor state: active tab, file path, language, encoding, cursor position. |
app_info | Application info: name, version, edition (Lite/Full), platform. |
list_recent_files | List recently opened file paths. |
find_in_tab | Find text inside one open tab (default: the active tab), selected by tab_id, index, or title — a literal substring, or a regular expression with {regex: true}. |
search_project | Search all open tabs plus files under the workspace folder, case-insensitive (capped at 200 matches) — a literal substring, or a regular expression with {regex: true}. Reports workspace_searched and scope, so a tabs-only search can never pass for a project-wide one. |
list_notes | List Noter notes with title, file name, and last-modified time. |
read_note | Read one Noter note by its file path. |
list_reminders | List your Noter reminders with note title, due time, and bucket (overdue / today / upcoming). |
git_status | Read-only git status for the workspace repository. |
git_diff | Read-only git diff, optionally scoped to one path. |
git_log | Read-only git log (recent commits; default 20, capped at 100). |
git_show | Read-only git show for a commit or ref. |
git_branch | Read-only list of branches in the workspace repository. |
validate_npd | Validate a Notepatra diagram (.npd) — a tab or supplied source; returns parse errors with line numbers. |
run_sql | Run a read-only SELECT over a CSV / Parquet / JSON file (or the active tab) and return columns + rows. SELECT-only and sandboxed — see the note below. |
list_languages | List the canonical language names set_language accepts (as shown in the Language menu). |
get_capabilities | Editor capability profile: edition, platform, version, tool count and tiers, and feature flags (DuckDB, WebEngine, Noter). |
get_diagram_source | Read the .npd source of an open Diagram tab. |
list_connections | List your saved database connections (name, driver, database) — never passwords. |
run_query | Run a read-only SELECT against a saved named connection (PostgreSQL / MySQL / SQL Server / SQLite / DuckDB) — what run_sql cannot reach; mutations rejected. Full edition (DuckDB) for DuckDB-driver connections. |
list_tables | List the user tables available over a saved named connection. |
run_sql stays safe. Every query is checked by the SQL classifier and rejected unless it is a pure SELECT — no INSERT, UPDATE, ATTACH, COPY, or DDL ever runs. On the Full (DuckDB) edition there is a second wall: the target CSV / Parquet / JSON is first materialized into an in-memory table, then DuckDB's external filesystem access is turned off (SET enable_external_access=false) before the untrusted SQL executes — so the query cannot reach host files even through read_text, read_csv_auto, glob, or a replacement scan. The csv_path argument is confined to your open workspace. Results are row- and cell-capped so a single answer can't exfiltrate a whole file.Tier 2 — Act (14 tools, visible and non-destructive)
| Tool | What it does |
|---|---|
open_file | Open a file in a new tab, or focus the tab that already has it. |
new_tab | Create a new untitled tab, optionally pre-filled with text. |
goto_line | Move the cursor to a line in a tab — for pointing you at a location. |
select_range | Select a range of text in a tab by line/column or character offset, so replace_selection has a selection to act on. |
set_language | Set a tab's syntax-highlighting language. Accepts friendly aliases (python, js, cpp, go) and resolves them to the canonical name. |
compare_tabs | Open Notepatra's side-by-side Compare view for two open tabs. |
format_json | Format JSON text and return the result (fails on invalid JSON). |
format_sql | Format SQL text and return the result. |
format_html | Format HTML text and return the result. |
open_note | Open one Noter note in the editor by its file path. |
create_diagram | Create a new Diagram (.npd) tab — optionally pre-filled — and focus it; reports parse validity. |
open_noter | Open or focus the Noter panel tab. |
open_data_analyst | Reveal and focus the AI dock in Data Analyst mode. |
render_chart | Render a chart inline in the Data Analyst transcript (Vega-Lite or the simplified {type,x,y,data} form). Full edition (WebEngine) only. |
Tier 3 — Write, with approval (11 tools, human-gated)
| Tool | What it does |
|---|---|
insert_text | Insert text into an open tab at the cursor, or at an explicit line/column. |
replace_selection | Replace the currently selected text with new text. |
apply_edit | Literal find-and-replace in one open tab — first occurrence, or all of them. |
save_tab | Save an open tab to its file on disk. |
create_note | Create a new Noter note with a title and body. |
append_note | Append text to an existing Noter note. |
set_reminder | Set a due-time reminder on a Noter note. |
export_diagram | Export a diagram tab to a PNG or PDF file on disk. |
set_diagram_source | Replace the entire .npd source of a Diagram tab; the canvas re-renders. |
export_query_results | Run a read-only SELECT against a saved connection and write the results to a CSV or JSON file. |
export_chart | Render a chart off-screen and export it to a PNG / SVG / HTML / spec file. Full edition (WebEngine) only. |
What changed in v0.1.127
Retesting v0.1.126 found a sixth credential door, and the interesting part is not the door — it is why there kept being another one.
select_range accepted any range inside a credential file and get_selection returned it as text. get_selection had been exempted on the reasoning that a selection is human-made, so reading it back implies consent — but select_range lets the client choose the range, which makes that reasoning false.
apply_edit was a quieter form of the same thing: it reads the buffer as a match oracle, so "no match" versus an approval card naming the match reports what a file contains, one probe at a time, without ever returning a byte of text.
Why the fix is structural rather than a sixth guard
Three consecutive releases shipped this same shape. v0.1.125 unified two hand-maintained copies of the deny list. v0.1.126 added the check to four verbs. The retest then found a fifth and a sixth.
Every one of those fixes was correct and none of them converged, because what kept going stale was not the list — it was the enumeration. Every new verb had to remember to ask, and the point of a security guard is that it does not depend on remembering.
Buffer text now leaves the editor through exactly one accessor, which refuses before it returns. select_range refuses to stage a selection over a credential file at all, so no future verb can inherit a leak that was already set up for it. And a test lints the bridge source and fails the build on any direct buffer read that bypasses the accessor — so a verb added next year cannot reintroduce the class without turning a test red.
Two smaller corrections
- The truncation marker was lying about the cap. Ask
read_tabformax_bytes: 100and the text still ended[truncated at 5 MB]— a constant left over from before the argument existed. It now reads[truncated: showing 100 of 2688894 characters]. The v0.1.126 notes also claimedread_tab"always reportstruncatedandtotal_chars"; that is true of the editor-to-sidecar wire and not of the tool result, which is plain text, so the marker is where those counts have to live. select_rangeadvertisedtab_idand then rejected it with-32602, so a client generating calls from the published schema failed. One verb out of thirteen slipped the v0.1.126 sweep. A test now walks the entire tool list and asserts every advertisedtab_idis genuinely accepted — a schema that lies is worse than a missing feature, because it makes correct clients fail. Separately,list_open_tabspublished the field asidwhile every verb takestab_id; both names are emitted for one release.
What changed in v0.1.126
v0.1.125 fixed a credential leak in search_project. A retest of that release found the fix covered one read path out of four — and eight other defects alongside it. Everything below is now the shipped behaviour.
search_project's filesystem walk to skip credential files, and that was the only place the check ran. So the leak survived in the plainest possible form — stop searching and just ask:
open_file {"path": ".../.ssh/id_rsa"} opened it with isError:false, and read_tab returned the entire private key. find_in_tab returned the matching key lines. And search_project's open-tab leg read the same file straight out of the buffer while its disk leg refused it.
All four now check the same list, and
open_file refuses before the tab exists — opening a key and then declining to read it would still put it on your screen.
Tools that reported success while returning the wrong answer
format_jsoninvented structure. The MCP path called the editor's JSON auto-fixer, which is right for the JSON panel — a human sees the repair and can undo it — and wrong here.[1,2came back as[1,2]withisError:false: a truncated config file made syntactically valid and semantically invented. Format-then-write-back would have fabricated data you never had. It validates strictly now and fails with a parse position.- A search that simply missed reported "No workspace folder is open". Whether a folder is open is not a fact about your query, but the error only fired when the open tabs happened to contain nothing — so the same tabs answered "not found" for one query and "you have no folder open" for another. Zero matches is a successful search now, with the reduced coverage carried in a
notice. - Without
--socket, everything was fabricated and nothing said so. Only--helpwarned, and the consumer is a model that never reads it. Every mock result now carries a[MOCK DATA]block and_meta.mock.
Tabs have a stable id
Every write verb used to address its target by tab_index. That index is positional: close a tab to its left and it silently re-points at a different document, underneath a caller with no way to notice. Out-of-range was the lucky case — you get an error. A shifted-but-still-valid index writes silently, with the approval card the only thing in the way.
list_open_tabs now publishes an id per tab that survives reordering, save-as, and its neighbours closing, and every verb that names a tab accepts tab_id. Asking for an id that is gone tells you it was closed, rather than reporting a bad index you never supplied. tab_index keeps working everywhere it did.
Smaller corrections
search_projectnow actually sends theworkspace_searchedandscopefields v0.1.125 promised — the editor had been sending them all along and the sidecar's own result struct discarded them.read_tabtakesmax_bytes, and a capped read ends with a marker stating how much you got of how much there is, so a multi-megabyte buffer can be paged rather than filling your context in one call.find_in_tabacceptstitle, whichread_tabalways accepted andfind_in_tabrejected as an unexpected argument.- An invalid pagination cursor on
tools/listis rejected instead of ignored — returning the full list is indistinguishable from "your cursor was valid, here is page 2".
The write-approval model
The eleven write tools never execute on arrival. Each incoming write request shows a non-modal approval card inside the Notepatra window — it does not steal focus or block your typing — describing what the assistant wants to do, with a preview of the text involved and two buttons: Approve and Deny.
- Nothing happens until you click Approve. The tool call blocks; the assistant just waits.
- 120-second auto-deny. If you don't respond within two minutes, the request is denied automatically and the assistant receives the error
approval timed out. - Deny is a first-class answer. Click Deny and the assistant receives
denied by user— as a tool error it can read and react to, not a silent failure. - One card at a time, in order. Multiple pending writes queue FIFO; you decide each one individually. No batch-approve.
- No headless fallback — by design. If there is no visible editor window to host the card, the write is denied with
approval unavailable. There is no auto-approve setting, no environment variable, and no config flag that bypasses the card.
The gate lives in the editor process itself (the C++ bridge), not in the MCP sidecar — so a modified or malicious MCP client still cannot write without a human clicking Approve inside Notepatra. Read and act tools don't prompt: they can't change buffer contents or touch disk.
Setup — pick your client
The server binary is notepatra-mcp. All snippets below pass --socket, which connects it to the running editor; make sure notepatra-mcp is on your PATH (or use its absolute path), and have Notepatra running when the assistant calls a tool.
Get notepatra-mcp
From v0.1.118, every GitHub release ships prebuilt notepatra-mcp binaries — cosign-signed like every other release artifact:
notepatra-mcp-linux-x64.tar.gznotepatra-mcp-linux-arm64.tar.gznotepatra-mcp-macos-arm64.tar.gz
Extract, chmod +x notepatra-mcp, and put it somewhere on your PATH:
tar xzf notepatra-mcp-linux-x64.tar.gz
chmod +x notepatra-mcp
sudo mv notepatra-mcp /usr/local/bin/
Or build it from source — the sidecar lives in notepatra-mcp/ in the repo and needs only stable Rust, no other dependencies:
cd notepatra-mcp
cargo build --release # binary at target/release/notepatra-mcp
Windows named-pipe transport arrived in v0.1.119 but did not actually work: it deadlocked on the first verb after the greeting, so no tool call ever completed. It is fixed in v0.1.120 — the sidecar connects to the running editor over a named pipe (\\.\pipe\…) and --socket works there for real. As of v0.1.120 the signed release bundle provides a prebuilt Windows binary (notepatra-mcp-windows-x64.zip, cosign-signed like the others) and a one-click Claude Desktop bundle (notepatra-mcp.mcpb — install it from Claude Desktop's Settings → Extensions), so Windows users no longer need to build from source. cargo install notepatra-mcp remains a fully supported alternative on every platform.
| Client | Vendor ecosystem | How it connects | Works with Notepatra? |
|---|---|---|---|
| Claude Desktop | Anthropic | stdio, via claude_desktop_config.json — or one-click via the notepatra-mcp.mcpb bundle from the next release | Yes |
| Claude Code | Anthropic | stdio, via claude mcp add | Yes |
| Codex CLI | OpenAI | stdio, via config.toml | Yes |
| OpenAI Agents SDK | OpenAI | stdio, via MCPServerStdio | Yes |
| Cursor, Windsurf, VS Code, others | Various | stdio, via each client's MCP config | Yes — any spec-compliant stdio MCP client |
| ChatGPT connectors | OpenAI | Remote URL only | No — cannot reach a desktop editor (see limitations) |
| claude.ai web connectors | Anthropic | Remote URL only | No — cannot reach a desktop editor (see limitations) |
Claude Desktop
Add Notepatra to claude_desktop_config.json (Settings → Developer → Edit Config):
{
"mcpServers": {
"notepatra": {
"command": "notepatra-mcp",
"args": ["--socket"]
}
}
}
Restart Claude Desktop; the Notepatra tools appear in the tools menu.
Claude Code
One command:
claude mcp add notepatra -- notepatra-mcp --socket
Then /mcp inside Claude Code shows the server and its 49 tools.
OpenAI Codex CLI
Add a block to ~/.codex/config.toml:
[mcp_servers.notepatra]
command = "notepatra-mcp"
args = ["--socket"]
OpenAI Agents SDK
Use the SDK's stdio server class (Python shown):
from agents import Agent, Runner
from agents.mcp import MCPServerStdio
async with MCPServerStdio(
params={"command": "notepatra-mcp", "args": ["--socket"]},
) as notepatra:
agent = Agent(
name="Editor assistant",
instructions="Help the user work with what they have open in Notepatra.",
mcp_servers=[notepatra],
)
result = await Runner.run(agent, "What tabs do I have open?")
Any other MCP client
Anything that can launch a stdio MCP server works — Cursor, Windsurf, VS Code, or your own client. Configure it to run the command notepatra-mcp with the argument --socket. The server identifies itself as notepatra-mcp and advertises the tools, resources, and prompts capabilities.
Tip: running notepatra-mcp without --socket starts it against a built-in mock editor — handy for testing a client integration without Notepatra running.
Resources and prompts
Beyond tools, the server exposes two other MCP capabilities:
Resources
notepatra://tab/N— each open tab, as plain text.resources/listenumerates them with their titles.notepatra://note/<basename>— each Noter note, as plain text. If two notes share a basename, the colliding ones are addressed by their URI-encoded full path instead, so every note stays reachable.
Clients that support MCP resources (Claude Desktop's attachment picker, for example) can pull a tab or note straight into the conversation without a tool call.
Prompts
| Prompt | What it does |
|---|---|
review-current-file | Code-review the file open in the active tab — embeds its live contents and language. |
explain-selection | Explain the text currently selected in the editor. |
summarize-notes | Summarize your Noter notes (embeds up to the 3 most recent). |
Limitations — the honest list
- The editor must be running. With
--socket, tools need a live Notepatra to talk to; if it isn't running, they return the clean error "Notepatra is not running (start the editor first)" rather than hanging. - Writes need a visible window. The approval card must appear somewhere; with no visible window, writes are denied with
approval unavailable. That is deliberate — see the approval model. - Cloud-only connector surfaces can't reach it. ChatGPT connectors and claude.ai web connectors only speak to servers reachable at a public URL. A desktop editor on your machine is not that, so those surfaces cannot use
notepatra-mcp. Use the desktop or CLI clients above instead. - Windows named-pipe transport (added v0.1.119, working from v0.1.120). The v0.1.119 implementation deadlocked on every verb, so Windows tool calls never completed; v0.1.120 fixes it. The sidecar connects to the running editor over a Windows named pipe (
\\.\pipe\…), so--socketworks on Linux, macOS, and Windows alike. Prebuilt Windows binaries (notepatra-mcp-windows-x64.zip) and the one-clicknotepatra-mcp.mcpbClaude Desktop bundle ship as v0.1.120 release assets;cargo install notepatra-mcpremains a fully supported alternative on every platform. - Large reads are capped.
read_tabtruncates at 5 MB (and says so with a marker in the text) and accepts a smallermax_bytes;search_projectreturns at most 200 matches per search. - Credential files are refused, and you cannot override it.
open_file,read_tab,find_in_taband both legs ofsearch_projectrefuse anything matching the credential deny-list — SSH/GPG/AWS key directories,id_rsaanywhere on a path,.envfiles,*.pem/*.key/*.pfx/*.p12/*.jks, Terraform state. This constrains the MCP surface, not you: open the file yourself and the editor shows it normally. A legitimate*.pemtest fixture will be refused, which is the intended trade — a refused read costs you one click, a leaked key does not. - The
Denybutton is less tested than the timeout. Approval-card denial by 120-second timeout is verified repeatedly; denial by clicking Deny is exercised in the automated suite but has had less real-hands testing. - Search defaults to literal.
find_in_tabandsearch_projectmatch a literal substring by default; pass{regex: true}for a regular-expression search (an invalid pattern is rejected with a clear error).
FAQ
Is my data sent anywhere?
Not by Notepatra. The notepatra-mcp binary you install makes no network connections: it talks to your AI client over stdio (pipes between two processes on your machine) and to the editor over a local per-user socket. Nothing leaves the machine through this path — and the shipped binary is not merely configured that way, it is compiled without networking at all. Every CI job runs the released binary and fails the build unless it reports built without remote support. One honest caveat that applies to every MCP server: whatever your assistant reads through these tools becomes part of its conversation, and goes wherever that client sends its conversations — a local model stays local, a cloud model does not. Choose the client and model accordingly.
What is the remote gateway in v0.1.120, and does it weaken any of this?
v0.1.120 adds an opt-in remote gateway, and it is off unless you deliberately build it: the code sits behind a
remote Cargo feature that is not in the default feature set, so cargo install notepatra-mcp and every
signed release binary contain no gateway and no networking or crypto dependencies at all. If you do build it
(cargo build --features remote), serve binds loopback only — 127.0.0.1, never a LAN or public
interface — and a client must complete an 8-digit one-time HMAC pairing to receive a 256-bit bearer token, of which only the SHA-256
is stored, mode 0600. Scopes are fail-closed: a client is read-only unless it paired for more, calls above its
scope are rejected at dispatch, and tools/list is filtered to the tiers it is allowed. A missing or invalid token never
elevates.
The approval gate is unchanged, and there is no remote approval. A remote write request is only forwarded to the editor, which raises the same Approve / Deny card on your screen. Nobody can approve a write from the far end of the connection, and there is no headless bypass. The editor itself never listens on the network — only the opt-in sidecar gateway binds a socket. Until LAN support and TLS land, use an SSH tunnel rather than exposing the port.
Can the AI edit my files without me noticing?
No. The only tools that can change a buffer or touch disk are the eleven write tools, and each call requires you to click Approve on a card inside the editor within 120 seconds. No response means denied.
Is this the same as Notepatra's built-in AI Assistant?
No — they're complementary. The AI Assistant dock (Ctrl+Shift+A) is Notepatra talking to a model you configure. MCP is the reverse direction: an external assistant you already use (Claude, Codex, ...) talking to Notepatra. Neither requires the other.
Does it work with the Lite build?
Yes. The MCP bridge is part of the editor from v0.1.118 on both editions, and app_info reports which edition is running.
Which tab is index 0?
Tab indices are zero-based, exactly as shown in the tab bar — including the Welcome tab when it's open. Use list_open_tabs first rather than guessing indices. Better still, use the id it returns: an index is positional and moves the moment a tab closes, while an id belongs to the document for as long as it is open.
What happened to the mock server?
Running notepatra-mcp without --socket still starts an in-memory demo editor, so any MCP client can exercise the protocol with nothing installed. As of v0.1.127 it says so on the wire: every result carries a [MOCK DATA] block and _meta.mock, and initialize reports serverInfo.mock. Before that, a config file that had lost its --socket served fabricated tabs an assistant could not tell from your real ones.