@nekzus/liop 1.2.0-alpha.10 → 1.2.0-alpha.9
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/README.md +2 -11
- package/dist/bin/agent.js +51 -222
- package/dist/bridge/index.js +6 -7
- package/dist/bridge/stream.js +11 -11
- package/dist/client/index.js +35 -46
- package/dist/crypto/verifier.js +19 -7
- package/dist/gateway/hybrid.d.ts +1 -3
- package/dist/gateway/hybrid.js +13 -38
- package/dist/gateway/router.d.ts +9 -25
- package/dist/gateway/router.js +133 -484
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/mesh/node.d.ts +0 -16
- package/dist/mesh/node.js +113 -394
- package/dist/rpc/proto.js +1 -2
- package/dist/rpc/server.d.ts +1 -1
- package/dist/rpc/server.js +3 -4
- package/dist/rpc/tls.js +2 -3
- package/dist/sandbox/wasi.d.ts +1 -1
- package/dist/sandbox/wasi.js +2 -13
- package/dist/security/guardian.js +2 -3
- package/dist/security/zk.d.ts +3 -2
- package/dist/security/zk.js +9 -22
- package/dist/server/index.d.ts +3 -47
- package/dist/server/index.js +41 -350
- package/dist/server/pii.d.ts +0 -12
- package/dist/server/pii.js +0 -90
- package/dist/types.d.ts +0 -16
- package/dist/workers/logic-execution.d.ts +1 -1
- package/dist/workers/logic-execution.js +20 -38
- package/dist/workers/zk-verifier.js +33 -37
- package/package.json +2 -14
- package/dist/crypto/logic-image-id.d.ts +0 -3
- package/dist/crypto/logic-image-id.js +0 -27
- package/dist/economy/estimator.d.ts +0 -53
- package/dist/economy/estimator.js +0 -69
- package/dist/economy/index.d.ts +0 -5
- package/dist/economy/index.js +0 -3
- package/dist/economy/otel.d.ts +0 -38
- package/dist/economy/otel.js +0 -100
- package/dist/economy/telemetry.d.ts +0 -77
- package/dist/economy/telemetry.js +0 -224
- package/dist/prompts/adapters.d.ts +0 -16
- package/dist/prompts/adapters.js +0 -55
- package/dist/utils/logger.d.ts +0 -21
- package/dist/utils/logger.js +0 -70
- package/dist/utils/mcpCompact.d.ts +0 -11
- package/dist/utils/mcpCompact.js +0 -29
package/README.md
CHANGED
|
@@ -36,10 +36,9 @@ 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
|
|
39
|
+
| **PII Shield** | Multi-layer egress filter with NIST/OWASP patterns (Email, Credit Card with Luhn, IP, Phone) and configurable forbidden 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). |
|
|
43
42
|
| **MCP Bridge** | `LiopMcpBridge` adapts any `LiopServer` to the JSON-RPC 2.0 / stdio protocol used by Claude Desktop, Cursor, etc. |
|
|
44
43
|
| **Post-Quantum Ready** | ML-KEM-768 (Kyber) handshake + AES-256-GCM symmetric encryption for transport-layer security. |
|
|
45
44
|
| **P2P Mesh** | Kademlia DHT discovery via `libp2p` with TCP + WebSocket + Yamux multiplexing and Noise encryption. |
|
|
@@ -264,21 +263,13 @@ await bridge.connect();
|
|
|
264
263
|
Built-in patterns with multi-layer verification:
|
|
265
264
|
|
|
266
265
|
```typescript
|
|
267
|
-
import { PII_PATTERNS
|
|
266
|
+
import { PII_PATTERNS } from "@nekzus/liop/server";
|
|
268
267
|
|
|
269
268
|
// Available patterns:
|
|
270
269
|
PII_PATTERNS.EMAIL // RFC 5322 compliant, excludes @example.com/@test.com
|
|
271
270
|
PII_PATTERNS.CREDIT_CARD // Visa/MC/Amex + Luhn algorithm validation
|
|
272
271
|
PII_PATTERNS.IP_ADDRESS // IPv4 with octet range validation (excludes localhost)
|
|
273
272
|
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
|
|
282
273
|
```
|
|
283
274
|
|
|
284
275
|
### Forbidden Keys
|
package/dist/bin/agent.js
CHANGED
|
@@ -2,105 +2,9 @@
|
|
|
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";
|
|
6
5
|
import { LiopMcpRouter } from "../gateway/router.js";
|
|
7
6
|
import { MeshNode } from "../mesh/index.js";
|
|
8
7
|
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
|
-
}
|
|
104
8
|
/**
|
|
105
9
|
* LIOP Agent (Zero-Config CLI)
|
|
106
10
|
*
|
|
@@ -111,8 +15,6 @@ function industrialAddressMapper(addr) {
|
|
|
111
15
|
* No hardcoded tools, PeerIDs, or port mappings.
|
|
112
16
|
*/
|
|
113
17
|
async function main() {
|
|
114
|
-
const buildTime = new Date().toISOString();
|
|
115
|
-
log.info(`[LIOP-Agent] 🚀 Version 1.2.0-alpha.9 | Build: ${buildTime}`);
|
|
116
18
|
const liopDir = path.join(os.homedir(), ".liop");
|
|
117
19
|
const identityPath = path.join(liopDir, "identity.json");
|
|
118
20
|
if (!fs.existsSync(liopDir)) {
|
|
@@ -125,41 +27,31 @@ async function main() {
|
|
|
125
27
|
if (args.length > 0) {
|
|
126
28
|
bootstrapNodes = args.filter((a) => a.startsWith("/"));
|
|
127
29
|
}
|
|
128
|
-
//
|
|
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
|
|
129
35
|
if (bootstrapNodes.length === 0) {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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) {
|
|
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) {
|
|
145
47
|
try {
|
|
146
|
-
if (fs.existsSync(
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
}
|
|
159
|
-
}
|
|
160
|
-
// If we found any beacons in this directory, we consider discovery successful for this layer
|
|
161
|
-
if (bootstrapNodes.length > 0)
|
|
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}`);
|
|
162
53
|
break;
|
|
54
|
+
}
|
|
163
55
|
}
|
|
164
56
|
}
|
|
165
57
|
catch (_e) {
|
|
@@ -167,141 +59,78 @@ async function main() {
|
|
|
167
59
|
}
|
|
168
60
|
}
|
|
169
61
|
}
|
|
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
|
-
});
|
|
203
62
|
// If no bootstrap nodes found, the agent operates in standalone mode.
|
|
204
63
|
// It will only serve local tools until peers are discovered.
|
|
205
64
|
if (bootstrapNodes.length === 0) {
|
|
206
|
-
|
|
207
|
-
|
|
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.");
|
|
208
67
|
}
|
|
209
68
|
// Initialize local server node (lightweight, no tools registered locally)
|
|
210
69
|
const liopServer = new LiopServer({
|
|
211
70
|
name: "@nekzus/liop-agent",
|
|
212
71
|
version: "1.0.0",
|
|
213
72
|
});
|
|
214
|
-
// Enable Zero-Shot Autonomy (Industrial Prompt Injection)
|
|
215
|
-
liopServer.enableZeroShotAutonomy();
|
|
216
73
|
// 2. Mesh Node Configuration
|
|
217
74
|
const meshNode = new MeshNode({
|
|
218
75
|
identityPath: identityPath,
|
|
219
76
|
bootstrapNodes: bootstrapNodes,
|
|
220
|
-
addressMapper: industrialAddressMapper,
|
|
221
77
|
});
|
|
222
78
|
// Start P2P Mesh
|
|
223
79
|
await meshNode.start();
|
|
224
80
|
// 3. Initialize the Dynamic Router
|
|
225
81
|
// No hardcoded tools — all discovery happens via liop:manifest protocol
|
|
226
82
|
const router = new LiopMcpRouter(liopServer, meshNode);
|
|
227
|
-
// Proactive Notification to Claude Desktop when tools
|
|
83
|
+
// Proactive Notification to Claude Desktop when tools are discovered dynamically
|
|
228
84
|
router.onToolsChanged = () => {
|
|
229
85
|
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`);
|
|
231
86
|
};
|
|
232
|
-
// Initial warming period (2s) then
|
|
233
|
-
//
|
|
234
|
-
// Uses exponential backoff to reduce polling load on stable meshes.
|
|
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.
|
|
235
89
|
setTimeout(() => {
|
|
236
90
|
// biome-ignore lint/suspicious/noExplicitAny: access internal for telemetry
|
|
237
91
|
const rtSize = meshNode.getRoutingTableSize?.() || 0;
|
|
238
|
-
|
|
92
|
+
console.error(`[LIOP-Agent] Warm-up complete. Routing Table size: ${rtSize}`);
|
|
239
93
|
router.refreshManifestCache(true).catch(() => { });
|
|
240
94
|
}, 2000);
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
});
|
|
95
|
+
setInterval(() => {
|
|
96
|
+
router.refreshManifestCache(true).catch(() => { });
|
|
97
|
+
}, 15000);
|
|
98
|
+
// 4. STDIO Transport implementation
|
|
270
99
|
process.stdout.on("error", (err) => {
|
|
271
100
|
if (err.code === "EPIPE") {
|
|
272
101
|
process.exit(0); // Graceful exit when Claude Desktop disconnects
|
|
273
102
|
}
|
|
274
103
|
});
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
if (!
|
|
104
|
+
process.stdin.on("data", async (data) => {
|
|
105
|
+
const payload = data.toString().trim();
|
|
106
|
+
if (!payload)
|
|
278
107
|
return;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
if (
|
|
284
|
-
|
|
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
|
+
}
|
|
285
117
|
}
|
|
286
118
|
}
|
|
119
|
+
catch (_err) {
|
|
120
|
+
// Silent catch for binary noise
|
|
121
|
+
}
|
|
287
122
|
}
|
|
288
|
-
catch (_err) {
|
|
289
|
-
// Silent catch for binary noise or malformed lines
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
rl.on("close", () => {
|
|
293
|
-
process.exit(0);
|
|
294
123
|
});
|
|
295
124
|
// Status directed only to stderr
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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");
|
|
299
128
|
process.on("SIGINT", async () => {
|
|
300
129
|
await meshNode.stop();
|
|
301
130
|
process.exit(0);
|
|
302
131
|
});
|
|
303
132
|
}
|
|
304
133
|
main().catch((err) => {
|
|
305
|
-
|
|
134
|
+
console.error(`[LIOP-Agent] Fatal Error: ${err.message}`);
|
|
306
135
|
process.exit(1);
|
|
307
136
|
});
|
package/dist/bridge/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
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";
|
|
4
3
|
/**
|
|
5
4
|
* LIOP MCP Bridge
|
|
6
5
|
* A bi-directional bridge that allows legacy MCP servers to join the LIOP mesh,
|
|
@@ -15,11 +14,11 @@ export class LiopMcpBridge {
|
|
|
15
14
|
// Determine mode: Exposing LIOP to MCP (Claude) or Wrapping MCP to LIOP (Mesh)
|
|
16
15
|
if (source instanceof LiopServer) {
|
|
17
16
|
this.liopServer = source;
|
|
18
|
-
|
|
17
|
+
console.error("[LIOP-Bridge] Mode: EXPOSE (LIOP -> MCP Stdio)");
|
|
19
18
|
}
|
|
20
19
|
else if (source instanceof McpServer) {
|
|
21
20
|
this.legacyMcpServer = source;
|
|
22
|
-
|
|
21
|
+
console.error("[LIOP-Bridge] Mode: WRAP (Legacy MCP -> LIOP Mesh)");
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
/**
|
|
@@ -48,7 +47,7 @@ export class LiopMcpBridge {
|
|
|
48
47
|
return null;
|
|
49
48
|
if (method === "initialize") {
|
|
50
49
|
return this.successResponse(id, {
|
|
51
|
-
protocolVersion: "2025-
|
|
50
|
+
protocolVersion: "2025-03-26",
|
|
52
51
|
capabilities: {
|
|
53
52
|
prompts: {},
|
|
54
53
|
resources: {},
|
|
@@ -168,7 +167,7 @@ export class LiopMcpBridge {
|
|
|
168
167
|
return true;
|
|
169
168
|
}
|
|
170
169
|
catch (e) {
|
|
171
|
-
|
|
170
|
+
console.error("[LIOP-Bridge] ZK-Verifier Failure:", e);
|
|
172
171
|
return false;
|
|
173
172
|
}
|
|
174
173
|
}
|
|
@@ -222,7 +221,7 @@ export class LiopMcpBridge {
|
|
|
222
221
|
terminal: false,
|
|
223
222
|
});
|
|
224
223
|
const shutdown = async () => {
|
|
225
|
-
|
|
224
|
+
console.error("[LIOP-Bridge] Disconnecting session...");
|
|
226
225
|
if (this.liopServer)
|
|
227
226
|
await this.liopServer.close();
|
|
228
227
|
process.exit(0);
|
|
@@ -241,7 +240,7 @@ export class LiopMcpBridge {
|
|
|
241
240
|
}
|
|
242
241
|
}
|
|
243
242
|
catch (e) {
|
|
244
|
-
|
|
243
|
+
console.error(`[LIOP-Bridge] Error: ${e.message}`);
|
|
245
244
|
}
|
|
246
245
|
});
|
|
247
246
|
}
|
package/dist/bridge/stream.js
CHANGED
|
@@ -3,7 +3,6 @@ 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";
|
|
7
6
|
import { LiopMcpBridge } from "./index.js";
|
|
8
7
|
const DEFAULT_MAX_SESSIONS_PER_IP = 10;
|
|
9
8
|
const DEFAULT_SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
@@ -54,7 +53,7 @@ export class LiopStreamBridge {
|
|
|
54
53
|
lastActivity: Date.now(),
|
|
55
54
|
clientIp,
|
|
56
55
|
});
|
|
57
|
-
|
|
56
|
+
console.error(`[LIOP-StreamBridge] Session opened: ${sessionId} (IP: ${clientIp})`);
|
|
58
57
|
},
|
|
59
58
|
});
|
|
60
59
|
// Wire the transport's incoming messages to the LiopMcpBridge JSON-RPC router
|
|
@@ -73,13 +72,13 @@ export class LiopStreamBridge {
|
|
|
73
72
|
}
|
|
74
73
|
}
|
|
75
74
|
catch (err) {
|
|
76
|
-
|
|
75
|
+
console.error("[LIOP-StreamBridge] JSON-RPC error:", err.message);
|
|
77
76
|
}
|
|
78
77
|
};
|
|
79
78
|
transport.onclose = () => {
|
|
80
79
|
if (transport.sessionId) {
|
|
81
80
|
this.activeSessions.delete(transport.sessionId);
|
|
82
|
-
|
|
81
|
+
console.error(`[LIOP-StreamBridge] Session closed: ${transport.sessionId}`);
|
|
83
82
|
}
|
|
84
83
|
};
|
|
85
84
|
return transport;
|
|
@@ -110,7 +109,7 @@ export class LiopStreamBridge {
|
|
|
110
109
|
const now = Date.now();
|
|
111
110
|
for (const [sessionId, entry] of this.activeSessions) {
|
|
112
111
|
if (now - entry.lastActivity > this.sessionTimeoutMs) {
|
|
113
|
-
|
|
112
|
+
console.error(`[LIOP-StreamBridge] Evicting idle session: ${sessionId}`);
|
|
114
113
|
entry.transport.close().catch(() => {
|
|
115
114
|
/* Swallow close errors */
|
|
116
115
|
});
|
|
@@ -125,9 +124,10 @@ export class LiopStreamBridge {
|
|
|
125
124
|
const auth = c.req.header("Authorization");
|
|
126
125
|
const expectedToken = process.env.ZERO_TRUST_TOKEN;
|
|
127
126
|
if (expectedToken) {
|
|
128
|
-
if (!auth
|
|
127
|
+
if (!auth ||
|
|
128
|
+
!auth.startsWith("Bearer ") ||
|
|
129
129
|
auth.split(" ")[1] !== expectedToken) {
|
|
130
|
-
|
|
130
|
+
console.error("[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
|
-
|
|
152
|
+
console.error(`[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
|
-
|
|
161
|
+
console.error(`[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
|
-
|
|
180
|
+
console.error(`[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
|
-
|
|
199
|
+
console.error("[LIOP-StreamBridge] HTTP ports released.");
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
}
|