You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 86e2149
Browse filesBrowse the repository at this point in the historyBrowse files
Fix(cli): desktop-managed zn uses the token zn connect saved for the app's server
The desktop launcher runs zn in app mode, and app mode took a server token
only from --token, ZENNOTES_REMOTE_TOKEN or the desktop profile. The app
keeps its own copy in the OS secret store, which zn cannot read, so a
desktop-managed zn, and every zn mcp it serves, got a 401 from the server
the app was connected to. Running `zn connect <url>` did not help: e614af4
left its credentials to terminal mode, so that nothing saved in the
terminal could redirect a desktop script (Kevin on Discord, 2026-09-25).
App mode keeps its order and falls back, last, to the token zn connect
saved for the URL the app chose. The lookup is keyed by that URL, so the
token can only authenticate the app's server, never pick another one, and
terminal workspace names still redirect nothing. Connecting the app and
running `zn connect <url> --no-default` once is now enough for an agent,
with no token in any MCP config. The test that pinned the old rule now
expects the saved token for a bare URL, and a new one covers the fallback
for the app's workspace, a --server name and a --vault name, with no
leak to other URLs and --token and the environment still first.
The 401 hint names that exact command. --no-default matters: a plain
`zn connect` also makes the server zn's own default, and an MCP server in
terminal mode would then stop following the app. When the rejected token
is ZENNOTES_REMOTE_TOKEN, which outranks a saved token so zn connect cannot
fix it, the hint says so instead. Under the desktop launcher, `zn connect`
no longer claims "zn uses it by default now": there only zn tui does, and
the other commands keep following the app. Verified with a real
token-protected server and the zn binary over stdio: zn 0.4.0 answers 401
in app mode, and this build lists the notes.
// A desktop-managed zn follows the app whatever zn's default says, so
127
+
// "zn uses it by default now" would be wrong here: only zn tui does.
128
+
ifmakeDefault {
129
+
emitLine("zn tui opens it by default now. Other zn commands and zn mcp keep following the ZenNotes app, and use this token whenever the app is connected to "+baseURL+".")
130
+
} else {
131
+
emitLine("zn commands and zn mcp use this token whenever the ZenNotes app is connected to "+baseURL+"; `--server "+baseURL+"` reaches it directly.")
132
+
}
133
+
returnnil
134
+
}
125
135
ifmakeDefault {
126
136
emitLine("zn and zn tui use it by default now. `zn use <name>` switches, `zn disconnect "+entry.Name+"` forgets it.")
if!strings.Contains(out, "zn tui opens it by default now") ||!strings.Contains(out, "keep following the ZenNotes app") ||strings.Contains(out, "zn and zn tui use it") {
146
+
t.Fatalf("connect output under the desktop launcher: %s", out)
Copy file name to clipboardExpand all lines: internal/mcp/server.go
+28-7Lines changed: 28 additions & 7 deletions
Original file line number
Diff line number
Diff line change
@@ -11,6 +11,7 @@ import (
11
11
"encoding/json"
12
12
"errors"
13
13
"fmt"
14
+
"os"
14
15
"sort"
15
16
"strings"
16
17
@@ -171,7 +172,7 @@ func tools() []toolDef {
171
172
ifdescription.Kind==backend.KindRemote {
172
173
notes:="This vault lives on a self-hosted ZenNotes server, the workspace the desktop app currently has open. Every tool works on it through the server API; paths are vault-relative POSIX paths exactly as the server reports them. "+pathNotes
173
174
if!description.AuthConfigured {
174
-
notes+=" No token is configured for this server in this process. If calls fail with 401, set ZENNOTES_REMOTE_TOKEN in the MCP server's environment to the token the server was started with."
175
+
notes+=" No token is configured for this server in this process. If calls fail with 401, "+tokenHint(description.BaseURL)
175
176
}
176
177
varserverName, vaultPath, vaultName*string
177
178
ifdescription.Name!="" {
@@ -998,16 +999,36 @@ func CallTool(ctx context.Context, name string, a map[string]any, b Backend) (an
998
999
999
1000
// DescribeToolError phrases a failure for the agent. A server that turns
1000
1001
// the request away is the one failure an agent cannot fix by retrying: the
1001
-
// desktop app keeps its token in the OS secret store, so this process only
1002
-
// has one if the environment supplied it.
1003
-
funcDescribeToolError(errerror) string {
1002
+
// desktop app keeps its token in the OS secret store, where zn cannot read
1003
+
// it, so say how to give this process one. b is the backend the call ran
returnmsg+" The MCP server has no valid token for this ZenNotes server. Set ZENNOTES_REMOTE_TOKEN in the MCP server's environment (the desktop app's copy lives in the OS secret store, which zn cannot read), or run zn with --token."
1008
+
serverURL:=""
1009
+
ifr, ok:=b.(*backend.Remote); ok {
1010
+
serverURL=r.Client().BaseURL
1011
+
// The environment outranks a saved token, so a stale one there
returnmsg+" The rejected token is ZENNOTES_REMOTE_TOKEN from the MCP server's environment, which wins over one saved with `zn connect`: update it there, or remove it and run `zn connect "+serverURL+" --no-default` once in a terminal."
1015
+
}
1016
+
}
1017
+
returnmsg+" The MCP server has no valid token for this ZenNotes server; the desktop app keeps its copy in the OS secret store, which zn cannot read. To fix it, "+tokenHint(serverURL)
1007
1018
}
1008
1019
returnmsg
1009
1020
}
1010
1021
1022
+
// tokenHint says how to hand zn a server token. `--no-default` matters: a
1023
+
// plain `zn connect` also makes the server zn's own default, and an MCP
1024
+
// server in terminal mode would then stop following the app.
1025
+
functokenHint(serverURLstring) string {
1026
+
ifserverURL=="" {
1027
+
serverURL="<server-url>"
1028
+
}
1029
+
return"run `zn connect "+serverURL+" --no-default` once in a terminal (it saves the token where zn and this MCP server look, and the next call uses it), or set ZENNOTES_REMOTE_TOKEN in the MCP server's environment."
1030
+
}
1031
+
1011
1032
// Payload renders a tool result the way the desktop server does: a string
1012
1033
// as-is, anything else as two-space-indented JSON.
0 commit comments