Repository navigation
Conversation
…ns on Since desktop 2.79.0 a table view can show only picked rows (pickedRowIds in schema.json), and + Row in the app's grid adds the new row to the picks of the view on screen, or it would vanish the moment it was made. zn knew nothing of picks. `zn base add` kept pickedRowIds when it rewrote schema.json but never added its row, so a database that opens on a picks view did not show the row in the app. The TUI's `a` had the same gap, on a table or a board. Doc.AddRowInView is the app's addRowInView (shared-domain database-records.ts, desktop commit 502d45d4): when the view has a pickedRowIds list, the new row is appended to it, and picksChanged says schema.json has to be written with the row. An empty list counts, since it is a view of no rows; null or no key is a view of every row. The list is edited in place in the ordered sidecar, so schema.json keeps every other byte: an add with picks matches the app's JSON.stringify(schema, null, 2) byte for byte, an option minted in the same write included. `zn base add` uses the view the app would open on, the database's activeViewId, as the desktop CLI does. When that view has picks the row joins them; when it has none, nothing about the views changes and schema.json is not rewritten. The text output says "added to the picked rows of the <View> view", before the page line, and --json carries pickedInView. The TUI uses the view on screen. Either way other views keep their picks: a curated view nobody is looking at does not collect every row added elsewhere. It does not teach the TUI to show picks. Its views still list every row and cannot pick or unpick; only the row it adds now stays in the app's view. Tests: AddRowInView joins only the named view's list (empty list, null and an unknown view too); `zn base add` on a picks view rewrites schema.json with nothing but the new id and reports pickedInView; on a view of every row it leaves schema.json byte-identical and emits no pickedInView; the text line and a --page record land together; the TUI's `a` joins Shelf, then Later after switching views, and changes nothing on All. The picks tests fail without this change. The README, `zn help` and the TUI's help say so. go vet and go test ./... clean.
zn mcp handed every client the whole vault. Connecting a personal vault to an assistant meant sharing all of it, or keeping a second vault, and the only brake was the client's own tool permissions (#21, @lewxdev). Four flags now scope the server: --allow-path and --deny-path (folders or notes, repeatable), --read-only, and --access read|comment|write. A non-empty allow list shows only what it names; deny hides paths even inside it. The server enforces the scope itself, in a backend wrapper every tool call goes through, so listings, search, tags, tasks, backlinks, assets and comments all agree, and a handler added later gets it for free. Paths are never trusted as given. Each one is looked up in the vault's own note and folder listings, which walk real directories and regular files only, so a symlink from a shared folder into a hidden one, a case variant of a hidden folder on a case-insensitive disk, or .zennotes has no match. A hidden note answers exactly like a missing one, and neither errors nor vault_info show the vault's absolute location. Search filters before it limits, so hidden matches can neither crowd out shared ones nor be counted by a short page. Tools above the access level are not listed; every tool now carries read-only and destructive hints for clients. Writes stay inside the scope: notes are created, moved and restored only in allowed folders, never through a subpath into a system, asset or internal directory under any case. Archiving and trashing a visible note are allowed as exits even when the archive or trash is not shared. A rename rewrites links only in notes the client can see, through a new vault.RenameNoteRewritingLinksIn; a server rewrites links in every note itself, so a scoped client cannot rename there. A folder moves or goes as a whole only when nothing inside it is hidden, and empty_trash is never offered once paths are restricted. A --deny-path that matches nothing in the vault shares nothing at all: the folder may have been renamed outside ZenNotes, and what it hid would otherwise reappear under the new name. stderr names the entry for the client's logs; the model never sees it. The design, the leak analysis, the decisions and what comes next (the desktop's bundled server, then per-vault profiles chosen from the app) are in docs/ideas/mcp-scoped-access.md.
The scope resolves every path against the vault's listings, which skip symbolic links, so a destination segment they did not know was taken as a new folder. Under --allow-path Projects, create_note, create_folder, move_note and rename_folder into Projects/inner, a link to a hidden folder, wrote into that folder. On a local vault the first segment the listings do not know must now have nothing on disk; anything else gets the usual refusal. A server's vault is left to the server's walker.
…ss contract internal/mcp/testdata/mcp-scope.json is a copy of the fixture the desktop's bundled MCP server runs (ZenNotes/zennotes bridge-contract/fixtures), with its provenance hash: flag refusals, the tools each scope lists and their annotations, every tool's answer for shared, hidden, linked, case- and accent-variant paths in root, inbox and remapped vaults, and the disk afterwards. The docs now say the bundled server takes the same flags, and the design doc records how Phase 1b was built and where the two engines still differ.
`zn help mcp` now says what the scope flags do, not only their names, and the README's feature list says zn mcp can share part of a vault. The design doc's status no longer calls Phase 1a and 1b uncommitted: zn 0.13.0 carries Phase 0 (#23) and Phase 1a with the link fix and the shared contract test, and Phase 1b is 7be9e993 in ZenNotes/zennotes, landing in desktop 2.80.0. Decision 10 records that Phase 0 merged on its own and then shipped with 1a.
TestSharedScopeContract checksums internal/mcp/testdata/mcp-scope.json against the desktop's copy, but Windows runners check text out with CRLF, so the checksum differed and both contract tests failed there. The folder now gets the eol=lf rule the other shared fixtures already have.
The fixture's full-access vault_info case expects the result to contain $VAULT, and two scoped cases expect it not to. The test replaced $VAULT with the root as is, but a result object is JSON, where a Windows root's backslashes are doubled, so the full-access case failed on Windows and the scoped ones could not have caught a leaked root there. A needle now matches the root as is (error text) or JSON-escaped (result objects). The fixture is unchanged.
adibhanna
added a commit
to ZenNotes/zennotes
that referenced
this pull request
Oct 30, 2026
The bundled CLI is the floor a desktop build guarantees, and 0.13.0 is the newest release (ZenNotes/tui#24). It answers ZenNotes/tui#21: zn mcp takes --allow-path, --deny-path, --read-only and --access and enforces them on every tool, the same contract this app's bundled MCP server follows since 969f50e. It also keeps every assistant out of hidden and system folders through moves, renames and subpaths (ZenNotes/tui#23), and puts a row zn base add makes into the picked rows of the view the database opens on, as + Row does in the grid. The pin is the release's terminal-release.json copied byte for byte after it verified against the zn-release-1 key this app trusts (cli-update-core.ts), naming source commit cae76aff (the v0.13.0 tag), integration protocol 1, and four archives that match both the manifest and checksums.txt. Staged through `npm run terminal:stage`; the native darwin-arm64 binary reports zn v0.13.0. The in-app manual's CLI card says 0.13.0 and what it adds.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
zn 0.13.0 on top of #23 (assistants stay inside the note folders, already on main).
zn mcpcan share part of a vault, read-only if asked (Support scoped note access and permissions for MCP #21). Four flags scope the MCP server:--allow-pathand--deny-path(folders or notes, repeatable),--read-only, and--access read|comment|write. A non-empty allow list shows only what it names, and deny hides paths even inside it. The server enforces the scope on every tool (listings, search, tags, tasks, backlinks, assets, comments, errors), and a hidden note answers exactly like a missing one. Tools above the access level are not listed, and every tool carries read-only and destructive hints.internal/mcp/testdata/mcp-scope.jsonis byte-identical to the desktop'spackages/bridge-contract/fixtures/mcp-scope.jsonfrom ZenNotes/zennotes 7be9e993 (sha256 076d5448...), andscope_contract_test.goruns it here, sozn mcpand the desktop's bundled server give the same answers.zn base addjoins the picks of the view it opens on. Since desktop 2.79.0 a table view can show picked rows only; a row zn added never joined them, so it did not show in the app. The TUI'sahad the same gap.zn help mcpsays what they do,docs/desktop-integration.mdcovers workspaces and the desktop engine, and the design doc's status reflects what ships.Verified:
gofmt,go vet ./...andgo test ./...clean on this branch; the contract fixture compared withcmpagainst the desktop commit; the built binary driven over stdio with--allow-path Projects --read-only(12 tools, a hidden note and a path through a link bothNote not found,write_noteunknown). Windows CI caught two harness bugs, fixed here: the fixture now checks out with LF like the other shared fixtures, and the contract finds the vault root in JSON results, where Windows doubles its backslashes.