@nodatachat/mcp 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +11 -1
  2. package/dist/index.js +98 -29
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -39,6 +39,14 @@ Add the server to Claude Code in one command:
39
39
  claude mcp add nodata -- npx @nodatachat/mcp --api-key YOUR_API_KEY
40
40
  ```
41
41
 
42
+ On **Windows**, native (non-WSL) `npx` needs the `cmd /c` wrapper:
43
+
44
+ ```cmd
45
+ claude mcp add nodata -- cmd /c npx @nodatachat/mcp --api-key YOUR_API_KEY
46
+ ```
47
+
48
+ After changing the server's config (e.g. adding a credential), **restart Claude Code** — the running server process keeps its old arguments until the session restarts.
49
+
42
50
  Or with a custom base URL (for self-hosted or staging):
43
51
 
44
52
  ```bash
@@ -53,7 +61,9 @@ claude mcp add nodata -- npx @nodatachat/mcp --api-key YOUR_API_KEY --base-url h
53
61
  | Grant token (agent) | `--grant-token` | `NODATA_GRANT_TOKEN` | — |
54
62
  | Base URL | `--base-url` | `NODATA_BASE_URL` | `https://www.nodatacapsule.com` |
55
63
 
56
- At least one of `--api-key` or `--grant-token` is required. API keys use the format `sk_live_…`; grant tokens are `ndca-…` (mint via `POST /api/v1/governance/grant`).
64
+ API keys use the format `sk_live_…` (get one free at [/developers/get-started](https://www.nodatacapsule.com/developers/get-started)); grant tokens are `ndca-…` (mint via `POST /api/v1/governance/grant` or the `nodata_grant` tool).
65
+
66
+ **No credential? Setup mode.** Started without a credential, the server still connects and exposes a single `nodata_get_started` tool that walks you (or your AI) through getting a key and reconnecting — it never fails the MCP handshake. Data tools stay disabled until a credential is provided. `--help` and `--version` are also available.
57
67
 
58
68
  ## Available Tools
59
69
 
package/dist/index.js CHANGED
@@ -31,6 +31,15 @@
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
+ const VERSION = "0.4.2";
35
+ const USAGE = `nodata-mcp v${VERSION} — NoData MCP server\n\n` +
36
+ " ADMIN mode: nodata-mcp --api-key sk_live_...\n" +
37
+ " AGENT mode: nodata-mcp --grant-token ndca-...\n" +
38
+ " Options: --base-url <url> (default https://www.nodatacapsule.com)\n" +
39
+ " --help · --version\n\n" +
40
+ "Env vars: NODATA_API_KEY · NODATA_GRANT_TOKEN · NODATA_BASE_URL\n" +
41
+ "Get a free API key: https://www.nodatacapsule.com/developers/get-started\n" +
42
+ "Mint a grant token: POST /api/v1/governance/grant (or the nodata_grant tool).";
34
43
  function parseArgs(argv) {
35
44
  let apiKey = "";
36
45
  let grantToken = "";
@@ -45,6 +54,18 @@ function parseArgs(argv) {
45
54
  else if (argv[i] === "--base-url" && argv[i + 1]) {
46
55
  baseUrl = argv[++i];
47
56
  }
57
+ else if (argv[i] === "--help" || argv[i] === "-h") {
58
+ console.log(USAGE);
59
+ process.exit(0);
60
+ }
61
+ else if (argv[i] === "--version" || argv[i] === "-v") {
62
+ console.log(VERSION);
63
+ process.exit(0);
64
+ }
65
+ else if (argv[i].startsWith("--")) {
66
+ // A typo like --apikey silently degrading to setup mode is worse than noise.
67
+ console.error(`nodata-mcp: unknown option '${argv[i]}' (see --help)`);
68
+ }
48
69
  }
49
70
  if (!apiKey)
50
71
  apiKey = process.env.NODATA_API_KEY ?? "";
@@ -52,26 +73,29 @@ function parseArgs(argv) {
52
73
  grantToken = process.env.NODATA_GRANT_TOKEN ?? "";
53
74
  if (process.env.NODATA_BASE_URL)
54
75
  baseUrl = process.env.NODATA_BASE_URL;
55
- if (!apiKey && !grantToken) {
56
- console.error("Error: a credential is required.\n\n" +
57
- " ADMIN mode: nodata-mcp --api-key sk_live_...\n" +
58
- " AGENT mode: nodata-mcp --grant-token ndca-...\n\n" +
59
- "Get an API key at https://www.nodatacapsule.com/developers/get-started\n" +
60
- "Mint a grant token via POST /api/v1/governance/grant.");
61
- process.exit(1);
62
- }
63
76
  baseUrl = baseUrl.replace(/\/+$/, "");
64
77
  return { apiKey, grantToken, baseUrl };
65
78
  }
66
- async function apiPost(baseUrl, bearer, path, body) {
67
- const res = await fetch(`${baseUrl}${path}`, {
68
- method: "POST",
69
- headers: {
70
- "Content-Type": "application/json",
71
- Authorization: `Bearer ${bearer}`,
72
- },
73
- body: JSON.stringify(body),
74
- });
79
+ // Without a deadline a dead network turns a tool call into an indefinite hang
80
+ // inside the MCP client; 30s comfortably covers a slow decide/retrieve.
81
+ const REQUEST_TIMEOUT_MS = 30_000;
82
+ async function doFetch(url, init) {
83
+ let res;
84
+ try {
85
+ res = await fetch(url, { ...init, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
86
+ }
87
+ catch (err) {
88
+ const timedOut = err instanceof Error && err.name === "TimeoutError";
89
+ return {
90
+ ok: false,
91
+ status: 0,
92
+ body: {
93
+ error: timedOut
94
+ ? `Request timed out after ${REQUEST_TIMEOUT_MS / 1000}s: ${url}`
95
+ : `Network error reaching ${url}: ${err instanceof Error ? err.message : String(err)}`,
96
+ },
97
+ };
98
+ }
75
99
  let parsed;
76
100
  try {
77
101
  parsed = await res.json();
@@ -81,24 +105,26 @@ async function apiPost(baseUrl, bearer, path, body) {
81
105
  }
82
106
  return { ok: res.ok, status: res.status, body: parsed };
83
107
  }
108
+ async function apiPost(baseUrl, bearer, path, body) {
109
+ return doFetch(`${baseUrl}${path}`, {
110
+ method: "POST",
111
+ headers: {
112
+ "Content-Type": "application/json",
113
+ Authorization: `Bearer ${bearer}`,
114
+ },
115
+ body: JSON.stringify(body),
116
+ });
117
+ }
84
118
  async function apiGet(baseUrl, bearer, path, params) {
85
119
  const url = new URL(`${baseUrl}${path}`);
86
120
  for (const [key, val] of Object.entries(params)) {
87
121
  if (val !== undefined && val !== "")
88
122
  url.searchParams.set(key, val);
89
123
  }
90
- const res = await fetch(url.toString(), {
124
+ return doFetch(url.toString(), {
91
125
  method: "GET",
92
126
  headers: { Authorization: `Bearer ${bearer}` },
93
127
  });
94
- let parsed;
95
- try {
96
- parsed = await res.json();
97
- }
98
- catch {
99
- parsed = { error: `Non-JSON response (HTTP ${res.status})` };
100
- }
101
- return { ok: res.ok, status: res.status, body: parsed };
102
128
  }
103
129
  function formatResult(resp) {
104
130
  const text = JSON.stringify(resp.body, null, 2);
@@ -248,6 +274,13 @@ function registerAdminTools(server, baseUrl, apiKey) {
248
274
  }
249
275
  function registerAgentTool(server, baseUrl, grantToken) {
250
276
  const handle = handleFromGrant(grantToken);
277
+ if (!handle) {
278
+ // decide/retrieve are bearer-only and still work; read/use need the handle.
279
+ // Say so at startup instead of surprising the agent mid-conversation.
280
+ console.error("nodata-mcp: warning — could not derive an agent handle from the grant token " +
281
+ "(expected ndca-<jwt> with a 'handle' claim). nodata_decide/nodata_retrieve will work; " +
282
+ "nodata_read/nodata_use will fail until a valid grant token is provided.");
283
+ }
251
284
  // ── Access Compute · the "may I?" oracle (P0) ───────────────────────
252
285
  // Returns ONLY a decision + a signed proof, never any content. A planner
253
286
  // calls this to learn what it can reach BEFORE moving data: allow, degrade
@@ -341,19 +374,55 @@ function registerAgentTool(server, baseUrl, grantToken) {
341
374
  });
342
375
  }
343
376
  // ---------------------------------------------------------------------------
377
+ // Setup mode — no credential yet
378
+ // ---------------------------------------------------------------------------
379
+ // The public one-liner (`claude mcp add nodata -- npx @nodatachat/mcp`) arrives
380
+ // here with no credential. Exiting 1 makes the MCP client report an opaque
381
+ // "connection failed" and the stderr explanation is never seen. Instead we
382
+ // connect successfully and expose a single tool whose answer walks the user
383
+ // (via their own AI) to a working credential.
384
+ const SETUP_TEXT = "This NoData server is connected but has NO credential yet, so no data tools are available.\n\n" +
385
+ "To finish setup:\n\n" +
386
+ "1. Get a free API key (takes seconds, no card):\n" +
387
+ " https://www.nodatacapsule.com/developers/get-started\n\n" +
388
+ "2. Reconnect with the key —\n" +
389
+ " Claude Code: claude mcp remove nodata\n" +
390
+ " claude mcp add nodata -- npx @nodatachat/mcp --api-key sk_live_YOUR_KEY\n" +
391
+ " Or keep the command credential-free and set the env var instead:\n" +
392
+ " claude mcp add nodata --env NODATA_API_KEY=sk_live_YOUR_KEY -- npx @nodatachat/mcp\n\n" +
393
+ " IMPORTANT: after changing the config, RESTART the MCP client (e.g. exit and reopen\n" +
394
+ " Claude Code) — this server process was spawned with the old config and a config edit\n" +
395
+ " does not affect it. Until the restart it will keep answering in setup mode.\n\n" +
396
+ "3. (Agent mode) If you were handed a grant token (ndca-...), use --grant-token ndca-YOUR_GRANT\n" +
397
+ " (or NODATA_GRANT_TOKEN) instead of an API key — the server then exposes only the\n" +
398
+ " decide/retrieve/read/use tools scoped to that grant.\n\n" +
399
+ "Admin mode (--api-key) manages governance: register tables, issue/revoke grants, encrypt,\n" +
400
+ "decrypt, deliver, evidence. Agent mode (--grant-token) is what you hand an AI agent.";
401
+ function registerSetupTool(server) {
402
+ server.registerTool("nodata_get_started", {
403
+ description: "NoData is connected WITHOUT a credential — data tools are disabled. Call this (no arguments) to get the exact steps to finish setup: where to get a free API key and how to reconnect in admin or agent mode. If the user asks why NoData tools are missing or how to set up NoData, call this and relay the steps.",
404
+ inputSchema: {},
405
+ }, async () => ({ content: [{ type: "text", text: SETUP_TEXT }] }));
406
+ }
407
+ // ---------------------------------------------------------------------------
344
408
  // Server setup
345
409
  // ---------------------------------------------------------------------------
346
410
  async function main() {
347
411
  const { apiKey, grantToken, baseUrl } = parseArgs(process.argv.slice(2));
348
- const mode = grantToken ? "agent" : "admin";
412
+ const mode = grantToken ? "agent" : apiKey ? "admin" : "setup";
349
413
  const server = new McpServer({
350
- name: mode === "agent" ? "NoData (agent-scoped)" : "NoData",
351
- version: "0.4.0",
414
+ name: mode === "agent" ? "NoData (agent-scoped)" : mode === "setup" ? "NoData (setup)" : "NoData",
415
+ version: VERSION,
352
416
  });
353
417
  if (apiKey)
354
418
  registerAdminTools(server, baseUrl, apiKey);
355
419
  if (grantToken)
356
420
  registerAgentTool(server, baseUrl, grantToken);
421
+ if (mode === "setup") {
422
+ registerSetupTool(server);
423
+ console.error("nodata-mcp: no credential given — running in setup mode (only nodata_get_started is available).\n" +
424
+ "Pass --api-key sk_live_... or --grant-token ndca-... to enable data tools. See --help.");
425
+ }
357
426
  const transport = new StdioServerTransport();
358
427
  await server.connect(transport);
359
428
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@nodatachat/mcp",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "NoData MCP Server — govern data for AI agents: decide (may I?) + retrieve (authorized context) + read + secret-blind invoke, plus admin governance/encrypt/decrypt/deliver",
5
5
  "bin": {
6
- "nodata-mcp": "./dist/index.js"
6
+ "nodata-mcp": "dist/index.js"
7
7
  },
8
8
  "type": "module",
9
9
  "scripts": {