@rel-time/mcp-server 1.2.1 → 1.2.3

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 +8 -7
  2. package/index.js +154 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,25 +10,26 @@ Official Model Context Protocol (MCP) server for **[Rel-Time](https://rel-time.c
10
10
 
11
11
  ---
12
12
 
13
- ## Quick Setup (Claude Desktop / Cursor / Windsurf / Antigravity)
13
+ ## Quick Setup (Claude Desktop / Claude Code / Cursor / Windsurf / Antigravity)
14
14
 
15
- Add the following to your `claude_desktop_config.json` or `.cursor/mcp.json`:
15
+ Add the following to your `.mcp.json`, `claude_desktop_config.json`, or `.cursor/mcp.json` (no API key or `.zshrc` environment variables needed to start!):
16
16
 
17
17
  ```json
18
18
  {
19
19
  "mcpServers": {
20
20
  "rel-time": {
21
21
  "command": "npx",
22
- "args": ["-y", "@rel-time/mcp-server"],
23
- "env": {
24
- "RELTIME_API_KEY": "YOUR_RELTIME_API_KEY"
25
- }
22
+ "args": ["-y", "@rel-time/mcp-server@latest"]
26
23
  }
27
24
  }
28
25
  }
29
26
  ```
30
27
 
31
- *(Note: `reltime_link_passkey`, `reltime_get_sun_stamp`, `reltime_check_proof`, and `reltime_provision_agent_key` work immediately even without an API key configured!)*
28
+ When you start a fresh chat and ask to anchor a file or provision a key, `@rel-time/mcp-server` automatically prompts your agent to ask which pack you want first:
29
+ 1. **Agent Launch Pack (`pack: "launch"`, `$5 / 50 Hashes`):** Priority 10-Minute Bitcoin Queue · Standard Mode only (Data Integrity + Biometric or Anonymous) · Includes 64-Ray Sun-Stamp, 1-Click Stamped Copy (`.PDF`/`.PNG`), and `.JSON` Doomsday Proof.
30
+ 2. **Autonomous Agents (`pack: "standard"`, `$50 / 500 Hashes`):** Dynamic 6-Hour Batch Queue · Supports both Temporal Mode (`T_index` sub-second VDF sequencing) and Standard Mode · Reloadable anytime (`+500` credits / `$50`).
31
+
32
+ Once paid via Lightning (or if you pass an existing `rel_key_...` key), the MCP server automatically saves your API key to `~/.reltime/api_key.json` (`0600` permissions) — no app restart or `.zshrc` edits required.
32
33
 
33
34
  ---
34
35
 
package/index.js CHANGED
@@ -20,18 +20,123 @@ const { exec } = require("child_process");
20
20
  const { PDFDocument } = require("pdf-lib");
21
21
  const sharp = require("sharp");
22
22
 
23
- const API_BASE = (process.env.RELTIME_API_URL || "https://rel-time.com/api/v1").replace(/\/+$/, "");
23
+ function normalizeApiBase(rawUrl) {
24
+ const cleaned = (rawUrl || "https://rel-time.com/api/v1").trim().replace(/\/+$/, "");
25
+ if (
26
+ cleaned === "https://rel-time.com/v1" ||
27
+ cleaned === "https://rel-time.com" ||
28
+ cleaned.includes("api.rel-time.com")
29
+ ) {
30
+ return "https://rel-time.com/api/v1";
31
+ }
32
+ return cleaned;
33
+ }
34
+
35
+ function isValidApiKeyCandidate(k) {
36
+ if (!k || typeof k !== "string") return false;
37
+ const trimmed = k.trim();
38
+ if (!trimmed || trimmed.length < 16) return false;
39
+ if (trimmed.includes("${") || trimmed.includes("...") || trimmed.includes("YOUR_") || trimmed.includes("<")) {
40
+ return false;
41
+ }
42
+ return trimmed.startsWith("rel_");
43
+ }
44
+
45
+ const API_BASE = normalizeApiBase(process.env.RELTIME_API_URL);
24
46
  const WEB_BASE = (process.env.RELTIME_WEB_URL || "https://rel-time.com").replace(/\/+$/, "");
25
- let CURRENT_API_KEY = process.env.RELTIME_API_KEY || "";
47
+ let CURRENT_API_KEY = isValidApiKeyCandidate(process.env.RELTIME_API_KEY)
48
+ ? process.env.RELTIME_API_KEY.trim()
49
+ : "";
26
50
 
27
51
  const PASSKEY_CONFIG_DIR = path.join(os.homedir(), ".reltime");
28
52
  const PASSKEY_CONFIG_PATH = path.join(PASSKEY_CONFIG_DIR, "passkey.json");
53
+ const API_KEY_CONFIG_PATH = path.join(PASSKEY_CONFIG_DIR, "api_key.json");
54
+ let LAST_INVOICE_PACK = null;
29
55
 
30
56
  const SERVER_INFO = {
31
57
  name: "rel-time-mcp-server",
32
- version: "1.2.1",
58
+ version: "1.2.3",
33
59
  };
34
60
 
61
+ /**
62
+ * Loads saved API key from ~/.reltime/api_key.json if not provided via env/args.
63
+ */
64
+ function loadSavedApiKey() {
65
+ if (isValidApiKeyCandidate(CURRENT_API_KEY)) {
66
+ return CURRENT_API_KEY.trim();
67
+ }
68
+ if (isValidApiKeyCandidate(process.env.RELTIME_API_KEY)) {
69
+ CURRENT_API_KEY = process.env.RELTIME_API_KEY.trim();
70
+ return CURRENT_API_KEY;
71
+ }
72
+ try {
73
+ if (fs.existsSync(API_KEY_CONFIG_PATH)) {
74
+ const parsed = JSON.parse(fs.readFileSync(API_KEY_CONFIG_PATH, "utf8"));
75
+ if (isValidApiKeyCandidate(parsed.api_key)) {
76
+ CURRENT_API_KEY = parsed.api_key.trim();
77
+ return CURRENT_API_KEY;
78
+ }
79
+ }
80
+ } catch (e) {
81
+ // Ignore corrupted file
82
+ }
83
+ return "";
84
+ }
85
+
86
+ function buildPackSelectionPrompt(contextMessage) {
87
+ return {
88
+ status: "pack_selection_required",
89
+ api_endpoint: API_BASE,
90
+ important_instruction_for_ai_agent:
91
+ "DO NOT tell the user to edit ~/.zshrc, .mcp.json, or restart Claude/Cursor! The MCP server manages and persists API keys automatically in ~/.reltime/api_key.json. Simply ask the user right here in chat which of the two Agent API Key packs below they want to provision (or if they already have a `rel_key_...` key to paste), then call `reltime_provision_agent_key` with `action: 'create_invoice'` and `pack: 'launch'` or `pack: 'standard'`.",
92
+ message:
93
+ contextMessage ||
94
+ "No Rel-Time API key is active yet. Ask the user which key pack they want to provision first:",
95
+ available_packs: [
96
+ {
97
+ pack: "launch",
98
+ name: "1. Agent Launch Pack ($5 / 50 Hashes)",
99
+ price_usd: "$5.00",
100
+ credits: 50,
101
+ settlement_queue: "Priority 10-Minute Bitcoin Queue",
102
+ hashing_modes: "Standard Mode only (Data Integrity + Biometric or Anonymous)",
103
+ outputs: "64-Ray Sun-Stamp SVG + 1-Click Stamped Copy (.PDF/.PNG) + .JSON Doomsday Proof",
104
+ },
105
+ {
106
+ pack: "standard",
107
+ name: "2. Autonomous Agents ($50 / 500 Hashes)",
108
+ price_usd: "$50.00",
109
+ credits: 500,
110
+ settlement_queue: "Dynamic 6-Hour Batch Queue",
111
+ hashing_modes: "Temporal Mode (T_index sub-second VDF sequencing) + Standard Mode (Biometric or Anonymous)",
112
+ outputs:
113
+ "64-Ray Sun-Stamp SVG + 1-Click Stamped Copy (.PDF/.PNG) + .JSON Doomsday Proof + Reloadable anytime (+500 credits / $50)",
114
+ },
115
+ ],
116
+ next_step:
117
+ "Ask the user: 'Would you like to start with the **Agent Launch Pack ($5 / 50 Hashes · 10-Min Queue · Standard Mode)** or the **Autonomous Agents Pack ($50 / 500 Hashes · 6-Hr Queue · Standard + Temporal Mode)**?' Once they answer, call `reltime_provision_agent_key({ action: 'create_invoice', pack: 'launch' | 'standard' })`.",
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Saves provisioned API key to ~/.reltime/api_key.json with 0600 permissions.
123
+ */
124
+ function saveApiKeyConfig(apiKey, pack) {
125
+ if (!fs.existsSync(PASSKEY_CONFIG_DIR)) {
126
+ fs.mkdirSync(PASSKEY_CONFIG_DIR, { recursive: true, mode: 0o700 });
127
+ }
128
+ const payload = {
129
+ api_key: apiKey.trim(),
130
+ pack: pack || "standard",
131
+ saved_at: new Date().toISOString(),
132
+ };
133
+ fs.writeFileSync(API_KEY_CONFIG_PATH, JSON.stringify(payload, null, 2), {
134
+ encoding: "utf8",
135
+ mode: 0o600,
136
+ });
137
+ return payload;
138
+ }
139
+
35
140
  /**
36
141
  * Loads linked Passkey credentials from env vars or ~/.reltime/passkey.json.
37
142
  */
@@ -383,13 +488,14 @@ const TOOLS = [
383
488
  {
384
489
  name: "reltime_account_status",
385
490
  description:
386
- "Checks the remaining Hash Credits, tier, and lifetime usage of the configured Rel-Time API key (`GET /v1/account`).",
491
+ "Checks the remaining Hash Credits, tier, and lifetime usage of the configured Rel-Time API key (`GET /api/v1/account`). If no API key is configured yet, returns the two available Agent API Key packs (`launch` $5/50 hashes vs `standard` $50/500 hashes) so you can ask the user which pack they want to provision in chat (without needing any .zshrc or config edits).",
387
492
  inputSchema: {
388
493
  type: "object",
389
494
  properties: {
390
495
  api_key: {
391
496
  type: "string",
392
- description: "Optional override for RELTIME_API_KEY.",
497
+ description:
498
+ "Optional: pass a `rel_key_...` API key to check its balance AND automatically save it to ~/.reltime/api_key.json for all future calls.",
393
499
  },
394
500
  },
395
501
  },
@@ -397,7 +503,7 @@ const TOOLS = [
397
503
  {
398
504
  name: "reltime_provision_agent_key",
399
505
  description:
400
- "Self-onboarding & Top-Up for Autonomous AI Agents: Requests a Bitcoin Lightning invoice (`POST /v1/agent/invoice`, $5 for 50 Hash Credits Launch Pack or $50 for 500 Hash Credits) or claims a paid invoice (`POST /v1/agent/claim`) to either provision a brand-new Tier 4 Autonomous Agent API key OR reload an existing key without changing the key.",
506
+ "Self-onboarding & Top-Up for Autonomous AI Agents via Bitcoin Lightning (`POST /v1/agent/invoice` & `POST /v1/agent/claim`). IMPORTANT: Before calling `create_invoice` for a new key, ALWAYS ask the user which pack they want first:\n1. **Agent Launch Pack (`pack: 'launch'`, $5 / 50 Hashes):** Priority 10-Minute Bitcoin Queue · Standard Mode only (Data Integrity + Biometric or Anonymous) · Includes Sun-Stamp, 1-Click Stamped Copy (.PDF/.PNG), and .JSON Doomsday Proof.\n2. **Autonomous Agents (`pack: 'standard'`, $50 / 500 Hashes):** Dynamic 6-Hour Batch Queue · Supports both Temporal Mode (`T_index` sub-second sequencing) and Standard Mode · Reloadable anytime (+500 credits / $50) with zero key rotation.",
401
507
  inputSchema: {
402
508
  type: "object",
403
509
  properties: {
@@ -411,7 +517,7 @@ const TOOLS = [
411
517
  type: "string",
412
518
  enum: ["launch", "standard"],
413
519
  description:
414
- "Optional: 'launch' ($5 for 50 Hash Credits) or 'standard' (default, $50 for 500 Hash Credits).",
520
+ "Which key pack the user selected: 'launch' (Agent Launch Pack — $5 for 50 Hash Credits, 10-Min Queue, Standard Mode only) or 'standard' (Autonomous Agents — $50 for 500 Hash Credits, 6-Hr Queue, Temporal & Standard Modes, reloadable). If omitted on 'create_invoice', the tool returns the two options so you can ask the user which pack they prefer.",
415
521
  },
416
522
  invoice_id: {
417
523
  type: "string",
@@ -434,7 +540,11 @@ const TOOLS = [
434
540
  ];
435
541
 
436
542
  async function executeTool(name, args = {}) {
437
- const key = (args.api_key || CURRENT_API_KEY || "").trim();
543
+ if (args.api_key && isValidApiKeyCandidate(args.api_key)) {
544
+ CURRENT_API_KEY = args.api_key.trim();
545
+ saveApiKeyConfig(CURRENT_API_KEY, "custom");
546
+ }
547
+ const key = (isValidApiKeyCandidate(args.api_key) ? args.api_key.trim() : loadSavedApiKey()) || "";
438
548
 
439
549
  if (name === "reltime_link_passkey") {
440
550
  if (args.public_key_hex && args.passkey_signature) {
@@ -461,15 +571,15 @@ async function executeTool(name, args = {}) {
461
571
  }
462
572
 
463
573
  if (name === "reltime_anchor_file") {
574
+ if (!key) {
575
+ return buildPackSelectionPrompt(
576
+ "Before anchoring files, a Rel-Time API key is required. Ask the user which pack they want to provision first:"
577
+ );
578
+ }
464
579
  const filePath = path.resolve(args.file_path);
465
580
  if (!fs.existsSync(filePath)) {
466
581
  throw new Error(`File not found at path: ${filePath}`);
467
582
  }
468
- if (!key) {
469
- throw new Error(
470
- "Missing RELTIME_API_KEY. Set RELTIME_API_KEY in your MCP config, pass `api_key`, or call `reltime_provision_agent_key` to get an Autonomous Agent key."
471
- );
472
- }
473
583
 
474
584
  // 1. Compute SHA-256 locally (Zero-Knowledge: raw file never leaves the device)
475
585
  const fileBuffer = fs.readFileSync(filePath);
@@ -614,7 +724,9 @@ async function executeTool(name, args = {}) {
614
724
 
615
725
  if (name === "reltime_anchor_hash") {
616
726
  if (!key) {
617
- throw new Error("Missing RELTIME_API_KEY. Set RELTIME_API_KEY in your MCP config or pass `api_key`.");
727
+ return buildPackSelectionPrompt(
728
+ "Before anchoring hashes, a Rel-Time API key is required. Ask the user which pack they want to provision first:"
729
+ );
618
730
  }
619
731
  const clientHash = (args.client_hash || "").trim().toLowerCase();
620
732
  if (!/^[0-9a-f]{64}$/.test(clientHash)) {
@@ -699,13 +811,26 @@ async function executeTool(name, args = {}) {
699
811
 
700
812
  if (name === "reltime_account_status") {
701
813
  if (!key) {
702
- throw new Error("Missing RELTIME_API_KEY.");
814
+ return buildPackSelectionPrompt(
815
+ "Rel-Time MCP is connected! No API key is saved yet. Ask the user which pack they would like to provision first:"
816
+ );
703
817
  }
704
818
  const res = await fetch(`${API_BASE}/account`, {
705
819
  headers: { Authorization: `Bearer ${key}` },
706
820
  });
707
- const data = await res.json();
821
+ const text = await res.text();
822
+ let data;
823
+ try {
824
+ data = JSON.parse(text);
825
+ } catch (e) {
826
+ throw new Error(`Rel-Time Account error (${res.status}) from ${API_BASE}/account: ${text.slice(0, 200)}`);
827
+ }
708
828
  if (!res.ok) {
829
+ if (res.status === 401 || res.status === 403) {
830
+ return buildPackSelectionPrompt(
831
+ "The configured API key was not recognized. Ask the user which pack they would like to provision:"
832
+ );
833
+ }
709
834
  throw new Error(`Rel-Time Account error (${res.status}): ${JSON.stringify(data)}`);
710
835
  }
711
836
  return data;
@@ -713,22 +838,30 @@ async function executeTool(name, args = {}) {
713
838
 
714
839
  if (name === "reltime_provision_agent_key") {
715
840
  if (args.action === "create_invoice") {
841
+ if (args.pack !== "launch" && args.pack !== "standard") {
842
+ return buildPackSelectionPrompt(
843
+ "Please ask the user which Rel-Time Agent API Key pack they would like to provision before generating the Lightning invoice:"
844
+ );
845
+ }
846
+
847
+ LAST_INVOICE_PACK = args.pack;
716
848
  const res = await fetch(`${API_BASE}/agent/invoice`, {
717
849
  method: "POST",
718
850
  headers: { "Content-Type": "application/json" },
719
851
  body: JSON.stringify({
720
852
  accept_terms: true,
721
- pack: args.pack === "launch" ? "launch" : "standard",
853
+ pack: args.pack,
722
854
  }),
723
855
  });
724
856
  return await res.json();
725
857
  } else if (args.action === "claim_invoice") {
726
858
  if (!args.invoice_id) throw new Error("invoice_id is required when action is 'claim_invoice'.");
859
+ const resolvedPack = args.pack || LAST_INVOICE_PACK || undefined;
727
860
  const claimPayload = {
728
861
  invoice_id: args.invoice_id,
729
- pack: args.pack === "launch" ? "launch" : undefined,
862
+ pack: resolvedPack === "launch" ? "launch" : undefined,
730
863
  };
731
- const targetReloadKey = args.api_key || (args.reload_existing ? CURRENT_API_KEY : "");
864
+ const targetReloadKey = args.api_key || (args.reload_existing ? loadSavedApiKey() : "");
732
865
  if (targetReloadKey && targetReloadKey.trim()) {
733
866
  claimPayload.api_key = targetReloadKey.trim();
734
867
  }
@@ -740,6 +873,8 @@ async function executeTool(name, args = {}) {
740
873
  const data = await res.json();
741
874
  if (data.api_key) {
742
875
  CURRENT_API_KEY = data.api_key;
876
+ saveApiKeyConfig(data.api_key, resolvedPack || "standard");
877
+ data.saved_to_config = API_KEY_CONFIG_PATH;
743
878
  }
744
879
  return data;
745
880
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rel-time/mcp-server",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Official Model Context Protocol (MCP) server for Rel-Time (rel-time.com) — Zero-Knowledge Bitcoin & VDF Provenance, Local File Hashing, and Sun-Stamp Generation for Claude, Cursor, Windsurf, and Antigravity.",
5
5
  "main": "index.js",
6
6
  "bin": {