@nekzus/liop 2.0.0-alpha.1 → 2.0.0-alpha.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.
- package/README.md +30 -20
- package/dist/bin/agent.d.ts +0 -1
- package/dist/bin/agent.js +5 -306
- package/dist/bin/agent.js.map +1 -0
- package/dist/{bridge/stream.d.ts → bridge.d.ts} +44 -3
- package/dist/bridge.js +2 -0
- package/dist/bridge.js.map +1 -0
- package/dist/chunk-7MAGL6ON.js +33 -0
- package/dist/chunk-7MAGL6ON.js.map +1 -0
- package/dist/chunk-ANFXJGMP.js +2 -0
- package/dist/chunk-ANFXJGMP.js.map +1 -0
- package/dist/chunk-DBXGYHKY.js +2 -0
- package/dist/chunk-DBXGYHKY.js.map +1 -0
- package/dist/chunk-FW6CICSY.js +29 -0
- package/dist/chunk-FW6CICSY.js.map +1 -0
- package/dist/chunk-HM77MWB6.js +2 -0
- package/dist/chunk-HM77MWB6.js.map +1 -0
- package/dist/chunk-HNDVAKEK.js +24 -0
- package/dist/chunk-HNDVAKEK.js.map +1 -0
- package/dist/chunk-HQZHZM6U.js +2 -0
- package/dist/chunk-HQZHZM6U.js.map +1 -0
- package/dist/chunk-JBMEAXYU.js +13 -0
- package/dist/chunk-JBMEAXYU.js.map +1 -0
- package/dist/chunk-LYULZHZO.js +3 -0
- package/dist/chunk-LYULZHZO.js.map +1 -0
- package/dist/chunk-P52IE4L6.js +2 -0
- package/dist/chunk-P52IE4L6.js.map +1 -0
- package/dist/chunk-PPCOS2NU.js +2 -0
- package/dist/chunk-PPCOS2NU.js.map +1 -0
- package/dist/chunk-RWRRBYG4.js +2 -0
- package/dist/chunk-RWRRBYG4.js.map +1 -0
- package/dist/chunk-S6RJHZV2.js +2 -0
- package/dist/chunk-S6RJHZV2.js.map +1 -0
- package/dist/chunk-UVTEJYHN.js +2 -0
- package/dist/chunk-UVTEJYHN.js.map +1 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -0
- package/dist/{gateway/router.d.ts → gateway.d.ts} +30 -5
- package/dist/gateway.js +2 -0
- package/dist/gateway.js.map +1 -0
- package/dist/{client/index.d.ts → index-CyxNLlz7.d.ts} +24 -5
- package/dist/index.d.ts +313 -12
- package/dist/index.js +31 -12
- package/dist/index.js.map +1 -0
- package/dist/kyber-2WDOTUQX.js +2 -0
- package/dist/kyber-2WDOTUQX.js.map +1 -0
- package/dist/{mesh/node.d.ts → mesh.d.ts} +5 -3
- package/dist/mesh.js +2 -0
- package/dist/mesh.js.map +1 -0
- package/dist/{server/index.d.ts → server.d.ts} +125 -12
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +17 -14
- package/dist/types.js +2 -26
- package/dist/types.js.map +1 -0
- package/dist/{crypto/verifier.d.ts → verifier-DTCD9imJ.d.ts} +3 -1
- package/dist/verifier-RQRYXA4C.js +2 -0
- package/dist/verifier-RQRYXA4C.js.map +1 -0
- package/dist/workers/logic-execution.d.ts +4 -2
- package/dist/workers/logic-execution.js +2 -123
- package/dist/workers/logic-execution.js.map +1 -0
- package/dist/workers/zk-verifier.d.ts +4 -2
- package/dist/workers/zk-verifier.js +2 -98
- package/dist/workers/zk-verifier.js.map +1 -0
- package/package.json +32 -19
- package/dist/bridge/index.d.ts +0 -37
- package/dist/bridge/index.js +0 -249
- package/dist/bridge/stream.js +0 -210
- package/dist/client/index.js +0 -275
- package/dist/crypto/logic-image-id.d.ts +0 -3
- package/dist/crypto/logic-image-id.js +0 -27
- package/dist/crypto/verifier.js +0 -97
- 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/errors.d.ts +0 -14
- package/dist/errors.js +0 -19
- package/dist/gateway/hybrid.d.ts +0 -23
- package/dist/gateway/hybrid.js +0 -199
- package/dist/gateway/router.js +0 -1054
- package/dist/mesh/index.d.ts +0 -1
- package/dist/mesh/index.js +0 -1
- package/dist/mesh/node.js +0 -853
- package/dist/prompts/adapters.d.ts +0 -16
- package/dist/prompts/adapters.js +0 -55
- package/dist/rpc/client.d.ts +0 -22
- package/dist/rpc/client.js +0 -40
- package/dist/rpc/codec/lpm.d.ts +0 -20
- package/dist/rpc/codec/lpm.js +0 -36
- package/dist/rpc/crypto/aes.d.ts +0 -22
- package/dist/rpc/crypto/aes.js +0 -47
- package/dist/rpc/crypto/kyber.d.ts +0 -27
- package/dist/rpc/crypto/kyber.js +0 -70
- package/dist/rpc/proto.d.ts +0 -2
- package/dist/rpc/proto.js +0 -33
- package/dist/rpc/server.d.ts +0 -13
- package/dist/rpc/server.js +0 -50
- package/dist/rpc/tls.d.ts +0 -26
- package/dist/rpc/tls.js +0 -54
- package/dist/rpc/types.d.ts +0 -28
- package/dist/rpc/types.js +0 -5
- package/dist/sandbox/guardian.d.ts +0 -18
- package/dist/sandbox/guardian.js +0 -58
- package/dist/sandbox/wasi.d.ts +0 -36
- package/dist/sandbox/wasi.js +0 -233
- package/dist/security/guardian.d.ts +0 -22
- package/dist/security/guardian.js +0 -52
- package/dist/security/zk.d.ts +0 -37
- package/dist/security/zk.js +0 -76
- package/dist/server/index.js +0 -1047
- package/dist/server/ner-scanner.d.ts +0 -29
- package/dist/server/ner-scanner.js +0 -141
- package/dist/server/pii.d.ts +0 -66
- package/dist/server/pii.js +0 -428
- 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/dist/bridge/index.js
DELETED
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { LiopServer } from "../server/index.js";
|
|
3
|
-
import { log } from "../utils/logger.js";
|
|
4
|
-
/**
|
|
5
|
-
* LIOP MCP Bridge
|
|
6
|
-
* A bi-directional bridge that allows legacy MCP servers to join the LIOP mesh,
|
|
7
|
-
* or exposes a LIOP server as an MCP-compatible stdio process for tools like Claude Desktop.
|
|
8
|
-
*/
|
|
9
|
-
export class LiopMcpBridge {
|
|
10
|
-
options;
|
|
11
|
-
liopServer = null;
|
|
12
|
-
legacyMcpServer = null;
|
|
13
|
-
constructor(source, options = {}) {
|
|
14
|
-
this.options = options;
|
|
15
|
-
// Determine mode: Exposing LIOP to MCP (Claude) or Wrapping MCP to LIOP (Mesh)
|
|
16
|
-
if (source instanceof LiopServer) {
|
|
17
|
-
this.liopServer = source;
|
|
18
|
-
log.info("[LIOP-Bridge] Mode: EXPOSE (LIOP -> MCP Stdio)");
|
|
19
|
-
}
|
|
20
|
-
else if (source instanceof McpServer) {
|
|
21
|
-
this.legacyMcpServer = source;
|
|
22
|
-
log.info("[LIOP-Bridge] Mode: WRAP (Legacy MCP -> LIOP Mesh)");
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Handles an incoming standard MCP JSON-RPC 2.0 payload.
|
|
27
|
-
* Pipes it to the underlying server (LIOP or Legacy MCP).
|
|
28
|
-
*/
|
|
29
|
-
async handleJsonRpcRequest(payload) {
|
|
30
|
-
const id = payload.id;
|
|
31
|
-
const method = payload.method;
|
|
32
|
-
const params = payload.params;
|
|
33
|
-
if (payload.jsonrpc !== "2.0") {
|
|
34
|
-
return this.errorResponse(id, -32600, "Invalid Request");
|
|
35
|
-
}
|
|
36
|
-
// Mode: EXPOSE (Standard behavior used by Claude Desktop)
|
|
37
|
-
if (this.liopServer) {
|
|
38
|
-
return this.handleLiopToMcp(id, method, params);
|
|
39
|
-
}
|
|
40
|
-
// Mode: WRAP (Redirecting via internal LiopServer after connect())
|
|
41
|
-
if (this.legacyMcpServer && this.liopServer) {
|
|
42
|
-
return this.handleLiopToMcp(id, method, params);
|
|
43
|
-
}
|
|
44
|
-
return this.errorResponse(id, -32601, "Bridge source not configured");
|
|
45
|
-
}
|
|
46
|
-
async handleLiopToMcp(id, method, params) {
|
|
47
|
-
if (!this.liopServer)
|
|
48
|
-
return null;
|
|
49
|
-
if (method === "initialize") {
|
|
50
|
-
return this.successResponse(id, {
|
|
51
|
-
protocolVersion: "2025-11-25",
|
|
52
|
-
capabilities: {
|
|
53
|
-
prompts: {},
|
|
54
|
-
resources: {},
|
|
55
|
-
tools: {},
|
|
56
|
-
},
|
|
57
|
-
serverInfo: this.liopServer.getServerInfo(),
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
if (method === "notifications/initialized")
|
|
61
|
-
return undefined;
|
|
62
|
-
if (method === "ping")
|
|
63
|
-
return this.successResponse(id, {});
|
|
64
|
-
if (method === "tools/list") {
|
|
65
|
-
const tools = this.liopServer.listTools();
|
|
66
|
-
return this.successResponse(id, { tools });
|
|
67
|
-
}
|
|
68
|
-
if (method === "resources/list") {
|
|
69
|
-
const resources = this.liopServer.listResources();
|
|
70
|
-
return this.successResponse(id, { resources });
|
|
71
|
-
}
|
|
72
|
-
if (method === "prompts/list") {
|
|
73
|
-
const prompts = this.liopServer.listPrompts();
|
|
74
|
-
return this.successResponse(id, { prompts });
|
|
75
|
-
}
|
|
76
|
-
if (method === "prompts/get") {
|
|
77
|
-
if (!params?.name) {
|
|
78
|
-
return this.errorResponse(id, -32602, "Missing prompt name");
|
|
79
|
-
}
|
|
80
|
-
try {
|
|
81
|
-
const result = await this.liopServer.getPrompt({
|
|
82
|
-
name: params.name,
|
|
83
|
-
arguments: params.arguments,
|
|
84
|
-
});
|
|
85
|
-
return this.successResponse(id, result);
|
|
86
|
-
}
|
|
87
|
-
catch (err) {
|
|
88
|
-
return this.errorResponse(id, -32000, err.message);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (method === "resources/read") {
|
|
92
|
-
if (!params?.uri) {
|
|
93
|
-
return this.errorResponse(id, -32602, "Missing resource URI");
|
|
94
|
-
}
|
|
95
|
-
try {
|
|
96
|
-
const result = await this.liopServer.readResource(params.uri);
|
|
97
|
-
return this.successResponse(id, result);
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
return this.errorResponse(id, -32000, err.message);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (method === "tools/call") {
|
|
104
|
-
if (!params?.name) {
|
|
105
|
-
return this.errorResponse(id, -32602, "Missing tool name");
|
|
106
|
-
}
|
|
107
|
-
const request = {
|
|
108
|
-
name: params.name,
|
|
109
|
-
arguments: params.arguments || {},
|
|
110
|
-
};
|
|
111
|
-
try {
|
|
112
|
-
const result = await this.liopServer.callTool(request);
|
|
113
|
-
const isVerified = await this.verifyZkReceipt(request, result);
|
|
114
|
-
if (!isVerified) {
|
|
115
|
-
return this.successResponse(id, {
|
|
116
|
-
content: [
|
|
117
|
-
{
|
|
118
|
-
type: "text",
|
|
119
|
-
text: "ALERT [LIOP ZERO-TRUST SHIELD] ZK Verification Failed. The mathematical ImageID does not match the original payload.",
|
|
120
|
-
},
|
|
121
|
-
],
|
|
122
|
-
isError: true,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
return this.successResponse(id, result);
|
|
126
|
-
}
|
|
127
|
-
catch (err) {
|
|
128
|
-
return this.errorResponse(id, -32000, err.message);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return this.errorResponse(id, -32601, "Method not found");
|
|
132
|
-
}
|
|
133
|
-
successResponse(id, result) {
|
|
134
|
-
return { jsonrpc: "2.0", id, result };
|
|
135
|
-
}
|
|
136
|
-
errorResponse(id, code, message) {
|
|
137
|
-
return { jsonrpc: "2.0", id, error: { code, message } };
|
|
138
|
-
}
|
|
139
|
-
async verifyZkReceipt(request, result) {
|
|
140
|
-
if (!request.arguments?.payload ||
|
|
141
|
-
typeof request.arguments.payload !== "string") {
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
try {
|
|
145
|
-
const payload = request.arguments.payload;
|
|
146
|
-
const contentText = result.content[0]?.text;
|
|
147
|
-
if (contentText && typeof contentText === "string") {
|
|
148
|
-
try {
|
|
149
|
-
const data = JSON.parse(contentText);
|
|
150
|
-
if (data.image_id || data.zk_receipt) {
|
|
151
|
-
// 1. Instantiate the Industrial Verifier ( backed by Piscina Worker Pool )
|
|
152
|
-
const { LiopVerifier } = await import("../crypto/verifier.js");
|
|
153
|
-
const verifier = new LiopVerifier();
|
|
154
|
-
// 2. Delegate the heavy mathematical check (ZK Journal + Seal)
|
|
155
|
-
const isAuthentic = await verifier.verifyZkReceipt(Buffer.from(payload, "utf-8"), data.image_id, Buffer.from(data.zk_receipt || "", "base64"));
|
|
156
|
-
if (!isAuthentic) {
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
data.audit_status =
|
|
160
|
-
"VERIFIED: ZK-Receipt & ImageID Mathematically Verified by LiopMcpBridge";
|
|
161
|
-
result.content[0].text = JSON.stringify(data);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
165
|
-
// Output not JSON
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
catch (e) {
|
|
171
|
-
log.info("[LIOP-Bridge] ZK-Verifier Failure:", e);
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Connects the bridge via stdio or Mesh depending on mode.
|
|
177
|
-
*/
|
|
178
|
-
async connect() {
|
|
179
|
-
// In WRAP mode, we actually need to create a LiopServer and join the mesh
|
|
180
|
-
if (this.legacyMcpServer) {
|
|
181
|
-
const { LiopServer } = await import("../server/index.js");
|
|
182
|
-
this.liopServer = new LiopServer(this.options.serverInfo || {
|
|
183
|
-
name: "liop-bridge",
|
|
184
|
-
version: "1.0.0",
|
|
185
|
-
}, { security: this.options.security });
|
|
186
|
-
if (this.options.publishToMesh) {
|
|
187
|
-
await this.liopServer.connect();
|
|
188
|
-
// Automatically Bridge Legacy Capabilities to LIOP Mesh
|
|
189
|
-
// biome-ignore lint/suspicious/noExplicitAny: Internal legacy MCP properties are completely opaque and unexported
|
|
190
|
-
const legacy = this.legacyMcpServer;
|
|
191
|
-
// 1. Sync Tools
|
|
192
|
-
if (legacy._registeredTools) {
|
|
193
|
-
for (const [name, tool] of Object.entries(legacy._registeredTools)) {
|
|
194
|
-
// biome-ignore lint/suspicious/noExplicitAny: Opaque legacy structure
|
|
195
|
-
const t = tool;
|
|
196
|
-
this.liopServer.tool(name, t.description || "", t.inputSchema || {},
|
|
197
|
-
// biome-ignore lint/suspicious/noExplicitAny: Opaque legacy callback args
|
|
198
|
-
async (args) => {
|
|
199
|
-
return await t.handler(args);
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
// 2. Sync Resources
|
|
204
|
-
if (legacy._registeredResources) {
|
|
205
|
-
for (const [uri, resource] of Object.entries(legacy._registeredResources)) {
|
|
206
|
-
// biome-ignore lint/suspicious/noExplicitAny: Opaque legacy structure
|
|
207
|
-
const r = resource;
|
|
208
|
-
this.liopServer.resource(r.name, uri, r.metadata?.description || "", r.metadata?.mimeType || "application/octet-stream", async () => {
|
|
209
|
-
const res = await r.readCallback(new URL(uri));
|
|
210
|
-
return res.contents[0].text;
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
// In EXPOSE mode, listen to stdio (Claude Desktop)
|
|
218
|
-
const readline = await import("node:readline");
|
|
219
|
-
const rl = readline.createInterface({
|
|
220
|
-
input: process.stdin,
|
|
221
|
-
output: process.stdout,
|
|
222
|
-
terminal: false,
|
|
223
|
-
});
|
|
224
|
-
const shutdown = async () => {
|
|
225
|
-
log.info("[LIOP-Bridge] Disconnecting session...");
|
|
226
|
-
if (this.liopServer)
|
|
227
|
-
await this.liopServer.close();
|
|
228
|
-
process.exit(0);
|
|
229
|
-
};
|
|
230
|
-
rl.on("close", shutdown);
|
|
231
|
-
process.on("SIGINT", shutdown);
|
|
232
|
-
process.on("SIGTERM", shutdown);
|
|
233
|
-
rl.on("line", async (line) => {
|
|
234
|
-
if (!line.trim())
|
|
235
|
-
return;
|
|
236
|
-
try {
|
|
237
|
-
const payload = JSON.parse(line);
|
|
238
|
-
const response = await this.handleJsonRpcRequest(payload);
|
|
239
|
-
if (response) {
|
|
240
|
-
process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
catch (e) {
|
|
244
|
-
log.error(`[LIOP-Bridge] Error: ${e.message}`);
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
export * from "./stream.js";
|
package/dist/bridge/stream.js
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { serve } from "@hono/node-server";
|
|
3
|
-
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
4
|
-
import { Hono } from "hono";
|
|
5
|
-
import { cors } from "hono/cors";
|
|
6
|
-
import { log } from "../utils/logger.js";
|
|
7
|
-
import { LiopMcpBridge } from "./index.js";
|
|
8
|
-
const DEFAULT_MAX_SESSIONS_PER_IP = 10;
|
|
9
|
-
const DEFAULT_SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
10
|
-
const EVICTION_INTERVAL_MS = 60 * 1000; // Check every minute
|
|
11
|
-
/**
|
|
12
|
-
* LiopStreamBridge
|
|
13
|
-
*
|
|
14
|
-
* Exposes a LiopServer over a remote HTTP network using the industry-standard
|
|
15
|
-
* MCP Streamable HTTP Transport + Hono JS.
|
|
16
|
-
*
|
|
17
|
-
* Supports concurrent multi-client connections via per-session transport instances (Map pattern).
|
|
18
|
-
* External agents connect using only a URL + Bearer Token (Zero-Trust).
|
|
19
|
-
*
|
|
20
|
-
* Security hardening:
|
|
21
|
-
* - Zero-Trust Bearer Token enforcement
|
|
22
|
-
* - Per-IP rate limiting on session creation
|
|
23
|
-
* - Automatic eviction of idle sessions (TTL)
|
|
24
|
-
*/
|
|
25
|
-
export class LiopStreamBridge {
|
|
26
|
-
options;
|
|
27
|
-
app;
|
|
28
|
-
httpServer = null;
|
|
29
|
-
bridgeLogic;
|
|
30
|
-
activeSessions;
|
|
31
|
-
evictionTimer = null;
|
|
32
|
-
maxSessionsPerIp;
|
|
33
|
-
sessionTimeoutMs;
|
|
34
|
-
constructor(internalServer, options = {}) {
|
|
35
|
-
this.options = options;
|
|
36
|
-
this.app = new Hono();
|
|
37
|
-
this.bridgeLogic = new LiopMcpBridge(internalServer);
|
|
38
|
-
this.activeSessions = new Map();
|
|
39
|
-
this.maxSessionsPerIp =
|
|
40
|
-
options.maxSessionsPerIp ?? DEFAULT_MAX_SESSIONS_PER_IP;
|
|
41
|
-
this.sessionTimeoutMs =
|
|
42
|
-
options.sessionTimeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS;
|
|
43
|
-
this.setupRoutes();
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Creates a new per-session transport instance and wires it to the LIOPMcpBridge logic.
|
|
47
|
-
*/
|
|
48
|
-
createSessionTransport(clientIp) {
|
|
49
|
-
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
50
|
-
sessionIdGenerator: () => randomUUID(),
|
|
51
|
-
onsessioninitialized: (sessionId) => {
|
|
52
|
-
this.activeSessions.set(sessionId, {
|
|
53
|
-
transport,
|
|
54
|
-
lastActivity: Date.now(),
|
|
55
|
-
clientIp,
|
|
56
|
-
});
|
|
57
|
-
log.info(`[LIOP-StreamBridge] Session opened: ${sessionId} (IP: ${clientIp})`);
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
// Wire the transport's incoming messages to the LiopMcpBridge JSON-RPC router
|
|
61
|
-
transport.onmessage = async (message) => {
|
|
62
|
-
// Touch activity timestamp on every message
|
|
63
|
-
if (transport.sessionId) {
|
|
64
|
-
const entry = this.activeSessions.get(transport.sessionId);
|
|
65
|
-
if (entry)
|
|
66
|
-
entry.lastActivity = Date.now();
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
const result = await this.bridgeLogic.handleJsonRpcRequest(message);
|
|
70
|
-
// Notifications return undefined — no response needed
|
|
71
|
-
if (result !== undefined) {
|
|
72
|
-
await transport.send(result);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
log.info("[LIOP-StreamBridge] JSON-RPC error:", err.message);
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
transport.onclose = () => {
|
|
80
|
-
if (transport.sessionId) {
|
|
81
|
-
this.activeSessions.delete(transport.sessionId);
|
|
82
|
-
log.info(`[LIOP-StreamBridge] Session closed: ${transport.sessionId}`);
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
return transport;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Returns the number of active sessions for a given IP.
|
|
89
|
-
*/
|
|
90
|
-
countSessionsByIp(ip) {
|
|
91
|
-
let count = 0;
|
|
92
|
-
for (const entry of this.activeSessions.values()) {
|
|
93
|
-
if (entry.clientIp === ip)
|
|
94
|
-
count++;
|
|
95
|
-
}
|
|
96
|
-
return count;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Extracts client IP from the request (supports X-Forwarded-For for reverse proxies).
|
|
100
|
-
*/
|
|
101
|
-
getClientIp(c) {
|
|
102
|
-
return (c.req.header("x-forwarded-for")?.split(",")[0]?.trim() ||
|
|
103
|
-
c.req.header("x-real-ip") ||
|
|
104
|
-
"unknown");
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Evicts sessions that have been idle longer than the configured timeout.
|
|
108
|
-
*/
|
|
109
|
-
evictIdleSessions() {
|
|
110
|
-
const now = Date.now();
|
|
111
|
-
for (const [sessionId, entry] of this.activeSessions) {
|
|
112
|
-
if (now - entry.lastActivity > this.sessionTimeoutMs) {
|
|
113
|
-
log.info(`[LIOP-StreamBridge] Evicting idle session: ${sessionId}`);
|
|
114
|
-
entry.transport.close().catch(() => {
|
|
115
|
-
/* Swallow close errors */
|
|
116
|
-
});
|
|
117
|
-
this.activeSessions.delete(sessionId);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
setupRoutes() {
|
|
122
|
-
this.app.use("*", cors());
|
|
123
|
-
// Initialize strict zero-trust token if not provided
|
|
124
|
-
if (!process.env.ZERO_TRUST_TOKEN) {
|
|
125
|
-
process.env.ZERO_TRUST_TOKEN = randomUUID();
|
|
126
|
-
log.info("=".repeat(60));
|
|
127
|
-
log.info("⚠️ STRICT ZERO-TRUST MODE ENABLED ⚠️");
|
|
128
|
-
log.info("No ZERO_TRUST_TOKEN found in environment.");
|
|
129
|
-
log.info("A secure ephemeral token has been generated for this session:");
|
|
130
|
-
log.info(`Token: ${process.env.ZERO_TRUST_TOKEN}`);
|
|
131
|
-
log.info("=".repeat(60));
|
|
132
|
-
}
|
|
133
|
-
// ZTA (Zero-Trust Architecture) Security Middleware
|
|
134
|
-
this.app.use("/mcp", async (c, next) => {
|
|
135
|
-
const auth = c.req.header("Authorization");
|
|
136
|
-
const expectedToken = process.env.ZERO_TRUST_TOKEN;
|
|
137
|
-
if (!auth?.startsWith("Bearer ") ||
|
|
138
|
-
auth.split(" ")[1] !== expectedToken) {
|
|
139
|
-
log.info("[LIOP-StreamBridge] ALERT: Access denied - Invalid Zero-Trust token.");
|
|
140
|
-
return c.json({ error: "Unauthorized: LIOP Zero-Trust Policy Enforced" }, 401);
|
|
141
|
-
}
|
|
142
|
-
await next();
|
|
143
|
-
});
|
|
144
|
-
// Multi-Session Streamable HTTP Handler
|
|
145
|
-
this.app.all("/mcp", async (c) => {
|
|
146
|
-
const sessionId = c.req.header("mcp-session-id");
|
|
147
|
-
// Route to existing session if session ID is present
|
|
148
|
-
if (sessionId) {
|
|
149
|
-
const existing = this.activeSessions.get(sessionId);
|
|
150
|
-
if (!existing) {
|
|
151
|
-
return c.json({ error: "Session not found" }, 404);
|
|
152
|
-
}
|
|
153
|
-
// Touch activity on every routed request
|
|
154
|
-
existing.lastActivity = Date.now();
|
|
155
|
-
const response = await existing.transport.handleRequest(c.req.raw);
|
|
156
|
-
// If DELETE, the transport closes internally but onclose may not fire.
|
|
157
|
-
// Explicitly clean up the session from the Map.
|
|
158
|
-
if (c.req.method === "DELETE") {
|
|
159
|
-
this.activeSessions.delete(sessionId);
|
|
160
|
-
log.info(`[LIOP-StreamBridge] Session closed (DELETE): ${sessionId}`);
|
|
161
|
-
}
|
|
162
|
-
return response;
|
|
163
|
-
}
|
|
164
|
-
// No session ID → New client initializing.
|
|
165
|
-
// Rate-limit: enforce max sessions per IP
|
|
166
|
-
const clientIp = this.getClientIp(c);
|
|
167
|
-
const currentSessions = this.countSessionsByIp(clientIp);
|
|
168
|
-
if (currentSessions >= this.maxSessionsPerIp) {
|
|
169
|
-
log.info(`[LIOP-StreamBridge] Rate limit hit for IP: ${clientIp} (${currentSessions} sessions)`);
|
|
170
|
-
return c.json({ error: "Too Many Sessions: Rate limit exceeded" }, 429);
|
|
171
|
-
}
|
|
172
|
-
const transport = this.createSessionTransport(clientIp);
|
|
173
|
-
return transport.handleRequest(c.req.raw);
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Starts the LiopStreamBridge HTTP server and session eviction timer.
|
|
178
|
-
*/
|
|
179
|
-
async start(port) {
|
|
180
|
-
const listenPort = port ?? this.options.port ?? 3000;
|
|
181
|
-
// Start the idle session eviction timer
|
|
182
|
-
this.evictionTimer = setInterval(() => this.evictIdleSessions(), EVICTION_INTERVAL_MS);
|
|
183
|
-
return new Promise((resolve) => {
|
|
184
|
-
this.httpServer = serve({
|
|
185
|
-
fetch: this.app.fetch,
|
|
186
|
-
port: listenPort,
|
|
187
|
-
}, (info) => {
|
|
188
|
-
log.info(`[LIOP-StreamBridge] Streamable HTTP Gateway on http://localhost:${info.port}/mcp`);
|
|
189
|
-
resolve();
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Graceful shutdown — closes all active sessions, stops timers, and releases port.
|
|
195
|
-
*/
|
|
196
|
-
async stop() {
|
|
197
|
-
if (this.evictionTimer) {
|
|
198
|
-
clearInterval(this.evictionTimer);
|
|
199
|
-
this.evictionTimer = null;
|
|
200
|
-
}
|
|
201
|
-
for (const [id, entry] of this.activeSessions) {
|
|
202
|
-
await entry.transport.close();
|
|
203
|
-
this.activeSessions.delete(id);
|
|
204
|
-
}
|
|
205
|
-
if (this.httpServer) {
|
|
206
|
-
this.httpServer.close();
|
|
207
|
-
log.info("[LIOP-StreamBridge] HTTP ports released.");
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|