@safekb/mcp 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +159 -40
  2. package/dist/safekb-mcp.js +15786 -14769
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -3,62 +3,120 @@
3
3
  Local MCP client for SafeKB (stdio). It proxies the hosted SafeKB tools and adds **sealed** documents
4
4
  and folders: content is encrypted on your machine, and the server stores only ciphertext.
5
5
 
6
- Needs Node 20 or newer.
7
-
8
6
  ## Setup
9
7
 
10
- ```sh
11
- npx -y @safekb/mcp --authorize --url <your SafeKB API URL>
12
- ```
13
-
14
- This opens the browser and prints a **pairing code**. Sign in on the page, choose the spaces, type the
15
- pairing code, and the client saves a service-account token to `~/.config/safekb/mcp.json` (mode 0600).
16
- It then prints the account it was authorized as; check that it is yours. There is no default URL.
8
+ ### Claude Desktop
17
9
 
18
- Last, it chooses where this machine keeps the keys of new seal roots (see [Where secrets are kept](#where-secrets-are-kept)).
19
- With a system keyring that is the choice. Without one, as on most servers, it warns and asks before it
20
- uses key files. `npx -y @safekb/mcp --configure` makes the same choice again without re-authorizing.
21
- A client authorized before this choice existed makes it by itself on its next start when the system
22
- keyring works; only key files need `--configure`, because they need your agreement.
10
+ 1. Download [`safekb.mcpb`](https://safekb.com/download/safekb.mcpb) and open it (or Settings →
11
+ Extensions → Advanced settings → Install Extension…), then click **Install**.
12
+ 2. A SafeKB page opens in your browser. Sign in, choose the spaces, check that the page shows the same
13
+ code as Claude, and click **Confirm**.
23
14
 
24
- How the token gets to the client: the page never sees it. The page hands the client a single-use code
25
- (valid for two minutes), and the client redeems that code itself, proving with a secret it never wrote
26
- anywhere that it is the process that started the flow. The URL in the browser launcher's arguments is
27
- readable by other users of the machine, so it holds nothing that completes the flow; the pairing code
28
- is what ties your sign-in to your terminal. Every authorization creates its own token: authorizing
29
- again never changes the token or the spaces of another client. Revoke one under Settings → service
30
- accounts.
15
+ The tools appear in Claude without a restart. Nothing else is needed: no Node, no config file.
31
16
 
32
- Claude Code:
17
+ ### Claude Code
33
18
 
34
19
  ```sh
35
- claude mcp add safekb -- npx -y @safekb/mcp
20
+ npx -y @safekb/mcp --setup
36
21
  ```
37
22
 
38
- Claude Desktop and Cursor (`mcpServers` in their MCP config file):
23
+ This opens the same page, then registers the server with Claude Code for all your projects
24
+ (`claude mcp add --scope user safekb -- npx -y @safekb/mcp --mcp`). What is already done is left as it
25
+ is, so it can be run again. A registration that `--setup` of a version before 0.5.0 wrote no longer
26
+ starts the server and is written again with `--mcp` and the arguments it had (see
27
+ [Upgrading to 0.5.0](#upgrading-to-050)). Needs Node 20 or newer.
28
+
29
+ Over SSH, or anywhere without a browser, the command prints a link instead. Open it on any device, sign
30
+ in and approve; the terminal then shows the account and a code, and you answer `y` when they match what
31
+ your browser shows.
32
+
33
+ ### Other MCP clients (Cursor, …)
39
34
 
40
35
  ```json
41
- { "mcpServers": { "safekb": { "command": "npx", "args": ["-y", "@safekb/mcp"] } } }
36
+ { "mcpServers": { "safekb": { "command": "npx", "args": ["-y", "@safekb/mcp", "--mcp"] } } }
42
37
  ```
43
38
 
39
+ `--mcp` is what starts the MCP server. Without any argument the command prints its usage text and exits.
40
+
41
+ Started with `--mcp` but without a token, the client serves one tool, `connect_safekb`, which opens the page and puts
42
+ the SafeKB tools in its place once you have confirmed. `SAFEKB_MCP_NO_BROWSER=1` makes it show the link
43
+ instead of opening a browser.
44
+
45
+ ### Upgrading to 0.5.0
46
+
47
+ Until 0.4.0 the bare command started the MCP server. From 0.5.0 it prints the usage text, and the
48
+ server is started by `--mcp`. A client configured with `"args": ["-y", "@safekb/mcp"]` therefore gets
49
+ no tools after the upgrade:
50
+
51
+ - Claude Code: run `npx -y @safekb/mcp --setup` again. What an older `--setup` registered (user scope,
52
+ `npx -y @safekb/mcp`, with or without `--config`) is registered again with `--mcp`. A registration
53
+ you made or changed yourself (other arguments, environment entries, local or project scope, a
54
+ pinned version, the `safekb-mcp` program) is not touched: the command stops and prints the two
55
+ `claude mcp` commands that add `--mcp` to it. A registration pinned to an exact version below 0.5.0
56
+ (`@safekb/mcp@0.4.0`) keeps working without `--mcp` and is left as it is.
57
+ - Claude Desktop: install the current `safekb.mcpb`.
58
+ - Other clients: add `"--mcp"` to the `args` in their MCP config.
59
+
60
+ ### A self-hosted SafeKB
61
+
62
+ The default server is `https://api.safekb.com`. For another one run
63
+ `npx -y @safekb/mcp --setup --url <your API URL>` first; the Desktop extension then uses the token that
64
+ command saved.
65
+
66
+ ### How the authorization is protected
67
+
68
+ - The page never sees the token. The client keeps a random secret, the link carries only its SHA-256,
69
+ and the client collects the token itself by presenting the secret to the server it was pointed at.
70
+ - Whoever sees the link could approve it for *their* account, to make your client write into it. So an
71
+ approval is taken only when it is tied to you: it carries the pairing code, or you confirmed it in
72
+ your terminal.
73
+ - The pairing code reaches the page in a file only your user can read (mode 0600), which the browser is
74
+ started with. The browser launcher's arguments, which other users of the machine can read, hold only
75
+ that file's path. This is why you check the code instead of typing it. When the page was opened by
76
+ hand in a client without a terminal, you type the code.
77
+ - Every authorization creates its own token: authorizing again never changes the token or the spaces of
78
+ another client. Revoke one under Settings → service accounts. The token is saved to
79
+ `~/.config/safekb/mcp.json` (mode 0600).
80
+
81
+ ### Where keys go
82
+
83
+ The keys of new seal roots go to the system keyring. On a machine without one (most servers) they are
84
+ kept as files, and the client says so in plain words on the page, in the terminal and in the result of
85
+ every tool that keeps a key this way; see [Where secrets are kept](#where-secrets-are-kept).
86
+ `npx -y @safekb/mcp --configure` makes the choice again, for example after a keyring was installed.
87
+
44
88
  ## Options
45
89
 
46
90
  | Flag | Environment | Meaning |
47
91
  | --- | --- | --- |
48
- | `--url <api url>` | `KB_API_URL` | API base URL. Otherwise the URL saved by `--authorize`. When serving, it must have the origin the token was issued for: the token is never sent anywhere else. |
92
+ | `--mcp` | | Start the MCP server on stdio. Modified by `--url`, `--keyring`, `--sealed-only` and `--sidecar-socket`. |
93
+ | `--setup` | | Authorize, choose the key store and register with Claude Code, in one step. Safe to repeat. |
94
+ | `--url <api url>` | `KB_API_URL` | API base URL. Otherwise the URL in the token file, or `https://api.safekb.com` when authorizing. When serving, it must have the origin the token was issued for: the token is never sent anywhere else. |
49
95
  | `--config <path>` | `SAFEKB_MCP_CONFIG` | Token file. Default `~/.config/safekb/mcp.json`. Use one per project for per-project access. |
50
- | `--keyring <dir>` | `SAFEKB_SEALED_KEYRING` | Directory of sealed key files (`*.json`). Otherwise the one `--configure` chose, if any. |
96
+ | `--keyring <dir>` | `SAFEKB_SEALED_KEYRING` | Directory of sealed key files (`*.json`). Otherwise the one in the store settings, if any. |
51
97
  | `--sealed-only` | | Expose only the sealed tools; the hosted tools are not proxied. |
52
- | `--authorize` | | Authorize in the browser and write the token file, then choose the key store. |
53
- | `--configure` | | Choose where new sealed keys are kept. Writes `stores.json` next to the token file. |
98
+ | `--authorize` | | Authorize again in the browser and replace the token file. |
99
+ | `--configure` | | Choose again where new sealed keys are kept. Writes `stores.json` next to the token file. |
54
100
  | `--sidecar` | | Run the sidecar in this terminal. With `--confirm` it asks before every release of a secret. |
55
101
  | `--sidecar-socket <path>` | `SAFEKB_SIDECAR_SOCKET` | The sidecar's socket. Default `sidecar.sock` next to the token file. |
56
102
  | `--export-keys <file>` | | Write every sealed key this machine keeps to a new passphrase-protected file. See [Moving keys to another machine](#moving-keys-to-another-machine). |
57
103
  | `--import-keys <file>` | | Read such a file and keep its keys on this machine. |
104
+ | `--list-keys` | | Print the sealed keys this machine keeps and how each stands with the server. See [Managing keys](#managing-keys). |
105
+ | `--create-key <name>` | | Create a sealed folder as a new seal root with a generated key. Needs `--space <space id>`; takes `--store`. |
106
+ | `--rename-key <ref>` | | Rename a seal root to `--name <new name>`. |
107
+ | `--rotate-key <ref>` | | Replace a seal root's secret with a new generated key; takes `--store`. |
108
+ | `--move-key <ref>` | | Move a seal root's current key to `--store system\|file`. |
109
+ | `--delete-key <ref>` | | Remove a seal root's keys from this machine, after a typed confirmation. |
58
110
  | | `SAFEKB_SEALED_IDLE_TTL` | Seconds an unlocked seal root may stay idle. Default 900. `unlock_sealed` can set it per root with `idle_ttl_seconds` (60 to 3600). |
59
111
  | | `SAFEKB_HOSTED_TIMEOUT` | Seconds a hosted tool call may take. Default 300. |
112
+ | | `SAFEKB_MCP_NO_BROWSER` | Set to anything: never start a browser, only show the authorize link. |
60
113
 
61
- The client refuses to start when the token file or a keyring file is readable by group or others,
114
+ Exactly one mode is given: `--mcp`, `--setup`, `--authorize`, `--configure`, `--sidecar`, or one of the
115
+ key modes. A flag that only modifies a mode (`--sealed-only`, `--keyring`, …) without one is an error;
116
+ it never starts the server by itself. A flag the given mode does not take is an error too, as is a
117
+ mode without the value or the modifier it needs, and any flag given an empty value.
118
+
119
+ With a token file, the client refuses to start when that file or a keyring file is readable by group or others,
62
120
  when `--url` names another origin than the token's, when the hosted endpoint cannot be reached, or
63
121
  when a hosted tool has the name of a sealed tool.
64
122
 
@@ -79,6 +137,14 @@ Its secret is a generated key or a passphrase.
79
137
  `update_sealed_document`, `replace_in_sealed_document`, and the move, archive, delete, export,
80
138
  attachment and deposit tools.
81
139
  - `lock_sealed`, the idle timeout, or the end of the process forget the keys.
140
+ - `change_seal_password` replaces the root's secret. Once the server has taken the new one, the
141
+ superseded key is removed from the store that kept it (the system keyring, or any key file in the
142
+ key file directory). A superseded passphrase, or a key file you had written elsewhere with
143
+ `key_out_path`, is yours to remove.
144
+ - Deleting a seal root (`delete_sealed_document`, or `export_sealed_document` of a standalone sealed
145
+ document) also removes its keys and its name from this machine.
146
+ - A tool parameter that names a seal root (`root_id`, `new_root_id`) takes its id or its
147
+ [name](#names-of-seal-roots). Documents and folders inside a root are named by id.
82
148
 
83
149
  ## Where secrets are kept
84
150
 
@@ -91,14 +157,16 @@ have it, it stops and asks for `store`.
91
157
  | `system` | a generated key | the operating system's keyring: Keychain (macOS), Credential Manager (Windows), Secret Service (Linux) | encrypted by the system |
92
158
  | `file` | a generated key | a file (mode 0600) in the keyring directory | not encrypted |
93
159
  | `sidecar` | a passphrase you type | the memory of a separate process, for a time you choose | nowhere |
94
- | `default` | | whichever of `system` and `file` `--configure` chose | |
160
+ | `default` | | `system` where the system keyring works, otherwise `file` | |
95
161
 
96
162
  `key_out_path` still writes a key file to a path of your choice, and `passphrase` still takes one as
97
163
  text (which then is in the conversation transcript).
98
164
 
99
- The system keyring is used only where `--configure` found it working. On Linux that means a running
165
+ The choice is made once, on the first start or by `--setup`, and saved in `stores.json`; `--configure`
166
+ makes it again. The system keyring is used only where it was found working. On Linux that means a running
100
167
  Secret Service (GNOME Keyring, KWallet, KeePassXC); the kernel keyring is never used, because it
101
- forgets its content at logout. Keys are kept there with [`@napi-rs/keyring`](https://github.com/Brooooooklyn/keyring-node).
168
+ forgets its content at logout. Where it does not work, keys are kept as files in `keys/` next to the
169
+ token file: readable only by your user, but not encrypted on disk. The client tells you when it does this. Keys are kept there with [`@napi-rs/keyring`](https://github.com/Brooooooklyn/keyring-node).
102
170
 
103
171
  ### The sidecar
104
172
 
@@ -126,14 +194,65 @@ ask it for a held secret, which is what `--confirm` is for.
126
194
 
127
195
  The server cannot read the name of a sealed folder, and an id like `6acdb031893533e0ae1f5456` is no
128
196
  name to type. So the client remembers the name of every seal root it creates or unlocks, in
129
- `roots.json` next to the token file (mode 0600), and follows a rename or a delete. The sidecar and
130
- the key export show a root as `"Tax papers" (6acd…)` and take the name wherever they take an id.
197
+ `roots.json` next to the token file (mode 0600), and follows a rename or a delete. The sidecar, the
198
+ key export and the key modes show a root as `"Tax papers" (6acd…)`, and the name is taken wherever a
199
+ seal root's id is: at the sidecar, in the key modes, and in the `root_id` of the MCP tools.
131
200
 
132
201
  - The names are in plaintext in that file: readable on this machine by programs running as you,
133
202
  never by the server. Delete the file to forget them; they come back as roots are unlocked.
134
- - A name is never put into a tool result or an error, so unlocking a root does not tell the model
135
- what it is called.
136
- - When two roots have the same name, the name stands for neither: use the id.
203
+ - The client never puts a name into a tool result, so unlocking a root does not tell the model what
204
+ it is called. An error repeats only the name the caller passed.
205
+ - A name stays a usable handle: creating or renaming a seal root is refused when the name has the
206
+ shape of an id (24 hex characters), or when another root known on this machine already has it.
207
+ - Text with the shape of an id always means the root with that id. A root that got such a title
208
+ elsewhere is not given a name here when it is unlocked or its key is imported: use its id.
209
+ - Two roots can still share a name when both were named elsewhere and then unlocked or imported
210
+ here. Such a name stands for neither and is refused as ambiguous: use the id. Such a root can
211
+ still be changed while its name stays the same.
212
+
213
+ ### Managing keys
214
+
215
+ ```sh
216
+ npx -y @safekb/mcp --list-keys
217
+ npx -y @safekb/mcp --create-key "Banking" --space <space id> [--store system|file]
218
+ npx -y @safekb/mcp --rename-key "Banking" --name "Bank"
219
+ npx -y @safekb/mcp --rotate-key "Bank" [--store system|file]
220
+ npx -y @safekb/mcp --move-key "Bank" --store file
221
+ npx -y @safekb/mcp --delete-key "Bank"
222
+ ```
223
+
224
+ A seal root is given by its [name](#names-of-seal-roots) or its id.
225
+
226
+ - `--list-keys` prints one line per key in the system keyring and the key file directory: the store,
227
+ the seal root, the key id, and its status. `current`: the root's seal expects this key.
228
+ `superseded`: the root exists but has a newer key. `root not found or not accessible with this
229
+ token`: the server knows no such root for this token (404 or 403), which may be limited to some
230
+ spaces. `its root id is not a seal root on the server`: the id is that of something else.
231
+ A key of another tenant is shown as such and is not asked about. When the server cannot be asked
232
+ (rejected token, server error, no network) the listing fails; a status is never guessed. It never
233
+ prints a key, and needs no terminal. Secrets held by the sidecar are in memory only and are not
234
+ listed.
235
+ - `--create-key` makes a sealed folder at the top of the space as a new seal root and keeps its
236
+ generated key in the default store, or the one named by `--store`.
237
+ - `--rename-key` changes the root's sealed title on the server and the name on this machine. The
238
+ root's key must be in a local store.
239
+ - `--rotate-key` gives the root a newly generated key, in the store that kept the old one or the one
240
+ named by `--store`, and removes the superseded key once the server has taken the new one. Like
241
+ `change_seal_password`, it does not revoke what a holder of the old key already read.
242
+ - `--move-key` puts the root's current key into the other store, reads it back from there, and only
243
+ then removes it from where it was. Nothing is written to the server. A move that was interrupted
244
+ with the key in both stores is finished by running it again, towards either store.
245
+ - `--delete-key` removes every key of the root from this machine's key stores and forgets its name.
246
+ The root on the server is not touched. It shows the keys with their status and asks you to type
247
+ the root's name or id. It warns when a current key is among them (without another copy the root's
248
+ content can never be opened again), and when a key's status could not be verified with this token
249
+ (root not found or not accessible, another tenant, not a seal root): such a key may still be the
250
+ only one that opens existing content. Only keys the server says are superseded come without a
251
+ warning. A name left behind without any key is removed the same way. It needs a terminal, and no
252
+ flag skips the question.
253
+
254
+ When a step after the point of no return fails (the superseded key cannot be removed after a rotation,
255
+ the old copy cannot be removed after a move), the command fails and says where the key now is.
137
256
 
138
257
  ### Moving keys to another machine
139
258
 
@@ -142,7 +261,7 @@ them to another machine, or to back them up:
142
261
 
143
262
  ```sh
144
263
  npx -y @safekb/mcp --export-keys keys.safekb # on the machine that has the keys
145
- npx -y @safekb/mcp --import-keys keys.safekb # on the other machine, after --authorize there
264
+ npx -y @safekb/mcp --import-keys keys.safekb # on the other machine, after --setup there
146
265
  ```
147
266
 
148
267
  - Export asks for a passphrase (typed twice, not shown; 12 or more characters are recommended, a
@@ -150,7 +269,7 @@ npx -y @safekb/mcp --import-keys keys.safekb # on the other machine, after --a
150
269
  with every key of the system keyring and of the key file directory, and the names of their roots.
151
270
  The content is encrypted with AES-256-GCM under a key derived from the passphrase with Argon2id.
152
271
  Without the passphrase the file opens for nobody.
153
- - Import asks for that passphrase and keeps the keys in the store `--configure` chose on that
272
+ - Import asks for that passphrase and keeps the keys in the default store of that
154
273
  machine. A key the machine already keeps is left as it is, so an import can be repeated.
155
274
  - Both need a terminal: the passphrase is never an argument, so it is in no shell history, no process
156
275
  list and no conversation.