@nekzus/liop 1.2.0-alpha.9 → 1.2.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 (50) hide show
  1. package/README.md +12 -3
  2. package/dist/bin/agent.js +222 -51
  3. package/dist/bridge/index.js +7 -6
  4. package/dist/bridge/stream.js +11 -11
  5. package/dist/client/index.js +46 -35
  6. package/dist/crypto/logic-image-id.d.ts +3 -0
  7. package/dist/crypto/logic-image-id.js +27 -0
  8. package/dist/crypto/verifier.js +7 -19
  9. package/dist/economy/estimator.d.ts +53 -0
  10. package/dist/economy/estimator.js +69 -0
  11. package/dist/economy/index.d.ts +5 -0
  12. package/dist/economy/index.js +3 -0
  13. package/dist/economy/otel.d.ts +38 -0
  14. package/dist/economy/otel.js +100 -0
  15. package/dist/economy/telemetry.d.ts +77 -0
  16. package/dist/economy/telemetry.js +224 -0
  17. package/dist/errors.d.ts +14 -0
  18. package/dist/errors.js +19 -0
  19. package/dist/gateway/hybrid.d.ts +3 -1
  20. package/dist/gateway/hybrid.js +38 -13
  21. package/dist/gateway/router.d.ts +25 -9
  22. package/dist/gateway/router.js +484 -133
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.js +3 -0
  25. package/dist/mesh/node.d.ts +16 -0
  26. package/dist/mesh/node.js +394 -113
  27. package/dist/prompts/adapters.d.ts +16 -0
  28. package/dist/prompts/adapters.js +55 -0
  29. package/dist/rpc/proto.js +2 -1
  30. package/dist/rpc/server.d.ts +1 -1
  31. package/dist/rpc/server.js +4 -3
  32. package/dist/rpc/tls.js +3 -2
  33. package/dist/sandbox/wasi.d.ts +1 -1
  34. package/dist/sandbox/wasi.js +43 -3
  35. package/dist/security/guardian.js +3 -2
  36. package/dist/security/zk.d.ts +2 -3
  37. package/dist/security/zk.js +22 -9
  38. package/dist/server/index.d.ts +53 -4
  39. package/dist/server/index.js +362 -49
  40. package/dist/server/pii.d.ts +12 -0
  41. package/dist/server/pii.js +90 -0
  42. package/dist/types.d.ts +16 -0
  43. package/dist/utils/logger.d.ts +21 -0
  44. package/dist/utils/logger.js +70 -0
  45. package/dist/utils/mcpCompact.d.ts +11 -0
  46. package/dist/utils/mcpCompact.js +29 -0
  47. package/dist/workers/logic-execution.d.ts +1 -1
  48. package/dist/workers/logic-execution.js +38 -20
  49. package/dist/workers/zk-verifier.js +37 -33
  50. package/package.json +14 -2
package/README.md CHANGED
@@ -36,9 +36,10 @@ This fundamentally solves the data privacy, bandwidth, and latency challenges of
36
36
  | **MCP Drop-in Replacement** | `LiopServer` mirrors the Anthropic MCP `Server` API — tools, resources, and prompts with `Zod` schemas. |
37
37
  | **Guardian AST** | Zero-time heuristic inspection blocks sandbox escapes (`require`, `fs`, `eval`, `fetch`, prototype pollution). |
38
38
  | **WASI Sandbox** | JavaScript payloads execute inside V8 isolates with CPU fuel limits and no access to Node.js globals. |
39
- | **PII Shield** | Multi-layer egress filter with NIST/OWASP patterns (Email, Credit Card with Luhn, IP, Phone) and configurable forbidden keys. |
39
+ | **PII Shield** | Multi-layer egress filter with Regional Presets (Email, Credit Card with Luhn, IP, Phone, SSN, IBAN Mod-97, Passport MRZ) and custom keys. |
40
40
  | **ZK-Receipts** | Cryptographic proof (SHA-256 + SHA-512 seal) that the returned result was computed honestly from the injected logic. |
41
41
  | **Worker Pool** | Heavy computation (crypto, sandboxing) dispatched to OS threads via `piscina`, unblocking the V8 event loop. |
42
+ | **Cross-AI Adapters**| Zero-Shot system prompts automatically adapt instructions for Claude (XML-heavy) vs OpenAI/Gemini (JSON-schema). |
42
43
  | **MCP Bridge** | `LiopMcpBridge` adapts any `LiopServer` to the JSON-RPC 2.0 / stdio protocol used by Claude Desktop, Cursor, etc. |
43
44
  | **Post-Quantum Ready** | ML-KEM-768 (Kyber) handshake + AES-256-GCM symmetric encryption for transport-layer security. |
44
45
  | **P2P Mesh** | Kademlia DHT discovery via `libp2p` with TCP + WebSocket + Yamux multiplexing and Noise encryption. |
@@ -263,13 +264,21 @@ await bridge.connect();
263
264
  Built-in patterns with multi-layer verification:
264
265
 
265
266
  ```typescript
266
- import { PII_PATTERNS } from "@nekzus/liop/server";
267
+ import { PII_PATTERNS, PII_PRESETS } from "@nekzus/liop/server";
267
268
 
268
269
  // Available patterns:
269
270
  PII_PATTERNS.EMAIL // RFC 5322 compliant, excludes @example.com/@test.com
270
271
  PII_PATTERNS.CREDIT_CARD // Visa/MC/Amex + Luhn algorithm validation
271
272
  PII_PATTERNS.IP_ADDRESS // IPv4 with octet range validation (excludes localhost)
272
273
  PII_PATTERNS.PHONE // International phone formats with digit-length validation
274
+ PII_PATTERNS.SSN // USA Social Security Number rules (blocks 000 segments)
275
+ PII_PATTERNS.IBAN // ISO 7064 Modulo 97-10 algorithm precision via BigInt
276
+ PII_PATTERNS.PASSPORT_MRZ // Strict 44-character TD3 international passport MRZ string
277
+
278
+ // Pre-built Regional Presets ready to drop-in via LiopServer(options):
279
+ PII_PRESETS.GLOBAL_STRICT
280
+ PII_PRESETS.US_COMPLIANT
281
+ PII_PRESETS.EU_GDPR
273
282
  ```
274
283
 
275
284
  ### Forbidden Keys
@@ -292,7 +301,7 @@ const server = new LiopServer(info, {
292
301
  The following shows a complete Logic-Injection-on-Origin execution cycle (handled internally by the SDK):
293
302
 
294
303
  ```
295
- 1. LLM generates JavaScript analysis code wrapped in ---BEGIN_LOGIC--- / ---END_LOGIC--- boundaries
304
+ 1. LLM generates JavaScript analysis code wrapped in @LIOP / @END boundaries
296
305
  2. LiopServer receives the payload via tools/call (JSON-RPC or direct)
297
306
  3. Guardian AST inspects for sandbox escapes (zero-time heuristic analysis)
298
307
  4. Code executes inside a V8 isolate with CPU fuel limits (no Node.js globals)
package/dist/bin/agent.js CHANGED
@@ -2,9 +2,105 @@
2
2
  import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
+ import { multiaddr } from "@multiformats/multiaddr";
5
6
  import { LiopMcpRouter } from "../gateway/router.js";
6
7
  import { MeshNode } from "../mesh/index.js";
7
8
  import { LiopServer } from "../server/index.js";
9
+ import { log } from "../utils/logger.js";
10
+ /**
11
+ * Resolves a full libp2p multiaddr (with PeerID) from a LIOP node's
12
+ * HTTP health endpoint. This enables zero-config bootstrap — users
13
+ * only need to provide a URL, not a cryptographic PeerID.
14
+ *
15
+ * @param url - HTTP URL of a LIOP node's health endpoint (e.g. "http://host:3000")
16
+ * @returns Full multiaddr string with PeerID, or null if resolution fails
17
+ */
18
+ async function resolveBootstrapFromUrl(url) {
19
+ try {
20
+ const healthUrl = url.endsWith("/health") ? url : `${url}/health`;
21
+ const response = await fetch(healthUrl, {
22
+ headers: { Accept: "application/json" },
23
+ signal: AbortSignal.timeout(10000), // Increased to 10s
24
+ });
25
+ if (!response.ok)
26
+ return null;
27
+ const data = await response.json();
28
+ if (!data.mesh?.multiaddrs?.length || !data.mesh?.peerId)
29
+ return null;
30
+ // Find TCP multiaddr (prefer non-websocket for stability)
31
+ const tcpAddr = data.mesh.multiaddrs.find((a) => a.includes("/tcp/") &&
32
+ !a.includes("/ws") &&
33
+ !a.includes("/ip4/127.0.0.1/"));
34
+ if (!tcpAddr)
35
+ return null;
36
+ // Rewrite internal Docker IP using industrial mapper if available
37
+ let resolved = industrialAddressMapper(tcpAddr);
38
+ if (!resolved || resolved === tcpAddr) {
39
+ const urlHost = new URL(url).hostname;
40
+ resolved = tcpAddr.replace(/\/ip4\/[^/]+/, `/ip4/${urlHost}`);
41
+ }
42
+ if (!resolved)
43
+ return null;
44
+ resolved += resolved.includes("/p2p/") ? "" : `/p2p/${data.mesh.peerId}`;
45
+ return resolved;
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ /**
52
+ * Normalizes a bootstrap multiaddr string.
53
+ * If the address contains a Docker bridge IP (172.16-31.x.x) or Loopback (127.0.0.1),
54
+ * rewrites it to the host accessible via LIOP_NEXUS_URL (e.g. WSL2 IP).
55
+ * This is critical when WSL2 mirror-mode networking is broken.
56
+ */
57
+ function normalizeBootstrap(addr) {
58
+ const trimmed = addr.trim();
59
+ // Remap Docker bridge IPs and ANY external physical IPs to 127.0.0.1
60
+ // because Test-NetConnection confirmed 127.0.0.1 is the only reliable path to Docker ports.
61
+ const dockerIpRegex = /\/ip4\/172\.(1[6-9]|2[0-9]|3[0-1])\.[0-9]{1,3}\.[0-9]{1,3}/;
62
+ const loopbackRegex = /\/ip4\/127\.0\.0\.1/;
63
+ const physicalIpRegex = /\/ip4\/192\.168\.[0-9]{1,3}\.[0-9]{1,3}/;
64
+ if (dockerIpRegex.test(trimmed) ||
65
+ loopbackRegex.test(trimmed) ||
66
+ physicalIpRegex.test(trimmed)) {
67
+ const targetIp = "127.0.0.1";
68
+ const normalized = trimmed
69
+ .replace(dockerIpRegex, `/ip4/${targetIp}`)
70
+ .replace(loopbackRegex, `/ip4/${targetIp}`)
71
+ .replace(physicalIpRegex, `/ip4/${targetIp}`);
72
+ if (normalized !== trimmed) {
73
+ log.info(`[LIOP-Agent] 🔄 Local Routing Hack → Forced 127.0.0.1: ${normalized}`);
74
+ }
75
+ return normalized;
76
+ }
77
+ return trimmed;
78
+ }
79
+ /**
80
+ * industrialAddressMapper
81
+ *
82
+ * Mapea IPs internas de Docker a puertos industriales mapeados en el Host.
83
+ * Nexus (172.20.0.10) -> 13001
84
+ * Vault (172.20.0.11) -> 13003
85
+ * Bank (172.20.0.12) -> 13004
86
+ * Oracle(172.20.0.13) -> 13005
87
+ */
88
+ function industrialAddressMapper(addr) {
89
+ if (addr.includes("/ip4/172.20.0.10"))
90
+ return addr.replace(/\/ip4\/172\.20\.0\.10\/tcp\/[0-9]+/, "/ip4/127.0.0.1/tcp/13001");
91
+ if (addr.includes("/ip4/172.20.0.11"))
92
+ return addr.replace(/\/ip4\/172\.20\.0\.11\/tcp\/[0-9]+/, "/ip4/127.0.0.1/tcp/13003");
93
+ if (addr.includes("/ip4/172.20.0.12"))
94
+ return addr.replace(/\/ip4\/172\.20\.0\.12\/tcp\/[0-9]+/, "/ip4/127.0.0.1/tcp/13004");
95
+ if (addr.includes("/ip4/172.20.0.13"))
96
+ return addr.replace(/\/ip4\/172\.20\.0\.13\/tcp\/[0-9]+/, "/ip4/127.0.0.1/tcp/13005");
97
+ // Drop container-internal loopbacks to prevent the Host Agent from dialing itself or conflicting ports
98
+ if (addr.includes("/ip4/127.0.0.1/tcp/4000") ||
99
+ addr.includes("/ip4/127.0.0.1/tcp/3000")) {
100
+ return null;
101
+ }
102
+ return addr;
103
+ }
8
104
  /**
9
105
  * LIOP Agent (Zero-Config CLI)
10
106
  *
@@ -15,6 +111,8 @@ import { LiopServer } from "../server/index.js";
15
111
  * No hardcoded tools, PeerIDs, or port mappings.
16
112
  */
17
113
  async function main() {
114
+ const buildTime = new Date().toISOString();
115
+ log.info(`[LIOP-Agent] 🚀 Version 1.2.0-alpha.9 | Build: ${buildTime}`);
18
116
  const liopDir = path.join(os.homedir(), ".liop");
19
117
  const identityPath = path.join(liopDir, "identity.json");
20
118
  if (!fs.existsSync(liopDir)) {
@@ -27,31 +125,41 @@ async function main() {
27
125
  if (args.length > 0) {
28
126
  bootstrapNodes = args.filter((a) => a.startsWith("/"));
29
127
  }
30
- // Environment variable
31
- if (bootstrapNodes.length === 0 && process.env.LIOP_BOOTSTRAP) {
32
- bootstrapNodes.push(process.env.LIOP_BOOTSTRAP.trim());
33
- }
34
- // Convenience: Try to read nexus.multiaddr from multiple locations
128
+ // Priority 1: Physical Beacons (Industrial Pattern) - DETERMINISTIC & INSTANT
35
129
  if (bootstrapNodes.length === 0) {
36
- const searchPaths = [
37
- path.join(process.cwd(), "nexus.multiaddr"),
38
- path.join(liopDir, "nexus.multiaddr"),
39
- // Try relative to the agent binary (dist/bin/agent.js -> root/nexus.multiaddr)
40
- path.join(path.dirname(new URL(import.meta.url).pathname), "../../nexus.multiaddr"),
41
- // Windows path fix (remove leading slash if present)
42
- path.join(path
43
- .dirname(new URL(import.meta.url).pathname)
44
- .replace(/^\/([A-Z]:)/, "$1"), "../../nexus.multiaddr"),
45
- ];
46
- for (const nexusPath of searchPaths) {
130
+ const searchDirs = [];
131
+ // Priority 1.1: Explicit file from environment variable
132
+ if (process.env.LIOP_BOOTSTRAP_FILE) {
133
+ const filePath = path.resolve(process.env.LIOP_BOOTSTRAP_FILE);
134
+ if (fs.existsSync(filePath)) {
135
+ const addr = fs.readFileSync(filePath, "utf8").trim();
136
+ if (addr)
137
+ bootstrapNodes.push(normalizeBootstrap(addr));
138
+ }
139
+ }
140
+ // Priority 1.2: Traditional locations (Scan for all *.multiaddr)
141
+ searchDirs.push(process.cwd(), path.join(process.cwd(), "tests/infra/nexus-data"), liopDir, path.join(path
142
+ .dirname(new URL(import.meta.url).pathname)
143
+ .replace(/^\/([A-Z]:)/, "$1"), "../../tests/infra/nexus-data"));
144
+ for (const dir of searchDirs) {
47
145
  try {
48
- if (fs.existsSync(nexusPath)) {
49
- const addr = fs.readFileSync(nexusPath, "utf8").trim();
50
- if (addr && !bootstrapNodes.includes(addr)) {
51
- bootstrapNodes.push(addr);
52
- console.error(`[LIOP-Agent] Found bootstrap at: ${nexusPath}`);
53
- break;
146
+ if (fs.existsSync(dir)) {
147
+ const files = fs.readdirSync(dir);
148
+ const multiaddrFiles = files.filter((f) => f.endsWith(".multiaddr"));
149
+ for (const file of multiaddrFiles) {
150
+ const filePath = path.join(dir, file);
151
+ const addr = fs.readFileSync(filePath, "utf8").trim();
152
+ if (addr) {
153
+ const normalized = normalizeBootstrap(addr);
154
+ if (!bootstrapNodes.includes(normalized)) {
155
+ bootstrapNodes.push(normalized);
156
+ log.info(`[LIOP-Agent] ✅ Loaded beacon: ${file} from ${dir}`);
157
+ }
158
+ }
54
159
  }
160
+ // If we found any beacons in this directory, we consider discovery successful for this layer
161
+ if (bootstrapNodes.length > 0)
162
+ break;
55
163
  }
56
164
  }
57
165
  catch (_e) {
@@ -59,78 +167,141 @@ async function main() {
59
167
  }
60
168
  }
61
169
  }
170
+ // Priority 2: Auto-Discovery via NEXUS URL (Aggressive Parallel Discovery)
171
+ if (process.env.LIOP_NEXUS_URL) {
172
+ const nexusUrl = process.env.LIOP_NEXUS_URL;
173
+ log.info(`[LIOP-Agent] 🌐 Running parallel discovery from: ${nexusUrl} (Sources Found: ${bootstrapNodes.length})`);
174
+ const resolved = await resolveBootstrapFromUrl(nexusUrl);
175
+ if (resolved) {
176
+ const normalized = normalizeBootstrap(resolved);
177
+ if (!bootstrapNodes.includes(normalized)) {
178
+ bootstrapNodes.push(normalized);
179
+ log.info(`[LIOP-Agent] ✅ Added bootstrap from URL discovery: ${normalized}`);
180
+ }
181
+ }
182
+ }
183
+ // Priority 3: Environment variable (direct multiaddr)
184
+ if (bootstrapNodes.length === 0 && process.env.LIOP_BOOTSTRAP) {
185
+ bootstrapNodes.push(process.env.LIOP_BOOTSTRAP.trim());
186
+ }
187
+ // Final fallback: local Nexus bootstrap for demo environments.
188
+ // Avoid injecting stale static peer IDs when discovery already found valid peers.
189
+ if (bootstrapNodes.length === 0) {
190
+ bootstrapNodes.push("/ip4/127.0.0.1/tcp/13001/p2p/12D3KooWD8FUFdnLQzzLFNdicsaTknM5cpD7os9sK9NWVSVABJMD");
191
+ }
192
+ // Sanitize/validate all candidate multiaddrs so malformed PeerIDs don't crash startup.
193
+ bootstrapNodes = bootstrapNodes.filter((addr) => {
194
+ try {
195
+ multiaddr(addr);
196
+ return true;
197
+ }
198
+ catch {
199
+ log.warn(`[LIOP-Agent] Ignoring invalid bootstrap multiaddr: ${addr}`);
200
+ return false;
201
+ }
202
+ });
62
203
  // If no bootstrap nodes found, the agent operates in standalone mode.
63
204
  // It will only serve local tools until peers are discovered.
64
205
  if (bootstrapNodes.length === 0) {
65
- console.error("[LIOP-Agent] No bootstrap nodes configured. Operating in standalone mode.");
66
- console.error("[LIOP-Agent] Pass a multiaddr as argument or create 'nexus.multiaddr' file.");
206
+ log.info("[LIOP-Agent] No bootstrap nodes configured. Operating in standalone mode.");
207
+ log.info("[LIOP-Agent] Pass a multiaddr as argument or create 'nexus.multiaddr' file.");
67
208
  }
68
209
  // Initialize local server node (lightweight, no tools registered locally)
69
210
  const liopServer = new LiopServer({
70
211
  name: "@nekzus/liop-agent",
71
212
  version: "1.0.0",
72
213
  });
214
+ // Enable Zero-Shot Autonomy (Industrial Prompt Injection)
215
+ liopServer.enableZeroShotAutonomy();
73
216
  // 2. Mesh Node Configuration
74
217
  const meshNode = new MeshNode({
75
218
  identityPath: identityPath,
76
219
  bootstrapNodes: bootstrapNodes,
220
+ addressMapper: industrialAddressMapper,
77
221
  });
78
222
  // Start P2P Mesh
79
223
  await meshNode.start();
80
224
  // 3. Initialize the Dynamic Router
81
225
  // No hardcoded tools — all discovery happens via liop:manifest protocol
82
226
  const router = new LiopMcpRouter(liopServer, meshNode);
83
- // Proactive Notification to Claude Desktop when tools are discovered dynamically
227
+ // Proactive Notification to Claude Desktop when tools/resources are discovered dynamically
84
228
  router.onToolsChanged = () => {
85
229
  process.stdout.write(`{"jsonrpc":"2.0","method":"notifications/tools/list_changed"}\n`);
230
+ process.stdout.write(`{"jsonrpc":"2.0","method":"notifications/resources/list_changed"}\n`);
86
231
  };
87
- // Initial warming period (2s) then Periodic Discovery Worker (every 10 seconds)
88
- // This silently polls the DHT for new nodes and triggers onToolsChanged if the topology shifts.
232
+ // Initial warming period (2s) then Adaptive Background Discovery
233
+ // Polls DHT for new nodes and triggers onToolsChanged when topology shifts.
234
+ // Uses exponential backoff to reduce polling load on stable meshes.
89
235
  setTimeout(() => {
90
236
  // biome-ignore lint/suspicious/noExplicitAny: access internal for telemetry
91
237
  const rtSize = meshNode.getRoutingTableSize?.() || 0;
92
- console.error(`[LIOP-Agent] Warm-up complete. Routing Table size: ${rtSize}`);
238
+ log.info(`[LIOP-Agent] Warm-up complete. Routing Table size: ${rtSize}`);
93
239
  router.refreshManifestCache(true).catch(() => { });
94
240
  }, 2000);
95
- setInterval(() => {
96
- router.refreshManifestCache(true).catch(() => { });
97
- }, 15000);
98
- // 4. STDIO Transport implementation
241
+ const POLL_BASE_MS = 10_000;
242
+ const POLL_MAX_MS = 120_000;
243
+ let pollIntervalMs = POLL_BASE_MS;
244
+ const scheduleAdaptivePoll = () => {
245
+ setTimeout(async () => {
246
+ const prevSize = router.getCacheSize();
247
+ await router.refreshManifestCache(true).catch(() => { });
248
+ const newSize = router.getCacheSize();
249
+ if (newSize !== prevSize) {
250
+ // Topology changed — reset to aggressive polling
251
+ pollIntervalMs = POLL_BASE_MS;
252
+ log.info(`[LIOP-Agent] Topology change detected (${prevSize} → ${newSize}). Resetting poll to ${POLL_BASE_MS / 1000}s.`);
253
+ }
254
+ else {
255
+ // Stable — relax polling interval (factor 1.5)
256
+ pollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), POLL_MAX_MS);
257
+ }
258
+ scheduleAdaptivePoll();
259
+ }, pollIntervalMs);
260
+ };
261
+ scheduleAdaptivePoll();
262
+ // 4. STDIO Transport — Buffered Line Reader
263
+ // Uses readline to guarantee complete JSON-RPC messages before parsing.
264
+ // Raw stdin.on("data") can fragment large payloads across multiple chunks.
265
+ const readline = await import("node:readline");
266
+ const rl = readline.createInterface({
267
+ input: process.stdin,
268
+ terminal: false,
269
+ });
99
270
  process.stdout.on("error", (err) => {
100
271
  if (err.code === "EPIPE") {
101
272
  process.exit(0); // Graceful exit when Claude Desktop disconnects
102
273
  }
103
274
  });
104
- process.stdin.on("data", async (data) => {
105
- const payload = data.toString().trim();
106
- if (!payload)
275
+ rl.on("line", async (line) => {
276
+ const trimmed = line.trim();
277
+ if (!trimmed)
107
278
  return;
108
- const messages = payload.split("\n");
109
- for (const msg of messages) {
110
- try {
111
- const request = JSON.parse(msg);
112
- if (request.method) {
113
- const response = await router.dispatch(request);
114
- if (response) {
115
- process.stdout.write(`${JSON.stringify(response)}\n`);
116
- }
279
+ try {
280
+ const request = JSON.parse(trimmed);
281
+ if (request.method) {
282
+ const response = await router.dispatch(request);
283
+ if (response) {
284
+ process.stdout.write(`${JSON.stringify(response)}\n`);
117
285
  }
118
286
  }
119
- catch (_err) {
120
- // Silent catch for binary noise
121
- }
122
287
  }
288
+ catch (_err) {
289
+ // Silent catch for binary noise or malformed lines
290
+ }
291
+ });
292
+ rl.on("close", () => {
293
+ process.exit(0);
123
294
  });
124
295
  // Status directed only to stderr
125
- console.error(`[LIOP-Agent] Guarding Claude Desktop via STDIO.`);
126
- console.error(`[LIOP-Agent] P2P Mesh: Joined (${bootstrapNodes.length} bootstraps)`);
127
- console.error("[LIOP-Agent] Tool discovery: Dynamic via /liop/manifest/1.0.0");
296
+ log.info(`[LIOP-Agent] Guarding Claude Desktop via STDIO.`);
297
+ log.info(`[LIOP-Agent] P2P Mesh: Joined (${bootstrapNodes.length} bootstraps)`);
298
+ log.info("[LIOP-Agent] Tool discovery: Dynamic via /liop/manifest/1.0.0");
128
299
  process.on("SIGINT", async () => {
129
300
  await meshNode.stop();
130
301
  process.exit(0);
131
302
  });
132
303
  }
133
304
  main().catch((err) => {
134
- console.error(`[LIOP-Agent] Fatal Error: ${err.message}`);
305
+ log.error(`[LIOP-Agent] Fatal Error: ${err.message}`);
135
306
  process.exit(1);
136
307
  });
@@ -1,5 +1,6 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { LiopServer } from "../server/index.js";
3
+ import { log } from "../utils/logger.js";
3
4
  /**
4
5
  * LIOP MCP Bridge
5
6
  * A bi-directional bridge that allows legacy MCP servers to join the LIOP mesh,
@@ -14,11 +15,11 @@ export class LiopMcpBridge {
14
15
  // Determine mode: Exposing LIOP to MCP (Claude) or Wrapping MCP to LIOP (Mesh)
15
16
  if (source instanceof LiopServer) {
16
17
  this.liopServer = source;
17
- console.error("[LIOP-Bridge] Mode: EXPOSE (LIOP -> MCP Stdio)");
18
+ log.info("[LIOP-Bridge] Mode: EXPOSE (LIOP -> MCP Stdio)");
18
19
  }
19
20
  else if (source instanceof McpServer) {
20
21
  this.legacyMcpServer = source;
21
- console.error("[LIOP-Bridge] Mode: WRAP (Legacy MCP -> LIOP Mesh)");
22
+ log.info("[LIOP-Bridge] Mode: WRAP (Legacy MCP -> LIOP Mesh)");
22
23
  }
23
24
  }
24
25
  /**
@@ -47,7 +48,7 @@ export class LiopMcpBridge {
47
48
  return null;
48
49
  if (method === "initialize") {
49
50
  return this.successResponse(id, {
50
- protocolVersion: "2025-03-26",
51
+ protocolVersion: "2025-11-25",
51
52
  capabilities: {
52
53
  prompts: {},
53
54
  resources: {},
@@ -167,7 +168,7 @@ export class LiopMcpBridge {
167
168
  return true;
168
169
  }
169
170
  catch (e) {
170
- console.error("[LIOP-Bridge] ZK-Verifier Failure:", e);
171
+ log.info("[LIOP-Bridge] ZK-Verifier Failure:", e);
171
172
  return false;
172
173
  }
173
174
  }
@@ -221,7 +222,7 @@ export class LiopMcpBridge {
221
222
  terminal: false,
222
223
  });
223
224
  const shutdown = async () => {
224
- console.error("[LIOP-Bridge] Disconnecting session...");
225
+ log.info("[LIOP-Bridge] Disconnecting session...");
225
226
  if (this.liopServer)
226
227
  await this.liopServer.close();
227
228
  process.exit(0);
@@ -240,7 +241,7 @@ export class LiopMcpBridge {
240
241
  }
241
242
  }
242
243
  catch (e) {
243
- console.error(`[LIOP-Bridge] Error: ${e.message}`);
244
+ log.error(`[LIOP-Bridge] Error: ${e.message}`);
244
245
  }
245
246
  });
246
247
  }
@@ -3,6 +3,7 @@ import { serve } from "@hono/node-server";
3
3
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
4
4
  import { Hono } from "hono";
5
5
  import { cors } from "hono/cors";
6
+ import { log } from "../utils/logger.js";
6
7
  import { LiopMcpBridge } from "./index.js";
7
8
  const DEFAULT_MAX_SESSIONS_PER_IP = 10;
8
9
  const DEFAULT_SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
@@ -53,7 +54,7 @@ export class LiopStreamBridge {
53
54
  lastActivity: Date.now(),
54
55
  clientIp,
55
56
  });
56
- console.error(`[LIOP-StreamBridge] Session opened: ${sessionId} (IP: ${clientIp})`);
57
+ log.info(`[LIOP-StreamBridge] Session opened: ${sessionId} (IP: ${clientIp})`);
57
58
  },
58
59
  });
59
60
  // Wire the transport's incoming messages to the LiopMcpBridge JSON-RPC router
@@ -72,13 +73,13 @@ export class LiopStreamBridge {
72
73
  }
73
74
  }
74
75
  catch (err) {
75
- console.error("[LIOP-StreamBridge] JSON-RPC error:", err.message);
76
+ log.info("[LIOP-StreamBridge] JSON-RPC error:", err.message);
76
77
  }
77
78
  };
78
79
  transport.onclose = () => {
79
80
  if (transport.sessionId) {
80
81
  this.activeSessions.delete(transport.sessionId);
81
- console.error(`[LIOP-StreamBridge] Session closed: ${transport.sessionId}`);
82
+ log.info(`[LIOP-StreamBridge] Session closed: ${transport.sessionId}`);
82
83
  }
83
84
  };
84
85
  return transport;
@@ -109,7 +110,7 @@ export class LiopStreamBridge {
109
110
  const now = Date.now();
110
111
  for (const [sessionId, entry] of this.activeSessions) {
111
112
  if (now - entry.lastActivity > this.sessionTimeoutMs) {
112
- console.error(`[LIOP-StreamBridge] Evicting idle session: ${sessionId}`);
113
+ log.info(`[LIOP-StreamBridge] Evicting idle session: ${sessionId}`);
113
114
  entry.transport.close().catch(() => {
114
115
  /* Swallow close errors */
115
116
  });
@@ -124,10 +125,9 @@ export class LiopStreamBridge {
124
125
  const auth = c.req.header("Authorization");
125
126
  const expectedToken = process.env.ZERO_TRUST_TOKEN;
126
127
  if (expectedToken) {
127
- if (!auth ||
128
- !auth.startsWith("Bearer ") ||
128
+ if (!auth?.startsWith("Bearer ") ||
129
129
  auth.split(" ")[1] !== expectedToken) {
130
- console.error("[LIOP-StreamBridge] ALERT: Access denied - Invalid Zero-Trust token.");
130
+ log.info("[LIOP-StreamBridge] ALERT: Access denied - Invalid Zero-Trust token.");
131
131
  return c.json({ error: "Unauthorized: LIOP Zero-Trust Policy Enforced" }, 401);
132
132
  }
133
133
  }
@@ -149,7 +149,7 @@ export class LiopStreamBridge {
149
149
  // Explicitly clean up the session from the Map.
150
150
  if (c.req.method === "DELETE") {
151
151
  this.activeSessions.delete(sessionId);
152
- console.error(`[LIOP-StreamBridge] Session closed (DELETE): ${sessionId}`);
152
+ log.info(`[LIOP-StreamBridge] Session closed (DELETE): ${sessionId}`);
153
153
  }
154
154
  return response;
155
155
  }
@@ -158,7 +158,7 @@ export class LiopStreamBridge {
158
158
  const clientIp = this.getClientIp(c);
159
159
  const currentSessions = this.countSessionsByIp(clientIp);
160
160
  if (currentSessions >= this.maxSessionsPerIp) {
161
- console.error(`[LIOP-StreamBridge] Rate limit hit for IP: ${clientIp} (${currentSessions} sessions)`);
161
+ log.info(`[LIOP-StreamBridge] Rate limit hit for IP: ${clientIp} (${currentSessions} sessions)`);
162
162
  return c.json({ error: "Too Many Sessions: Rate limit exceeded" }, 429);
163
163
  }
164
164
  const transport = this.createSessionTransport(clientIp);
@@ -177,7 +177,7 @@ export class LiopStreamBridge {
177
177
  fetch: this.app.fetch,
178
178
  port: listenPort,
179
179
  }, (info) => {
180
- console.error(`[LIOP-StreamBridge] Streamable HTTP Gateway on http://localhost:${info.port}/mcp`);
180
+ log.info(`[LIOP-StreamBridge] Streamable HTTP Gateway on http://localhost:${info.port}/mcp`);
181
181
  resolve();
182
182
  });
183
183
  });
@@ -196,7 +196,7 @@ export class LiopStreamBridge {
196
196
  }
197
197
  if (this.httpServer) {
198
198
  this.httpServer.close();
199
- console.error("[LIOP-StreamBridge] HTTP ports released.");
199
+ log.info("[LIOP-StreamBridge] HTTP ports released.");
200
200
  }
201
201
  }
202
202
  }