@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.
- package/dist/index.js +134 -65
- 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.
|
|
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
|
|
599
|
-
" Give any agent real power over your data, without
|
|
600
|
-
" (Possession is not permission:
|
|
601
|
-
// Setup
|
|
602
|
-
//
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
"
|
|
607
|
-
"
|
|
608
|
-
"
|
|
609
|
-
"
|
|
610
|
-
"
|
|
611
|
-
"
|
|
612
|
-
"
|
|
613
|
-
"
|
|
614
|
-
|
|
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
|
|
617
|
-
const START_OUTCOMES_AGENT = "WHAT YOU CAN DO — scoped to your grant
|
|
618
|
-
"
|
|
619
|
-
"
|
|
620
|
-
"
|
|
621
|
-
"
|
|
622
|
-
|
|
623
|
-
"
|
|
624
|
-
"
|
|
625
|
-
"
|
|
626
|
-
"
|
|
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
|
|
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
|
|
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).
|
|
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.
|
|
691
|
-
"browser (email + company, ~30s — also where your recovery phrase + email verification " +
|
|
692
|
-
"happen)
|
|
693
|
-
"is saved
|
|
694
|
-
"
|
|
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 ? "
|
|
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
|
-
"
|
|
715
|
-
"
|
|
716
|
-
"is the only way back into
|
|
717
|
-
"
|
|
718
|
-
|
|
719
|
-
|
|
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: `
|
|
766
|
-
"at ~/.nodata/credentials.json (readable only by you). The org key you pasted was NOT
|
|
767
|
-
"every start now proves possession by signing a fresh challenge and gets a 15-minute token.\n\n" +
|
|
768
|
-
"
|
|
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
|
-
? "
|
|
797
|
-
: `
|
|
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
|
-
"
|
|
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.
|
|
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": {
|