@nodatachat/mcp 0.7.0 → 0.9.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 (2) hide show
  1. package/dist/index.js +134 -65
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -31,12 +31,16 @@
31
31
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
32
32
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
33
33
  import { z } from "zod";
34
+ // Our deepest content scanner, shipped as its own zero-dep, Node-native npm
35
+ // package. nodata_scan_folder wires to it directly (not a reimplementation) so
36
+ // the "SEE what's sensitive" step runs INSIDE this one command, entirely local.
37
+ import { runFolderScan, silentLogger, formatFolderScan } from "@nodatachat/folder-scan";
34
38
  import { homedir, hostname } from "node:os";
35
- import { join } from "node:path";
39
+ import { join, resolve, basename } from "node:path";
36
40
  import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "node:fs";
37
41
  import { spawn } from "node:child_process";
38
42
  import { generateKeyPairSync, createPrivateKey, sign, randomBytes } from "node:crypto";
39
- const VERSION = "0.7.0";
43
+ const VERSION = "0.9.0";
40
44
  // ---------------------------------------------------------------------------
41
45
  // Local credential store — "register once, autoload forever"
42
46
  // ---------------------------------------------------------------------------
@@ -595,58 +599,59 @@ function registerAgentTool(server, baseUrl, grantToken) {
595
599
  return formatResult(resp);
596
600
  });
597
601
  }
598
- const START_ACHIEVEMENT = "WHAT YOU GET WITH NODATA — in one line:\n" +
599
- " Give any agent real power over your data, without ever giving it the power to leak it.\n" +
600
- " (Possession is not permission: an agent can hold the key and still not read what you didn't allow.)\n";
601
- // Setup / admin readers are the developer wiring governance up — outcomes point
602
- // at the admin verbs (register / grant / encrypt / deliver) and the no-setup web door.
603
- const START_OUTCOMES_ADMIN = "PICK AN OUTCOME — the win first, the step under it:\n\n" +
604
- "[1] An agent that cannot leak\n" +
605
- " It sees only the classifications you allow; the rest never decrypts for it, and every\n" +
606
- " disclosure returns a signed receipt.\n" +
607
- " Start: tell me \"give <agent> public + internal, hide the rest\" — I register the data\n" +
608
- " (nodata_register_table) and issue the grant (nodata_grant); nodata_revoke cuts it off anytime.\n\n" +
609
- "[2] See what is already exposed — no setup\n" +
610
- " Know exactly which sensitive fields your agent already received, before you change anything.\n" +
611
- " Start: https://www.nodatacapsule.com/trace-inspector (runs in your browser, nothing uploaded).\n\n" +
612
- "[3] A secret you hold that NoData cannot read\n" +
613
- " Encrypt a value even NoData's server cannot open, then hand it over as a one-time link.\n" +
614
- " Start: nodata_encrypt, then nodata_deliver.\n";
602
+ const START_ACHIEVEMENT = "WHAT NODATA DOES — in one line:\n" +
603
+ " Give any agent real power over your data, without the power to leak it.\n" +
604
+ " (Possession is not permission: it can hold the key and still not read what you didn't allow.)\n";
605
+ // Setup mode: NOT connected yet. The one job here is to switch the tools on, so
606
+ // this is a numbered do-this-exactly list — three steps, not a catalogue. The
607
+ // last line answers the two questions everyone asks: "how do I activate?" (/mcp)
608
+ // and "what about next time?" (nothing).
609
+ const SETUP_STEPS = "TURN THE DATA TOOLS ON — 3 steps, about a minute, once:\n\n" +
610
+ " 1. GET A KEY call nodata_connect (no arguments)\n" +
611
+ " -> opens the signup in your browser: email + company (~30s), a 12-word\n" +
612
+ " recovery phrase to keep, and an ORG KEY shown ONCE. Copy that key.\n\n" +
613
+ " 2. SAVE IT call nodata_connect again, this time with\n" +
614
+ " api_key: \"<the key you copied>\"\n" +
615
+ " -> saved on this machine; you never paste it again.\n\n" +
616
+ " 3. ACTIVATE type /mcp (or restart Claude Code) so the tools load.\n\n" +
617
+ "COMING BACK LATER? Nothing to redo — it starts ready. If the tools look missing, just /mcp.\n";
618
+ // Admin mode: connected. Frame the whole product as the developer's real journey
619
+ // — SEE -> CHOOSE -> PROVE — each step a plain-language ask mapped to a tool.
620
+ const START_OUTCOMES_ADMIN = "NOW — TURN YOUR DATA INTO AN AGENT THAT CAN'T LEAK, in 3 asks (just say them in plain words):\n\n" +
621
+ " 1. SEE what's sensitive run nodata_scan_folder — scans a local folder right here, nothing\n" +
622
+ " uploads (or open https://www.nodatacapsule.com/ai-folder in a browser).\n\n" +
623
+ " 2. CHOOSE what to release say \"let <agent> read name + plan on <table>, hide the rest\"\n" +
624
+ " -> I run nodata_register_table + nodata_grant; the agent gets a token.\n\n" +
625
+ " 3. PROVE & control the agent now reads ONLY those columns — the rest never decrypt.\n" +
626
+ " \"what did it see?\" -> nodata_evidence · \"cut it off\" -> nodata_revoke\n\n" +
627
+ "Also: a secret even NoData can't read -> nodata_encrypt, then hand it over once -> nodata_deliver.\n";
615
628
  // The agent-scoped reader IS the governed agent: it holds only decide/retrieve/read/use,
616
- // so its outcomes name those verbs (pointing it at admin tools it lacks would dead-end it).
617
- const START_OUTCOMES_AGENT = "WHAT YOU CAN DO — scoped to your grant, the win first, the step under it:\n\n" +
618
- "[1] Know before you read\n" +
619
- " Check whether you MAY reach something without pulling any data — allow / degrade to the\n" +
620
- " reachable subset / deny, with an access-distance cost and a signed proof.\n" +
621
- " Start: nodata_decide.\n\n" +
622
- "[2] Pull only what you're allowed\n" +
623
- " Prompt-ready context filtered to exactly your permitted fields; denied fields never decrypt,\n" +
624
- " and you get an accounting of what was withheld plus a receipt.\n" +
625
- " Start: nodata_retrieve (or nodata_read for governed rows).\n\n" +
626
- "[3] Use a secret you never see\n" +
627
- " Invoke a pre-registered capability; the Capsule injects the sealed credential server-side and\n" +
628
- " returns only the result. The secret never reaches you.\n" +
629
- " Start: nodata_use.\n";
630
- const START_UNSTUCK = "IF IT SEEMS STUCK — there is always a next step:\n" +
631
- " - Don't see the nodata_* tools yet? They load when the MCP client starts. Restart Claude Code\n" +
632
- " (or run /mcp). This is expected, not an error.\n" +
633
- " - No MCP at all, or it won't connect? The same governance runs without it — one curl, or the\n" +
634
- " guided page at https://www.nodatacapsule.com/developers/get-started\n";
629
+ // so its steps name those verbs (pointing it at admin tools it lacks would dead-end it).
630
+ const START_OUTCOMES_AGENT = "WHAT YOU CAN DO — scoped to your grant; you reach only what it allows, the rest never decrypts:\n\n" +
631
+ " 1. PLAN nodata_decide \"may I reach X?\" -> allow / partial / deny + a signed proof. No data moves.\n" +
632
+ " 2. PULL nodata_retrieve prompt-ready context, allowed fields only, with a receipt.\n" +
633
+ " (nodata_read for raw governed rows.)\n" +
634
+ " 3. ACT nodata_use run a capability with a secret injected server-side — you never see it.\n";
635
+ const START_UNSTUCK = "STUCK? There's always a next step:\n" +
636
+ " - Don't see the nodata_* tools? They load when the client starts — type /mcp or restart.\n" +
637
+ " (Expected, not an error.)\n" +
638
+ " - No MCP at all? The same governance runs from one curl, or the guided page:\n" +
639
+ " https://www.nodatacapsule.com/developers/get-started\n";
635
640
  function startHeader(mode) {
636
641
  if (mode === "setup") {
637
- return ("You're connected to NoData, but WITHOUT a credential yet, so the data tools are still off.\n" +
638
- "Fastest unlock — nothing to retype: call nodata_connect. It opens the free signup in your\n" +
639
- "browser (email + company, ~30s, with your recovery phrase + email verify), then you call it\n" +
640
- "once more with the key it shows and it's SAVED — every later start comes up ready, and the\n" +
641
- "only step left is one /mcp so the client reloads the tools.\n\n");
642
+ return "You're connected to NoData — but there's NO key yet, so the data tools are still off.\n\n";
642
643
  }
643
644
  if (mode === "agent") {
644
- return ("You're connected to NoData, scoped to one grant token: you can reach only what that grant\n" +
645
- "allows, and denied fields never decrypt for you. Plan with nodata_decide, then nodata_retrieve.\n\n");
645
+ return "You're connected to NoData, scoped to one grant token.\n\n";
646
646
  }
647
- return "You're connected to NoData (admin). Everything below is one plain-language ask away.\n\n";
647
+ return "You're connected to NoData (admin mode). You don't memorize commands — just ask in plain words.\n\n";
648
648
  }
649
649
  function getStartedText(mode) {
650
+ // Setup leads with the 3 connect steps (the only thing that matters when off);
651
+ // admin/agent lead with the journey they can act on right now.
652
+ if (mode === "setup") {
653
+ return startHeader(mode) + START_ACHIEVEMENT + "\n" + SETUP_STEPS + "\n" + START_UNSTUCK;
654
+ }
650
655
  const outcomes = mode === "agent" ? START_OUTCOMES_AGENT : START_OUTCOMES_ADMIN;
651
656
  return startHeader(mode) + START_ACHIEVEMENT + "\n" + outcomes + "\n" + START_UNSTUCK;
652
657
  }
@@ -687,11 +692,11 @@ function openInBrowser(url) {
687
692
  }
688
693
  function registerConnectTool(server, baseUrl) {
689
694
  server.registerTool("nodata_connect", {
690
- description: "Switch NoData's data tools on. Call with NO arguments to open the free signup in a " +
691
- "browser (email + company, ~30s — also where your recovery phrase + email verification " +
692
- "happen). It shows an org key ONCE; call this tool again with that key as `api_key` and it " +
693
- "is saved locally, so every later start comes up ready with nothing to retype. After saving, " +
694
- "reconnect (/mcp) or restart so the client loads the tools.",
695
+ description: "Switch NoData's data tools on. Step 1: call with NO arguments to open the free signup in a " +
696
+ "browser (email + company, ~30s — also where your 12-word recovery phrase + email verification " +
697
+ "happen); it shows an ORG KEY once. Step 2: call this tool again with that key as `api_key` and " +
698
+ "it is saved on this machine, so every later start comes up ready with nothing to retype. " +
699
+ "Step 3: type /mcp (or restart) so the client loads the tools.",
695
700
  inputSchema: {
696
701
  api_key: z
697
702
  .string()
@@ -708,15 +713,16 @@ function registerConnectTool(server, baseUrl) {
708
713
  content: [
709
714
  {
710
715
  type: "text",
711
- text: (opened ? "Opened the NoData signup in your browser:\n " : "Open the NoData signup in your browser:\n ") +
716
+ text: (opened ? "STEP 1 of 3 — opened the NoData signup in your browser:\n " : "STEP 1 of 3 — open the NoData signup in your browser:\n ") +
712
717
  url +
713
718
  "\n\n" +
714
- "It takes ~30s (email + company; a personal email is fine for the sandbox). You'll also\n" +
715
- "set a 12-word recovery phrase and verify your email there — keep the recovery phrase, it\n" +
716
- "is the only way back into the org.\n\n" +
717
- "The page shows an ORG KEY exactly once. Copy it, then call nodata_connect again with\n" +
718
- ' api_key: "<that key>"\n' +
719
- "and I'll save it so you never paste it again.",
719
+ "There, about a minute:\n" +
720
+ " - email + company (a personal email is fine for the sandbox)\n" +
721
+ " - a 12-WORD RECOVERY PHRASE — save it; it is the only way back into your org\n" +
722
+ " - your ORG KEY, shown ONCE — copy it\n\n" +
723
+ "STEP 2 — come back and call this tool again with the key:\n" +
724
+ ' nodata_connect api_key: "<the key you copied>"\n' +
725
+ "and I'll save it so you never paste it again. (Then STEP 3: type /mcp to activate.)",
720
726
  },
721
727
  ],
722
728
  };
@@ -762,10 +768,10 @@ function registerConnectTool(server, baseUrl) {
762
768
  content: [
763
769
  {
764
770
  type: "text",
765
- text: `Done — this device is enrolled as "${label}" (${handle}), and only its PRIVATE key is saved\n` +
766
- "at ~/.nodata/credentials.json (readable only by you). The org key you pasted was NOT stored:\n" +
767
- "every start now proves possession by signing a fresh challenge and gets a 15-minute token.\n\n" +
768
- "Reconnect (run /mcp, or restart Claude Code) and the admin tools will be there.",
771
+ text: `STEP 2 done — this device is enrolled as "${label}" (${handle}), and only its PRIVATE key is\n` +
772
+ "saved at ~/.nodata/credentials.json (readable only by you). The org key you pasted was NOT\n" +
773
+ "stored: every start now proves possession by signing a fresh challenge and gets a 15-minute token.\n\n" +
774
+ "STEP 3 — type /mcp (or restart Claude Code) and the admin tools appear. Next time: nothing to do.",
769
775
  },
770
776
  ],
771
777
  };
@@ -793,16 +799,75 @@ function registerConnectTool(server, baseUrl) {
793
799
  {
794
800
  type: "text",
795
801
  text: (attestUnavailable
796
- ? "Saved your key locally (device attestation isn't available on this server yet, so this is the\nkey-at-rest path). "
797
- : `Saved your key locally (device enrollment didn't succeed: ${enrolled.error}). `) +
802
+ ? "STEP 2 done — saved your key on this machine (device attestation isn't available on this\nserver yet, so this is the key-at-rest path). "
803
+ : `STEP 2 done — saved your key on this machine (device enrollment didn't succeed: ${enrolled.error}). `) +
798
804
  "Every future start loads it automatically — no re-register, no re-paste.\n\n" +
799
- "Reconnect (run /mcp, or restart Claude Code) and the admin tools will be there.",
805
+ "STEP 3 — type /mcp (or restart Claude Code) and the admin tools appear. Next time: nothing to do.",
800
806
  },
801
807
  ],
802
808
  };
803
809
  });
804
810
  }
805
811
  // ---------------------------------------------------------------------------
812
+ // nodata_scan_folder — SEE what's sensitive, locally, before you govern it
813
+ // ---------------------------------------------------------------------------
814
+ // The journey's first step ("SEE") brought inside the one command. It runs our
815
+ // deep content scanner (the published @nodatachat/folder-scan — the same detection
816
+ // engine the NoData agent uses: 24 secret patterns + checksum-validated PII, and
817
+ // PDF/Office text extraction) ENTIRELY on this machine — nothing uploads — over a
818
+ // folder the developer names, then points at register_table + grant for what it
819
+ // surfaces. Local compute needs no credential, so this is offered even in setup
820
+ // mode: scan before you even sign up.
821
+ function formatScanReport(r) {
822
+ // The neutral report comes from the scanner package (shared with its CLI);
823
+ // the MCP only adds the governance bridge — turn what the scan surfaced into
824
+ // the next step of the same journey.
825
+ const base = formatFolderScan(r);
826
+ if (r.error)
827
+ return base;
828
+ return (base +
829
+ "\n\nGOVERN what it surfaced — same journey, inside this command:\n" +
830
+ " · CHOOSE what an agent may read: nodata_register_table then nodata_grant (denied columns never decrypt)\n" +
831
+ " · PROVE every access afterwards: nodata_evidence\n" +
832
+ "\nWant a standalone deep scan any time? npx @nodatachat/folder-scan (this same engine).");
833
+ }
834
+ function registerScanFolderTool(server) {
835
+ server.registerTool("nodata_scan_folder", {
836
+ description: "SEE what's sensitive before you govern it. Scans a LOCAL folder for hardcoded secrets and unencrypted PII (checksum-validated — Israeli ID / Luhn credit-card / IBAN) with NoData's deep content scanner, reading PDFs and Office files too, ENTIRELY on this machine — 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.",
837
+ inputSchema: {
838
+ path: z
839
+ .string()
840
+ .optional()
841
+ .describe("Folder to scan (absolute or relative). Default: the directory the server runs in."),
842
+ },
843
+ }, async ({ path: scanPath }) => {
844
+ const dir = (scanPath ?? ".").trim() || ".";
845
+ const absDir = resolve(dir);
846
+ if (!existsSync(absDir)) {
847
+ return {
848
+ content: [{ type: "text", text: `No folder found at: ${absDir}\nPass an existing path, e.g. nodata_scan_folder path: "."` }],
849
+ };
850
+ }
851
+ try {
852
+ // Deep scanner: async, takes an injected logger. silentLogger keeps the
853
+ // MCP's stdout (JSON-RPC) clean; the scan never writes to stdout itself.
854
+ const report = await runFolderScan({
855
+ target_id: "mcp",
856
+ target_label: basename(absDir),
857
+ target_path: absDir,
858
+ log: silentLogger,
859
+ });
860
+ return { content: [{ type: "text", text: formatScanReport(report) }] };
861
+ }
862
+ catch (e) {
863
+ return {
864
+ content: [{ type: "text", text: `Scan failed: ${e instanceof Error ? e.message : String(e)}` }],
865
+ isError: true,
866
+ };
867
+ }
868
+ });
869
+ }
870
+ // ---------------------------------------------------------------------------
806
871
  // Server setup
807
872
  // ---------------------------------------------------------------------------
808
873
  async function main() {
@@ -821,6 +886,10 @@ async function main() {
821
886
  if (grantToken)
822
887
  registerAgentTool(server, baseUrl, grantToken);
823
888
  registerGetStarted(server, mode);
889
+ // The local exposure scan needs no credential — offer it wherever the owner is
890
+ // driving (setup, before signup, or admin), just not to a scoped agent.
891
+ if (mode !== "agent")
892
+ registerScanFolderTool(server);
824
893
  if (mode === "setup") {
825
894
  registerConnectTool(server, baseUrl);
826
895
  console.error("nodata-mcp: no credential given — running in setup mode (data tools disabled).\n" +
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nodatachat/mcp",
3
- "version": "0.7.0",
4
- "description": "NoData MCP Server — govern data AND code for AI agents: decide (may I?) + retrieve (authorized context) + read + secret-blind invoke + blind code advise/patch (fix code without exposure), plus admin governance/encrypt/decrypt/deliver",
3
+ "version": "0.9.0",
4
+ "description": "NoData MCP Server — scan a folder for exposure (locally, nothing uploads) + govern data AND code for AI agents: decide (may I?) + retrieve (authorized context) + read + secret-blind invoke + blind code advise/patch (fix code without exposure), plus admin governance/encrypt/decrypt/deliver",
5
5
  "bin": {
6
6
  "nodata-mcp": "dist/index.js"
7
7
  },
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "@modelcontextprotocol/sdk": "^1.29.0",
16
+ "@nodatachat/folder-scan": "^1.0.0",
16
17
  "zod": "^4.0.0"
17
18
  },
18
19
  "devDependencies": {