@nodatachat/mcp 1.0.0 → 1.1.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 (4) hide show
  1. package/LICENSE.md +105 -105
  2. package/README.md +163 -161
  3. package/dist/index.js +35 -34
  4. package/package.json +49 -49
package/LICENSE.md CHANGED
@@ -1,105 +1,105 @@
1
- # Functional Source License, Version 1.1, ALv2 Future License
2
-
3
- ## Abbreviation
4
-
5
- FSL-1.1-ALv2
6
-
7
- ## Notice
8
-
9
- Copyright 2026 Capsule Ltd.
10
-
11
- ## Terms and Conditions
12
-
13
- ### Licensor ("We")
14
-
15
- The party offering the Software under these Terms and Conditions.
16
-
17
- ### The Software
18
-
19
- The "Software" is each version of the software that we make available under
20
- these Terms and Conditions, as indicated by our inclusion of these Terms and
21
- Conditions with the Software.
22
-
23
- ### License Grant
24
-
25
- Subject to your compliance with this License Grant and the Patents,
26
- Redistribution and Trademark clauses below, we hereby grant you the right to
27
- use, copy, modify, create derivative works, publicly perform, publicly display
28
- and redistribute the Software for any Permitted Purpose identified below.
29
-
30
- ### Permitted Purpose
31
-
32
- A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
- means making the Software available to others in a commercial product or
34
- service that:
35
-
36
- 1. substitutes for the Software;
37
-
38
- 2. substitutes for any other product or service we offer using the Software
39
- that exists as of the date we make the Software available; or
40
-
41
- 3. offers the same or substantially similar functionality as the Software.
42
-
43
- Permitted Purposes specifically include using the Software:
44
-
45
- 1. for your internal use and access;
46
-
47
- 2. for non-commercial education;
48
-
49
- 3. for non-commercial research; and
50
-
51
- 4. in connection with professional services that you provide to a licensee
52
- using the Software in accordance with these Terms and Conditions.
53
-
54
- ### Patents
55
-
56
- To the extent your use for a Permitted Purpose would necessarily infringe our
57
- patents, the license grant above includes a license under our patents. If you
58
- make a claim against any party that the Software infringes or contributes to
59
- the infringement of any patent, then your patent license to the Software ends
60
- immediately.
61
-
62
- ### Redistribution
63
-
64
- The Terms and Conditions apply to all copies, modifications and derivatives of
65
- the Software.
66
-
67
- If you redistribute any copies, modifications or derivatives of the Software,
68
- you must include a copy of or a link to these Terms and Conditions and not
69
- remove any copyright notices provided in or with the Software.
70
-
71
- ### Disclaimer
72
-
73
- THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
- IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
- PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
-
77
- IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
- SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
- EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
-
81
- ### Trademarks
82
-
83
- Except for displaying the License Details and identifying us as the origin of
84
- the Software, you have no right under these Terms and Conditions to use our
85
- trademarks, trade names, service marks or product names.
86
-
87
- ## Grant of Future License
88
-
89
- We hereby irrevocably grant you an additional license to use the Software under
90
- the Apache License, Version 2.0 that is effective on the second anniversary of
91
- the date we make the Software available. On or after that date, you may use the
92
- Software under the Apache License, Version 2.0, in which case the following
93
- will apply:
94
-
95
- Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96
- this file except in compliance with the License.
97
-
98
- You may obtain a copy of the License at
99
-
100
- http://www.apache.org/licenses/LICENSE-2.0
101
-
102
- Unless required by applicable law or agreed to in writing, software distributed
103
- under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
- CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
- specific language governing permissions and limitations under the License.
1
+ # Functional Source License, Version 1.1, ALv2 Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-ALv2
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 Capsule Ltd.
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the Apache License, Version 2.0 that is effective on the second anniversary of
91
+ the date we make the Software available. On or after that date, you may use the
92
+ Software under the Apache License, Version 2.0, in which case the following
93
+ will apply:
94
+
95
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96
+ this file except in compliance with the License.
97
+
98
+ You may obtain a copy of the License at
99
+
100
+ http://www.apache.org/licenses/LICENSE-2.0
101
+
102
+ Unless required by applicable law or agreed to in writing, software distributed
103
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
+ specific language governing permissions and limitations under the License.
package/README.md CHANGED
@@ -1,161 +1,163 @@
1
- # @nodatachat/mcp
2
-
3
- The MCP door to **NoData, the Information Access Processor**.
4
-
5
- **Give an AI agent real power over your data — without the power to leak it.**
6
-
7
- One line connects any MCP client (Claude Code, an agent runtime) to NoData's governed‑access layer. Your agent asks *"may I reach this?"* and only ever sees the columns you granted. Denied columns are **never decrypted on the server** — there is nothing to leak — and every access leaves a signed, verifiable receipt.
8
-
9
- ```bash
10
- claude mcp add nodata -- npx @nodatachat/mcp
11
- ```
12
-
13
- Any other MCP client (Codex, Cursor, VS Code, Windsurf, Cline) runs the same server from its config:
14
-
15
- ```json
16
- { "mcpServers": { "nodata": { "command": "npx", "args": ["-y", "@nodatachat/mcp"] } } }
17
- ```
18
-
19
- That's it — **no flags**. Self‑serve: registering opens an instant free **sandbox key** (no approval wall) and a **12‑word recovery phrase only you hold**. No card. **10,000 governed decisions per month free**, then $0.25 per 1,000.
20
-
21
- > **The one idea:** *API access ≠ data access.* A successful call is a request for a **decision**, not a handoff of data. **`no key = no plaintext`**, and every decision carries a proof.
22
-
23
- ---
24
-
25
- ## Get started in one command
26
-
27
- Run the line above with no credential. The server still connects (it never fails the MCP handshake) and exposes two setup tools:
28
-
29
- - **`nodata_get_started`** — call it first. It explains what you get and the single next step.
30
- - **`nodata_connect`** — register once, autoload forever:
31
- 1. `nodata_connect` (no args) → opens the free signup in your browser (work email + company, ~30s). Your **12‑word recovery phrase** is generated **in your browser — NoData never sees it**. The page shows your org key **once**.
32
- 2. `nodata_connect` with `api_key: "<that key>"` → saved to `~/.nodata/credentials.json` (chmod 600).
33
- 3. Restart, or run `/mcp` → every later start loads your identity with nothing to retype. *Registration ≠ login.*
34
-
35
- Keep the 12‑word phrase somewhere safe — it is the only way back into your org, and NoData cannot reissue it.
36
-
37
- ---
38
-
39
- ## One journey, five verbs, two ways in
40
-
41
- The whole product is one primitive shown five ways — **Classify · Protect · Route · Retrieve · Prove** — entered from **a table** (govern the DB columns an agent reads) **or a folder/document** (seal into a Capsule). Same journey, same proof.
42
-
43
- | Verb | What you do | Tool |
44
- |------|-------------|------|
45
- | **Classify** | connect a table (or seal a document) | `nodata_register_table` |
46
- | **Protect** | grant an agent only the columns you name | `nodata_grant` → `ndca-` token |
47
- | **Route** | *"may this agent reach X?"* — plan, no data moves | `nodata_decide` |
48
- | **Retrieve** | pull only the authorized columns into the prompt | `nodata_retrieve` / `nodata_read` |
49
- | **Prove** | a signed receipt on every read **and** every deny | `nodata_evidence` · `/decisions` |
50
-
51
- ---
52
-
53
- ## Try it — the "wow" in four calls
54
-
55
- After `nodata_connect`, ask your agent in plain language (*"grant a support bot first_name and last_name on agent_demo_records, then read it"*), or run the raw API with your key (`export NDP=ndp_test_…`):
56
-
57
- ```bash
58
- # 1 · grant an agent two columns of the built-in demo table (auto-seeds sample rows)
59
- curl -s -X POST https://www.nodatacapsule.com/api/v1/governance/grant \
60
- -H "Authorization: Bearer $NDP" -H 'content-type: application/json' \
61
- -d '{"agent":"support-copilot","table":"agent_demo_records","columns":["first_name","last_name"]}'
62
- # → { "grant_token": "ndca-…", "handle": "…" }
63
-
64
- # 2 · the agent reads what it's allowed — RELEASED
65
- curl -s -X POST https://www.nodatacapsule.com/api/agents/<handle>/read \
66
- -H "Authorization: Bearer ndca-…" -H 'content-type: application/json' \
67
- -d '{"table":"agent_demo_records","columns":["first_name","last_name"],"limit":5}'
68
- # → 200 { "rows": [ { "first_name": "…", "last_name": "…" }, … ] }
69
-
70
- # 3 · the agent asks for a column it was NOT granted — WITHHELD
71
- curl -s -X POST https://www.nodatacapsule.com/api/agents/<handle>/read \
72
- -H "Authorization: Bearer ndca-…" -H 'content-type: application/json' \
73
- -d '{"table":"agent_demo_records","columns":["email"]}'
74
- # → 403 { "error": "scope_violation", "denied_columns": ["email"] } ← the key for email is never derived
75
-
76
- # 4 · prove it — signed receipts for the allow AND the deny, verifiable without the data:
77
- # https://www.nodatacapsule.com/decisions
78
- ```
79
-
80
- `email` came back **absent, not blanked** — there is no decrypt‑all‑then‑filter path. That is *"you decide what the AI sees,"* as math, not a promise.
81
-
82
- No terminal? Same idea in the browser, zero install: **https://www.nodatacapsule.com/ai-folder** — drop a folder, see exactly what an AI would be allowed to read (nothing uploads).
83
-
84
- ---
85
-
86
- ## Two modes — the credential decides what the server can do
87
-
88
- **Agent mode (`--grant-token ndca-…`)** — *permission management for language models.* Hand an AI agent a grant token; the server exposes only tools hard‑scoped to that grant. The agent may request **only** the columns its grant allows — denied columns never decrypt server‑side, and every action emits a receipt. Point any MCP client at it and it **physically cannot exceed its claims**.
89
-
90
- ```bash
91
- claude mcp add nodata -- npx @nodatachat/mcp --grant-token ndca-YOUR_GRANT
92
- ```
93
-
94
- **Admin mode (`--api-key ndp_… / sk_live_…`)** — the owner seat: register tables, issue and revoke grants, and use blind relay (encrypt / decrypt / deliver / evidence) from inside any MCP client.
95
-
96
- ```bash
97
- claude mcp add nodata -- npx @nodatachat/mcp --api-key YOUR_API_KEY
98
- ```
99
-
100
- Credential precedence: `--api-key` / `--grant-token` › `NODATA_API_KEY` / `NODATA_GRANT_TOKEN` › the saved `~/.nodata` file. An explicit flag or env always wins.
101
-
102
- On **Windows**, native (non‑WSL) `npx` needs the `cmd /c` wrapper: `claude mcp add nodata -- cmd /c npx @nodatachat/mcp …`. After changing the credential, **restart** — the running server keeps its old arguments.
103
-
104
- ---
105
-
106
- ## Available tools
107
-
108
- **Agent mode** (`--grant-token`):
109
-
110
- | Tool | What it does |
111
- |------|--------------|
112
- | `nodata_decide` | May the agent reach a resource? allow / degrade (reachable subset) / deny + an access‑distance cost + a signed proof. No data. Plan before you read. |
113
- | `nodata_retrieve` | Prompt‑ready authorized context, filtered to allowed columns, with an accounting of what was withheld + a proof. |
114
- | `nodata_read` | Read governed rows within the grant; denied columns never decrypt; every read is receipted. |
115
- | `nodata_use` | Invoke a pre‑registered secret‑blind capability — the Capsule injects the sealed credential server‑side and returns only the result. The secret never reaches the agent. |
116
-
117
- **Admin mode** (`--api-key`):
118
-
119
- | Tool | What it does |
120
- |------|--------------|
121
- | `nodata_register_table` | Register one of your tables as AI‑agent‑readable ("connect your data"). |
122
- | `nodata_grant` | Issue an agent a claim‑scoped grant; returns a `grant_token` (shown once). |
123
- | `nodata_revoke` | Revoke a grant by handle or jti — access stops at once. |
124
- | `nodata_encrypt` / `nodata_decrypt` | Field‑level AES‑256‑GCM; NoData stores nothing of the plaintext. |
125
- | `nodata_deliver` | Create a burn‑after‑read secure link with OTP. |
126
- | `nodata_evidence` | Retrieve the audit trail — metadata only, never values. |
127
-
128
- ---
129
-
130
- ## Security & privacy model
131
-
132
- - **Content‑blind.** For governed reads, denied columns are **never decrypted on the server** — the key release is scoped to the grant, not "we promise not to look."
133
- - **Every action is a receipt.** Reads, grants, revokes and denials mint a signed, hash‑chained decision receipt (Ed25519, publicly verifiable), written **before** the release it authorizes — if the receipt can't be written, nothing is served.
134
- - **Structural isolation.** A key is bound to its org; every governance query is scoped to that tenant, so a key cannot aim at another org's data.
135
- - **The honest boundary.** This governs data the agent reaches **through the Capsule**. It does not sandbox a process already executing as you on your own machine.
136
-
137
- ---
138
-
139
- ## Configuration
140
-
141
- | Option | Flag | Env var | Default |
142
- |--------|------|---------|---------|
143
- | API key (admin) | `--api-key` | `NODATA_API_KEY` | — |
144
- | Grant token (agent) | `--grant-token` | `NODATA_GRANT_TOKEN` | — |
145
- | Base URL | `--base-url` | `NODATA_BASE_URL` | `https://www.nodatacapsule.com` |
146
-
147
- Get a key free at **https://www.nodatacapsule.com/capsule-api/register** (issues `ndp_test_…` instantly). `--help` and `--version` are also available.
148
-
149
- ## NoData on npm
150
-
151
- - **`@nodatachat/nodata`**: the main package, with all protection, scanning and governance capabilities, from the terminal.
152
- - **`@nodatachat/sdk`**: integration for developers.
153
- - **`@nodatachat/mcp`** (this one): integration for AI agents and MCP clients.
154
-
155
- ## License
156
-
157
- [FSL-1.1-ALv2](LICENSE.md) (Functional Source License 1.1, Apache 2.0 future license).
158
- Use it for any purpose except a competing product or service. Each release becomes
159
- available under the Apache License 2.0 two years after it is published. Versions up
160
- to 0.9.0 were published under MIT and remain available under that license.
161
- Copyright 2026 Capsule Ltd.
1
+ # @nodatachat/mcp
2
+
3
+ The MCP door to **NoData, the Information Access Processor**.
4
+
5
+ **Give an AI agent real power over your data — without the power to leak it.**
6
+
7
+ One line connects any MCP client (Claude Code, an agent runtime) to NoData's governed‑access layer. Your agent asks *"may I reach this?"* and only ever sees the columns you granted. Denied columns are **never decrypted on the server** — there is nothing to leak — and every access leaves a signed, verifiable receipt.
8
+
9
+ ```bash
10
+ claude mcp add nodata -- npx @nodatachat/mcp
11
+ ```
12
+
13
+ Any other MCP client (Codex, Cursor, VS Code, Windsurf, Cline) runs the same server from its config:
14
+
15
+ ```json
16
+ { "mcpServers": { "nodata": { "command": "npx", "args": ["-y", "@nodatachat/mcp"] } } }
17
+ ```
18
+
19
+ That's it — **no flags**. Self‑serve: registering opens an instant free **sandbox key** (no approval wall) and a **12‑word recovery phrase only you hold**. No card. **10,000 governed decisions per month free**, then $0.25 per 1,000.
20
+
21
+ > **The one idea:** *API access ≠ data access.* A successful call is a request for a **decision**, not a handoff of data. **`no key = no plaintext`**, and every decision carries a proof.
22
+
23
+ ---
24
+
25
+ ## Get started in one command
26
+
27
+ Run the line above with no credential. The server still connects (it never fails the MCP handshake) and exposes two setup tools:
28
+
29
+ - **`nodata_get_started`** — call it first. It explains what you get and the single next step.
30
+ - **`nodata_connect`** — register once, autoload forever:
31
+ 1. `nodata_connect` (no args) → opens the free signup in your browser (work email + company, ~30s). Your **12‑word recovery phrase** is generated **in your browser — NoData never sees it**. The page shows your org key **once**.
32
+ 2. `nodata_connect` with `api_key: "<that key>"` → saved to `~/.nodata/credentials.json` (chmod 600).
33
+ 3. Restart, or run `/mcp` → every later start loads your identity with nothing to retype. *Registration ≠ login.*
34
+
35
+ Keep the 12‑word phrase somewhere safe — it is the only way back into your org, and NoData cannot reissue it.
36
+
37
+ ---
38
+
39
+ ## One journey, five verbs, two ways in
40
+
41
+ The whole product is one primitive shown five ways — **Classify · Protect · Route · Retrieve · Prove** — entered from **a table** (govern the DB columns an agent reads) **or a folder/document** (seal into a Capsule). Same journey, same proof.
42
+
43
+ | Verb | What you do | Tool |
44
+ |------|-------------|------|
45
+ | **Classify** | register a table's policy, then connect your own Supabase as its source | `nodata_register_table` → `nodata_connect_source` |
46
+ | **Protect** | grant an agent only the columns you name | `nodata_grant` → `ndca-` token |
47
+ | **Route** | *"may this agent reach X?"* — plan, no data moves | `nodata_decide` |
48
+ | **Retrieve** | pull only the authorized columns into the prompt | `nodata_retrieve` / `nodata_read` |
49
+ | **Prove** | a signed receipt on every grant, read **and** deny | `nodata_proofs` · `/decisions` |
50
+
51
+ ---
52
+
53
+ ## Try it — the "wow" in four calls
54
+
55
+ After `nodata_connect`, ask your agent in plain language (*"grant a support bot first_name and last_name on agent_demo_records, then read it"*), or run the raw API with your key (`export NDP=ndp_test_…`):
56
+
57
+ ```bash
58
+ # 1 · grant an agent two columns of the built-in demo table (auto-seeds sample rows)
59
+ curl -s -X POST https://www.nodatacapsule.com/api/v1/governance/grant \
60
+ -H "Authorization: Bearer $NDP" -H 'content-type: application/json' \
61
+ -d '{"agent":"support-copilot","table":"agent_demo_records","columns":["first_name","last_name"]}'
62
+ # → { "grant_token": "ndca-…", "handle": "…" }
63
+
64
+ # 2 · the agent reads what it's allowed — RELEASED
65
+ curl -s -X POST https://www.nodatacapsule.com/api/agents/<handle>/read \
66
+ -H "Authorization: Bearer ndca-…" -H 'content-type: application/json' \
67
+ -d '{"table":"agent_demo_records","columns":["first_name","last_name"],"limit":5}'
68
+ # → 200 { "rows": [ { "first_name": "…", "last_name": "…" }, … ] }
69
+
70
+ # 3 · the agent asks for a column it was NOT granted — WITHHELD
71
+ curl -s -X POST https://www.nodatacapsule.com/api/agents/<handle>/read \
72
+ -H "Authorization: Bearer ndca-…" -H 'content-type: application/json' \
73
+ -d '{"table":"agent_demo_records","columns":["email"]}'
74
+ # → 403 { "error": "scope_violation", "denied_columns": ["email"] } ← the key for email is never derived
75
+
76
+ # 4 · prove it — signed receipts for the allow AND the deny, verifiable without the data:
77
+ # https://www.nodatacapsule.com/decisions
78
+ ```
79
+
80
+ `email` came back **absent, not blanked** — there is no decrypt‑all‑then‑filter path. That is *"you decide what the AI sees,"* as math, not a promise.
81
+
82
+ No terminal? Same idea in the browser, zero install: **https://www.nodatacapsule.com/ai-folder** — drop a folder, see exactly what an AI would be allowed to read (nothing uploads).
83
+
84
+ ---
85
+
86
+ ## Two modes — the credential decides what the server can do
87
+
88
+ **Agent mode (`--grant-token ndca-…`)** — *permission management for language models.* Hand an AI agent a grant token; the server exposes only tools hard‑scoped to that grant. The agent may request **only** the columns its grant allows — denied columns never decrypt server‑side, and every action emits a receipt. Point any MCP client at it and it **physically cannot exceed its claims**.
89
+
90
+ ```bash
91
+ claude mcp add nodata -- npx @nodatachat/mcp --grant-token ndca-YOUR_GRANT
92
+ ```
93
+
94
+ **Admin mode (`--api-key ndp_… / sk_live_…`)** — the owner seat: register tables, issue and revoke grants, and use blind relay (encrypt / decrypt / deliver / evidence) from inside any MCP client.
95
+
96
+ ```bash
97
+ claude mcp add nodata -- npx @nodatachat/mcp --api-key YOUR_API_KEY
98
+ ```
99
+
100
+ Credential precedence: `--api-key` / `--grant-token` › `NODATA_API_KEY` / `NODATA_GRANT_TOKEN` › the saved `~/.nodata` file. An explicit flag or env always wins.
101
+
102
+ On **Windows**, native (non‑WSL) `npx` needs the `cmd /c` wrapper: `claude mcp add nodata -- cmd /c npx @nodatachat/mcp …`. After changing the credential, **restart** — the running server keeps its old arguments.
103
+
104
+ ---
105
+
106
+ ## Available tools
107
+
108
+ **Agent mode** (`--grant-token`):
109
+
110
+ | Tool | What it does |
111
+ |------|--------------|
112
+ | `nodata_decide` | May the agent reach a resource? allow / degrade (reachable subset) / deny + an access‑distance cost + a signed proof. No data. Plan before you read. |
113
+ | `nodata_retrieve` | Prompt‑ready authorized context, filtered to allowed columns, with an accounting of what was withheld + a proof. |
114
+ | `nodata_read` | Read governed rows within the grant; denied columns never decrypt; every read is receipted. |
115
+ | `nodata_use` | Invoke a pre‑registered secret‑blind capability — the Capsule injects the sealed credential server‑side and returns only the result. The secret never reaches the agent. |
116
+
117
+ **Admin mode** (`--api-key`):
118
+
119
+ | Tool | What it does |
120
+ |------|--------------|
121
+ | `nodata_register_table` | Register one of your tables' **policy** (which columns are sensitive, which are metadata). Encrypts nothing — the response says what your table still needs. |
122
+ | `nodata_connect_source` | Connect your own Supabase project as the data source. Reads the URL, anon key and JWT secret from your **local** `.env.local` / `.env` and sends them straight to NoData — the model never sees them. |
123
+ | `nodata_grant` | Issue **one** agent a claim‑scoped grant — over one table, or several with `tables: [...]`; returns a `grant_token` (shown once). |
124
+ | `nodata_revoke` | Revoke a grant by handle or jti — access stops at once. |
125
+ | `nodata_encrypt` / `nodata_decrypt` | Field‑level AES‑256‑GCM; NoData stores nothing of the plaintext. |
126
+ | `nodata_deliver` | Create a burn‑after‑read secure link with OTP. |
127
+ | `nodata_proofs` | Your organization's signed proofs — every grant, read and deny — each verifiable by anyone at `/api/access/verify`. Every org key. Also returns `plan`: which proof types your plan includes, and which need an upgrade (named, never hidden) — the same list the site's proofs hub shows. |
128
+ | `nodata_evidence` | Field‑level encrypt/decrypt/deliver events. Requires the Engine scope. |
129
+
130
+ ---
131
+
132
+ ## Security & privacy model
133
+
134
+ - **Content‑blind.** For governed reads, denied columns are **never decrypted on the server** — the key release is scoped to the grant, not "we promise not to look."
135
+ - **Every action is a receipt.** Reads, grants, revokes and denials mint a signed, hash‑chained decision receipt (Ed25519, publicly verifiable), written **before** the release it authorizes — if the receipt can't be written, nothing is served.
136
+ - **Structural isolation.** A key is bound to its org; every governance query is scoped to that tenant, so a key cannot aim at another org's data.
137
+ - **The honest boundary.** This governs data the agent reaches **through the Capsule**. It does not sandbox a process already executing as you on your own machine.
138
+
139
+ ---
140
+
141
+ ## Configuration
142
+
143
+ | Option | Flag | Env var | Default |
144
+ |--------|------|---------|---------|
145
+ | API key (admin) | `--api-key` | `NODATA_API_KEY` | — |
146
+ | Grant token (agent) | `--grant-token` | `NODATA_GRANT_TOKEN` | — |
147
+ | Base URL | `--base-url` | `NODATA_BASE_URL` | `https://www.nodatacapsule.com` |
148
+
149
+ Get a key free at **https://www.nodatacapsule.com/capsule-api/register** (issues `ndp_test_…` instantly). `--help` and `--version` are also available.
150
+
151
+ ## NoData on npm
152
+
153
+ - **`@nodatachat/nodata`**: the main package, with all protection, scanning and governance capabilities, from the terminal.
154
+ - **`@nodatachat/sdk`**: integration for developers.
155
+ - **`@nodatachat/mcp`** (this one): integration for AI agents and MCP clients.
156
+
157
+ ## License
158
+
159
+ [FSL-1.1-ALv2](LICENSE.md) (Functional Source License 1.1, Apache 2.0 future license).
160
+ Use it for any purpose except a competing product or service. Each release becomes
161
+ available under the Apache License 2.0 two years after it is published. Versions up
162
+ to 0.9.0 were published under MIT and remain available under that license.
163
+ Copyright 2026 Capsule Ltd.
package/dist/index.js CHANGED
@@ -1,24 +1,24 @@
1
1
  #!/usr/bin/env node
2
- /*! @nodatachat/mcp 1.0.0 | Copyright 2026 Capsule Ltd. | FSL-1.1-ALv2 | see LICENSE.md */
3
- import{McpServer as gt}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as mt}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as c}from"zod";import{readdirSync as _e,readFileSync as ve,statSync as J}from"node:fs";import{join as ct}from"node:path";var ie=[{id:"aws-access-key",name:"AWS Access Key",pattern:/(?:^|[^A-Z0-9])AKIA[A-Z2-7]{16}(?:[^A-Z2-7]|$)/,severity:"critical"},{id:"aws-secret-key",name:"AWS Secret Key",pattern:/(?:aws_secret_access_key|aws_secret_key|secret_access_key)\s*[=:]\s*['"]?[A-Za-z0-9/+=]{40}['"]?/,severity:"critical"},{id:"gcp-api-key",name:"GCP API Key",pattern:/AIza[0-9A-Za-z_-]{35}/,severity:"critical"},{id:"gcp-service-account",name:"GCP Service Account",pattern:/"type"\s*:\s*"service_account"/,severity:"critical"},{id:"github-token",name:"GitHub Token",pattern:/gh[pousr]_[0-9a-zA-Z]{36,}/,severity:"critical"},{id:"github-fine-grained",name:"GitHub Fine-Grained Token",pattern:/github_pat_[0-9a-zA-Z_]{22,}/,severity:"critical"},{id:"gitlab-token",name:"GitLab Token",pattern:/glpat-[0-9a-zA-Z_-]{20,}/,severity:"critical"},{id:"stripe-secret",name:"Stripe Secret Key",pattern:/sk_live_[0-9a-zA-Z]{24,}/,severity:"critical"},{id:"stripe-restricted",name:"Stripe Restricted Key",pattern:/rk_live_[0-9a-zA-Z]{24,}/,severity:"critical"},{id:"slack-token",name:"Slack Token",pattern:/xox[baprs]-[0-9a-zA-Z-]{10,}/,severity:"high"},{id:"slack-webhook",name:"Slack Webhook",pattern:/hooks\.slack\.com\/services\/T[0-9A-Z]{8,}\/B[0-9A-Z]{8,}\/[0-9a-zA-Z]{24}/,severity:"high"},{id:"twilio-api-key",name:"Twilio API Key",pattern:/SK[0-9a-fA-F]{32}/,severity:"high"},{id:"sendgrid-api-key",name:"SendGrid API Key",pattern:/SG\.[0-9a-zA-Z_-]{22}\.[0-9a-zA-Z_-]{43}/,severity:"critical"},{id:"postgres-uri",name:"PostgreSQL Connection String",pattern:/postgres(?:ql)?:\/\/[^\s'"]{10,}/,severity:"critical"},{id:"mongodb-uri",name:"MongoDB Connection String",pattern:/mongodb(?:\+srv)?:\/\/[^\s'"]{10,}/,severity:"critical"},{id:"redis-uri",name:"Redis Connection String",pattern:/redis(?:s)?:\/\/[^\s'"]{10,}/,severity:"high"},{id:"openai-key",name:"OpenAI API Key",pattern:/sk-[0-9a-zA-Z]{20,}T3BlbkFJ[0-9a-zA-Z]{20,}/,severity:"critical"},{id:"anthropic-key",name:"Anthropic API Key",pattern:/sk-ant-[0-9a-zA-Z_-]{40,}/,severity:"critical"},{id:"firebase-key",name:"Firebase API Key",pattern:/AIza[0-9A-Za-z_-]{35}/,severity:"high"},{id:"supabase-key",name:"Supabase Service Key",pattern:/eyJ[0-9a-zA-Z_-]{100,}/,severity:"critical"},{id:"private-key",name:"Private Key",pattern:/-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/,severity:"critical"},{id:"jwt-secret",name:"JWT/Generic Secret",pattern:/(?:jwt_secret|JWT_SECRET|secret_key|SECRET_KEY|api_secret|API_SECRET)\s*[=:]\s*['"][^'"]{8,}['"]/,severity:"high"},{id:"password-in-code",name:"Password in Code",pattern:/(?:password|passwd|pwd)\s*[=:]\s*['"][^'"]{8,}['"]/,severity:"high"},{id:"bearer-token",name:"Bearer Token in Code",pattern:/['"]Bearer\s+[0-9a-zA-Z._-]{20,}['"]/,severity:"high"}],ae=/node_modules\/|\.next\/|\.test\.|\.spec\.|__tests__\/|\.env\.example$|\.nodata-proof\.json$|\.git\//,Oe=[{name:"lodash",below:"4.17.21",severity:"critical",cve:"CVE-2021-23337",desc:"Command injection via template"},{name:"minimist",below:"1.2.6",severity:"critical",cve:"CVE-2021-44906",desc:"Prototype pollution"},{name:"json5",below:"2.2.2",severity:"high",cve:"CVE-2022-46175",desc:"Prototype pollution"},{name:"express",below:"4.19.2",severity:"high",cve:"CVE-2024-29041",desc:"Open redirect"},{name:"path-to-regexp",below:"6.3.0",severity:"high",cve:"CVE-2024-45296",desc:"ReDoS"},{name:"sanitize-html",below:"2.12.1",severity:"high",cve:"CVE-2024-21501",desc:"XSS bypass"},{name:"dompurify",below:"3.0.9",severity:"high",cve:"CVE-2024-45801",desc:"mXSS"},{name:"jsonwebtoken",below:"9.0.0",severity:"critical",cve:"CVE-2022-23529",desc:"Key confusion attack"},{name:"jose",below:"4.15.5",severity:"high",cve:"CVE-2024-28176",desc:"DoS via JWE"},{name:"axios",below:"1.6.0",severity:"high",cve:"CVE-2023-45857",desc:"CSRF cookie leak"},{name:"node-fetch",below:"2.6.7",severity:"high",cve:"CVE-2022-0235",desc:"Header leak on redirect"},{name:"serialize-javascript",below:"6.0.2",severity:"critical",cve:"CVE-2024-11831",desc:"XSS via serialization"},{name:"xml2js",below:"0.6.0",severity:"high",cve:"CVE-2023-0842",desc:"Prototype pollution"},{name:"next",below:"14.1.1",severity:"high",cve:"CVE-2024-34350",desc:"SSRF via headers"},{name:"sharp",below:"0.32.6",severity:"high",cve:"CVE-2023-4863",desc:"libwebp heap overflow"},{name:"multer",below:"1.4.5",severity:"high",cve:"CVE-2022-24434",desc:"Prototype pollution"},{name:"tough-cookie",below:"4.1.3",severity:"medium",cve:"CVE-2023-26136",desc:"Prototype pollution"},{name:"semver",below:"7.5.4",severity:"medium",cve:"CVE-2022-25883",desc:"ReDoS"},{name:"ip",below:"2.0.1",severity:"critical",cve:"CVE-2024-29415",desc:"SSRF bypass via octal IP"},{name:"tar",below:"6.2.1",severity:"high",cve:"CVE-2024-28863",desc:"DoS via path traversal"}];function De(e,t){let n=o=>o.replace(/^[^0-9]*/,"").split(".").map(a=>parseInt(a,10)||0),i=n(e),r=n(t);for(let o=0;o<3;o++){if((i[o]||0)<(r[o]||0))return!0;if((i[o]||0)>(r[o]||0))return!1}return!1}function oe(e){let t=[],n;try{n=JSON.parse(e)}catch{return t}let i={...n.dependencies??{},...n.devDependencies??{}};for(let[r,o]of Object.entries(i)){let a=(o||"").replace(/^[\^~>=<\s]+/,""),s=Oe.find(l=>l.name===r&&De(a,l.below));s&&t.push({name:r,version:a,vuln:s})}return t}var Le=[{pattern:/^(full_?name|first_?name|last_?name|spouse_?name|beneficiary_?name)$/i,type:"name",label:"Person name"},{pattern:/^(email|e_?mail|email_?address)$/i,type:"email",label:"Email address"},{pattern:/^(phone|mobile|telephone|phone_?number|cell_?phone)$/i,type:"phone",label:"Phone number"},{pattern:/^(id_?number|identity|ssn|social_?security|national_?id|teudat_?zehut)$/i,type:"id",label:"ID number"},{pattern:/^(address|street_?address|home_?address)$/i,type:"address",label:"Address"},{pattern:/^(date_?of_?birth|dob|birth_?date|birthday)$/i,type:"dob",label:"Date of birth"},{pattern:/^(bank_?account|iban|account_?number|bank_?details|routing_?number)$/i,type:"financial",label:"Bank account"},{pattern:/^(credit_?card|card_?number|cvv|cc_?number)$/i,type:"financial",label:"Credit card"},{pattern:/^(salary|income|commission|premium|compensation)$/i,type:"financial",label:"Salary / income"},{pattern:/^(medical_?info|health_?conditions|diagnosis|medication|prescription)$/i,type:"medical",label:"Medical"},{pattern:/^(passport|passport_?number|passport_?id)$/i,type:"id",label:"Passport"},{pattern:/^(driver_?license|drivers_?license|dl_?number)$/i,type:"id",label:"Driver license"},{pattern:/^(tax_?id|tin|vat_?number|ein)$/i,type:"financial",label:"Tax ID"},{pattern:/^(insurance_?number|policy_?number|member_?id)$/i,type:"id",label:"Insurance/Policy"},{pattern:/^(biometric|fingerprint|face_?id|retina)$/i,type:"biometric",label:"Biometric"},{pattern:/^(latitude|longitude|lat|lng|geo_?location|coordinates)$/i,type:"location",label:"Geolocation"}],Fe=[{pattern:/^(?:שם|שם_מלא|שם פרטי|שם_פרטי|שם משפחה|שם_משפחה|שם הלקוח|שם בן זוג|שם המבוטח|שם המוטב|שם הילד|שם הקטין)$/i,type:"name",label:"\u05E9\u05DD \u05D0\u05D3\u05DD"},{pattern:/^(?:ת"ז|ת\.ז|ת ז|תז|תעודת זהות|תעודת_זהות|מספר זהות|מס' זהות|teudat_zehut|tz|israeli_id|il_id)$/i,type:"id",label:"\u05EA\u05E2\u05D5\u05D3\u05EA \u05D6\u05D4\u05D5\u05EA"},{pattern:/^(?:דרכון|מספר דרכון|רישיון נהיגה|רישיון_נהיגה|רשיון נהיגה)$/i,type:"id",label:"\u05D3\u05E8\u05DB\u05D5\u05DF/\u05E8\u05D9\u05E9\u05D9\u05D5\u05DF"},{pattern:/^(?:מייל|דוא"ל|דוא_ל|דואר אלקטרוני|אימייל|כתובת מייל)$/i,type:"email",label:"\u05D3\u05D5\u05D0\u05E8 \u05D0\u05DC\u05E7\u05D8\u05E8\u05D5\u05E0\u05D9"},{pattern:/^(?:טלפון|טל'|נייד|פלאפון|סלולרי|מספר טלפון|טלפון נייד)$/i,type:"phone",label:"\u05DE\u05E1\u05E4\u05E8 \u05D8\u05DC\u05E4\u05D5\u05DF"},{pattern:/^(?:כתובת|כתובת מגורים|רחוב|מס בית|עיר|ישוב|יישוב|מיקוד|כתובת מלאה|מען)$/i,type:"address",label:"\u05DB\u05EA\u05D5\u05D1\u05EA"},{pattern:/^(?:תאריך לידה|ת לידה|תאריך הולדת|יום הולדת|גיל)$/i,type:"dob",label:"\u05EA\u05D0\u05E8\u05D9\u05DA \u05DC\u05D9\u05D3\u05D4"},{pattern:/^(?:חשבון בנק|מספר חשבון|מס חשבון|בנק|סניף|iban|iban_il)$/i,type:"financial",label:"\u05D7\u05E9\u05D1\u05D5\u05DF \u05D1\u05E0\u05E7"},{pattern:/^(?:כרטיס אשראי|מספר אשראי|cvv|אשראי)$/i,type:"financial",label:"\u05DB\u05E8\u05D8\u05D9\u05E1 \u05D0\u05E9\u05E8\u05D0\u05D9"},{pattern:/^(?:שכר|משכורת|הכנסה|פרמיה|שכר ברוטו|שכר נטו|תגמול|בונוס|פיצוי)$/i,type:"financial",label:"\u05E9\u05DB\u05E8/\u05D4\u05DB\u05E0\u05E1\u05D4"},{pattern:/^(?:אבחון|אבחנה|מחלה|תרופה|מרשם|בדיקה רפואית|מצב רפואי|חיסון|רגישות|אלרגיה|סוג דם)$/i,type:"medical",label:"\u05DE\u05D9\u05D3\u05E2 \u05E8\u05E4\u05D5\u05D0\u05D9"},{pattern:/^(?:מספר פוליסה|מס פוליסה|פוליסה|מספר חבר|מספר ביטוח)$/i,type:"id",label:"\u05E4\u05D5\u05DC\u05D9\u05E1\u05D4/\u05D7\u05D1\u05E8\u05D5\u05EA"},{pattern:/^(?:שם הילד|תעודת זהות של ילד|ילדים|קטין|מספר ילדים|תעודת לידה)$/i,type:"minor_data",label:"\u05DE\u05D9\u05D3\u05E2 \u05E2\u05DC \u05E7\u05D8\u05D9\u05DF"},{pattern:/^(?:דת|לאום|מין|נטייה מינית|השקפה פוליטית|מצב משפחתי|עדה|מוצא)$/i,type:"sensitive_personal",label:"\u05DE\u05D9\u05D3\u05E2 \u05D0\u05D9\u05E9\u05D9 \u05E8\u05D2\u05D9\u05E9"},{pattern:/^(?:מספר רכב|מס רכב|לוחית רישוי|מספר רישוי)$/i,type:"tracking",label:"\u05DE\u05E1\u05E4\u05E8 \u05E8\u05DB\u05D1"}],je=/^(id|uuid|created_?at|updated_?at|deleted_?at|status|type|category|description|title|slug|url|path|key|value|enabled|active|public|version|metadata|config|settings|data|content|note|comment|label|color|icon)$/i;function W(e){if(!e)return null;let t=e.trim();if(je.test(t)||/^name$/i.test(t))return null;for(let n of[...Fe,...Le])if(n.pattern.test(t))return{type:n.type,label:n.label};return null}var G={id:"critical",financial:"critical",medical:"critical",biometric:"critical",minor_data:"critical",sensitive_personal:"critical",email:"high",phone:"high",name:"high",address:"high",dob:"high",tracking:"medium",location:"medium"};function B(e){let t=String(e??"").replace(/\D/g,"");if(t.length===0||t.length>9)return!1;let n=t.padStart(9,"0"),i=0;for(let r=0;r<9;r++){let o=parseInt(n[r]??"0",10)*(r%2+1);o>9&&(o-=9),i+=o}return i%10===0&&parseInt(n,10)>0}var F=/(?:\+972[\s-]?|0)(?:5[0-9]|[2-4]|[89])(?:[\s-]?\d){7}/g;var j=/\bIL\d{2}\s?(?:\d{4}\s?){4}\d{3}\b/g;function Me(e){let t=String(e??"").replace(/\D/g,"");if(t.length<12||t.length>19)return!1;let n=0,i=!1;for(let r=t.length-1;r>=0;r-=1){let o=parseInt(t[r]??"0",10);i&&(o*=2,o>9&&(o-=9)),n+=o,i=!i}return n%10===0}function Be(e){let t=String(e??"").replace(/\s/g,"").toUpperCase();if(!/^[A-Z]{2}\d{2}[A-Z0-9]{10,30}$/.test(t))return!1;let n=t.slice(4)+t.slice(0,4),i=0;for(let r of n){let o=r>="A"&&r<="Z"?String(r.charCodeAt(0)-55):r;for(let a of o)i=(i*10+Number(a))%97}return i===1}var H="(?<![\\d.,])",V="(?![\\d.,])",Ue=[{subkind:"israeli_id",severity:"high",pattern:new RegExp(`${H}\\d{9}${V}`,"g"),validate:B,hint:"Israeli national ID (check digit verified) in plaintext. Encrypt the column or mask it at rest."},{subkind:"credit_card",severity:"critical",pattern:new RegExp(`${H}(?:\\d[ -]?){12,18}\\d${V}`,"g"),validate:Me,hint:"Payment card number (Luhn verified) in plaintext. This is PCI scope."},{subkind:"iban",severity:"high",pattern:/\b[A-Z]{2}\d{2}[A-Z0-9]{10,30}\b/g,validate:Be,hint:"Bank account in IBAN form (mod-97 verified) in plaintext."},{subkind:"israeli_phone",severity:"medium",pattern:new RegExp(`${H}(?:\\+972[- ]?|0)(?:5\\d|[2-489])[- ]?\\d{3}[- ]?\\d{4}${V}`,"g"),validate:e=>{let t=e.replace(/\D/g,"").replace(/^972/,"0");return t.length===10&&t.startsWith("0")},hint:"Israeli phone number in plaintext."}];function se(e){let t=[];for(let n of Ue){n.pattern.lastIndex=0;let i=new Set,r,o=0;for(;(r=n.pattern.exec(e))!==null&&(o+=1,!(o>2e5));){let a=r[0];if(n.validate(a)&&i.add(a.replace(/[\s-]/g,"")),i.size>=1e5)break}i.size>0&&t.push({subkind:n.subkind,severity:n.severity,hint:n.hint,count:i.size})}return t}function Z(e,t){let n=[],i="",r=!1;for(let o=0;o<e.length;o++){let a=e[o];r?a==='"'&&e[o+1]==='"'?(i+='"',o++):a==='"'?r=!1:i+=a:a==='"'?r=!0:a===t?(n.push(i),i=""):i+=a}return n.push(i),n}import{inflateRawSync as Je}from"node:zlib";import{inflateSync as ze}from"node:zlib";var Y=new Set([".pdf"]),ce=8*1024*1024;function Ke(e){let t=e.toString("latin1"),n=new Map,i=/(\d+)\s+(\d+)\s+obj\b/g,r;for(;(r=i.exec(t))!==null;){let o=Number(r[1]),a=i.lastIndex,s=t.indexOf("endobj",a);if(s<0)continue;let l=t.indexOf("stream",a),d,p=null;if(l>=0&&l<s){d=t.slice(a,l);let f=l+6;t[f]==="\r"&&(f+=1),t[f]===`
4
- `&&(f+=1);let S=t.indexOf("endstream",f);S>f&&(p=e.subarray(f,S))}else d=t.slice(a,s);n.set(o,{num:o,dict:d,stream:p})}return n}function He(e){let t=e.toString("latin1").replace(/\s/g,"");t.startsWith("<~")&&(t=t.slice(2));let n=t.indexOf("~>");n>=0&&(t=t.slice(0,n));let i=[],r=0,o=0;for(let a of t){if(a==="z"&&o===0){i.push(0,0,0,0);continue}let s=a.charCodeAt(0)-33;if(s<0||s>84)return null;r=r*85+s,o+=1,o===5&&(i.push(r>>>24&255,r>>>16&255,r>>>8&255,r&255),r=0,o=0)}if(o>0){for(let s=o;s<5;s+=1)r=r*85+84;let a=[r>>>24&255,r>>>16&255,r>>>8&255,r&255];for(let s=0;s<o-1;s+=1)i.push(a[s]??0)}return Buffer.from(i)}function Ve(e){let t=e.toString("latin1").replace(/\s/g,"").split(">")[0]??"",n=t.length%2?t+"0":t;return/^[0-9A-Fa-f]*$/.test(n)?Buffer.from(n,"hex"):null}function We(e){let t=/\/Filter\s*(\[[^\]]*\]|\/\w+)/.exec(e);return!t||!t[1]?[]:[...t[1].matchAll(/\/(\w+)/g)].map(n=>n[1]??"")}function de(e){if(!e.stream)return null;let t=e.stream;for(let n of We(e.dict)){let i=null;if(n==="FlateDecode")try{i=ze(t)}catch{return null}else if(n==="ASCII85Decode")i=He(t);else if(n==="ASCIIHexDecode")i=Ve(t);else return null;if(i===null)return null;t=i}return t.toString("latin1")}function Ge(e){let t=new Map,n=1,i=/begincodespacerange([\s\S]*?)endcodespacerange/.exec(e);if(i&&i[1]){let l=/<([0-9A-Fa-f]+)>/.exec(i[1]);l&&l[1]&&l[1].length>=4&&(n=2)}let r=l=>{let d="";for(let p=0;p+3<l.length+1;p+=4){let f=parseInt(l.slice(p,p+4),16);Number.isFinite(f)&&f>0&&(d+=String.fromCharCode(f))}return d},o=/beginbfchar([\s\S]*?)endbfchar/g,a;for(;(a=o.exec(e))!==null;){let l=/<([0-9A-Fa-f]+)>\s*<([0-9A-Fa-f]*)>/g,d;for(;(d=l.exec(a[1]??""))!==null;)t.set(parseInt(d[1]??"0",16),r(d[2]??""))}let s=/beginbfrange([\s\S]*?)endbfrange/g;for(;(a=s.exec(e))!==null;){let l=a[1]??"",d=/<([0-9A-Fa-f]+)>\s*<([0-9A-Fa-f]+)>\s*<([0-9A-Fa-f]*)>/g,p;for(;(p=d.exec(l))!==null;){let f=parseInt(p[1]??"0",16),S=parseInt(p[2]??"0",16),P=parseInt((p[3]??"0").slice(0,4)||"0",16);if(!(S-f>65535))for(let R=f;R<=S;R+=1)t.set(R,String.fromCharCode(P+(R-f)))}}return{map:t,bytes:n}}function Ze(e){let t=new Map,n=new Map,i=[];for(let r of e.values()){let o=/\/Font\s*<<([\s\S]*?)>>/.exec(r.dict);o?.[1]&&i.push(o[1]);let a=/\/Font\s+(\d+)\s+\d+\s+R/.exec(r.dict);if(a){let s=e.get(Number(a[1]));s&&i.push(s.dict)}}for(let r of i){let o=/\/([^\s/<>[\]()]+)\s+(\d+)\s+\d+\s+R/g,a;for(;(a=o.exec(r))!==null;)n.set(a[1]??"",Number(a[2]))}for(let[r,o]of n){let a=e.get(o);if(!a)continue;let s=/\/ToUnicode\s+(\d+)\s+\d+\s+R/.exec(a.dict);if(!s)continue;let l=e.get(Number(s[1]));if(!l)continue;let d=de(l);d&&t.set(r,Ge(d))}return t}function pe(e){let t=[];for(let n=0;n<e.length;n+=1){let i=e[n];if(i!=="\\"){t.push(i.charCodeAt(0)&255);continue}let r=e[n+1];if(r===void 0)break;if(r>="0"&&r<="7"){let a="",s=n+1;for(;s<e.length&&a.length<3&&e[s]>="0"&&e[s]<="7";)a+=e[s],s+=1;t.push(parseInt(a,8)&255),n=s-1;continue}let o={n:10,r:13,t:9,b:8,f:12};t.push(o[r]??r.charCodeAt(0)&255),n+=1}return t}function ue(e){if(e.length<2||e[0]!==254||e[1]!==255)return null;let t="";for(let n=2;n+1<e.length;n+=2)t+=String.fromCharCode((e[n]??0)<<8|(e[n+1]??0));return t}function le(e,t){let n=ue(e);if(n!==null)return n;if(!t)return e.map(r=>String.fromCharCode(r)).join("");let i="";if(t.bytes===2)for(let r=0;r+1<e.length;r+=2){let o=(e[r]??0)<<8|(e[r+1]??0);i+=t.map.get(o)??""}else for(let r of e)i+=t.map.get(r)??String.fromCharCode(r);return i}function Ye(e,t){let n="",i,r=/\/([^\s/<>[\]()]+)\s+[\d.]+\s+Tf|\((?:\\.|[^\\)])*\)|<[0-9A-Fa-f\s]*>|TJ|Tj|'|"/g,o,a="";for(;(o=r.exec(e))!==null;){let s=o[0];if(s.endsWith("Tf")){i=t.get(o[1]??"");continue}if(s==="TJ"||s==="Tj"||s==="'"||s==='"'){a&&(n+=a+`
5
- `,a="");continue}if(s.startsWith("("))a+=le(pe(s.slice(1,-1)),i);else if(s.startsWith("<")){let l=s.slice(1,-1).replace(/\s+/g,""),d=[];for(let p=0;p+1<l.length;p+=2)d.push(parseInt(l.slice(p,p+2),16));a+=le(d,i)}}return a&&(n+=a+`
6
- `),n}function Xe(e,t){let n=e.toString("latin1"),i=/\/Info\s+(\d+)\s+\d+\s+R/.exec(n),r=i?t.get(Number(i[1]))?.dict:void 0;if(!r)return"";let o=[],a=/\/(Author|Title|Subject|Keywords|Creator|Producer)\s*(\((?:\\.|[^\\)])*\)|<[0-9A-Fa-f\s]*>)/g,s;for(;(s=a.exec(r))!==null;){let l=s[2]??"";if(l.startsWith("(")){let d=pe(l.slice(1,-1));o.push(ue(d)??d.map(p=>String.fromCharCode(p)).join(""))}else{let d=l.slice(1,-1).replace(/\s+/g,""),p="";if(d.toUpperCase().startsWith("FEFF"))for(let f=4;f+3<d.length+1;f+=4)p+=String.fromCharCode(parseInt(d.slice(f,f+4),16));else for(let f=0;f+1<d.length;f+=2)p+=String.fromCharCode(parseInt(d.slice(f,f+2),16));o.push(p)}}return o.join(`
7
- `)}function fe(e){if(!e.subarray(0,8).toString("latin1").startsWith("%PDF-")||e.toString("latin1",0,Math.min(e.length,4096)).includes("/Encrypt"))return null;let n=Ke(e);if(n.size===0)return null;let i=Ze(n),r=[];r.push(Xe(e,n));let o=0,a=0,s=!1;for(let l of n.values()){if(!l.stream||/\/Subtype|\/Type\s*\/(XObject|Font|Metadata)|\/FontFile/.test(l.dict))continue;let d=de(l);if(d===null||!/(TJ|Tj)\b/.test(d))continue;let p=Ye(d,i);if(p.trim().length!==0){if(a+=1,o+p.length>ce){r.push(p.slice(0,Math.max(0,ce-o))),s=!0;break}r.push(p),o+=p.length}}return a===0?null:{text:r.join(`
8
- `),truncated:s,streams:a}}var he=new Set([".xlsx",".xlsm",".docx",".pptx"]),X=8*1024*1024,qe=101010256,Qe=33639248,et=67324752;function tt(e){let t=Math.min(e.length,67584),n=-1;for(let a=e.length-22;a>=e.length-t&&a>=0;a-=1)if(e.readUInt32LE(a)===qe){n=a;break}if(n<0)return null;let i=e.readUInt16LE(n+10),r=e.readUInt32LE(n+16);if(r>=e.length)return null;let o=[];for(let a=0;a<i&&!(r+46>e.length||e.readUInt32LE(r)!==Qe);a+=1){let s=e.readUInt16LE(r+10),l=e.readUInt32LE(r+20),d=e.readUInt32LE(r+24),p=e.readUInt16LE(r+28),f=e.readUInt16LE(r+30),S=e.readUInt16LE(r+32),P=e.readUInt32LE(r+42),R=e.subarray(r+46,r+46+p).toString("utf8");o.push({name:R,method:s,compSize:l,uncompSize:d,localHeaderOffset:P}),r+=46+p+f+S}return o}function nt(e,t){let n=t.localHeaderOffset;if(n+30>e.length||e.readUInt32LE(n)!==et)return null;let i=e.readUInt16LE(n+26),r=e.readUInt16LE(n+28),o=n+30+i+r,a=o+t.compSize;if(a>e.length)return null;let s=e.subarray(o,a);try{if(t.method===0)return s.toString("utf8");if(t.method===8)return Je(s).toString("utf8")}catch{return null}return null}var rt={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&apos;":"'"};function ge(e){return e.replace(/&(amp|lt|gt|quot|apos);/g,t=>rt[t]??t).replace(/&#(\d+);/g,(t,n)=>{let i=Number(n);return i>0&&i<1114112?String.fromCodePoint(i):""}).replace(/&#x([0-9a-fA-F]+);/g,(t,n)=>{let i=parseInt(n,16);return i>0&&i<1114112?String.fromCodePoint(i):""})}function it(e){return ge(e.replace(/<[^>]*>/g,`
2
+ /*! @nodatachat/mcp 1.1.0 | Copyright 2026 Capsule Ltd. | FSL-1.1-ALv2 | see LICENSE.md */
3
+ import{McpServer as _t}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as vt}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as c}from"zod";import{readdirSync as be,readFileSync as Se,statSync as Q}from"node:fs";import{join as pt}from"node:path";var oe=[{id:"aws-access-key",name:"AWS Access Key",pattern:/(?:^|[^A-Z0-9])AKIA[A-Z2-7]{16}(?:[^A-Z2-7]|$)/,severity:"critical"},{id:"aws-secret-key",name:"AWS Secret Key",pattern:/(?:aws_secret_access_key|aws_secret_key|secret_access_key)\s*[=:]\s*['"]?[A-Za-z0-9/+=]{40}['"]?/,severity:"critical"},{id:"gcp-api-key",name:"GCP API Key",pattern:/AIza[0-9A-Za-z_-]{35}/,severity:"critical"},{id:"gcp-service-account",name:"GCP Service Account",pattern:/"type"\s*:\s*"service_account"/,severity:"critical"},{id:"github-token",name:"GitHub Token",pattern:/gh[pousr]_[0-9a-zA-Z]{36,}/,severity:"critical"},{id:"github-fine-grained",name:"GitHub Fine-Grained Token",pattern:/github_pat_[0-9a-zA-Z_]{22,}/,severity:"critical"},{id:"gitlab-token",name:"GitLab Token",pattern:/glpat-[0-9a-zA-Z_-]{20,}/,severity:"critical"},{id:"stripe-secret",name:"Stripe Secret Key",pattern:/sk_live_[0-9a-zA-Z]{24,}/,severity:"critical"},{id:"stripe-restricted",name:"Stripe Restricted Key",pattern:/rk_live_[0-9a-zA-Z]{24,}/,severity:"critical"},{id:"slack-token",name:"Slack Token",pattern:/xox[baprs]-[0-9a-zA-Z-]{10,}/,severity:"high"},{id:"slack-webhook",name:"Slack Webhook",pattern:/hooks\.slack\.com\/services\/T[0-9A-Z]{8,}\/B[0-9A-Z]{8,}\/[0-9a-zA-Z]{24}/,severity:"high"},{id:"twilio-api-key",name:"Twilio API Key",pattern:/SK[0-9a-fA-F]{32}/,severity:"high"},{id:"sendgrid-api-key",name:"SendGrid API Key",pattern:/SG\.[0-9a-zA-Z_-]{22}\.[0-9a-zA-Z_-]{43}/,severity:"critical"},{id:"postgres-uri",name:"PostgreSQL Connection String",pattern:/postgres(?:ql)?:\/\/[^\s'"]{10,}/,severity:"critical"},{id:"mongodb-uri",name:"MongoDB Connection String",pattern:/mongodb(?:\+srv)?:\/\/[^\s'"]{10,}/,severity:"critical"},{id:"redis-uri",name:"Redis Connection String",pattern:/redis(?:s)?:\/\/[^\s'"]{10,}/,severity:"high"},{id:"openai-key",name:"OpenAI API Key",pattern:/sk-[0-9a-zA-Z]{20,}T3BlbkFJ[0-9a-zA-Z]{20,}/,severity:"critical"},{id:"anthropic-key",name:"Anthropic API Key",pattern:/sk-ant-[0-9a-zA-Z_-]{40,}/,severity:"critical"},{id:"firebase-key",name:"Firebase API Key",pattern:/AIza[0-9A-Za-z_-]{35}/,severity:"high"},{id:"supabase-key",name:"Supabase Service Key",pattern:/eyJ[0-9a-zA-Z_-]{100,}/,severity:"critical"},{id:"private-key",name:"Private Key",pattern:/-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/,severity:"critical"},{id:"jwt-secret",name:"JWT/Generic Secret",pattern:/(?:jwt_secret|JWT_SECRET|secret_key|SECRET_KEY|api_secret|API_SECRET)\s*[=:]\s*['"][^'"]{8,}['"]/,severity:"high"},{id:"password-in-code",name:"Password in Code",pattern:/(?:password|passwd|pwd)\s*[=:]\s*['"][^'"]{8,}['"]/,severity:"high"},{id:"bearer-token",name:"Bearer Token in Code",pattern:/['"]Bearer\s+[0-9a-zA-Z._-]{20,}['"]/,severity:"high"}],se=/node_modules\/|\.next\/|\.test\.|\.spec\.|__tests__\/|\.env\.example$|\.nodata-proof\.json$|\.git\//,Ue=[{name:"lodash",below:"4.17.21",severity:"critical",cve:"CVE-2021-23337",desc:"Command injection via template"},{name:"minimist",below:"1.2.6",severity:"critical",cve:"CVE-2021-44906",desc:"Prototype pollution"},{name:"json5",below:"2.2.2",severity:"high",cve:"CVE-2022-46175",desc:"Prototype pollution"},{name:"express",below:"4.19.2",severity:"high",cve:"CVE-2024-29041",desc:"Open redirect"},{name:"path-to-regexp",below:"6.3.0",severity:"high",cve:"CVE-2024-45296",desc:"ReDoS"},{name:"sanitize-html",below:"2.12.1",severity:"high",cve:"CVE-2024-21501",desc:"XSS bypass"},{name:"dompurify",below:"3.0.9",severity:"high",cve:"CVE-2024-45801",desc:"mXSS"},{name:"jsonwebtoken",below:"9.0.0",severity:"critical",cve:"CVE-2022-23529",desc:"Key confusion attack"},{name:"jose",below:"4.15.5",severity:"high",cve:"CVE-2024-28176",desc:"DoS via JWE"},{name:"axios",below:"1.6.0",severity:"high",cve:"CVE-2023-45857",desc:"CSRF cookie leak"},{name:"node-fetch",below:"2.6.7",severity:"high",cve:"CVE-2022-0235",desc:"Header leak on redirect"},{name:"serialize-javascript",below:"6.0.2",severity:"critical",cve:"CVE-2024-11831",desc:"XSS via serialization"},{name:"xml2js",below:"0.6.0",severity:"high",cve:"CVE-2023-0842",desc:"Prototype pollution"},{name:"next",below:"14.1.1",severity:"high",cve:"CVE-2024-34350",desc:"SSRF via headers"},{name:"sharp",below:"0.32.6",severity:"high",cve:"CVE-2023-4863",desc:"libwebp heap overflow"},{name:"multer",below:"1.4.5",severity:"high",cve:"CVE-2022-24434",desc:"Prototype pollution"},{name:"tough-cookie",below:"4.1.3",severity:"medium",cve:"CVE-2023-26136",desc:"Prototype pollution"},{name:"semver",below:"7.5.4",severity:"medium",cve:"CVE-2022-25883",desc:"ReDoS"},{name:"ip",below:"2.0.1",severity:"critical",cve:"CVE-2024-29415",desc:"SSRF bypass via octal IP"},{name:"tar",below:"6.2.1",severity:"high",cve:"CVE-2024-28863",desc:"DoS via path traversal"}];function Be(e,t){let n=o=>o.replace(/^[^0-9]*/,"").split(".").map(r=>parseInt(r,10)||0),a=n(e),i=n(t);for(let o=0;o<3;o++){if((a[o]||0)<(i[o]||0))return!0;if((a[o]||0)>(i[o]||0))return!1}return!1}function ce(e){let t=[],n;try{n=JSON.parse(e)}catch{return t}let a={...n.dependencies??{},...n.devDependencies??{}};for(let[i,o]of Object.entries(a)){let r=(o||"").replace(/^[\^~>=<\s]+/,""),s=Ue.find(l=>l.name===i&&Be(r,l.below));s&&t.push({name:i,version:r,vuln:s})}return t}var je=[{pattern:/^(full_?name|first_?name|last_?name|spouse_?name|beneficiary_?name)$/i,type:"name",label:"Person name"},{pattern:/^(email|e_?mail|email_?address)$/i,type:"email",label:"Email address"},{pattern:/^(phone|mobile|telephone|phone_?number|cell_?phone)$/i,type:"phone",label:"Phone number"},{pattern:/^(id_?number|identity|ssn|social_?security|national_?id|teudat_?zehut)$/i,type:"id",label:"ID number"},{pattern:/^(address|street_?address|home_?address)$/i,type:"address",label:"Address"},{pattern:/^(date_?of_?birth|dob|birth_?date|birthday)$/i,type:"dob",label:"Date of birth"},{pattern:/^(bank_?account|iban|account_?number|bank_?details|routing_?number)$/i,type:"financial",label:"Bank account"},{pattern:/^(credit_?card|card_?number|cvv|cc_?number)$/i,type:"financial",label:"Credit card"},{pattern:/^(salary|income|commission|premium|compensation)$/i,type:"financial",label:"Salary / income"},{pattern:/^(medical_?info|health_?conditions|diagnosis|medication|prescription)$/i,type:"medical",label:"Medical"},{pattern:/^(passport|passport_?number|passport_?id)$/i,type:"id",label:"Passport"},{pattern:/^(driver_?license|drivers_?license|dl_?number)$/i,type:"id",label:"Driver license"},{pattern:/^(tax_?id|tin|vat_?number|ein)$/i,type:"financial",label:"Tax ID"},{pattern:/^(insurance_?number|policy_?number|member_?id)$/i,type:"id",label:"Insurance/Policy"},{pattern:/^(biometric|fingerprint|face_?id|retina)$/i,type:"biometric",label:"Biometric"},{pattern:/^(latitude|longitude|lat|lng|geo_?location|coordinates)$/i,type:"location",label:"Geolocation"}],Fe=[{pattern:/^(?:שם|שם_מלא|שם פרטי|שם_פרטי|שם משפחה|שם_משפחה|שם הלקוח|שם בן זוג|שם המבוטח|שם המוטב|שם הילד|שם הקטין)$/i,type:"name",label:"\u05E9\u05DD \u05D0\u05D3\u05DD"},{pattern:/^(?:ת"ז|ת\.ז|ת ז|תז|תעודת זהות|תעודת_זהות|מספר זהות|מס' זהות|teudat_zehut|tz|israeli_id|il_id)$/i,type:"id",label:"\u05EA\u05E2\u05D5\u05D3\u05EA \u05D6\u05D4\u05D5\u05EA"},{pattern:/^(?:דרכון|מספר דרכון|רישיון נהיגה|רישיון_נהיגה|רשיון נהיגה)$/i,type:"id",label:"\u05D3\u05E8\u05DB\u05D5\u05DF/\u05E8\u05D9\u05E9\u05D9\u05D5\u05DF"},{pattern:/^(?:מייל|דוא"ל|דוא_ל|דואר אלקטרוני|אימייל|כתובת מייל)$/i,type:"email",label:"\u05D3\u05D5\u05D0\u05E8 \u05D0\u05DC\u05E7\u05D8\u05E8\u05D5\u05E0\u05D9"},{pattern:/^(?:טלפון|טל'|נייד|פלאפון|סלולרי|מספר טלפון|טלפון נייד)$/i,type:"phone",label:"\u05DE\u05E1\u05E4\u05E8 \u05D8\u05DC\u05E4\u05D5\u05DF"},{pattern:/^(?:כתובת|כתובת מגורים|רחוב|מס בית|עיר|ישוב|יישוב|מיקוד|כתובת מלאה|מען)$/i,type:"address",label:"\u05DB\u05EA\u05D5\u05D1\u05EA"},{pattern:/^(?:תאריך לידה|ת לידה|תאריך הולדת|יום הולדת|גיל)$/i,type:"dob",label:"\u05EA\u05D0\u05E8\u05D9\u05DA \u05DC\u05D9\u05D3\u05D4"},{pattern:/^(?:חשבון בנק|מספר חשבון|מס חשבון|בנק|סניף|iban|iban_il)$/i,type:"financial",label:"\u05D7\u05E9\u05D1\u05D5\u05DF \u05D1\u05E0\u05E7"},{pattern:/^(?:כרטיס אשראי|מספר אשראי|cvv|אשראי)$/i,type:"financial",label:"\u05DB\u05E8\u05D8\u05D9\u05E1 \u05D0\u05E9\u05E8\u05D0\u05D9"},{pattern:/^(?:שכר|משכורת|הכנסה|פרמיה|שכר ברוטו|שכר נטו|תגמול|בונוס|פיצוי)$/i,type:"financial",label:"\u05E9\u05DB\u05E8/\u05D4\u05DB\u05E0\u05E1\u05D4"},{pattern:/^(?:אבחון|אבחנה|מחלה|תרופה|מרשם|בדיקה רפואית|מצב רפואי|חיסון|רגישות|אלרגיה|סוג דם)$/i,type:"medical",label:"\u05DE\u05D9\u05D3\u05E2 \u05E8\u05E4\u05D5\u05D0\u05D9"},{pattern:/^(?:מספר פוליסה|מס פוליסה|פוליסה|מספר חבר|מספר ביטוח)$/i,type:"id",label:"\u05E4\u05D5\u05DC\u05D9\u05E1\u05D4/\u05D7\u05D1\u05E8\u05D5\u05EA"},{pattern:/^(?:שם הילד|תעודת זהות של ילד|ילדים|קטין|מספר ילדים|תעודת לידה)$/i,type:"minor_data",label:"\u05DE\u05D9\u05D3\u05E2 \u05E2\u05DC \u05E7\u05D8\u05D9\u05DF"},{pattern:/^(?:דת|לאום|מין|נטייה מינית|השקפה פוליטית|מצב משפחתי|עדה|מוצא)$/i,type:"sensitive_personal",label:"\u05DE\u05D9\u05D3\u05E2 \u05D0\u05D9\u05E9\u05D9 \u05E8\u05D2\u05D9\u05E9"},{pattern:/^(?:מספר רכב|מס רכב|לוחית רישוי|מספר רישוי)$/i,type:"tracking",label:"\u05DE\u05E1\u05E4\u05E8 \u05E8\u05DB\u05D1"}],Me=/^(id|uuid|created_?at|updated_?at|deleted_?at|status|type|category|description|title|slug|url|path|key|value|enabled|active|public|version|metadata|config|settings|data|content|note|comment|label|color|icon)$/i;function G(e){if(!e)return null;let t=e.trim();if(Me.test(t)||/^name$/i.test(t))return null;for(let n of[...Fe,...je])if(n.pattern.test(t))return{type:n.type,label:n.label};return null}var Z={id:"critical",financial:"critical",medical:"critical",biometric:"critical",minor_data:"critical",sensitive_personal:"critical",email:"high",phone:"high",name:"high",address:"high",dob:"high",tracking:"medium",location:"medium"};function F(e){let t=String(e??"").replace(/\D/g,"");if(t.length===0||t.length>9)return!1;let n=t.padStart(9,"0"),a=0;for(let i=0;i<9;i++){let o=parseInt(n[i]??"0",10)*(i%2+1);o>9&&(o-=9),a+=o}return a%10===0&&parseInt(n,10)>0}var U=/(?:\+972[\s-]?|0)(?:5[0-9]|[2-4]|[89])(?:[\s-]?\d){7}/g;var B=/\bIL\d{2}\s?(?:\d{4}\s?){4}\d{3}\b/g;function Ke(e){let t=String(e??"").replace(/\D/g,"");if(t.length<12||t.length>19)return!1;let n=0,a=!1;for(let i=t.length-1;i>=0;i-=1){let o=parseInt(t[i]??"0",10);a&&(o*=2,o>9&&(o-=9)),n+=o,a=!a}return n%10===0}function He(e){let t=String(e??"").replace(/\s/g,"").toUpperCase();if(!/^[A-Z]{2}\d{2}[A-Z0-9]{10,30}$/.test(t))return!1;let n=t.slice(4)+t.slice(0,4),a=0;for(let i of n){let o=i>="A"&&i<="Z"?String(i.charCodeAt(0)-55):i;for(let r of o)a=(a*10+Number(r))%97}return a===1}var W="(?<![\\d.,])",Y="(?![\\d.,])",ze=[{subkind:"israeli_id",severity:"high",pattern:new RegExp(`${W}\\d{9}${Y}`,"g"),validate:F,hint:"Israeli national ID (check digit verified) in plaintext. Encrypt the column or mask it at rest."},{subkind:"credit_card",severity:"critical",pattern:new RegExp(`${W}(?:\\d[ -]?){12,18}\\d${Y}`,"g"),validate:Ke,hint:"Payment card number (Luhn verified) in plaintext. This is PCI scope."},{subkind:"iban",severity:"high",pattern:/\b[A-Z]{2}\d{2}[A-Z0-9]{10,30}\b/g,validate:He,hint:"Bank account in IBAN form (mod-97 verified) in plaintext."},{subkind:"israeli_phone",severity:"medium",pattern:new RegExp(`${W}(?:\\+972[- ]?|0)(?:5\\d|[2-489])[- ]?\\d{3}[- ]?\\d{4}${Y}`,"g"),validate:e=>{let t=e.replace(/\D/g,"").replace(/^972/,"0");return t.length===10&&t.startsWith("0")},hint:"Israeli phone number in plaintext."}];function le(e){let t=[];for(let n of ze){n.pattern.lastIndex=0;let a=new Set,i,o=0;for(;(i=n.pattern.exec(e))!==null&&(o+=1,!(o>2e5));){let r=i[0];if(n.validate(r)&&a.add(r.replace(/[\s-]/g,"")),a.size>=1e5)break}a.size>0&&t.push({subkind:n.subkind,severity:n.severity,hint:n.hint,count:a.size})}return t}function X(e,t){let n=[],a="",i=!1;for(let o=0;o<e.length;o++){let r=e[o];i?r==='"'&&e[o+1]==='"'?(a+='"',o++):r==='"'?i=!1:a+=r:r==='"'?i=!0:r===t?(n.push(a),a=""):a+=r}return n.push(a),n}import{inflateRawSync as et}from"node:zlib";import{inflateSync as Ve}from"node:zlib";var J=new Set([".pdf"]),de=8*1024*1024;function We(e){let t=e.toString("latin1"),n=new Map,a=/(\d+)\s+(\d+)\s+obj\b/g,i;for(;(i=a.exec(t))!==null;){let o=Number(i[1]),r=a.lastIndex,s=t.indexOf("endobj",r);if(s<0)continue;let l=t.indexOf("stream",r),d,p=null;if(l>=0&&l<s){d=t.slice(r,l);let f=l+6;t[f]==="\r"&&(f+=1),t[f]===`
4
+ `&&(f+=1);let E=t.indexOf("endstream",f);E>f&&(p=e.subarray(f,E))}else d=t.slice(r,s);n.set(o,{num:o,dict:d,stream:p})}return n}function Ye(e){let t=e.toString("latin1").replace(/\s/g,"");t.startsWith("<~")&&(t=t.slice(2));let n=t.indexOf("~>");n>=0&&(t=t.slice(0,n));let a=[],i=0,o=0;for(let r of t){if(r==="z"&&o===0){a.push(0,0,0,0);continue}let s=r.charCodeAt(0)-33;if(s<0||s>84)return null;i=i*85+s,o+=1,o===5&&(a.push(i>>>24&255,i>>>16&255,i>>>8&255,i&255),i=0,o=0)}if(o>0){for(let s=o;s<5;s+=1)i=i*85+84;let r=[i>>>24&255,i>>>16&255,i>>>8&255,i&255];for(let s=0;s<o-1;s+=1)a.push(r[s]??0)}return Buffer.from(a)}function Ge(e){let t=e.toString("latin1").replace(/\s/g,"").split(">")[0]??"",n=t.length%2?t+"0":t;return/^[0-9A-Fa-f]*$/.test(n)?Buffer.from(n,"hex"):null}function Ze(e){let t=/\/Filter\s*(\[[^\]]*\]|\/\w+)/.exec(e);return!t||!t[1]?[]:[...t[1].matchAll(/\/(\w+)/g)].map(n=>n[1]??"")}function ue(e){if(!e.stream)return null;let t=e.stream;for(let n of Ze(e.dict)){let a=null;if(n==="FlateDecode")try{a=Ve(t)}catch{return null}else if(n==="ASCII85Decode")a=Ye(t);else if(n==="ASCIIHexDecode")a=Ge(t);else return null;if(a===null)return null;t=a}return t.toString("latin1")}function Xe(e){let t=new Map,n=1,a=/begincodespacerange([\s\S]*?)endcodespacerange/.exec(e);if(a&&a[1]){let l=/<([0-9A-Fa-f]+)>/.exec(a[1]);l&&l[1]&&l[1].length>=4&&(n=2)}let i=l=>{let d="";for(let p=0;p+3<l.length+1;p+=4){let f=parseInt(l.slice(p,p+4),16);Number.isFinite(f)&&f>0&&(d+=String.fromCharCode(f))}return d},o=/beginbfchar([\s\S]*?)endbfchar/g,r;for(;(r=o.exec(e))!==null;){let l=/<([0-9A-Fa-f]+)>\s*<([0-9A-Fa-f]*)>/g,d;for(;(d=l.exec(r[1]??""))!==null;)t.set(parseInt(d[1]??"0",16),i(d[2]??""))}let s=/beginbfrange([\s\S]*?)endbfrange/g;for(;(r=s.exec(e))!==null;){let l=r[1]??"",d=/<([0-9A-Fa-f]+)>\s*<([0-9A-Fa-f]+)>\s*<([0-9A-Fa-f]*)>/g,p;for(;(p=d.exec(l))!==null;){let f=parseInt(p[1]??"0",16),E=parseInt(p[2]??"0",16),P=parseInt((p[3]??"0").slice(0,4)||"0",16);if(!(E-f>65535))for(let R=f;R<=E;R+=1)t.set(R,String.fromCharCode(P+(R-f)))}}return{map:t,bytes:n}}function Je(e){let t=new Map,n=new Map,a=[];for(let i of e.values()){let o=/\/Font\s*<<([\s\S]*?)>>/.exec(i.dict);o?.[1]&&a.push(o[1]);let r=/\/Font\s+(\d+)\s+\d+\s+R/.exec(i.dict);if(r){let s=e.get(Number(r[1]));s&&a.push(s.dict)}}for(let i of a){let o=/\/([^\s/<>[\]()]+)\s+(\d+)\s+\d+\s+R/g,r;for(;(r=o.exec(i))!==null;)n.set(r[1]??"",Number(r[2]))}for(let[i,o]of n){let r=e.get(o);if(!r)continue;let s=/\/ToUnicode\s+(\d+)\s+\d+\s+R/.exec(r.dict);if(!s)continue;let l=e.get(Number(s[1]));if(!l)continue;let d=ue(l);d&&t.set(i,Xe(d))}return t}function fe(e){let t=[];for(let n=0;n<e.length;n+=1){let a=e[n];if(a!=="\\"){t.push(a.charCodeAt(0)&255);continue}let i=e[n+1];if(i===void 0)break;if(i>="0"&&i<="7"){let r="",s=n+1;for(;s<e.length&&r.length<3&&e[s]>="0"&&e[s]<="7";)r+=e[s],s+=1;t.push(parseInt(r,8)&255),n=s-1;continue}let o={n:10,r:13,t:9,b:8,f:12};t.push(o[i]??i.charCodeAt(0)&255),n+=1}return t}function he(e){if(e.length<2||e[0]!==254||e[1]!==255)return null;let t="";for(let n=2;n+1<e.length;n+=2)t+=String.fromCharCode((e[n]??0)<<8|(e[n+1]??0));return t}function pe(e,t){let n=he(e);if(n!==null)return n;if(!t)return e.map(i=>String.fromCharCode(i)).join("");let a="";if(t.bytes===2)for(let i=0;i+1<e.length;i+=2){let o=(e[i]??0)<<8|(e[i+1]??0);a+=t.map.get(o)??""}else for(let i of e)a+=t.map.get(i)??String.fromCharCode(i);return a}function qe(e,t){let n="",a,i=/\/([^\s/<>[\]()]+)\s+[\d.]+\s+Tf|\((?:\\.|[^\\)])*\)|<[0-9A-Fa-f\s]*>|TJ|Tj|'|"/g,o,r="";for(;(o=i.exec(e))!==null;){let s=o[0];if(s.endsWith("Tf")){a=t.get(o[1]??"");continue}if(s==="TJ"||s==="Tj"||s==="'"||s==='"'){r&&(n+=r+`
5
+ `,r="");continue}if(s.startsWith("("))r+=pe(fe(s.slice(1,-1)),a);else if(s.startsWith("<")){let l=s.slice(1,-1).replace(/\s+/g,""),d=[];for(let p=0;p+1<l.length;p+=2)d.push(parseInt(l.slice(p,p+2),16));r+=pe(d,a)}}return r&&(n+=r+`
6
+ `),n}function Qe(e,t){let n=e.toString("latin1"),a=/\/Info\s+(\d+)\s+\d+\s+R/.exec(n),i=a?t.get(Number(a[1]))?.dict:void 0;if(!i)return"";let o=[],r=/\/(Author|Title|Subject|Keywords|Creator|Producer)\s*(\((?:\\.|[^\\)])*\)|<[0-9A-Fa-f\s]*>)/g,s;for(;(s=r.exec(i))!==null;){let l=s[2]??"";if(l.startsWith("(")){let d=fe(l.slice(1,-1));o.push(he(d)??d.map(p=>String.fromCharCode(p)).join(""))}else{let d=l.slice(1,-1).replace(/\s+/g,""),p="";if(d.toUpperCase().startsWith("FEFF"))for(let f=4;f+3<d.length+1;f+=4)p+=String.fromCharCode(parseInt(d.slice(f,f+4),16));else for(let f=0;f+1<d.length;f+=2)p+=String.fromCharCode(parseInt(d.slice(f,f+2),16));o.push(p)}}return o.join(`
7
+ `)}function ge(e){if(!e.subarray(0,8).toString("latin1").startsWith("%PDF-")||e.toString("latin1",0,Math.min(e.length,4096)).includes("/Encrypt"))return null;let n=We(e);if(n.size===0)return null;let a=Je(n),i=[];i.push(Qe(e,n));let o=0,r=0,s=!1;for(let l of n.values()){if(!l.stream||/\/Subtype|\/Type\s*\/(XObject|Font|Metadata)|\/FontFile/.test(l.dict))continue;let d=ue(l);if(d===null||!/(TJ|Tj)\b/.test(d))continue;let p=qe(d,a);if(p.trim().length!==0){if(r+=1,o+p.length>de){i.push(p.slice(0,Math.max(0,de-o))),s=!0;break}i.push(p),o+=p.length}}return r===0?null:{text:i.join(`
8
+ `),truncated:s,streams:r}}var me=new Set([".xlsx",".xlsm",".docx",".pptx"]),q=8*1024*1024,tt=101010256,nt=33639248,rt=67324752;function it(e){let t=Math.min(e.length,67584),n=-1;for(let r=e.length-22;r>=e.length-t&&r>=0;r-=1)if(e.readUInt32LE(r)===tt){n=r;break}if(n<0)return null;let a=e.readUInt16LE(n+10),i=e.readUInt32LE(n+16);if(i>=e.length)return null;let o=[];for(let r=0;r<a&&!(i+46>e.length||e.readUInt32LE(i)!==nt);r+=1){let s=e.readUInt16LE(i+10),l=e.readUInt32LE(i+20),d=e.readUInt32LE(i+24),p=e.readUInt16LE(i+28),f=e.readUInt16LE(i+30),E=e.readUInt16LE(i+32),P=e.readUInt32LE(i+42),R=e.subarray(i+46,i+46+p).toString("utf8");o.push({name:R,method:s,compSize:l,uncompSize:d,localHeaderOffset:P}),i+=46+p+f+E}return o}function at(e,t){let n=t.localHeaderOffset;if(n+30>e.length||e.readUInt32LE(n)!==rt)return null;let a=e.readUInt16LE(n+26),i=e.readUInt16LE(n+28),o=n+30+a+i,r=o+t.compSize;if(r>e.length)return null;let s=e.subarray(o,r);try{if(t.method===0)return s.toString("utf8");if(t.method===8)return et(s).toString("utf8")}catch{return null}return null}var ot={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&apos;":"'"};function ye(e){return e.replace(/&(amp|lt|gt|quot|apos);/g,t=>ot[t]??t).replace(/&#(\d+);/g,(t,n)=>{let a=Number(n);return a>0&&a<1114112?String.fromCodePoint(a):""}).replace(/&#x([0-9a-fA-F]+);/g,(t,n)=>{let a=parseInt(n,16);return a>0&&a<1114112?String.fromCodePoint(a):""})}function st(e){return ye(e.replace(/<[^>]*>/g,`
9
9
  `)).replace(/\n{2,}/g,`
10
- `)}function at(e,t,n){let i=new RegExp(`<${t}\\b[^>]*\\b${n}="([^"]*)"`,"g"),r=[],o;for(;(o=i.exec(e))!==null;)o[1]&&r.push(ge(o[1]));return r}function ot(e,t){let n=[];for(let i of t)(i==="docProps/core.xml"||i==="docProps/app.xml")&&n.push(i);if(e===".xlsx"||e===".xlsm")for(let i of t)(i==="xl/sharedStrings.xml"||i==="xl/workbook.xml"||/^xl\/worksheets\/sheet\d+\.xml$/.test(i)||/^xl\/comments\d*\.xml$/.test(i))&&n.push(i);else if(e===".docx")for(let i of t)(i==="word/document.xml"||/^word\/(header|footer|footnotes|endnotes|comments)\d*\.xml$/.test(i))&&n.push(i);else if(e===".pptx")for(let i of t)/^ppt\/(slides|notesSlides)\/(slide|notesSlide)\d+\.xml$/.test(i)&&n.push(i);return n}function st(e,t){let n=tt(e);if(!n||n.length===0)return null;let i=new Map(n.map(d=>[d.name,d])),r=ot(t,n.map(d=>d.name));if(r.length===0)return null;let o=[],a=[],s=0,l=!1;for(let d of r){if(s>=X){l=!0;break}let p=i.get(d);if(!p)continue;let f=nt(e,p);if(f===null)continue;let S=it(f);if(d==="xl/workbook.xml"){let P=at(f,"sheet","name");P.length&&(S+=`
10
+ `)}function ct(e,t,n){let a=new RegExp(`<${t}\\b[^>]*\\b${n}="([^"]*)"`,"g"),i=[],o;for(;(o=a.exec(e))!==null;)o[1]&&i.push(ye(o[1]));return i}function lt(e,t){let n=[];for(let a of t)(a==="docProps/core.xml"||a==="docProps/app.xml")&&n.push(a);if(e===".xlsx"||e===".xlsm")for(let a of t)(a==="xl/sharedStrings.xml"||a==="xl/workbook.xml"||/^xl\/worksheets\/sheet\d+\.xml$/.test(a)||/^xl\/comments\d*\.xml$/.test(a))&&n.push(a);else if(e===".docx")for(let a of t)(a==="word/document.xml"||/^word\/(header|footer|footnotes|endnotes|comments)\d*\.xml$/.test(a))&&n.push(a);else if(e===".pptx")for(let a of t)/^ppt\/(slides|notesSlides)\/(slide|notesSlide)\d+\.xml$/.test(a)&&n.push(a);return n}function dt(e,t){let n=it(e);if(!n||n.length===0)return null;let a=new Map(n.map(d=>[d.name,d])),i=lt(t,n.map(d=>d.name));if(i.length===0)return null;let o=[],r=[],s=0,l=!1;for(let d of i){if(s>=q){l=!0;break}let p=a.get(d);if(!p)continue;let f=at(e,p);if(f===null)continue;let E=st(f);if(d==="xl/workbook.xml"){let P=ct(f,"sheet","name");P.length&&(E+=`
11
11
  `+P.join(`
12
- `))}s+S.length>X&&(S=S.slice(0,Math.max(0,X-s)),l=!0),S.trim().length!==0&&(o.push(S),a.push(d),s+=S.length)}return o.length===0?null:{text:o.join(`
13
- `),truncated:l,parts:a}}function me(e,t){if(he.has(t))return st(e,t);if(Y.has(t)){let n=fe(e);return n?{text:n.text,truncated:n.truncated,parts:[`pdf:${n.streams}-streams`]}:null}return null}var ye=new Set([...he,...Y]);var q=10,M=5e3,lt=64*1024*1024,Q=32*1024,dt=new Set([".exe",".dll",".so",".dylib",".msi",".bin",".iso",".zip",".tar",".gz",".7z",".rar",".png",".jpg",".jpeg",".gif",".bmp",".tif",".tiff",".webp",".ico",".mp3",".mp4",".mov",".avi",".mkv",".wav",".flac",".ogg",".psd",".ai",".fla",".dwg",".dxf",".sqlite",".sqlite3",".db",".pyc",".class"]),pt=new Set(["node_modules",".git",".hg",".svn"]),ut=[{re:/\b\d{3}-\d{2}-\d{4}\b/,kind:"pii_unencrypted",subkind:"ssn",severity:"high",hint:"US Social Security Number pattern in plaintext."},{re:/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i,kind:"pii_unencrypted",subkind:"email",severity:"low",hint:"Email addresses in plaintext."},{re:/(passport|teudat.?zehut|(?<![א-ת])תעודת.?זהות(?![א-ת])|(?<![א-ת])ת["'׳״]?\.?ז(?![א-ת]))/i,kind:"pii_unencrypted",subkind:"passport_keyword",severity:"medium",hint:"Filename / content references passport / national-ID keyword."}],ft=[{re:/(salary|payroll|משכורת|ssn|social.?security|passport|דרכון|תעודת.?זהות)/i,kind:"pii_unencrypted",subkind:"sensitive_filename",severity:"medium",hint:"Filename suggests sensitive PII content."},{re:/\.(env|env\.local|pem|key|p12|pfx)$/i,kind:"pii_unencrypted",subkind:"secret_file",severity:"high",hint:"File extension implies secret / credential bundle in plaintext."}];function ht(e){let t=e.lastIndexOf(".");return t<0?"":e.slice(t).toLowerCase()}async function ee(e){let t=Date.now(),n=[],i=[],r=0,o=0,a={hit_file_cap:!1,hit_depth_cap:!1,max_files:M,max_depth:q,skipped_by_extension:0,documents_extracted:0,documents_truncated:0,documents_unreadable:0,files_too_large:0,files_sampled_only:0,sample_bytes:Q};function s(y){return(y.startsWith(e.target_path)?y.slice(e.target_path.length).replace(/^[\\/]+/,""):y).replace(/\\/g,"/")}function l(y,g,h,v,k){let m=s(y);m!==""&&i.push({target_id:e.target_id,path:m,kind:g,size_bytes:h,modified_at:v?v.toISOString():null,child_count:k})}function d(y,g){n.push({target_id:e.target_id,relative_path:y,finding_kind:g.kind,finding_subkind:g.subkind,severity:g.severity,remediation_hint:g.hint})}function p(y,g,h,v,k){n.push({target_id:e.target_id,relative_path:y,finding_kind:g,finding_subkind:v,severity:h,remediation_hint:k})}function f(y,g){if(r+=1,r>M){a.hit_file_cap=!0;return}let h=s(y);for(let u of ft)u.re.test(g)&&d(h,u);let v=ht(g);if(dt.has(v)){a.skipped_by_extension+=1;return}let k=ye.has(v),m=0;try{m=J(y).size}catch{return}if(k&&m>lt){a.files_too_large+=1;return}let x;try{let u=ve(y,{flag:"r"});x=k?u:u.subarray(0,Q)}catch{return}o+=1;let T;if(k){let u=me(x,v);if(!u){a.documents_unreadable+=1;return}T=u.text,a.documents_extracted+=1,u.truncated&&(a.documents_truncated+=1)}else{let u=0;for(let _=0;_<x.length;_+=1)x[_]===0&&(u+=1);if(x.length>0&&u/x.length>.05)return;T=x.toString("utf8"),m>Q&&(a.files_sampled_only+=1)}let w=new Set;for(let u of ut)if(u.re.test(T)){let _=`${u.subkind}::${h}`;w.has(_)||(w.add(_),d(h,u))}for(let u of se(T)){let _=`validated::${u.subkind}::${h}`;w.has(_)||(w.add(_),p(h,"pii_unencrypted",u.severity,u.subkind,`${u.hint} ${u.count} distinct value${u.count===1?"":"s"} in this file.`))}if(!ae.test(h)){let u=T.split(`
14
- `),_=Math.min(u.length,2e3);for(let b=0;b<_;b++){let E=u[b];if(!E)continue;let $=E.trimStart();if(!($.startsWith("//")||$.startsWith("/*")||$.startsWith("*")||$.startsWith("#"))&&!/process\.env/.test(E)&&!/\$\{[A-Z_]+/.test(E)){for(let C of ie)if(C.pattern.test(E)){let N=`secret::${C.id}::${h}::${b+1}`;if(w.has(N))break;w.add(N),p(h,"hardcoded_secret",C.severity,C.id,`Looks like a ${C.name} hardcoded near line ${b+1}. Move to an env var or secret manager.`);break}}}}if(g==="package.json"&&!h.includes("node_modules"))try{let u=ve(y,"utf8"),_=oe(u);for(let b of _){let E=`vuln::${b.name}::${h}`;w.has(E)||(w.add(E),p(h,"vulnerable_dependency",b.vuln.severity,b.name,`${b.name}@${b.version} is below ${b.vuln.below} (${b.vuln.cve} \xB7 ${b.vuln.desc}). Bump to ${b.vuln.below} or higher.`))}}catch{}if(v===".csv"||v===".tsv"){let u=v===".tsv"?" ":",",_=T.split(/\r?\n/);if(_.length>0){let b=Z(_[0]??"",u),E=_.slice(1,51).map($=>Z($??"",u));b.forEach(($,C)=>{let N=($??"").trim();if(!N)return;let O=W(N)?.type??null;if(!O){let D=[];for(let Ne of E){let K=Ne[C];if(K&&K.trim().length>0&&D.push(K),D.length>=50)break}let L=P(D);L&&(O=L)}if(O){let D=G[O]??"medium",L=`header::${h}::${N}::${O}`;if(w.has(L))return;w.add(L),p(h,"pii_header_match",D,`${O}:${N}`,`Column "${N}" looks like ${O} data. Wrap with envelope or move to a vault-backed reference.`)}})}}if(v===".json"&&!g.endsWith(".nodata-proof.json"))try{let u=JSON.parse(T),_=Array.isArray(u)?u[0]:u;if(_&&typeof _=="object")for(let b of Object.keys(_)){let E=W(b);if(E){let $=G[E.type]??"medium",C=`jsonkey::${h}::${b}::${E.type}`;if(w.has(C))continue;w.add(C),p(h,"pii_header_match",$,`${E.type}:${b}`,`JSON key "${b}" looks like ${E.type} data.`)}}}catch{}/\.(txt|log|md|csv|tsv|json|jsonl|xml|html?|yaml|yml|sql)$/i.test(g)&&S(T,h,w)}function S(y,g,h){let v=y.split(`
15
- `),k=Math.min(v.length,1e3),m=-1,x=-1,T=-1;for(let w=0;w<k;w++){let u=v[w]??"";if(T<0){let _=u.match(/\b\d{9}\b/);_&&B(_[0])&&(T=w+1)}if(m<0&&(F.lastIndex=0,F.test(u)&&(m=w+1)),x<0&&(j.lastIndex=0,j.test(u)&&(x=w+1)),T>0&&m>0&&x>0)break}T>0&&!h.has(`ilid::${g}`)&&(h.add(`ilid::${g}`),p(g,"israeli_id_value","critical",`near_line:${T}`,`Israeli ID with valid Luhn checksum detected near line ${T}. Strong signal that real customer IDs are in plaintext.`)),m>0&&!h.has(`ilphone::${g}`)&&(h.add(`ilphone::${g}`),p(g,"israeli_phone_value","high",`near_line:${m}`,`Israeli phone number detected near line ${m}.`)),x>0&&!h.has(`iliban::${g}`)&&(h.add(`iliban::${g}`),p(g,"israeli_iban_value","critical",`near_line:${x}`,`Israeli IBAN (IL prefix) detected near line ${x}.`))}function P(y){if(y.length===0)return null;let g=0,h=0,v=0;for(let m of y)typeof m=="string"&&(B(m)&&g++,F.lastIndex=0,F.test(m)&&h++,j.lastIndex=0,j.test(m)&&v++);let k=y.length;return g/k>=.3?"id":h/k>=.3?"phone":v/k>=.3?"financial":null}function R(y,g){if(g>q){a.hit_depth_cap=!0;return}if(r>M){a.hit_file_cap=!0;return}let h;try{h=_e(y)}catch{return}for(let v of h){if(r>M)return;let k=ct(y,v),m;try{m=J(k)}catch{continue}if(!m.isSymbolicLink())if(m.isDirectory()){if(pt.has(v))continue;let x=null;try{x=_e(k).length}catch{x=null}l(k,"folder",null,m.mtime??null,x),R(k,g+1)}else m.isFile()&&(l(k,"file",m.size,m.mtime??null,null),f(k,v))}}let z;try{z=J(e.target_path).isDirectory()}catch{z=!1}return z?(e.log.info("folder-scanner: starting",{target_label:e.target_label,target_path:e.target_path,max_files:M,max_depth:q}),R(e.target_path,0),{target_id:e.target_id,target_label:e.target_label,target_path:e.target_path,files_walked:r,files_inspected:o,duration_ms:Date.now()-t,findings:n,inventory:i,coverage:a}):{target_id:e.target_id,target_label:e.target_label,target_path:e.target_path,files_walked:0,files_inspected:0,duration_ms:Date.now()-t,findings:[],inventory:[],coverage:a,error:"target_path_not_found_or_not_directory"}}var te={info(){}};var be={critical:4,high:3,medium:2,low:1,info:0};function ne(e){let t=[];if(t.push(`Scanned "${e.target_label||e.target_path}" on this machine \u2014 nothing left your computer.
12
+ `))}s+E.length>q&&(E=E.slice(0,Math.max(0,q-s)),l=!0),E.trim().length!==0&&(o.push(E),r.push(d),s+=E.length)}return o.length===0?null:{text:o.join(`
13
+ `),truncated:l,parts:r}}function _e(e,t){if(me.has(t))return dt(e,t);if(J.has(t)){let n=ge(e);return n?{text:n.text,truncated:n.truncated,parts:[`pdf:${n.streams}-streams`]}:null}return null}var ve=new Set([...me,...J]);var ee=10,j=5e3,ut=64*1024*1024,te=32*1024,ft=new Set([".exe",".dll",".so",".dylib",".msi",".bin",".iso",".zip",".tar",".gz",".7z",".rar",".png",".jpg",".jpeg",".gif",".bmp",".tif",".tiff",".webp",".ico",".mp3",".mp4",".mov",".avi",".mkv",".wav",".flac",".ogg",".psd",".ai",".fla",".dwg",".dxf",".sqlite",".sqlite3",".db",".pyc",".class"]),ht=new Set(["node_modules",".git",".hg",".svn"]),gt=[{re:/\b\d{3}-\d{2}-\d{4}\b/,kind:"pii_unencrypted",subkind:"ssn",severity:"high",hint:"US Social Security Number pattern in plaintext."},{re:/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i,kind:"pii_unencrypted",subkind:"email",severity:"low",hint:"Email addresses in plaintext."},{re:/(passport|teudat.?zehut|(?<![א-ת])תעודת.?זהות(?![א-ת])|(?<![א-ת])ת["'׳״]?\.?ז(?![א-ת]))/i,kind:"pii_unencrypted",subkind:"passport_keyword",severity:"medium",hint:"Filename / content references passport / national-ID keyword."}],mt=[{re:/(salary|payroll|משכורת|ssn|social.?security|passport|דרכון|תעודת.?זהות)/i,kind:"pii_unencrypted",subkind:"sensitive_filename",severity:"medium",hint:"Filename suggests sensitive PII content."},{re:/\.(env|env\.local|pem|key|p12|pfx)$/i,kind:"pii_unencrypted",subkind:"secret_file",severity:"high",hint:"File extension implies secret / credential bundle in plaintext."}];function yt(e){let t=e.lastIndexOf(".");return t<0?"":e.slice(t).toLowerCase()}async function ne(e){let t=Date.now(),n=[],a=[],i=0,o=0,r={hit_file_cap:!1,hit_depth_cap:!1,max_files:j,max_depth:ee,skipped_by_extension:0,documents_extracted:0,documents_truncated:0,documents_unreadable:0,files_too_large:0,files_sampled_only:0,sample_bytes:te};function s(y){return(y.startsWith(e.target_path)?y.slice(e.target_path.length).replace(/^[\\/]+/,""):y).replace(/\\/g,"/")}function l(y,g,h,v,S){let m=s(y);m!==""&&a.push({target_id:e.target_id,path:m,kind:g,size_bytes:h,modified_at:v?v.toISOString():null,child_count:S})}function d(y,g){n.push({target_id:e.target_id,relative_path:y,finding_kind:g.kind,finding_subkind:g.subkind,severity:g.severity,remediation_hint:g.hint})}function p(y,g,h,v,S){n.push({target_id:e.target_id,relative_path:y,finding_kind:g,finding_subkind:v,severity:h,remediation_hint:S})}function f(y,g){if(i+=1,i>j){r.hit_file_cap=!0;return}let h=s(y);for(let u of mt)u.re.test(g)&&d(h,u);let v=yt(g);if(ft.has(v)){r.skipped_by_extension+=1;return}let S=ve.has(v),m=0;try{m=Q(y).size}catch{return}if(S&&m>ut){r.files_too_large+=1;return}let w;try{let u=Se(y,{flag:"r"});w=S?u:u.subarray(0,te)}catch{return}o+=1;let T;if(S){let u=_e(w,v);if(!u){r.documents_unreadable+=1;return}T=u.text,r.documents_extracted+=1,u.truncated&&(r.documents_truncated+=1)}else{let u=0;for(let _=0;_<w.length;_+=1)w[_]===0&&(u+=1);if(w.length>0&&u/w.length>.05)return;T=w.toString("utf8"),m>te&&(r.files_sampled_only+=1)}let k=new Set;for(let u of gt)if(u.re.test(T)){let _=`${u.subkind}::${h}`;k.has(_)||(k.add(_),d(h,u))}for(let u of le(T)){let _=`validated::${u.subkind}::${h}`;k.has(_)||(k.add(_),p(h,"pii_unencrypted",u.severity,u.subkind,`${u.hint} ${u.count} distinct value${u.count===1?"":"s"} in this file.`))}if(!se.test(h)){let u=T.split(`
14
+ `),_=Math.min(u.length,2e3);for(let b=0;b<_;b++){let x=u[b];if(!x)continue;let C=x.trimStart();if(!(C.startsWith("//")||C.startsWith("/*")||C.startsWith("*")||C.startsWith("#"))&&!/process\.env/.test(x)&&!/\$\{[A-Z_]+/.test(x)){for(let $ of oe)if($.pattern.test(x)){let N=`secret::${$.id}::${h}::${b+1}`;if(k.has(N))break;k.add(N),p(h,"hardcoded_secret",$.severity,$.id,`Looks like a ${$.name} hardcoded near line ${b+1}. Move to an env var or secret manager.`);break}}}}if(g==="package.json"&&!h.includes("node_modules"))try{let u=Se(y,"utf8"),_=ce(u);for(let b of _){let x=`vuln::${b.name}::${h}`;k.has(x)||(k.add(x),p(h,"vulnerable_dependency",b.vuln.severity,b.name,`${b.name}@${b.version} is below ${b.vuln.below} (${b.vuln.cve} \xB7 ${b.vuln.desc}). Bump to ${b.vuln.below} or higher.`))}}catch{}if(v===".csv"||v===".tsv"){let u=v===".tsv"?" ":",",_=T.split(/\r?\n/);if(_.length>0){let b=X(_[0]??"",u),x=_.slice(1,51).map(C=>X(C??"",u));b.forEach((C,$)=>{let N=(C??"").trim();if(!N)return;let O=G(N)?.type??null;if(!O){let D=[];for(let Le of x){let V=Le[$];if(V&&V.trim().length>0&&D.push(V),D.length>=50)break}let L=P(D);L&&(O=L)}if(O){let D=Z[O]??"medium",L=`header::${h}::${N}::${O}`;if(k.has(L))return;k.add(L),p(h,"pii_header_match",D,`${O}:${N}`,`Column "${N}" looks like ${O} data. Wrap with envelope or move to a vault-backed reference.`)}})}}if(v===".json"&&!g.endsWith(".nodata-proof.json"))try{let u=JSON.parse(T),_=Array.isArray(u)?u[0]:u;if(_&&typeof _=="object")for(let b of Object.keys(_)){let x=G(b);if(x){let C=Z[x.type]??"medium",$=`jsonkey::${h}::${b}::${x.type}`;if(k.has($))continue;k.add($),p(h,"pii_header_match",C,`${x.type}:${b}`,`JSON key "${b}" looks like ${x.type} data.`)}}}catch{}/\.(txt|log|md|csv|tsv|json|jsonl|xml|html?|yaml|yml|sql)$/i.test(g)&&E(T,h,k)}function E(y,g,h){let v=y.split(`
15
+ `),S=Math.min(v.length,1e3),m=-1,w=-1,T=-1;for(let k=0;k<S;k++){let u=v[k]??"";if(T<0){let _=u.match(/\b\d{9}\b/);_&&F(_[0])&&(T=k+1)}if(m<0&&(U.lastIndex=0,U.test(u)&&(m=k+1)),w<0&&(B.lastIndex=0,B.test(u)&&(w=k+1)),T>0&&m>0&&w>0)break}T>0&&!h.has(`ilid::${g}`)&&(h.add(`ilid::${g}`),p(g,"israeli_id_value","critical",`near_line:${T}`,`Israeli ID with valid Luhn checksum detected near line ${T}. Strong signal that real customer IDs are in plaintext.`)),m>0&&!h.has(`ilphone::${g}`)&&(h.add(`ilphone::${g}`),p(g,"israeli_phone_value","high",`near_line:${m}`,`Israeli phone number detected near line ${m}.`)),w>0&&!h.has(`iliban::${g}`)&&(h.add(`iliban::${g}`),p(g,"israeli_iban_value","critical",`near_line:${w}`,`Israeli IBAN (IL prefix) detected near line ${w}.`))}function P(y){if(y.length===0)return null;let g=0,h=0,v=0;for(let m of y)typeof m=="string"&&(F(m)&&g++,U.lastIndex=0,U.test(m)&&h++,B.lastIndex=0,B.test(m)&&v++);let S=y.length;return g/S>=.3?"id":h/S>=.3?"phone":v/S>=.3?"financial":null}function R(y,g){if(g>ee){r.hit_depth_cap=!0;return}if(i>j){r.hit_file_cap=!0;return}let h;try{h=be(y)}catch{return}for(let v of h){if(i>j)return;let S=pt(y,v),m;try{m=Q(S)}catch{continue}if(!m.isSymbolicLink())if(m.isDirectory()){if(ht.has(v))continue;let w=null;try{w=be(S).length}catch{w=null}l(S,"folder",null,m.mtime??null,w),R(S,g+1)}else m.isFile()&&(l(S,"file",m.size,m.mtime??null,null),f(S,v))}}let z;try{z=Q(e.target_path).isDirectory()}catch{z=!1}return z?(e.log.info("folder-scanner: starting",{target_label:e.target_label,target_path:e.target_path,max_files:j,max_depth:ee}),R(e.target_path,0),{target_id:e.target_id,target_label:e.target_label,target_path:e.target_path,files_walked:i,files_inspected:o,duration_ms:Date.now()-t,findings:n,inventory:a,coverage:r}):{target_id:e.target_id,target_label:e.target_label,target_path:e.target_path,files_walked:0,files_inspected:0,duration_ms:Date.now()-t,findings:[],inventory:[],coverage:r,error:"target_path_not_found_or_not_directory"}}var re={info(){}};var ke={critical:4,high:3,medium:2,low:1,info:0};function ie(e){let t=[];if(t.push(`Scanned "${e.target_label||e.target_path}" on this machine \u2014 nothing left your computer.
16
16
  `),e.error)return t.push(`Could not scan: ${e.error}`),t.join(`
17
- `);t.push(`WALKED ${e.files_walked} files (${e.files_inspected} inspected) in ${e.duration_ms}ms`);let n=e.findings??[],i={};for(let s of n)i[s.severity]=(i[s.severity]??0)+1;let r=["critical","high","medium","low","info"].filter(s=>i[s]).map(s=>`${i[s]} ${s}`).join(" \xB7 ");if(t.push(`EXPOSURE ${n.length} findings${r?" \u2014 "+r:""}
17
+ `);t.push(`WALKED ${e.files_walked} files (${e.files_inspected} inspected) in ${e.duration_ms}ms`);let n=e.findings??[],a={};for(let s of n)a[s.severity]=(a[s.severity]??0)+1;let i=["critical","high","medium","low","info"].filter(s=>a[s]).map(s=>`${a[s]} ${s}`).join(" \xB7 ");if(t.push(`EXPOSURE ${n.length} findings${i?" \u2014 "+i:""}
18
18
  `),n.length===0)t.push(`No hardcoded secrets or unencrypted PII found in the files scanned. Clean.
19
- `);else{let s=[...n].sort((d,p)=>(be[p.severity]??0)-(be[d.severity]??0));t.push("TOP EXPOSURES:");for(let d of s.slice(0,8))t.push(` \u2717 [${d.severity.toUpperCase()}] ${d.finding_subkind||d.finding_kind} \u2014 ${d.relative_path}`);s.length>8&&t.push(` \u2026 and ${s.length-8} more.`),t.push("");let l=s.find(d=>d.remediation_hint);l?.remediation_hint&&t.push(`FIRST FIX ${l.remediation_hint}
20
- `)}let o=e.coverage,a=[];return o?.hit_file_cap&&a.push(`stopped at the ${o.max_files}-file cap`),o?.hit_depth_cap&&a.push(`folders deeper than ${o.max_depth} levels were not descended`),o?.files_too_large&&a.push(`${o.files_too_large} file(s) too large to read`),o?.documents_unreadable&&a.push(`${o.documents_unreadable} document(s) unreadable`),a.length&&t.push(`COVERAGE ${a.join("; ")}.`),t.join(`
21
- `)}import{homedir as yt,hostname as Ae}from"node:os";import{join as Ie,resolve as _t,basename as vt}from"node:path";import{existsSync as $e,readFileSync as bt,writeFileSync as kt,mkdirSync as wt,chmodSync as xt}from"node:fs";import{spawn as St}from"node:child_process";import{generateKeyPairSync as Et,createPrivateKey as Tt,sign as At,randomBytes as It}from"node:crypto";var re="1.0.0",Ce=Ie(yt(),".nodata"),U=Ie(Ce,"credentials.json");function $t(){try{if(!$e(U))return null;let e=JSON.parse(bt(U,"utf8"));if(!e)return null;let t=e.device&&typeof e.device.private_key_pkcs8_b64=="string",n=typeof e.api_key=="string"&&e.api_key;return t||n?e:null}catch{return null}}function ke(e){wt(Ce,{recursive:!0});let t=JSON.stringify({...e,saved_at:new Date().toISOString()},null,2);kt(U,t,{mode:384});try{xt(U,384)}catch{}}var Ct=`nodata-mcp v${re} \u2014 NoData MCP server
19
+ `);else{let s=[...n].sort((d,p)=>(ke[p.severity]??0)-(ke[d.severity]??0));t.push("TOP EXPOSURES:");for(let d of s.slice(0,8))t.push(` \u2717 [${d.severity.toUpperCase()}] ${d.finding_subkind||d.finding_kind} \u2014 ${d.relative_path}`);s.length>8&&t.push(` \u2026 and ${s.length-8} more.`),t.push("");let l=s.find(d=>d.remediation_hint);l?.remediation_hint&&t.push(`FIRST FIX ${l.remediation_hint}
20
+ `)}let o=e.coverage,r=[];return o?.hit_file_cap&&r.push(`stopped at the ${o.max_files}-file cap`),o?.hit_depth_cap&&r.push(`folders deeper than ${o.max_depth} levels were not descended`),o?.files_too_large&&r.push(`${o.files_too_large} file(s) too large to read`),o?.documents_unreadable&&r.push(`${o.documents_unreadable} document(s) unreadable`),r.length&&t.push(`COVERAGE ${r.join("; ")}.`),t.join(`
21
+ `)}import{homedir as bt,hostname as $e}from"node:os";import{join as Re,resolve as M,basename as St}from"node:path";import{existsSync as K,readFileSync as Pe,writeFileSync as kt,mkdirSync as wt,chmodSync as Et}from"node:fs";import{spawn as xt}from"node:child_process";import{generateKeyPairSync as At,createPrivateKey as Tt,sign as It,randomBytes as Ct}from"node:crypto";var ae="1.1.0",Ne=Re(bt(),".nodata"),H=Re(Ne,"credentials.json");function $t(){try{if(!K(H))return null;let e=JSON.parse(Pe(H,"utf8"));if(!e)return null;let t=e.device&&typeof e.device.private_key_pkcs8_b64=="string",n=typeof e.api_key=="string"&&e.api_key;return t||n?e:null}catch{return null}}function we(e){wt(Ne,{recursive:!0});let t=JSON.stringify({...e,saved_at:new Date().toISOString()},null,2);kt(H,t,{mode:384});try{Et(H,384)}catch{}}var Rt=`nodata-mcp v${ae} \u2014 NoData MCP server
22
22
 
23
23
  ADMIN mode: nodata-mcp --api-key sk_live_...
24
24
  AGENT mode: nodata-mcp --grant-token ndca-...
@@ -27,11 +27,12 @@ import{McpServer as gt}from"@modelcontextprotocol/sdk/server/mcp.js";import{Stdi
27
27
 
28
28
  Env vars: NODATA_API_KEY \xB7 NODATA_GRANT_TOKEN \xB7 NODATA_BASE_URL
29
29
  Get a free API key: https://www.nodatacapsule.com/developers/get-started
30
- Mint a grant token: POST /api/v1/governance/grant (or the nodata_grant tool).`;function Rt(e){let t="",n="",i="https://www.nodatacapsule.com";for(let a=0;a<e.length;a++)e[a]==="--api-key"&&e[a+1]?t=e[++a]:e[a]==="--grant-token"&&e[a+1]?n=e[++a]:e[a]==="--base-url"&&e[a+1]?i=e[++a]:e[a]==="--help"||e[a]==="-h"?(console.log(Ct),process.exit(0)):e[a]==="--version"||e[a]==="-v"?(console.log(re),process.exit(0)):e[a].startsWith("--")&&console.error(`nodata-mcp: unknown option '${e[a]}' (see --help)`);let r=t||n?"flag":"none";!t&&process.env.NODATA_API_KEY&&(t=process.env.NODATA_API_KEY,r="env"),!n&&process.env.NODATA_GRANT_TOKEN&&(n=process.env.NODATA_GRANT_TOKEN,r==="none"&&(r="env")),process.env.NODATA_BASE_URL&&(i=process.env.NODATA_BASE_URL);let o;if(!t&&!n){let a=$t();a&&(!process.env.NODATA_BASE_URL&&i==="https://www.nodatacapsule.com"&&a.base_url&&(i=a.base_url),a.device?(o=a.device,r="stored"):a.api_key&&(t=a.api_key,r="stored"))}return i=i.replace(/\/+$/,""),{apiKey:t,grantToken:n,baseUrl:i,credentialSource:r,device:o}}var we=3e4;async function Re(e,t){let n;try{n=await fetch(e,{...t,signal:AbortSignal.timeout(we)})}catch(r){return{ok:!1,status:0,body:{error:r instanceof Error&&r.name==="TimeoutError"?`Request timed out after ${we/1e3}s: ${e}`:`Network error reaching ${e}: ${r instanceof Error?r.message:String(r)}`}}}let i;try{i=await n.json()}catch{i={error:`Non-JSON response (HTTP ${n.status})`}}return{ok:n.ok,status:n.status,body:i}}async function Pe(e){if(typeof e=="string")return{token:e};try{return{token:await e()}}catch(t){return{error:{ok:!1,status:0,body:{error:`Could not obtain an access token: ${t instanceof Error?t.message:String(t)}`}}}}}async function A(e,t,n,i){let r=await Pe(t);return"error"in r?r.error:Re(`${e}${n}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r.token}`},body:JSON.stringify(i)})}async function Pt(e,t,n,i){let r=await Pe(t);if("error"in r)return r.error;let o=new URL(`${e}${n}`);for(let[a,s]of Object.entries(i))s!==void 0&&s!==""&&o.searchParams.set(a,s);return Re(o.toString(),{method:"GET",headers:{Authorization:`Bearer ${r.token}`}})}function I(e){let t=JSON.stringify(e.body,null,2);return e.ok?{content:[{type:"text",text:t}]}:{content:[{type:"text",text:`Error (HTTP ${e.status}):
31
- ${t}`}],isError:!0}}function Nt(e){try{let n=(e.startsWith("ndca-")?e.slice(5):e).split(".")[1];return n?JSON.parse(Buffer.from(n.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8")).handle??null:null}catch{return null}}var Ot=5*6e4;function Dt(){let{publicKey:e,privateKey:t}=Et("ed25519"),n=e.export({format:"der",type:"spki"}),i=t.export({format:"der",type:"pkcs8"});return{publicKeyHex:n.subarray(-32).toString("hex"),privateKeyPkcs8B64:i.toString("base64")}}function Lt(e,t){let n=Tt({key:Buffer.from(e,"base64"),format:"der",type:"pkcs8"});return At(null,Buffer.from(t,"utf8"),n).toString("base64")}function Ft(){let e=Ae().toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,20)||"device";return`${/^[a-z]/.test(e)?e:`d-${e}`}-${It(3).toString("hex")}`.slice(0,32)}async function jt(e,t,n,i,r){let o=await A(e,t,"/api/v1/attest/devices",{handle:n,label:i,ed25519_pubkey_hex:r});if(o.ok){let a=o.body.org_id??"";return a?{ok:!0,orgId:a}:{ok:!1,status:o.status,error:"enroll response missing org_id"}}return{ok:!1,status:o.status,error:o.body?.error??`HTTP ${o.status}`}}async function Mt(e,t){let n=await A(e,"","/api/v1/attest/challenge",{org_id:t.org_id,handle:t.handle});if(!n.ok)throw new Error(`challenge: ${n.body?.error??`HTTP ${n.status}`}`);let{challenge:i,nonce:r}=n.body;if(!i||!r)throw new Error("challenge response malformed");let o=Lt(t.private_key_pkcs8_b64,i),a=await A(e,"","/api/v1/attest",{org_id:t.org_id,handle:t.handle,nonce:r,signature:o});if(!a.ok)throw new Error(`attest: ${a.body?.error??`HTTP ${a.status}`}`);let s=a.body;if(!s.allowed||!s.access_token)throw new Error(`attestation rejected: ${s.reason??"denied"}`);return{token:s.access_token,expiresAtMs:s.expires_at?Date.parse(s.expires_at):Date.now()+15*6e4}}function Bt(e,t){let n=null;return async()=>((!n||Date.now()>=n.expiresAtMs-Ot)&&(n=await Mt(e,t)),n.token)}function xe(e,t,n){e.registerTool("nodata_register_table",{description:"Register one of your own tables as AI-agent-readable ('connect your data'). Declares which columns are encrypted (per-column key release) and which are plaintext metadata. After this, grants can target the table.",inputSchema:{table_name:c.string().describe("Your SQL table name"),encrypted_columns:c.array(c.string()).describe("Columns stored as <col>_ct/_iv/_tag triplets"),plaintext_columns:c.array(c.string()).optional().describe("Plaintext metadata/filter columns"),classification_column:c.string().optional().describe("Plaintext column carrying the classification value")}},async({table_name:i,encrypted_columns:r,plaintext_columns:o,classification_column:a})=>{let s=await A(t,n,"/api/v1/governance/tables",{table_name:i,encrypted_columns:r,plaintext_columns:o??[],classification_column:a});return I(s)}),e.registerTool("nodata_grant",{description:"Issue an AI agent a claim-scoped grant over your data \u2014 claims, not blanket access. Returns a grant_token (shown once) the agent presents to read. Denied columns never decrypt.",inputSchema:{agent:c.string().describe("Agent label (becomes its handle)"),table:c.string().optional().describe("Target table (default agent_demo_records)"),columns:c.array(c.string()).describe("Columns the agent may read"),where:c.record(c.string(),c.union([c.string(),c.number(),c.boolean()])).optional().describe("Equality filter (plaintext cols only)"),classifications:c.array(c.string()).optional().describe("Allowed classification values; empty = no gating"),ttl_seconds:c.number().optional().describe("Token lifetime (default 30d, max 1y)")}},async({agent:i,table:r,columns:o,where:a,classifications:s,ttl_seconds:l})=>{let d=await A(t,n,"/api/v1/governance/grant",{agent:i,table:r,columns:o,where:a,classifications:s,ttl_seconds:l});return I(d)}),e.registerTool("nodata_revoke",{description:"Revoke an agent's grant by handle (kills all its active credentials) or by a specific jti. Revocation is mathematics \u2014 the next read 401s and any cached data is governance-dead.",inputSchema:{handle:c.string().optional().describe("Agent handle to revoke"),jti:c.string().optional().describe("Specific credential jti to revoke"),reason:c.string().optional()}},async({handle:i,jti:r,reason:o})=>{let a=await A(t,n,"/api/v1/governance/revoke",{handle:i,jti:r,reason:o});return I(a)}),e.registerTool("nodata_encrypt",{description:"Encrypt a sensitive field value using Capsule Blind Relay. Returns ciphertext. NoData stores nothing \u2014 the key is embedded in the ciphertext for YOU to store.",inputSchema:{field:c.string(),value:c.string(),context:c.string().optional()}},async({field:i,value:r,context:o})=>{let a={field:i,value:r};return o&&(a.context=o),I(await A(t,n,"/api/v1/encrypt",a))}),e.registerTool("nodata_decrypt",{description:"Decrypt a Capsule ciphertext back to plaintext (aes256gcm:v1:iv:cipher:key format).",inputSchema:{ciphertext:c.string(),field:c.string().optional(),requester:c.string().optional()}},async({ciphertext:i,field:r,requester:o})=>{let a={ciphertext:i};return r&&(a.field=r),o&&(a.requester=o),I(await A(t,n,"/api/v1/decrypt",a))}),e.registerTool("nodata_deliver",{description:"Create a burn-after-read secure link for delivering sensitive content. Returns a view URL and OTP.",inputSchema:{content:c.string(),to:c.string().optional(),maxViews:c.number().optional(),ttl:c.string().optional(),burn:c.boolean().optional()}},async({content:i,to:r,maxViews:o,ttl:a,burn:s})=>{let l={content:i};return r&&(l.to=r),o!==void 0&&(l.maxViews=o),a&&(l.ttl=a),s!==void 0&&(l.burn=s),I(await A(t,n,"/api/v1/deliver",l))}),e.registerTool("nodata_evidence",{description:"Retrieve the audit trail of governance/encryption events. Metadata only \u2014 never content, keys, or plaintext.",inputSchema:{from:c.string().optional(),to:c.string().optional(),action:c.string().optional(),limit:c.number().optional()}},async({from:i,to:r,action:o,limit:a})=>{let s={from:i,to:r,action:o,limit:a!==void 0?String(a):void 0};return I(await Pt(t,n,"/api/v1/evidence",s))})}function Ut(e,t,n){let i=Nt(n);i||console.error("nodata-mcp: warning \u2014 could not derive an agent handle from the grant token (expected ndca-<jwt> with a 'handle' claim). nodata_decide/nodata_retrieve will work; nodata_read/nodata_use will fail until a valid grant token is provided."),e.registerTool("nodata_decide",{description:"Ask whether this agent MAY reach a resource, WITHOUT reading it. Returns a decision (allow / degrade / deny), the reachable column subset, an access-distance cost, and a signed proof \u2014 never any data. Use it to plan: check reachability before you retrieve. Hard-scoped to this server's grant token.",inputSchema:{table:c.string().describe("Table to check (must be in your grant)"),columns:c.array(c.string()).describe("Columns you want to reach"),where:c.record(c.string(),c.union([c.string(),c.number(),c.boolean()])).optional().describe("Equality filter (readable columns only)")}},async({table:r,columns:o,where:a})=>{let s=await A(t,n,"/api/access/decide",{resource:{table:r,columns:o,where:a},intent:"read"});return I(s)}),e.registerTool("nodata_retrieve",{description:"Retrieve prompt-ready context for a resource, filtered to exactly what this agent may see. Denied columns are never decrypted; the response includes an accounting of what was withheld (columns + estimated tokens saved) and a signed proof. Prefer this over a raw read when assembling an LLM prompt. Hard-scoped to this server's grant token.",inputSchema:{table:c.string().describe("Table to retrieve from (must be in your grant)"),columns:c.array(c.string()).describe("Columns to include in context"),where:c.record(c.string(),c.union([c.string(),c.number(),c.boolean()])).optional(),top_k:c.number().optional().describe("Max rows (default 50, max 200)")}},async({table:r,columns:o,where:a,top_k:s})=>{let l=await A(t,n,"/api/access/retrieve",{resource:{table:r,columns:o,where:a},top_k:s});return I(l)}),e.registerTool("nodata_read",{description:"Read governed data within this agent's grant. You may only request columns your grant allows \u2014 denied columns never decrypt server-side, and every read emits a cryptographic receipt. This tool is hard-scoped to the grant token this server was started with.",inputSchema:{table:c.string().describe("Table to read (must be in your grant)"),columns:c.array(c.string()).describe("Columns to read (must be in your grant)"),where:c.record(c.string(),c.union([c.string(),c.number(),c.boolean()])).optional(),limit:c.number().optional().describe("Max rows (default 50, max 200)")}},async({table:r,columns:o,where:a,limit:s})=>{if(!i)return{content:[{type:"text",text:"Error: could not derive agent handle from grant token."}],isError:!0};let l=await A(t,n,`/api/agents/${encodeURIComponent(i)}/read`,{table:r,columns:o,where:a,limit:s});return I(l)}),e.registerTool("nodata_use",{description:"Invoke a pre-registered capability (deploy, send, charge, query, \u2026) WITHOUT receiving the secret. The Capsule injects the sealed credential server-side and returns only the result. You may invoke only capabilities your grant allows; the secret never reaches you. Hard-scoped to the grant token this server was started with.",inputSchema:{capability:c.string().describe("Capability name to invoke (must be in your grant, e.g. 'deploy-prod')"),params:c.record(c.string(),c.unknown()).optional().describe("Allowlisted params; unknown keys are dropped server-side"),resource_key:c.string().optional().describe("Optional folder/resource the action targets (enforced against corpus scope)")}},async({capability:r,params:o,resource_key:a})=>{if(!i)return{content:[{type:"text",text:"Error: could not derive agent handle from grant token."}],isError:!0};let s=await A(t,n,`/api/agents/${encodeURIComponent(i)}/use`,{capability:r,params:o,resource_key:a});return I(s)}),e.registerTool("nodata_code_advise",{description:"Ask the blind code advisor to diagnose/fix a bug in a code capsule WITHOUT seeing its source. You receive guidance + a targeted diff + an exposure proof (source never left the boundary); the source is never returned to you. Requires the capsule owner to have granted this agent (scope.fabric_developers). Hard-scoped to this server's grant token.",inputSchema:{capsule_id:c.string().describe("The code capsule to advise on (you must be granted on it)"),question:c.string().optional().describe("What to fix / improve, in natural language"),note:c.string().optional().describe("Extra context for the advisor"),error:c.object({message:c.string().optional(),stack:c.string().optional(),kind:c.string().optional()}).optional().describe("An error the code produced, to guide the fix"),steer:c.string().optional().describe("A steer on a refine turn (e.g. 'no, round half-up')"),prior_diff:c.string().optional().describe("The previously proposed diff, to refine")}},async({capsule_id:r,question:o,note:a,error:s,steer:l,prior_diff:d})=>{if(!i)return{content:[{type:"text",text:"Error: could not derive agent handle from grant token."}],isError:!0};let p=await A(t,n,`/api/agents/${encodeURIComponent(i)}/code-capsule/${encodeURIComponent(r)}/advise`,{question:o,note:a,error:s,steer:l,prior_diff:d});return I(p)}),e.registerTool("nodata_code_patch",{description:"Submit a fix to a code capsule as a SIGNED, queued patch WITHOUT seeing its source. Provide a raw unified `diff`, or a whole-file `replace` ({path, content}) that is diffed server-side against the sealed source. The source is never persisted or returned; you get back a patch id, the diff hash, and a receipt. Requires a capsule grant (scope.fabric_developers). Hard-scoped to this server's grant token.",inputSchema:{capsule_id:c.string().describe("The code capsule to patch (you must be granted on it)"),summary:c.string().optional().describe("Short human-readable summary of the fix"),diff:c.string().optional().describe("A raw unified diff (advanced)"),replace:c.object({path:c.string(),content:c.string()}).optional().describe("Whole-file replacement; the diff is synthesized server-side against sealed source")}},async({capsule_id:r,summary:o,diff:a,replace:s})=>{if(!i)return{content:[{type:"text",text:"Error: could not derive agent handle from grant token."}],isError:!0};let l=await A(t,n,`/api/agents/${encodeURIComponent(i)}/code-capsule/${encodeURIComponent(r)}/patch`,{summary:o,diff:a,replace:s});return I(l)})}var Se=`WHAT NODATA DOES \u2014 in one line:
30
+ Mint a grant token: POST /api/v1/governance/grant (or the nodata_grant tool).`;function Pt(e){let t="",n="",a="https://www.nodatacapsule.com";for(let r=0;r<e.length;r++)e[r]==="--api-key"&&e[r+1]?t=e[++r]:e[r]==="--grant-token"&&e[r+1]?n=e[++r]:e[r]==="--base-url"&&e[r+1]?a=e[++r]:e[r]==="--help"||e[r]==="-h"?(console.log(Rt),process.exit(0)):e[r]==="--version"||e[r]==="-v"?(console.log(ae),process.exit(0)):e[r].startsWith("--")&&console.error(`nodata-mcp: unknown option '${e[r]}' (see --help)`);let i=t||n?"flag":"none";!t&&process.env.NODATA_API_KEY&&(t=process.env.NODATA_API_KEY,i="env"),!n&&process.env.NODATA_GRANT_TOKEN&&(n=process.env.NODATA_GRANT_TOKEN,i==="none"&&(i="env")),process.env.NODATA_BASE_URL&&(a=process.env.NODATA_BASE_URL);let o;if(!t&&!n){let r=$t();r&&(!process.env.NODATA_BASE_URL&&a==="https://www.nodatacapsule.com"&&r.base_url&&(a=r.base_url),r.device?(o=r.device,i="stored"):r.api_key&&(t=r.api_key,i="stored"))}return a=a.replace(/\/+$/,""),{apiKey:t,grantToken:n,baseUrl:a,credentialSource:i,device:o}}var Ee=3e4;async function Oe(e,t){let n;try{n=await fetch(e,{...t,signal:AbortSignal.timeout(Ee)})}catch(i){return{ok:!1,status:0,body:{error:i instanceof Error&&i.name==="TimeoutError"?`Request timed out after ${Ee/1e3}s: ${e}`:`Network error reaching ${e}: ${i instanceof Error?i.message:String(i)}`}}}let a;try{a=await n.json()}catch{a={error:`Non-JSON response (HTTP ${n.status})`}}return{ok:n.ok,status:n.status,body:a}}async function De(e){if(typeof e=="string")return{token:e};try{return{token:await e()}}catch(t){return{error:{ok:!1,status:0,body:{error:`Could not obtain an access token: ${t instanceof Error?t.message:String(t)}`}}}}}async function A(e,t,n,a){let i=await De(t);return"error"in i?i.error:Oe(`${e}${n}`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i.token}`},body:JSON.stringify(a)})}async function xe(e,t,n,a){let i=await De(t);if("error"in i)return i.error;let o=new URL(`${e}${n}`);for(let[r,s]of Object.entries(a))s!==void 0&&s!==""&&o.searchParams.set(r,s);return Oe(o.toString(),{method:"GET",headers:{Authorization:`Bearer ${i.token}`}})}function I(e){let t=JSON.stringify(e.body,null,2);return e.ok?{content:[{type:"text",text:t}]}:{content:[{type:"text",text:`Error (HTTP ${e.status}):
31
+ ${t}`}],isError:!0}}function Nt(e){let t=e?[M(e)]:[M(".env.local"),M(".env")];if(e&&!K(t[0]))return{error:`env file not found: ${t[0]}`};let n={},a=[];for(let o of t)if(K(o)){a.push(o);for(let r of Pe(o,"utf8").split(/\r?\n/)){let s=r.match(/^\s*(?:export\s+)?([A-Z0-9_]+)\s*=\s*(.*)\s*$/);!s||s[1]in n||(n[s[1]]=s[2].replace(/^(['"])(.*)\1$/,"$2").trim())}}let i=o=>{for(let r of o){let s=n[r]??process.env[r];if(s)return{name:r,value:s}}};return{url:i(["SUPABASE_URL","NEXT_PUBLIC_SUPABASE_URL","VITE_SUPABASE_URL","EXPO_PUBLIC_SUPABASE_URL"]),anon:i(["SUPABASE_ANON_KEY","NEXT_PUBLIC_SUPABASE_ANON_KEY","VITE_SUPABASE_ANON_KEY","EXPO_PUBLIC_SUPABASE_ANON_KEY","SUPABASE_PUBLISHABLE_KEY","NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY"]),jwt:i(["SUPABASE_JWT_SECRET","JWT_SECRET"]),files:a}}function Ot(e){try{let n=(e.startsWith("ndca-")?e.slice(5):e).split(".")[1];return n?JSON.parse(Buffer.from(n.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8")).handle??null:null}catch{return null}}var Dt=5*6e4;function Lt(){let{publicKey:e,privateKey:t}=At("ed25519"),n=e.export({format:"der",type:"spki"}),a=t.export({format:"der",type:"pkcs8"});return{publicKeyHex:n.subarray(-32).toString("hex"),privateKeyPkcs8B64:a.toString("base64")}}function Ut(e,t){let n=Tt({key:Buffer.from(e,"base64"),format:"der",type:"pkcs8"});return It(null,Buffer.from(t,"utf8"),n).toString("base64")}function Bt(){let e=$e().toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,20)||"device";return`${/^[a-z]/.test(e)?e:`d-${e}`}-${Ct(3).toString("hex")}`.slice(0,32)}async function jt(e,t,n,a,i){let o=await A(e,t,"/api/v1/attest/devices",{handle:n,label:a,ed25519_pubkey_hex:i});if(o.ok){let r=o.body.org_id??"";return r?{ok:!0,orgId:r}:{ok:!1,status:o.status,error:"enroll response missing org_id"}}return{ok:!1,status:o.status,error:o.body?.error??`HTTP ${o.status}`}}async function Ft(e,t){let n=await A(e,"","/api/v1/attest/challenge",{org_id:t.org_id,handle:t.handle});if(!n.ok)throw new Error(`challenge: ${n.body?.error??`HTTP ${n.status}`}`);let{challenge:a,nonce:i}=n.body;if(!a||!i)throw new Error("challenge response malformed");let o=Ut(t.private_key_pkcs8_b64,a),r=await A(e,"","/api/v1/attest",{org_id:t.org_id,handle:t.handle,nonce:i,signature:o});if(!r.ok)throw new Error(`attest: ${r.body?.error??`HTTP ${r.status}`}`);let s=r.body;if(!s.allowed||!s.access_token)throw new Error(`attestation rejected: ${s.reason??"denied"}`);return{token:s.access_token,expiresAtMs:s.expires_at?Date.parse(s.expires_at):Date.now()+15*6e4}}function Mt(e,t){let n=null;return async()=>((!n||Date.now()>=n.expiresAtMs-Dt)&&(n=await Ft(e,t)),n.token)}function Ae(e,t,n){e.registerTool("nodata_register_table",{description:"Register one of your own tables as AI-agent-readable. This writes POLICY only: it declares which columns are sensitive (released per-column only if their ciphertext exists) and which are plaintext metadata. It does NOT encrypt or change anything in the user's database \u2014 never tell the user their data is now encrypted. Relay the response's `message` and `next_steps` verbatim. After this, grants can target the table.",inputSchema:{table_name:c.string().describe("Your SQL table name"),encrypted_columns:c.array(c.string()).describe("Columns stored as <col>_ct/_iv/_tag triplets"),plaintext_columns:c.array(c.string()).optional().describe("Plaintext metadata/filter columns"),classification_column:c.string().optional().describe("Plaintext column carrying the classification value")}},async({table_name:a,encrypted_columns:i,plaintext_columns:o,classification_column:r})=>{let s=await A(t,n,"/api/v1/governance/tables",{table_name:a,encrypted_columns:i,plaintext_columns:o??[],classification_column:r});return I(s)}),e.registerTool("nodata_connect_source",{description:"Connect the user's own Supabase project as the data source for their registered tables, so granted agents can read metadata through NoData. Reads the project URL, anon key and JWT secret from the user's LOCAL env file (e.g. .env.local) and sends them directly to NoData \u2014 never ask the user to paste a key or secret into the chat, and never print them. Only allowed_tables are readable (deny-by-default).",inputSchema:{allowed_tables:c.array(c.string()).describe("Tables agents may read through this source"),env_file:c.string().optional().describe("Path to the env file holding the Supabase keys (default: .env.local, then .env in the working directory)"),source_url:c.string().optional().describe("Override the project URL (https://<ref>.supabase.co) if the env file lacks it")}},async({allowed_tables:a,env_file:i,source_url:o})=>{let r=Nt(i);if("error"in r)return{content:[{type:"text",text:`Error: ${r.error}`}],isError:!0};let s=o??r.url?.value,l=[s?null:"project URL (SUPABASE_URL / NEXT_PUBLIC_SUPABASE_URL)",r.anon?null:"anon key (SUPABASE_ANON_KEY / NEXT_PUBLIC_SUPABASE_ANON_KEY)",r.jwt?null:"JWT secret (SUPABASE_JWT_SECRET \xB7 Supabase \u2192 Settings \u2192 API \u2192 JWT secret)"].filter(Boolean);if(l.length>0)return{content:[{type:"text",text:`Not sent. Missing in ${r.files.join(", ")||"the environment"}: ${l.join("; ")}.
32
+ Ask the user to add them to that file themselves (not in this chat), then call this tool again.`}],isError:!0};let d=await A(t,n,"/api/connector/secrets",{source_url:s,allowed_tables:a,anon_key:r.anon.value,jwt_secret:r.jwt.value}),p={read_from:r.files,variables_used:[r.url?.name,r.anon.name,r.jwt.name].filter(Boolean)};return I({...d,body:{...d.body,...p}})}),e.registerTool("nodata_grant",{description:"Issue ONE AI agent a claim-scoped grant over your data \u2014 claims, not blanket access. Returns a grant_token (shown once) the agent presents to read. Denied columns never decrypt. For one agent over several tables pass `tables` in a single call \u2014 do NOT call this once per table, that creates a separate agent each time.",inputSchema:{agent:c.string().describe("Agent label (becomes its handle)"),tables:c.array(c.object({table:c.string(),columns:c.array(c.string()).describe("Columns the agent may read in this table"),where:c.record(c.string(),c.union([c.string(),c.number(),c.boolean()])).optional()})).optional().describe("Several tables for the same agent. When set, table/columns/where are ignored."),table:c.string().optional().describe("Single target table (default agent_demo_records)"),columns:c.array(c.string()).optional().describe("Columns the agent may read (single-table form)"),where:c.record(c.string(),c.union([c.string(),c.number(),c.boolean()])).optional().describe("Equality filter (plaintext cols only)"),classifications:c.array(c.string()).optional().describe("Allowed classification values; empty = no gating"),ttl_seconds:c.number().optional().describe("Token lifetime (default 30d, max 1y)")}},async({agent:a,tables:i,table:o,columns:r,where:s,classifications:l,ttl_seconds:d})=>{let p=await A(t,n,"/api/v1/governance/grant",{agent:a,...i?{tables:i}:{table:o,columns:r,where:s},classifications:l,ttl_seconds:d});return I(p)}),e.registerTool("nodata_revoke",{description:"Revoke an agent's grant by handle (kills all its active credentials) or by a specific jti. Revocation is mathematics \u2014 the next read 401s and any cached data is governance-dead.",inputSchema:{handle:c.string().optional().describe("Agent handle to revoke"),jti:c.string().optional().describe("Specific credential jti to revoke"),reason:c.string().optional()}},async({handle:a,jti:i,reason:o})=>{let r=await A(t,n,"/api/v1/governance/revoke",{handle:a,jti:i,reason:o});return I(r)}),e.registerTool("nodata_encrypt",{description:"Encrypt a sensitive field value using Capsule Blind Relay. Returns ciphertext. NoData stores nothing \u2014 the key is embedded in the ciphertext for YOU to store.",inputSchema:{field:c.string(),value:c.string(),context:c.string().optional()}},async({field:a,value:i,context:o})=>{let r={field:a,value:i};return o&&(r.context=o),I(await A(t,n,"/api/v1/encrypt",r))}),e.registerTool("nodata_decrypt",{description:"Decrypt a Capsule ciphertext back to plaintext (aes256gcm:v1:iv:cipher:key format).",inputSchema:{ciphertext:c.string(),field:c.string().optional(),requester:c.string().optional()}},async({ciphertext:a,field:i,requester:o})=>{let r={ciphertext:a};return i&&(r.field=i),o&&(r.requester=o),I(await A(t,n,"/api/v1/decrypt",r))}),e.registerTool("nodata_deliver",{description:"Create a burn-after-read secure link for delivering sensitive content. Returns a view URL and OTP.",inputSchema:{content:c.string(),to:c.string().optional(),maxViews:c.number().optional(),ttl:c.string().optional(),burn:c.boolean().optional()}},async({content:a,to:i,maxViews:o,ttl:r,burn:s})=>{let l={content:a};return i&&(l.to=i),o!==void 0&&(l.maxViews=o),r&&(l.ttl=r),s!==void 0&&(l.burn=s),I(await A(t,n,"/api/v1/deliver",l))}),e.registerTool("nodata_evidence",{description:"Field-level encrypt/decrypt/deliver events (field_access_log). Requires the Engine scope on the key. For the signed decision-receipt chain \u2014 grants, agent reads, denials \u2014 use nodata_proofs instead, which every org key can call. Metadata only.",inputSchema:{from:c.string().optional(),to:c.string().optional(),action:c.string().optional(),limit:c.number().optional()}},async({from:a,to:i,action:o,limit:r})=>{let s={from:a,to:i,action:o,limit:r!==void 0?String(r):void 0};return I(await xe(t,n,"/api/v1/evidence",s))}),e.registerTool("nodata_proofs",{description:"List your organization's signed proofs: every grant, agent read and denial, newest first. Content-free (labels, hashes, timestamps). Each proof can be verified by anyone, without an account, by POSTing its receipt_id to /api/access/verify. The response's `plan` says which proof types the user's plan includes and which need an upgrade (with the plan name) \u2014 relay it as is.",inputSchema:{receipt_id:c.string().optional().describe("Fetch one proof by id"),surface:c.string().optional().describe("Filter, e.g. 'governance.grant', 'agent.read'"),decision:c.string().optional().describe("Filter: 'grant' | 'allow' | 'deny' | 'revoke'"),from:c.string().optional().describe("ISO timestamp"),to:c.string().optional().describe("ISO timestamp"),limit:c.number().optional().describe("1..1000, default 50")}},async({receipt_id:a,surface:i,decision:o,from:r,to:s,limit:l})=>I(await xe(t,n,"/api/v1/proofs",{receipt_id:a,surface:i,decision:o,from:r,to:s,limit:l!==void 0?String(l):void 0})))}function Kt(e,t,n){let a=Ot(n);a||console.error("nodata-mcp: warning \u2014 could not derive an agent handle from the grant token (expected ndca-<jwt> with a 'handle' claim). nodata_decide/nodata_retrieve will work; nodata_read/nodata_use will fail until a valid grant token is provided."),e.registerTool("nodata_decide",{description:"Ask whether this agent MAY reach a resource, WITHOUT reading it. Returns a decision (allow / degrade / deny), the reachable column subset, an access-distance cost, and a signed proof \u2014 never any data. Use it to plan: check reachability before you retrieve. Hard-scoped to this server's grant token.",inputSchema:{table:c.string().describe("Table to check (must be in your grant)"),columns:c.array(c.string()).describe("Columns you want to reach"),where:c.record(c.string(),c.union([c.string(),c.number(),c.boolean()])).optional().describe("Equality filter (readable columns only)")}},async({table:i,columns:o,where:r})=>{let s=await A(t,n,"/api/access/decide",{resource:{table:i,columns:o,where:r},intent:"read"});return I(s)}),e.registerTool("nodata_retrieve",{description:"Retrieve prompt-ready context for a resource, filtered to exactly what this agent may see. Denied columns are never decrypted; the response includes an accounting of what was withheld (columns + estimated tokens saved) and a signed proof. Prefer this over a raw read when assembling an LLM prompt. Hard-scoped to this server's grant token.",inputSchema:{table:c.string().describe("Table to retrieve from (must be in your grant)"),columns:c.array(c.string()).describe("Columns to include in context"),where:c.record(c.string(),c.union([c.string(),c.number(),c.boolean()])).optional(),top_k:c.number().optional().describe("Max rows (default 50, max 200)")}},async({table:i,columns:o,where:r,top_k:s})=>{let l=await A(t,n,"/api/access/retrieve",{resource:{table:i,columns:o,where:r},top_k:s});return I(l)}),e.registerTool("nodata_read",{description:"Read governed data within this agent's grant. You may only request columns your grant allows \u2014 denied columns never decrypt server-side, and every read emits a cryptographic receipt. This tool is hard-scoped to the grant token this server was started with.",inputSchema:{table:c.string().describe("Table to read (must be in your grant)"),columns:c.array(c.string()).describe("Columns to read (must be in your grant)"),where:c.record(c.string(),c.union([c.string(),c.number(),c.boolean()])).optional(),limit:c.number().optional().describe("Max rows (default 50, max 200)")}},async({table:i,columns:o,where:r,limit:s})=>{if(!a)return{content:[{type:"text",text:"Error: could not derive agent handle from grant token."}],isError:!0};let l=await A(t,n,`/api/agents/${encodeURIComponent(a)}/read`,{table:i,columns:o,where:r,limit:s});return I(l)}),e.registerTool("nodata_use",{description:"Invoke a pre-registered capability (deploy, send, charge, query, \u2026) WITHOUT receiving the secret. The Capsule injects the sealed credential server-side and returns only the result. You may invoke only capabilities your grant allows; the secret never reaches you. Hard-scoped to the grant token this server was started with.",inputSchema:{capability:c.string().describe("Capability name to invoke (must be in your grant, e.g. 'deploy-prod')"),params:c.record(c.string(),c.unknown()).optional().describe("Allowlisted params; unknown keys are dropped server-side"),resource_key:c.string().optional().describe("Optional folder/resource the action targets (enforced against corpus scope)")}},async({capability:i,params:o,resource_key:r})=>{if(!a)return{content:[{type:"text",text:"Error: could not derive agent handle from grant token."}],isError:!0};let s=await A(t,n,`/api/agents/${encodeURIComponent(a)}/use`,{capability:i,params:o,resource_key:r});return I(s)}),e.registerTool("nodata_code_advise",{description:"Ask the blind code advisor to diagnose/fix a bug in a code capsule WITHOUT seeing its source. You receive guidance + a targeted diff + an exposure proof (source never left the boundary); the source is never returned to you. Requires the capsule owner to have granted this agent (scope.fabric_developers). Hard-scoped to this server's grant token.",inputSchema:{capsule_id:c.string().describe("The code capsule to advise on (you must be granted on it)"),question:c.string().optional().describe("What to fix / improve, in natural language"),note:c.string().optional().describe("Extra context for the advisor"),error:c.object({message:c.string().optional(),stack:c.string().optional(),kind:c.string().optional()}).optional().describe("An error the code produced, to guide the fix"),steer:c.string().optional().describe("A steer on a refine turn (e.g. 'no, round half-up')"),prior_diff:c.string().optional().describe("The previously proposed diff, to refine")}},async({capsule_id:i,question:o,note:r,error:s,steer:l,prior_diff:d})=>{if(!a)return{content:[{type:"text",text:"Error: could not derive agent handle from grant token."}],isError:!0};let p=await A(t,n,`/api/agents/${encodeURIComponent(a)}/code-capsule/${encodeURIComponent(i)}/advise`,{question:o,note:r,error:s,steer:l,prior_diff:d});return I(p)}),e.registerTool("nodata_code_patch",{description:"Submit a fix to a code capsule as a SIGNED, queued patch WITHOUT seeing its source. Provide a raw unified `diff`, or a whole-file `replace` ({path, content}) that is diffed server-side against the sealed source. The source is never persisted or returned; you get back a patch id, the diff hash, and a receipt. Requires a capsule grant (scope.fabric_developers). Hard-scoped to this server's grant token.",inputSchema:{capsule_id:c.string().describe("The code capsule to patch (you must be granted on it)"),summary:c.string().optional().describe("Short human-readable summary of the fix"),diff:c.string().optional().describe("A raw unified diff (advanced)"),replace:c.object({path:c.string(),content:c.string()}).optional().describe("Whole-file replacement; the diff is synthesized server-side against sealed source")}},async({capsule_id:i,summary:o,diff:r,replace:s})=>{if(!a)return{content:[{type:"text",text:"Error: could not derive agent handle from grant token."}],isError:!0};let l=await A(t,n,`/api/agents/${encodeURIComponent(a)}/code-capsule/${encodeURIComponent(i)}/patch`,{summary:o,diff:r,replace:s});return I(l)})}var Te=`WHAT NODATA DOES \u2014 in one line:
32
33
  Give any agent real power over your data, without the power to leak it.
33
34
  (Possession is not permission: it can hold the key and still not read what you didn't allow.)
34
- `,zt=`TURN THE DATA TOOLS ON \u2014 3 steps, about a minute, once:
35
+ `,Ht=`TURN THE DATA TOOLS ON \u2014 3 steps, about a minute, once:
35
36
 
36
37
  1. GET A KEY call nodata_connect (no arguments)
37
38
  -> opens the signup in your browser: email + company (~30s), a 12-word
@@ -44,7 +45,7 @@ ${t}`}],isError:!0}}function Nt(e){try{let n=(e.startsWith("ndca-")?e.slice(5):e
44
45
  3. ACTIVATE type /mcp (or restart Claude Code) so the tools load.
45
46
 
46
47
  COMING BACK LATER? Nothing to redo \u2014 it starts ready. If the tools look missing, just /mcp.
47
- `,Kt=`NOW \u2014 TURN YOUR DATA INTO AN AGENT THAT CAN'T LEAK, in 3 asks (just say them in plain words):
48
+ `,zt=`NOW \u2014 TURN YOUR DATA INTO AN AGENT THAT CAN'T LEAK, in 3 asks (just say them in plain words):
48
49
 
49
50
  1. SEE what's sensitive run nodata_scan_folder \u2014 scans a local folder right here, nothing
50
51
  uploads (or open https://www.nodatacapsule.com/ai-folder in a browser).
@@ -56,28 +57,28 @@ COMING BACK LATER? Nothing to redo \u2014 it starts ready. If the tools look mis
56
57
  "what did it see?" -> nodata_evidence \xB7 "cut it off" -> nodata_revoke
57
58
 
58
59
  Also: a secret even NoData can't read -> nodata_encrypt, then hand it over once -> nodata_deliver.
59
- `,Ht=`WHAT YOU CAN DO \u2014 scoped to your grant; you reach only what it allows, the rest never decrypts:
60
+ `,Vt=`WHAT YOU CAN DO \u2014 scoped to your grant; you reach only what it allows, the rest never decrypts:
60
61
 
61
62
  1. PLAN nodata_decide "may I reach X?" -> allow / partial / deny + a signed proof. No data moves.
62
63
  2. PULL nodata_retrieve prompt-ready context, allowed fields only, with a receipt.
63
64
  (nodata_read for raw governed rows.)
64
65
  3. ACT nodata_use run a capability with a secret injected server-side \u2014 you never see it.
65
- `,Ee=`STUCK? There's always a next step:
66
+ `,Ie=`STUCK? There's always a next step:
66
67
  - Don't see the nodata_* tools? They load when the client starts \u2014 type /mcp or restart.
67
68
  (Expected, not an error.)
68
69
  - No MCP at all? The same governance runs from one curl, or the guided page:
69
70
  https://www.nodatacapsule.com/developers/get-started
70
- `;function Te(e){return e==="setup"?`You're connected to NoData \u2014 but there's NO key yet, so the data tools are still off.
71
+ `;function Ce(e){return e==="setup"?`You're connected to NoData \u2014 but there's NO key yet, so the data tools are still off.
71
72
 
72
73
  `:e==="agent"?`You're connected to NoData, scoped to one grant token.
73
74
 
74
75
  `:`You're connected to NoData (admin mode). You don't memorize commands \u2014 just ask in plain words.
75
76
 
76
- `}function Vt(e){if(e==="setup")return Te(e)+Se+`
77
- `+zt+`
78
- `+Ee;let t=e==="agent"?Ht:Kt;return Te(e)+Se+`
77
+ `}function Wt(e){if(e==="setup")return Ce(e)+Te+`
78
+ `+Ht+`
79
+ `+Ie;let t=e==="agent"?Vt:zt;return Ce(e)+Te+`
79
80
  `+t+`
80
- `+Ee}function Wt(e,t){e.registerTool("nodata_get_started",{description:"Call this FIRST (no arguments). NoData in one line: what you can achieve \u2014 an agent that cannot leak, a look at what is already exposed, a secret we cannot read \u2014 with the single next step for each, and how to recover if the tools or MCP seem stuck. If the user asks what NoData can do, how to start, or why the tools are missing, call this and relay it.",inputSchema:{}},async()=>({content:[{type:"text",text:Vt(t)}]}))}var Gt="/capsule-api/register";function Zt(e){let t=process.platform==="win32",n=process.platform==="darwin"?"open":t?"cmd":"xdg-open",i=t?["/c","start","",e]:[e];try{let r=St(n,i,{stdio:"ignore",detached:!0});return r.on("error",()=>{}),r.unref(),!0}catch{return!1}}function Yt(e,t){e.registerTool("nodata_connect",{description:"Switch NoData's data tools on. Step 1: call with NO arguments to open the free signup in a browser (email + company, ~30s \u2014 also where your 12-word recovery phrase + email verification happen); it shows an ORG KEY once. Step 2: call this tool again with that key as `api_key` and it is saved on this machine, so every later start comes up ready with nothing to retype. Step 3: type /mcp (or restart) so the client loads the tools.",inputSchema:{api_key:c.string().optional().describe("The org key shown once at signup (ndp_\u2026 or sk_\u2026). Omit on the first call to open signup.")}},async({api_key:n})=>{let i=(n??"").trim();if(!i){let p=t+Gt;return{content:[{type:"text",text:(Zt(p)?`STEP 1 of 3 \u2014 opened the NoData signup in your browser:
81
+ `+Ie}function Yt(e,t){e.registerTool("nodata_get_started",{description:"Call this FIRST (no arguments). NoData in one line: what you can achieve \u2014 an agent that cannot leak, a look at what is already exposed, a secret we cannot read \u2014 with the single next step for each, and how to recover if the tools or MCP seem stuck. If the user asks what NoData can do, how to start, or why the tools are missing, call this and relay it.",inputSchema:{}},async()=>({content:[{type:"text",text:Wt(t)}]}))}var Gt="/capsule-api/register";function Zt(e){let t=process.platform==="win32",n=process.platform==="darwin"?"open":t?"cmd":"xdg-open",a=t?["/c","start","",e]:[e];try{let i=xt(n,a,{stdio:"ignore",detached:!0});return i.on("error",()=>{}),i.unref(),!0}catch{return!1}}function Xt(e,t){e.registerTool("nodata_connect",{description:"Switch NoData's data tools on. Step 1: call with NO arguments to open the free signup in a browser (email + company, ~30s \u2014 also where your 12-word recovery phrase + email verification happen); it shows an ORG KEY once. Step 2: call this tool again with that key as `api_key` and it is saved on this machine, so every later start comes up ready with nothing to retype. Step 3: type /mcp (or restart) so the client loads the tools.",inputSchema:{api_key:c.string().optional().describe("The org key shown once at signup (ndp_\u2026 or sk_\u2026). Omit on the first call to open signup.")}},async({api_key:n})=>{let a=(n??"").trim();if(!a){let p=t+Gt;return{content:[{type:"text",text:(Zt(p)?`STEP 1 of 3 \u2014 opened the NoData signup in your browser:
81
82
  `:`STEP 1 of 3 \u2014 open the NoData signup in your browser:
82
83
  `)+p+`
83
84
 
@@ -88,21 +89,21 @@ There, about a minute:
88
89
 
89
90
  STEP 2 \u2014 come back and call this tool again with the key:
90
91
  nodata_connect api_key: "<the key you copied>"
91
- and I'll save it so you never paste it again. (Then STEP 3: type /mcp to activate.)`}]}}if(!/^(ndp_|sk_)/.test(i))return{content:[{type:"text",text:`That doesn't look like a NoData org key (it should start with ndp_ or sk_).
92
- Copy the key shown once on the signup page and try again: nodata_connect api_key: "ndp_\u2026".`}]};let{publicKeyHex:r,privateKeyPkcs8B64:o}=Dt(),a=Ft(),s=Ae()||"this device",l=await jt(t,i,a,s,r);if(l.ok){try{ke({device:{org_id:l.orgId,handle:a,label:s,private_key_pkcs8_b64:o,public_key_hex:r},base_url:t})}catch(p){return{content:[{type:"text",text:"Enrolled the device, but couldn't save it locally: "+(p instanceof Error?p.message:String(p))}]}}return{content:[{type:"text",text:`STEP 2 done \u2014 this device is enrolled as "${s}" (${a}), and only its PRIVATE key is
92
+ and I'll save it so you never paste it again. (Then STEP 3: type /mcp to activate.)`}]}}if(!/^(ndp_|sk_)/.test(a))return{content:[{type:"text",text:`That doesn't look like a NoData org key (it should start with ndp_ or sk_).
93
+ Copy the key shown once on the signup page and try again: nodata_connect api_key: "ndp_\u2026".`}]};let{publicKeyHex:i,privateKeyPkcs8B64:o}=Lt(),r=Bt(),s=$e()||"this device",l=await jt(t,a,r,s,i);if(l.ok){try{we({device:{org_id:l.orgId,handle:r,label:s,private_key_pkcs8_b64:o,public_key_hex:i},base_url:t})}catch(p){return{content:[{type:"text",text:"Enrolled the device, but couldn't save it locally: "+(p instanceof Error?p.message:String(p))}]}}return{content:[{type:"text",text:`STEP 2 done \u2014 this device is enrolled as "${s}" (${r}), and only its PRIVATE key is
93
94
  saved at ~/.nodata/credentials.json (readable only by you). The org key you pasted was NOT
94
95
  stored: every start now proves possession by signing a fresh challenge and gets a 15-minute token.
95
96
 
96
- STEP 3 \u2014 type /mcp (or restart Claude Code) and the admin tools appear. Next time: nothing to do.`}]}}let d=l.status===404||l.status===0;try{ke({api_key:i,key_prefix:i.slice(0,12),base_url:t})}catch(p){return{content:[{type:"text",text:"Couldn't save locally: "+(p instanceof Error?p.message:String(p))+`
97
+ STEP 3 \u2014 type /mcp (or restart Claude Code) and the admin tools appear. Next time: nothing to do.`}]}}let d=l.status===404||l.status===0;try{we({api_key:a,key_prefix:a.slice(0,12),base_url:t})}catch(p){return{content:[{type:"text",text:"Couldn't save locally: "+(p instanceof Error?p.message:String(p))+`
97
98
  As a fallback, pass it on launch: claude mcp add nodata --env NODATA_API_KEY=<your key> -- npx @nodatachat/mcp`}]}}return{content:[{type:"text",text:(d?`STEP 2 done \u2014 saved your key on this machine (device attestation isn't available on this
98
99
  server yet, so this is the key-at-rest path). `:`STEP 2 done \u2014 saved your key on this machine (device enrollment didn't succeed: ${l.error}). `)+`Every future start loads it automatically \u2014 no re-register, no re-paste.
99
100
 
100
- STEP 3 \u2014 type /mcp (or restart Claude Code) and the admin tools appear. Next time: nothing to do.`}]}})}function Xt(e){let t=ne(e);return e.error?t:t+`
101
+ STEP 3 \u2014 type /mcp (or restart Claude Code) and the admin tools appear. Next time: nothing to do.`}]}})}function Jt(e){let t=ie(e);return e.error?t:t+`
101
102
 
102
103
  GOVERN what it surfaced \u2014 same journey, inside this command:
103
104
  \xB7 CHOOSE what an agent may read: nodata_register_table then nodata_grant (denied columns never decrypt)
104
105
  \xB7 PROVE every access afterwards: nodata_evidence
105
106
 
106
- Want a standalone deep scan any time? npx @nodatachat/nodata scan <folder> (this same engine).`}function Jt(e){e.registerTool("nodata_scan_folder",{description:"SEE what's sensitive before you govern it. Scans a LOCAL folder for hardcoded secrets and unencrypted PII (checksum-validated \u2014 Israeli ID / Luhn credit-card / IBAN) with NoData's deep content scanner, reading PDFs and Office files too, ENTIRELY on this machine \u2014 nothing uploads, no credential needed. Returns the findings by file and severity plus the first fix, then points at nodata_register_table + nodata_grant to govern what it found. If the user asks to scan / audit / check a folder or repo for sensitive data, secrets, or exposure, call this.",inputSchema:{path:c.string().optional().describe("Folder to scan (absolute or relative). Default: the directory the server runs in.")}},async({path:t})=>{let n=(t??".").trim()||".",i=_t(n);if(!$e(i))return{content:[{type:"text",text:`No folder found at: ${i}
107
- Pass an existing path, e.g. nodata_scan_folder path: "."`}]};try{let r=await ee({target_id:"mcp",target_label:vt(i),target_path:i,log:te});return{content:[{type:"text",text:Xt(r)}]}}catch(r){return{content:[{type:"text",text:`Scan failed: ${r instanceof Error?r.message:String(r)}`}],isError:!0}}})}async function qt(){let{apiKey:e,grantToken:t,baseUrl:n,credentialSource:i,device:r}=Rt(process.argv.slice(2)),o=t?"agent":e||r?"admin":"setup",a=new gt({name:o==="agent"?"NoData (agent-scoped)":o==="setup"?"NoData (setup)":"NoData",version:re});r?xe(a,n,Bt(n,r)):e&&xe(a,n,e),t&&Ut(a,n,t),Wt(a,o),o!=="agent"&&Jt(a),o==="setup"?(Yt(a,n),console.error(`nodata-mcp: no credential given \u2014 running in setup mode (data tools disabled).
108
- Call nodata_connect (opens signup, enrolls this device), or pass --api-key / --grant-token. See --help.`)):r?console.error(`nodata-mcp: loaded device identity "${r.label}" (${r.handle}) \u2014 attest mode; tokens are minted per session.`):i==="stored"&&console.error("nodata-mcp: loaded saved credential from ~/.nodata/credentials.json (admin mode).");let s=new mt;await a.connect(s)}qt().catch(e=>{console.error("Fatal error:",e),process.exit(1)});
107
+ Want a standalone deep scan any time? npx @nodatachat/nodata scan <folder> (this same engine).`}function qt(e){e.registerTool("nodata_scan_folder",{description:"SEE what's sensitive before you govern it. Scans a LOCAL folder for hardcoded secrets and unencrypted PII (checksum-validated \u2014 Israeli ID / Luhn credit-card / IBAN) with NoData's deep content scanner, reading PDFs and Office files too, ENTIRELY on this machine \u2014 nothing uploads, no credential needed. Returns the findings by file and severity plus the first fix, then points at nodata_register_table + nodata_grant to govern what it found. If the user asks to scan / audit / check a folder or repo for sensitive data, secrets, or exposure, call this.",inputSchema:{path:c.string().optional().describe("Folder to scan (absolute or relative). Default: the directory the server runs in.")}},async({path:t})=>{let n=(t??".").trim()||".",a=M(n);if(!K(a))return{content:[{type:"text",text:`No folder found at: ${a}
108
+ Pass an existing path, e.g. nodata_scan_folder path: "."`}]};try{let i=await ne({target_id:"mcp",target_label:St(a),target_path:a,log:re});return{content:[{type:"text",text:Jt(i)}]}}catch(i){return{content:[{type:"text",text:`Scan failed: ${i instanceof Error?i.message:String(i)}`}],isError:!0}}})}async function Qt(){let{apiKey:e,grantToken:t,baseUrl:n,credentialSource:a,device:i}=Pt(process.argv.slice(2)),o=t?"agent":e||i?"admin":"setup",r=new _t({name:o==="agent"?"NoData (agent-scoped)":o==="setup"?"NoData (setup)":"NoData",version:ae});i?Ae(r,n,Mt(n,i)):e&&Ae(r,n,e),t&&Kt(r,n,t),Yt(r,o),o!=="agent"&&qt(r),o==="setup"?(Xt(r,n),console.error(`nodata-mcp: no credential given \u2014 running in setup mode (data tools disabled).
109
+ Call nodata_connect (opens signup, enrolls this device), or pass --api-key / --grant-token. See --help.`)):i?console.error(`nodata-mcp: loaded device identity "${i.label}" (${i.handle}) \u2014 attest mode; tokens are minted per session.`):a==="stored"&&console.error("nodata-mcp: loaded saved credential from ~/.nodata/credentials.json (admin mode).");let s=new vt;await r.connect(s)}Qt().catch(e=>{console.error("Fatal error:",e),process.exit(1)});
package/package.json CHANGED
@@ -1,49 +1,49 @@
1
- {
2
- "name": "@nodatachat/mcp",
3
- "version": "1.0.0",
4
- "description": "NoData MCP server: connect any MCP client (Claude Code, Codex, Cursor, VS Code) to the NoData Information Access Processor. Governed access for AI agents, with a verifiable proof for every decision.",
5
- "bin": {
6
- "nodata-mcp": "dist/index.js"
7
- },
8
- "type": "module",
9
- "scripts": {
10
- "build": "tsc --noEmit && node build.mjs",
11
- "dev": "tsx src/index.ts",
12
- "prepublishOnly": "npm run build"
13
- },
14
- "dependencies": {
15
- "@modelcontextprotocol/sdk": "^1.29.0",
16
- "zod": "^4.0.0"
17
- },
18
- "devDependencies": {
19
- "@nodatachat/folder-scan": "^1.0.0",
20
- "@types/node": "^22.0.0",
21
- "esbuild": "^0.27.4",
22
- "tsx": "^4.0.0",
23
- "typescript": "^5.7.0"
24
- },
25
- "engines": {
26
- "node": ">=18.0.0"
27
- },
28
- "files": [
29
- "dist",
30
- "README.md",
31
- "LICENSE.md"
32
- ],
33
- "publishConfig": {
34
- "access": "public"
35
- },
36
- "homepage": "https://www.nodatacapsule.com/developers",
37
- "keywords": [
38
- "nodata",
39
- "mcp",
40
- "model-context-protocol",
41
- "information-access-processor",
42
- "ai-agents",
43
- "access-control",
44
- "governance",
45
- "receipts"
46
- ],
47
- "license": "FSL-1.1-ALv2",
48
- "author": "Capsule Ltd."
49
- }
1
+ {
2
+ "name": "@nodatachat/mcp",
3
+ "version": "1.1.0",
4
+ "description": "NoData MCP server: connect any MCP client (Claude Code, Codex, Cursor, VS Code) to the NoData Information Access Processor. Governed access for AI agents, with a verifiable proof for every decision.",
5
+ "bin": {
6
+ "nodata-mcp": "dist/index.js"
7
+ },
8
+ "type": "module",
9
+ "scripts": {
10
+ "build": "tsc --noEmit && node build.mjs",
11
+ "dev": "tsx src/index.ts",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.29.0",
16
+ "zod": "^4.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "@nodatachat/folder-scan": "^1.0.0",
20
+ "@types/node": "^22.0.0",
21
+ "esbuild": "^0.27.4",
22
+ "tsx": "^4.0.0",
23
+ "typescript": "^5.7.0"
24
+ },
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "LICENSE.md"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "homepage": "https://www.nodatacapsule.com/developers",
37
+ "keywords": [
38
+ "nodata",
39
+ "mcp",
40
+ "model-context-protocol",
41
+ "information-access-processor",
42
+ "ai-agents",
43
+ "access-control",
44
+ "governance",
45
+ "receipts"
46
+ ],
47
+ "license": "FSL-1.1-ALv2",
48
+ "author": "Capsule Ltd."
49
+ }