@rel-time/mcp-server 1.2.3 → 1.2.4

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/index.js +84 -12
  2. package/package.json +2 -1
package/index.js CHANGED
@@ -18,6 +18,7 @@ const path = require("path");
18
18
  const readline = require("readline");
19
19
  const { exec } = require("child_process");
20
20
  const { PDFDocument } = require("pdf-lib");
21
+ const QRCode = require("qrcode");
21
22
  const sharp = require("sharp");
22
23
 
23
24
  function normalizeApiBase(rawUrl) {
@@ -55,9 +56,38 @@ let LAST_INVOICE_PACK = null;
55
56
 
56
57
  const SERVER_INFO = {
57
58
  name: "rel-time-mcp-server",
58
- version: "1.2.3",
59
+ version: "1.2.4",
59
60
  };
60
61
 
62
+ /**
63
+ * Resolves a local file path, automatically matching macOS screenshot filenames
64
+ * that contain Narrow No-Break Spaces (U+202F) or Non-Breaking Spaces (U+00A0)
65
+ * when the caller passes normal ASCII spaces.
66
+ */
67
+ function resolveLocalFilePath(rawPath) {
68
+ const resolved = path.resolve(rawPath);
69
+ if (fs.existsSync(resolved)) {
70
+ return resolved;
71
+ }
72
+ const dir = path.dirname(resolved);
73
+ const targetBase = path.basename(resolved);
74
+ const normalizeSpaces = (s) => s.normalize("NFC").replace(/[\u00A0\u202F\u2007\u2009\s]+/g, " ");
75
+ const normalizedTarget = normalizeSpaces(targetBase);
76
+ try {
77
+ if (fs.existsSync(dir)) {
78
+ const entries = fs.readdirSync(dir);
79
+ for (const entry of entries) {
80
+ if (normalizeSpaces(entry) === normalizedTarget) {
81
+ return path.join(dir, entry);
82
+ }
83
+ }
84
+ }
85
+ } catch (e) {
86
+ // Ignore directory read error
87
+ }
88
+ return resolved;
89
+ }
90
+
61
91
  /**
62
92
  * Loads saved API key from ~/.reltime/api_key.json if not provided via env/args.
63
93
  */
@@ -203,7 +233,7 @@ function openSystemBrowser(url) {
203
233
  * Rel-Time WebAuthn Passkey Bridge in the user's browser so they can touch
204
234
  * TouchID / FaceID / YubiKey.
205
235
  */
206
- function launchBrowserPasskeyBridge(timeoutMs = 120000) {
236
+ function launchBrowserPasskeyBridge(timeoutMs = 300000) {
207
237
  return new Promise((resolve, reject) => {
208
238
  const server = http.createServer((req, res) => {
209
239
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -503,7 +533,7 @@ const TOOLS = [
503
533
  {
504
534
  name: "reltime_provision_agent_key",
505
535
  description:
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.",
536
+ "Self-onboarding & Top-Up for Autonomous AI Agents via Bitcoin Lightning (`POST /v1/agent/invoice` & `POST /v1/agent/claim`). IMPORTANT:\n1. Before calling `create_invoice` for a new key, ALWAYS ask the user which pack they want first:\n - **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.\n - **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.\n2. When `create_invoice` returns, ALWAYS present BOTH the scannable QR code image (by calling Read on `qr_code_png_path` so the image renders inline in chat!) AND the full, un-truncated `ln_invoice` (`bolt11`) string in a copyable code block on the VERY FIRST try!",
507
537
  inputSchema: {
508
538
  type: "object",
509
539
  properties: {
@@ -576,7 +606,7 @@ async function executeTool(name, args = {}) {
576
606
  "Before anchoring files, a Rel-Time API key is required. Ask the user which pack they want to provision first:"
577
607
  );
578
608
  }
579
- const filePath = path.resolve(args.file_path);
609
+ const filePath = resolveLocalFilePath(args.file_path);
580
610
  if (!fs.existsSync(filePath)) {
581
611
  throw new Error(`File not found at path: ${filePath}`);
582
612
  }
@@ -696,7 +726,7 @@ async function executeTool(name, args = {}) {
696
726
  }
697
727
 
698
728
  if (name === "reltime_stamp_file") {
699
- const filePath = path.resolve(args.file_path);
729
+ const filePath = resolveLocalFilePath(args.file_path);
700
730
  if (!fs.existsSync(filePath)) {
701
731
  throw new Error(`File not found at path: ${filePath}`);
702
732
  }
@@ -853,7 +883,40 @@ async function executeTool(name, args = {}) {
853
883
  pack: args.pack,
854
884
  }),
855
885
  });
856
- return await res.json();
886
+ const data = await res.json();
887
+ const bolt11 = data.ln_invoice || data.bolt11 || "";
888
+ if (bolt11) {
889
+ try {
890
+ if (!fs.existsSync(PASSKEY_CONFIG_DIR)) {
891
+ fs.mkdirSync(PASSKEY_CONFIG_DIR, { recursive: true, mode: 0o700 });
892
+ }
893
+ const qrPngBuffer = await QRCode.toBuffer(bolt11.toUpperCase(), {
894
+ type: "png",
895
+ width: 560,
896
+ margin: 2,
897
+ errorCorrectionLevel: "M",
898
+ });
899
+ const homeQrPath = path.join(PASSKEY_CONFIG_DIR, "lightning-invoice-qr.png");
900
+ const tmpQrPath = "/tmp/reltime-lightning-qr.png";
901
+ fs.writeFileSync(homeQrPath, qrPngBuffer);
902
+ try {
903
+ fs.writeFileSync(tmpQrPath, qrPngBuffer);
904
+ } catch (e) {
905
+ // Ignore /tmp write error
906
+ }
907
+ data.bolt11 = bolt11;
908
+ data.ln_invoice = bolt11;
909
+ data.qr_code_png_path = homeQrPath;
910
+ data.qr_code_tmp_png_path = tmpQrPath;
911
+ data.invoice_expiration = "60 minutes (3,600 seconds)";
912
+ data.mandatory_first_reply_instructions =
913
+ "CRITICAL INSTRUCTION FOR AI AGENT: On this very first reply, you MUST show the user BOTH: (1) the scannable QR code image (immediately call your Read tool on `qr_code_png_path` BEFORE sending your message so the QR code PNG renders inline in the chat window right now!) AND (2) the full, un-truncated `bolt11` (`lnbc...`) Lightning invoice string inside a copyable code block so the user can either scan with their phone or copy-paste on the first try without asking!";
914
+ data._mcp_image_base64 = qrPngBuffer.toString("base64");
915
+ } catch (qrErr) {
916
+ data.qr_error = qrErr.message;
917
+ }
918
+ }
919
+ return data;
857
920
  } else if (args.action === "claim_invoice") {
858
921
  if (!args.invoice_id) throw new Error("invoice_id is required when action is 'claim_invoice'.");
859
922
  const resolvedPack = args.pack || LAST_INVOICE_PACK || undefined;
@@ -933,16 +996,25 @@ rl.on("line", async (line) => {
933
996
  const toolArgs = params?.arguments || {};
934
997
  try {
935
998
  const resultObj = await executeTool(toolName, toolArgs);
999
+ const contentItems = [];
1000
+ if (resultObj && resultObj._mcp_image_base64) {
1001
+ const b64 = resultObj._mcp_image_base64;
1002
+ delete resultObj._mcp_image_base64;
1003
+ contentItems.push({
1004
+ type: "image",
1005
+ data: b64,
1006
+ mimeType: "image/png",
1007
+ });
1008
+ }
1009
+ contentItems.push({
1010
+ type: "text",
1011
+ text: JSON.stringify(resultObj, null, 2),
1012
+ });
936
1013
  sendJsonRpc({
937
1014
  jsonrpc: "2.0",
938
1015
  id,
939
1016
  result: {
940
- content: [
941
- {
942
- type: "text",
943
- text: JSON.stringify(resultObj, null, 2),
944
- },
945
- ],
1017
+ content: contentItems,
946
1018
  },
947
1019
  });
948
1020
  } catch (toolErr) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rel-time/mcp-server",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
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": {
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "pdf-lib": "^1.17.1",
35
+ "qrcode": "^1.5.4",
35
36
  "sharp": "^0.35.5"
36
37
  }
37
38
  }