@shieldfive/mcp 0.2.0 → 0.4.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,80 @@ All notable changes to `@shieldfive/mcp` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 0.4.0 — 2026-09-20
9
+
10
+ Connecting a vault no longer involves copying anything. Ask the assistant to
11
+ tidy your vault, authorize it in the ShieldFive tab that opens, and carry on.
12
+
13
+ ### Added
14
+
15
+ - `vault_connect`: opens ShieldFive in the user's browser, where the owner
16
+ chooses folders, permissions and expiry as before, and receives the new
17
+ connection over `127.0.0.1`. It is stored in the system keychain. The tool
18
+ reports back while the owner is still deciding and picks up the result on the
19
+ next call, because a tool call cannot wait ten minutes.
20
+ - `npx @shieldfive/mcp login` now opens that same page; `login --paste` keeps
21
+ the old behaviour for a machine with no browser.
22
+ - `SHIELDFIVE_GRANT=none` keeps one client local-only on a machine whose
23
+ keychain holds a connection for another.
24
+
25
+ ### Changed
26
+
27
+ - The vault tools are registered as soon as a connection exists, including one
28
+ made mid-conversation, which the server announces with
29
+ `notifications/tools/list_changed`. Until then only `vault_connect` is
30
+ registered: a tool that cannot work is still not offered.
31
+ - A revoked or expired connection now tells the assistant to call
32
+ `vault_connect`, instead of sending the user to the terminal.
33
+
34
+ ### Security
35
+
36
+ - The hand-off is the only inbound socket in this package and the only
37
+ subprocess it starts. The listener binds a random loopback port, accepts one
38
+ POST to `/callback` with the loopback `Host`, no `Origin` other than
39
+ ShieldFive's and a 256-bit state compared in constant time, then closes. It
40
+ opens no connection of its own, and the browser is launched with a fixed
41
+ command and no shell.
42
+ - The ShieldFive page builds the callback address from a port number and
43
+ accepts no callback URL, so a crafted link cannot deliver a connection
44
+ anywhere but the machine the browser runs on. The connection string is sent
45
+ in a form body, never in a URL, so it does not reach browser history.
46
+
47
+ ## 0.3.0 — 2026-09-19
48
+
49
+ Vault tools. The server can now work on a ShieldFive vault through an **agent
50
+ grant**: a connection the user creates in ShieldFive → Settings → AI
51
+ assistants, limited to chosen folders and permissions, expiring (at most 90
52
+ days), revocable, audited, and enforced by the server on every request.
53
+
54
+ ### Added
55
+
56
+ - `vault_list_files`, `vault_search_files`, `vault_storage_stats`,
57
+ `vault_find_duplicates` and `vault_read_file` (read), and `vault_rename`,
58
+ `vault_move`, `vault_create_folder` and `vault_trash` (organize). They are
59
+ registered only when a connection is configured.
60
+ - `npx @shieldfive/mcp login | logout | status`. The connection string is
61
+ stored in the OS keychain through `@napi-rs/keyring`, with `SHIELDFIVE_GRANT`
62
+ as the fallback for headless use.
63
+ - Decryption of all three vault formats (post-quantum hybrid, AES-GCM v1 and
64
+ legacy v0) in memory, through `@shieldfive/crypto`. Names are decrypted on a
65
+ worker pool and cached in memory.
66
+ - Progress notifications for name decryption and duplicate hashing.
67
+ - Every vault result is marked as data rather than instructions. File contents
68
+ are fenced with a random marker the file cannot close.
69
+
70
+ ### Changed
71
+
72
+ - **The security boundary is restated, not removed.** 0.2.0 held no credential
73
+ and made no network request. That still holds for the local tools, which
74
+ import nothing from the vault half, so a server with no connection behaves as
75
+ before. The vault half talks to one https origin with one scoped credential.
76
+ `test/boundary.test.mjs` asserts the new lines: network access only in
77
+ `vault/api.mjs`, no filesystem access in any vault module, no cipher outside
78
+ `@shieldfive/crypto`, no account credential anywhere.
79
+ - A server started with a connection and no roots registers only the vault
80
+ tools.
81
+
8
82
  ## Unreleased
9
83
 
10
84
  ## 0.2.0 - 2026-09-16
package/README.md CHANGED
@@ -1,28 +1,177 @@
1
1
  # @shieldfive/mcp
2
2
 
3
- A [Model Context Protocol](https://modelcontextprotocol.io) server that lets an
4
- AI assistant manage files on your own machine: find duplicates by content, find
5
- what is large or stale, and move, rename or trash them.
3
+ A [Model Context Protocol](https://modelcontextprotocol.io) server that lets
4
+ Claude, ChatGPT, Cursor or a local model tidy two things:
6
5
 
7
- It holds no ShieldFive credential, makes no network request, and does not import
8
- `@shieldfive/crypto`. Those are not gaps to be filled in a later version. They
9
- are the security boundary, and the section below explains what they cost you.
6
+ - **your ShieldFive vault**: find duplicates, see what takes the space, rename,
7
+ move, and move to the Bin. It works on the folders you grant, decrypts on your
8
+ machine, and every change can be undone;
9
+ - **folders on your own disk**: the same jobs, with no network access at all.
10
10
 
11
- ```sh
12
- npx @shieldfive/mcp ~/Documents ~/Downloads
13
- ```
11
+ ShieldFive's servers never see a file name or a byte of content in the clear,
12
+ and that holds with this server running too. Decryption happens inside this
13
+ process, on your computer. What the assistant then does with what it reads is a
14
+ separate question, answered under [Security model](#security-model).
14
15
 
15
- ## Install
16
+ <!-- DEMO: a 40-second screen recording of a duplicate-cleanup session in Claude
17
+ Desktop: "find duplicates in my Photos" → groups with sizes → "trash the copies"
18
+ → preview → confirm → the items appear in ShieldFive's Bin → one is undone from
19
+ Settings → AI assistants. Record against a demo vault, never a real one. -->
20
+
21
+ ## Connect your vault in 60 seconds
16
22
 
17
23
  Requires Node 20 or newer.
18
24
 
25
+ 1. Add the server to your assistant. For Claude Desktop, add this to
26
+ `claude_desktop_config.json` (Cursor uses the same block in `~/.cursor/mcp.json`):
27
+
28
+ ```json
29
+ {
30
+ "mcpServers": {
31
+ "shieldfive": { "command": "npx", "args": ["-y", "@shieldfive/mcp"] }
32
+ }
33
+ }
34
+ ```
35
+
36
+ For Claude Code: `claude mcp add shieldfive -- npx -y @shieldfive/mcp`
37
+
38
+ 2. Restart the assistant and ask it to *tidy up my ShieldFive vault*. It calls
39
+ `vault_connect`, which opens ShieldFive in your browser.
40
+ 3. In that tab, choose the folders, *Read only* or *Read and organize*, and an
41
+ expiry (1 hour to 90 days), then click **Authorize**.
42
+
43
+ That is the whole setup: the connection is delivered straight to the server
44
+ running on your computer — over `127.0.0.1`, never through ShieldFive — and
45
+ stored in your system keychain. Nothing is copied by hand.
46
+
47
+ To connect before you start a conversation, run `npx -y @shieldfive/mcp login`:
48
+ same browser page, same result. `login --paste` takes a connection string you
49
+ copied from Settings → AI assistants instead, for a machine with no browser.
50
+
51
+ `npx @shieldfive/mcp status` shows which connection is configured and whether
52
+ ShieldFive still accepts it. `npx @shieldfive/mcp logout` removes it from the
53
+ keychain. Revoking it in ShieldFive is what cuts off access everywhere.
54
+
55
+ For CI or a machine without a keychain, set `SHIELDFIVE_GRANT` to the connection
56
+ string instead. Anything that can read the server's environment can then read
57
+ the connection, so prefer the keychain wherever there is one. Setting
58
+ `SHIELDFIVE_GRANT=none` keeps one client local-only on a machine whose keychain
59
+ holds a connection for another.
60
+
61
+ ### How the browser hand-off is kept honest
62
+
63
+ - The page never accepts a callback URL, only a port number, and builds
64
+ `http://127.0.0.1:<port>/callback` itself. A crafted link cannot send your
65
+ connection anywhere but your own machine.
66
+ - The listener accepts exactly one delivery: a POST to `/callback`, `Host`
67
+ exactly the loopback address (so a rebound DNS name is refused), no `Origin`
68
+ but ShieldFive's, and a 256-bit state compared in constant time. Then it
69
+ closes.
70
+ - The connection string travels in a form body, never in a URL, so it does not
71
+ land in browser history.
72
+ - The listener exists only while a connection is being authorized, and for at
73
+ most 10 minutes.
74
+
75
+ ## Security model
76
+
77
+ In plain terms:
78
+
79
+ - **The connection string holds two things.** A *token* the server checks on
80
+ every request, and a *secret* that never leaves your machine. ShieldFive stores
81
+ only a hash of the token and has never seen the secret.
82
+ - **The secret opens only the folders you chose.** When you create a connection,
83
+ your browser wraps those folders' keys under a key derived from the secret. It
84
+ wraps nothing else: not your vault root key, not your password, not your
85
+ post-quantum secret key. Subfolders open through the vault's normal folder-key
86
+ chain. A folder you did not choose cannot be opened with anything this server
87
+ holds.
88
+ - **ShieldFive enforces scope, expiry and revocation on every request.** The
89
+ checks in this process only produce clearer errors; the server is the
90
+ boundary. Revoking a connection makes its next request fail. Nothing is
91
+ cached that would outlive a revocation.
92
+ - **Decryption happens here, in memory.** No plaintext, key or ciphertext is
93
+ written to disk. The vault modules do not import the filesystem, and a test
94
+ asserts that.
95
+ - **Nothing is deleted.** `vault_trash` moves items into a folder in your Bin
96
+ that belongs to the connection. There is no permanent-delete tool, and the
97
+ API a connection can reach has no delete route. Every rename, move and trash
98
+ appears in *Settings → AI assistants → Activity* with an Undo button.
99
+ - **Every change is previewed first.** Mutating tools report a plan, and the
100
+ confirmed call must carry that plan's token and is refused if the items
101
+ changed in between.
102
+
103
+ What this does not protect:
104
+
105
+ - **Your AI provider sees what the assistant reads.** File names, and the
106
+ contents of files the assistant opens, go to the assistant, and for a cloud
107
+ assistant that means to its provider, like the rest of your conversation. The
108
+ only way to avoid that is a local model.
109
+ - **Revoking cannot un-read.** Anything the assistant has already read stays
110
+ read. Nothing else survives it: file contents are streamed through
111
+ ShieldFive on each request, so there is no download link to outlive a
112
+ revocation.
113
+ - **A connection is built from what the server shows your browser when you
114
+ create it.** Every later extension is checked against your own keys, so a
115
+ compromised server cannot widen a connection afterwards. At the moment of
116
+ creation, though, a compromised server could mislabel which folder you
117
+ picked.
118
+ - **A copied connection string is a live key** to the folders it covers until
119
+ it expires or you revoke it. Keep it in the keychain.
120
+ - **Files can contain instructions aimed at the assistant.** This server marks
121
+ every name and file content as data, fences file contents in a block the file
122
+ cannot close, caps `vault_trash` at 50 items per call, requires a preview for
123
+ every change, and keeps every change undoable. A model can still be talked
124
+ into a reversible mistake inside the folders you granted.
125
+
126
+ The full design, including the threat model and the reasoning behind each
127
+ decision, is in
128
+ [`docs/mcp-grants-design.md`](https://github.com/shieldfive/web/blob/main/docs/mcp-grants-design.md).
129
+
130
+ ## Vault tools
131
+
132
+ `vault_connect` is always available. The rest are registered once a connection
133
+ exists — connecting mid-conversation announces them with
134
+ `notifications/tools/list_changed`. Everything below names things by id; paths
135
+ are for people.
136
+
137
+ | Tool | Needs | What it does |
138
+ |---|---|---|
139
+ | `vault_connect` | — | opens ShieldFive in the browser to authorize a connection, and stores it in the keychain |
140
+ | `vault_list_files` | read | files and folders in scope, with decrypted names, paths, sizes, dates |
141
+ | `vault_search_files` | read | by name, path, extension, size or date, run locally over decrypted names |
142
+ | `vault_storage_stats` | read | totals, the biggest folders and files, a breakdown by type |
143
+ | `vault_find_duplicates` | read | same-size files decrypted in memory and compared by SHA-256; budgeted, and says when a result is a lower bound |
144
+ | `vault_read_file` | read | text files as fenced, untrusted content (up to 1 M characters); other types return details only |
145
+ | `vault_rename` | organize | rename a file or folder |
146
+ | `vault_move` | organize | move into another folder in scope |
147
+ | `vault_create_folder` | organize | create a folder in scope |
148
+ | `vault_trash` | organize | up to 50 items into the connection's folder in the Bin |
149
+
150
+ Limits a user may meet:
151
+
152
+ - **Post-quantum files uploaded from a phone or the CLI** show as
153
+ `readable: false` until you next open ShieldFive on the web, which adds the key
154
+ the connection needs. Files uploaded in the web app are ready straight away.
155
+ - **The first listing of a large vault takes a while.** Each name costs about
156
+ 70 ms of Argon2id, spread over your CPU cores (about 20 seconds for 2,000
157
+ names on 8 cores). Names are cached in memory for the rest of the session.
158
+ - **Items at the very top of a whole-vault connection** can be read and moved
159
+ into a folder, but not renamed in place, and nothing can be moved to the top.
160
+ Their names are sealed under your vault root key, which a connection never
161
+ holds.
162
+ - **Uploads are not available yet.** The design allows them, but they ship
163
+ after the read and organize tools have been in use for a while.
164
+
165
+ ## Local files
166
+
167
+ Every path after the package name is a **root**. The local tools can read and
168
+ write inside those directories and nowhere else, and make no network request.
169
+
19
170
  ```sh
20
- npm install -g @shieldfive/mcp
171
+ npx @shieldfive/mcp ~/Documents ~/Downloads
21
172
  ```
22
173
 
23
- ### Claude Desktop
24
-
25
- Add to `claude_desktop_config.json`:
174
+ In `claude_desktop_config.json`:
26
175
 
27
176
  ```json
28
177
  {
@@ -35,10 +184,9 @@ Add to `claude_desktop_config.json`:
35
184
  }
36
185
  ```
37
186
 
38
- Every path after the package name is a **root**. The server can read and write
39
- inside those directories and nowhere else. There is no default root and no
40
- override flag a server started with no roots will refuse every call and tell
41
- you so.
187
+ With a connection configured and no roots, only the vault tools are registered.
188
+ With roots and no connection, only the local tools are, and the server behaves
189
+ exactly as 0.2.0 did. With both, you get both.
42
190
 
43
191
  `SHIELDFIVE_MCP_ROOTS` adds roots as well — the two are combined, not
44
192
  alternatives — as a list separated by your platform's path separator (`:` on
@@ -51,7 +199,7 @@ SHIELDFIVE_MCP_ROOTS="/Users/you/Documents:/Volumes/Archive" npx @shieldfive/mcp
51
199
  Whitespace around a root is ignored. In a path given to a tool it is not: there,
52
200
  every character is part of the path.
53
201
 
54
- ## See it work first
202
+ ### See it work first
55
203
 
56
204
  ```bash
57
205
  npm run demo
@@ -63,33 +211,19 @@ a two-year-old PDF — runs the read tools over them, previews a trash call, the
63
211
  confirms it and shows the manifest. It touches nothing outside that directory
64
212
  and removes it at the end (`--keep` leaves it in place).
65
213
 
66
- ## What this cannot do
67
-
68
- **It cannot see your ShieldFive vault.** Not the file list, not the names, not
69
- the sizes. It will not tell you whether a local file is already backed up,
70
- because it has no way to know and it is not permitted to guess.
71
-
72
- That is a deliberate trade. The alternative was to authenticate with a full
73
- ShieldFive account JWT the only credential the vault API accepts. That token
74
- also opens `/api/vault-key`, which returns your wrapped root key and an ML-KEM
75
- public key, and every content-download route, and **none of it can be scoped
76
- away**, because no scoped vault credential exists. A server holding that token
77
- would be *declining* to read your files rather than being *unable* to, with the
78
- difference resting on a client-side denylist and on nothing else on your machine
79
- reading the token file. A server holding no token cannot read them at all.
80
-
81
- When a scoped, metadata-only key exists, vault tools can be added behind it.
82
- Until then this is a local file manager that happens to be published by the
83
- people who make an encrypted vault.
84
-
85
- **It cannot stop the results from reaching your AI provider.** This server
86
- makes no network request, and that is worth exactly what it says and no more:
87
- everything it returns — paths, file names, sizes, dates, the digests it
88
- reports — goes back to the AI client that called it, and if that client is a
214
+ ## What the local tools cannot do
215
+
216
+ **They cannot tell you whether a local file is already in your vault.** Matching
217
+ a local name and size against a vault listing is how a tool deletes the only
218
+ copy of something, and this server will not guess.
219
+
220
+ **They cannot stop the results from reaching your AI provider.** The local
221
+ tools make no network request, and that is worth exactly what it says and no
222
+ more:
223
+ everything they return paths, file names, sizes, dates, the digests they
224
+ report goes back to the AI client that called it, and if that client is a
89
225
  cloud assistant, those names travel to the assistant's provider like the rest
90
- of your conversation. The server's silence is not the client's. Choose roots on
91
- that basis: point it at the folders you would be willing to describe out loud,
92
- and it will never see anything else.
226
+ of your conversation. Choose roots on that basis.
93
227
 
94
228
  **It will never infer that two files are the same from their names and sizes.**
95
229
  Duplicate detection reads both files and compares a full SHA-256 of their
@@ -143,7 +277,7 @@ naming what changed. A token performs one change and expires after ten minutes.
143
277
  So a directory that grew, a destination that appeared, or a path that now points
144
278
  at a different file stops the call instead of silently widening it.
145
279
 
146
- ## Tools
280
+ ## Local tools
147
281
 
148
282
  | Tool | Reads | Writes |
149
283
  |---|---|---|
@@ -290,7 +424,7 @@ renames, and a file created in that instant would be replaced.
290
424
 
291
425
  ## What the tests assert
292
426
 
293
- `npm test` runs 161 tests. The ones worth knowing about:
427
+ `npm test` runs 204 tests. The ones worth knowing about:
294
428
 
295
429
  - A symlink pointing out of a root is refused, on both the read and the write
296
430
  side, and so is a dangling symlink on a write path.
@@ -310,18 +444,38 @@ renames, and a file created in that instant would be replaced.
310
444
  replaced.
311
445
  - A cancelled request moves nothing, and a trash batch cancelled midway says
312
446
  exactly what it moved.
313
- - No file under `src/` imports a networking module, calls `fetch`, spawns a
314
- subprocess, or reads any environment variable other than
315
- `SHIELDFIVE_MCP_ROOTS`.
316
- - A real MCP client over a real stdio transport sees nine tools and no vault
317
- tool, including when the server is started through a symlink the way npm
318
- installs it.
319
-
320
- The network assertion has a limit worth stating: it proves nothing in `src/`
321
- reaches the network. It does not prove the dependency tree is network-free —
322
- `@modelcontextprotocol/sdk` ships HTTP transports for other people's servers,
323
- and claiming otherwise would be false. What closes that gap is that `server.mjs`
324
- imports the stdio transport and no HTTP one, which is also asserted.
447
+ - Only `src/vault/api.mjs` calls `fetch`, and only to an https ShieldFive
448
+ origin. No local-tool module imports anything from the vault half, and the
449
+ only environment variables read are `SHIELDFIVE_MCP_ROOTS`,
450
+ `SHIELDFIVE_GRANT` and `SHIELDFIVE_API_URL`.
451
+ - The browser hand-off is the one inbound socket and the one subprocess in the
452
+ package: `src/vault/connect.mjs` binds a random port on `127.0.0.1`, opens no
453
+ connection of its own, and launches the browser with a fixed command and no
454
+ shell. A delivery from another origin, with another state, to another `Host`,
455
+ by another method, or after the first one, is refused.
456
+ - The vault modules import no filesystem module, so decrypted data cannot be
457
+ written to disk. No module uses a cipher, HMAC or KDF of its own. All
458
+ cryptography comes from `@shieldfive/crypto`.
459
+ - Against an in-memory ShieldFive that serves real ciphertext in all three vault
460
+ formats, over a real MCP client:
461
+ - files decrypt through the grant's keys only, and nothing outside the scope
462
+ is listed, read or even requested;
463
+ - a revoked or expired connection fails the very next call;
464
+ - renames and moves re-seal names and keys so the owner's own keys still
465
+ open them;
466
+ - the grant secret and token never appear in any request body or path.
467
+ - A file whose contents tell the assistant to trash everything comes back
468
+ inside a fence it cannot close. Reading it issues only reads, and a 51-item
469
+ trash call is refused.
470
+ - A real MCP client over a real stdio transport sees exactly the nine local
471
+ tools when no connection is configured, including when the server is started
472
+ through a symlink the way npm installs it.
473
+
474
+ The network assertion has a limit worth stating: it proves what `src/` does,
475
+ not what the dependency tree could do. `@modelcontextprotocol/sdk` ships HTTP
476
+ transports for other people's servers. What closes that gap is that
477
+ `server.mjs` imports the stdio transport and no HTTP one, which is also
478
+ asserted.
325
479
 
326
480
  ## Limits
327
481
 
package/SECURITY.md CHANGED
@@ -40,17 +40,21 @@ shipped the fix.
40
40
  ## Scope
41
41
 
42
42
  In scope: this package's own source — path containment, the walk, the mutating
43
- tools, the trash, the MCP surface, and the claims its README makes.
43
+ tools, the trash, the vault tools and how they handle grant keys, names and
44
+ decrypted content, the MCP surface, and the claims its README makes.
44
45
 
45
46
  Out of scope for this repository, with the right destination:
46
47
 
47
48
  - The ShieldFive vault, web application and API — `security@shieldfive.com`,
48
49
  same address, different codebase.
49
- - `@shieldfive/crypto` its own repository. **This package does not depend on
50
- it**, and a test asserts no `@shieldfive/*` or `@supabase/*` package is a
51
- dependency.
52
- - `@modelcontextprotocol/sdk` and `zod`, this package's only two dependencies
53
- report upstream. A vulnerability in how *this* package uses them is in scope.
50
+ - The agent-grant API and its enforcement (scope, expiry, revocation, the
51
+ audit log) `shieldfive/web`, same address.
52
+ - `@shieldfive/crypto` — its own repository. The vault half depends on it for
53
+ every cryptographic operation; a test asserts no module here implements a
54
+ cipher, HMAC or KDF of its own.
55
+ - `@modelcontextprotocol/sdk`, `zod`, `libsodium-wrappers-sumo` and
56
+ `@napi-rs/keyring` — report upstream. A vulnerability in how *this* package
57
+ uses them is in scope.
54
58
 
55
59
  ## Threat model
56
60
 
@@ -76,8 +80,13 @@ has.
76
80
  | An assistant acting without the user seeing the plan | Mutating tools are inert without `confirm: true` and report what they would displace as well as what they would move | yes |
77
81
  | An oversized argument | `limit`, `max_files`, `max_files_hashed`, `paths`, path length and `new_name` are capped at the MCP schema and again in the handler; values echoed in a refusal are cut short | yes |
78
82
  | A cancelled request still changing files | Nothing starts once a request is cancelled; a trash batch stops between items and says what moved; the outcome is logged, because the SDK sends no response to a cancelled request | yes |
79
- | Credential exposure in this code | No credential is read or stored. `SHIELDFIVE_MCP_ROOTS` is the only environment variable read | yes |
80
- | Exfiltration over the network | No networking module imported, `fetch` never called; the transport is stdio | yes, for this package's source |
83
+ | Credential exposure in this code | The only credential is an agent grant, read from `SHIELDFIVE_GRANT` or the OS keychain; never logged, never returned, never written to a file. The only environment variables read are `SHIELDFIVE_MCP_ROOTS`, `SHIELDFIVE_GRANT` and `SHIELDFIVE_API_URL`. No account password, service key or vault-key route is referenced anywhere | yes |
84
+ | Network access | Only `vault/api.mjs` calls `fetch`, only to an https ShieldFive origin (or localhost for development). Local tools import nothing from the vault half; the transport is stdio | yes, for this package's source |
85
+ | A grant opening more than its scope | Keys come only from the grant's own wraps (AAD-bound to grant, kind and object) and the folder chain below them. The server enforces scope on every request; local checks only produce clearer errors | yes, against an in-memory server with real ciphertext |
86
+ | Revocation not taking effect | Every tool call re-fetches the grant and its listing; file contents are streamed through the API per request (no storage URL is ever held); nothing that would outlive a revocation is cached | yes |
87
+ | Decrypted data reaching disk | Vault modules import no filesystem module; plaintext exists only in memory for the duration of a call; decrypted names are cached in memory only | yes |
88
+ | Prompt injection through names or file contents | Names are stripped of control and bidi characters; every result is marked as data; file contents are fenced with a random marker the file cannot close; every change needs a preview and its plan token; `vault_trash` is capped at 50 items; nothing is ever deleted and every change is undoable by the owner | yes, with an injection fixture |
89
+ | A write the owner cannot read back | Names are re-sealed as v6 envelopes under the destination folder key and content keys re-wrapped under it; a name that failed to decrypt is never re-sealed | yes, the owner's keys re-open every write |
81
90
  | Credential exposure via a subprocess | No subprocess is spawned at all. Spawning `sf` would inherit `SF_PASSWORD` from the environment whether or not this code named it | yes |
82
91
 
83
92
  ## Known limits
@@ -139,6 +148,16 @@ has.
139
148
  import would evade it.
140
149
  - **File paths reach the model.** If a path is itself sensitive, do not give
141
150
  this server the root it sits in.
151
+ - **Everything the vault tools return reaches the model, and so its provider.**
152
+ A grant limits which folders the assistant can open; it cannot limit what the
153
+ assistant's provider sees of what was opened. Revocation cannot un-read.
154
+ - **A connection string is a bearer credential plus its key.** Held by anyone,
155
+ it gives that connection's access until it expires or is revoked. In the
156
+ keychain it is protected like any other stored secret; in `SHIELDFIVE_GRANT`
157
+ it is readable by anything that can read the process environment.
158
+ - **Instruction-following is the model's, not this server's.** The fencing and
159
+ caps above bound the damage of an injected instruction to reversible changes
160
+ inside the granted folders; they do not make a model ignore text.
142
161
  - **Windows is untested.** No POSIX-only API is used and paths go through
143
162
  `node:path`, but nobody has run it there, and the cross-device tests run only
144
163
  on macOS.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@shieldfive/mcp",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
- "description": "Model Context Protocol server for local file management. Finds duplicates, large files and stale files across the drives you allow it, and reorganises them. Holds no ShieldFive credential and makes no network calls.",
5
+ "description": "Model Context Protocol server for your ShieldFive vault and local files: find duplicates, reclaim space and reorganise, with decryption on your machine and scoped, revocable, audited access.",
6
6
  "license": "Apache-2.0",
7
7
  "author": "Cho Garcia <security@shieldfive.com>",
8
8
  "homepage": "https://github.com/shieldfive/mcp",
@@ -14,13 +14,17 @@
14
14
  "url": "https://github.com/shieldfive/mcp/issues"
15
15
  },
16
16
  "keywords": [
17
- "shieldfive",
18
- "mcp",
19
- "model-context-protocol",
20
17
  "claude",
21
- "filesystem",
18
+ "disk-space",
22
19
  "duplicate-files",
23
- "disk-space"
20
+ "encryption",
21
+ "end-to-end-encryption",
22
+ "filesystem",
23
+ "mcp",
24
+ "model-context-protocol",
25
+ "post-quantum",
26
+ "shieldfive",
27
+ "vault"
24
28
  ],
25
29
  "bin": {
26
30
  "shieldfive-mcp": "src/server.mjs"
@@ -29,21 +33,27 @@
29
33
  "access": "public"
30
34
  },
31
35
  "files": [
32
- "src",
36
+ "CHANGELOG.md",
37
+ "LICENSE",
33
38
  "README.md",
34
39
  "SECURITY.md",
35
- "CHANGELOG.md",
36
- "LICENSE"
40
+ "server.json",
41
+ "src"
37
42
  ],
38
43
  "engines": {
39
44
  "node": ">=20"
40
45
  },
41
46
  "scripts": {
42
47
  "test": "node --test test/*.test.mjs",
43
- "demo": "node demo/run-demo.mjs"
48
+ "demo": "node demo/run-demo.mjs",
49
+ "build:mcpb": "node scripts/build-mcpb.mjs"
44
50
  },
45
51
  "dependencies": {
46
52
  "@modelcontextprotocol/sdk": "^1.30.0",
53
+ "@napi-rs/keyring": "^2.1.0",
54
+ "@shieldfive/crypto": "1.0.0-rc.5",
55
+ "libsodium-wrappers-sumo": "^0.8.4",
47
56
  "zod": "^3.25.0"
48
- }
57
+ },
58
+ "mcpName": "io.github.shieldfive/mcp"
49
59
  }
package/server.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.shieldfive/mcp",
4
+ "title": "ShieldFive",
5
+ "description": "Tidy your E2E-encrypted ShieldFive vault and local folders. Decrypts locally; revocable access.",
6
+ "version": "0.4.0",
7
+ "repository": {
8
+ "url": "https://github.com/shieldfive/mcp",
9
+ "source": "github"
10
+ },
11
+ "websiteUrl": "https://shieldfive.com",
12
+ "packages": [
13
+ {
14
+ "registryType": "npm",
15
+ "identifier": "@shieldfive/mcp",
16
+ "version": "0.4.0",
17
+ "transport": {
18
+ "type": "stdio"
19
+ },
20
+ "environmentVariables": [
21
+ {
22
+ "name": "SHIELDFIVE_GRANT",
23
+ "description": "Connection string from ShieldFive → Settings → AI assistants. Optional: `npx @shieldfive/mcp login` stores it in the OS keychain instead.",
24
+ "isRequired": false,
25
+ "isSecret": true
26
+ },
27
+ {
28
+ "name": "SHIELDFIVE_MCP_ROOTS",
29
+ "description": "Local directories the local tools may use, separated by the platform path separator. Optional.",
30
+ "isRequired": false,
31
+ "isSecret": false
32
+ }
33
+ ]
34
+ }
35
+ ]
36
+ }