@nekzus/liop 2.4.0-alpha.25 → 2.4.0-alpha.26

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 (61) hide show
  1. package/dist/bin/agent.js +27 -933
  2. package/dist/bridge.js +1 -5
  3. package/dist/chunk-2XHSALII.js +3 -0
  4. package/dist/chunk-2ZYX736J.js +2 -0
  5. package/dist/chunk-3WRKTVOF.js +2 -0
  6. package/dist/chunk-6KFKO6ZX.js +2 -0
  7. package/dist/chunk-6VK2XALI.js +2 -0
  8. package/dist/chunk-AYDF3IFZ.js +2 -0
  9. package/dist/chunk-CTTTLCGL.js +31 -0
  10. package/dist/chunk-ECRS6N33.js +4 -0
  11. package/dist/chunk-FDJMH4CB.js +2 -0
  12. package/dist/chunk-FTF6AMF6.js +0 -1
  13. package/dist/chunk-GWVSCZUS.js +2 -0
  14. package/dist/chunk-LEXMMIQI.js +15 -0
  15. package/dist/chunk-LM26HOJ6.js +31 -0
  16. package/dist/chunk-MQ5SU26J.js +2 -0
  17. package/dist/chunk-N35BC62H.js +2 -0
  18. package/dist/chunk-P6IJ3VBD.js +3 -0
  19. package/dist/chunk-TKLRW52V.js +2 -0
  20. package/dist/chunk-V35CXJQN.js +2 -0
  21. package/dist/chunk-WP5ER7RH.js +3 -0
  22. package/dist/chunk-XLVEDKM6.js +2 -0
  23. package/dist/chunk-Y675R34Z.js +2 -0
  24. package/dist/chunk-YD2AF2M6.js +46 -0
  25. package/dist/chunk-YLCNV764.js +3 -0
  26. package/dist/chunk-ZZMSFCJP.js +3 -0
  27. package/dist/client.js +1 -12
  28. package/dist/gateway.js +1 -13
  29. package/dist/index.js +4 -320
  30. package/dist/kyber-PULV7PQX.js +2 -0
  31. package/dist/mesh.js +1 -5
  32. package/dist/server.js +1 -10
  33. package/dist/types.js +1 -3
  34. package/dist/verifier-J4NQYZX5.js +2 -0
  35. package/dist/workers/logic-execution.js +1 -355
  36. package/dist/workers/zk-verifier.js +1 -275
  37. package/package.json +1 -1
  38. package/dist/chunk-3664H3CV.js +0 -136
  39. package/dist/chunk-3QH44NKZ.js +0 -115
  40. package/dist/chunk-6MYJBM2E.js +0 -818
  41. package/dist/chunk-7PITYWOV.js +0 -68
  42. package/dist/chunk-DSOB2JKM.js +0 -1507
  43. package/dist/chunk-ET6VRUK2.js +0 -58
  44. package/dist/chunk-FIVRGOA4.js +0 -280
  45. package/dist/chunk-IHKKWXHJ.js +0 -876
  46. package/dist/chunk-IK7CGWAZ.js +0 -24
  47. package/dist/chunk-JPWC6XG6.js +0 -941
  48. package/dist/chunk-K3DGJLUY.js +0 -106
  49. package/dist/chunk-KR5UZUEY.js +0 -98
  50. package/dist/chunk-NXCAFF73.js +0 -59
  51. package/dist/chunk-Q3FKHJ3B.js +0 -345
  52. package/dist/chunk-R52TZEIL.js +0 -426
  53. package/dist/chunk-S3RAZTEM.js +0 -807
  54. package/dist/chunk-V47P2AQR.js +0 -281
  55. package/dist/chunk-WXOWJXY6.js +0 -564
  56. package/dist/chunk-WXYDFCIW.js +0 -293
  57. package/dist/chunk-Y62GORQF.js +0 -51
  58. package/dist/chunk-ZP3VVXOI.js +0 -66
  59. package/dist/chunk-ZU3WVVLC.js +0 -3752
  60. package/dist/kyber-CGOA7Q6A.js +0 -3
  61. package/dist/verifier-55HW6AFN.js +0 -6
@@ -1,818 +0,0 @@
1
- import { LiopMcpRouter } from './chunk-DSOB2JKM.js';
2
- import { LIOP_SCOPES } from './chunk-NXCAFF73.js';
3
- import { meshPeersConnected, manifestCacheSize, protocolMetrics } from './chunk-Q3FKHJ3B.js';
4
- import { log } from './chunk-K3DGJLUY.js';
5
- import * as http from 'http';
6
- import * as http2 from 'http2';
7
- import * as net from 'net';
8
-
9
- // src/security/prm.ts
10
- function buildProtectedResourceMetadata(issuer, audience) {
11
- return {
12
- resource: audience,
13
- authorization_servers: [issuer],
14
- scopes_supported: LIOP_SCOPES,
15
- bearer_methods_supported: ["header"],
16
- resource_documentation: "https://github.com/nekzus/liop"
17
- };
18
- }
19
-
20
- // src/gateway/grpc-web.ts
21
- var GRPC_WEB_CONSTANTS = {
22
- FLAG_DATA: 0,
23
- FLAG_TRAILERS: 128,
24
- HEADER_LENGTH: 5,
25
- CONTENT_TYPE_PREFIX: "application/grpc-web",
26
- STATUS_OK: 0,
27
- STATUS_CANCELLED: 1,
28
- STATUS_UNKNOWN: 2,
29
- STATUS_INVALID_ARGUMENT: 3,
30
- STATUS_DEADLINE_EXCEEDED: 4,
31
- STATUS_NOT_FOUND: 5,
32
- STATUS_PERMISSION_DENIED: 7,
33
- STATUS_UNAUTHENTICATED: 16,
34
- STATUS_INTERNAL: 13
35
- };
36
- function isGrpcWebRequest(contentType) {
37
- if (!contentType) return false;
38
- const lower = contentType.toLowerCase();
39
- return lower.startsWith("application/grpc-web") || lower.startsWith("application/grpc-web+proto") || lower.startsWith("application/grpc-web+json");
40
- }
41
- function encodeDataFrame(payload) {
42
- const buf = Buffer.isBuffer(payload) ? payload : Buffer.from(payload);
43
- const header = Buffer.alloc(GRPC_WEB_CONSTANTS.HEADER_LENGTH);
44
- header.writeUInt8(GRPC_WEB_CONSTANTS.FLAG_DATA, 0);
45
- header.writeUInt32BE(buf.length, 1);
46
- return Buffer.concat([header, buf]);
47
- }
48
- function encodeTrailerFrame(status = GRPC_WEB_CONSTANTS.STATUS_OK, message = "", customTrailers = {}) {
49
- const lines = [
50
- `grpc-status:${status}`,
51
- `grpc-message:${encodeURIComponent(message)}`
52
- ];
53
- for (const [key, value] of Object.entries(customTrailers)) {
54
- lines.push(`${key}:${value}`);
55
- }
56
- const trailerText = `${lines.join("\r\n")}\r
57
- `;
58
- const trailerBuf = Buffer.from(trailerText, "utf-8");
59
- const header = Buffer.alloc(GRPC_WEB_CONSTANTS.HEADER_LENGTH);
60
- header.writeUInt8(GRPC_WEB_CONSTANTS.FLAG_TRAILERS, 0);
61
- header.writeUInt32BE(trailerBuf.length, 1);
62
- return Buffer.concat([header, trailerBuf]);
63
- }
64
- function decodeFrames(buffer) {
65
- const frames = [];
66
- let offset = 0;
67
- while (offset + GRPC_WEB_CONSTANTS.HEADER_LENGTH <= buffer.length) {
68
- const flag = buffer.readUInt8(offset);
69
- const length = buffer.readUInt32BE(offset + 1);
70
- const payloadStart = offset + GRPC_WEB_CONSTANTS.HEADER_LENGTH;
71
- const payloadEnd = payloadStart + length;
72
- if (payloadEnd > buffer.length) {
73
- break;
74
- }
75
- const payload = buffer.subarray(payloadStart, payloadEnd);
76
- frames.push({
77
- isTrailer: (flag & GRPC_WEB_CONSTANTS.FLAG_TRAILERS) === GRPC_WEB_CONSTANTS.FLAG_TRAILERS,
78
- payload: Buffer.from(payload)
79
- });
80
- offset = payloadEnd;
81
- }
82
- return frames;
83
- }
84
- async function dispatchGrpcWebRequest(req, res, handler) {
85
- const chunks = [];
86
- req.on("data", (chunk) => {
87
- chunks.push(chunk);
88
- });
89
- req.on("end", async () => {
90
- try {
91
- const fullBody = Buffer.concat(chunks);
92
- const frames = decodeFrames(fullBody);
93
- const dataPayload = frames.find((f) => !f.isTrailer)?.payload ?? Buffer.alloc(0);
94
- const requestPath = req.url || "/";
95
- log.info(
96
- `[gRPC-Web] Handling request for ${requestPath} (${dataPayload.length} bytes)`
97
- );
98
- const result = await handler(requestPath, dataPayload);
99
- res.statusCode = 200;
100
- res.setHeader("content-type", "application/grpc-web+proto");
101
- res.setHeader("access-control-allow-origin", "*");
102
- res.setHeader(
103
- "access-control-expose-headers",
104
- "grpc-status, grpc-message, content-type"
105
- );
106
- if (result.data && result.data.length > 0) {
107
- res.write(encodeDataFrame(result.data));
108
- }
109
- res.write(encodeTrailerFrame(result.status, result.message || ""));
110
- res.end();
111
- } catch (error) {
112
- log.error(`[gRPC-Web] Dispatch error: ${error}`);
113
- res.statusCode = 200;
114
- res.setHeader("content-type", "application/grpc-web+proto");
115
- res.write(
116
- encodeTrailerFrame(
117
- GRPC_WEB_CONSTANTS.STATUS_INTERNAL,
118
- error.message || "Internal server error"
119
- )
120
- );
121
- res.end();
122
- }
123
- });
124
- }
125
-
126
- // src/gateway/interceptor.ts
127
- var DEFAULT_TIMEOUT_MS = 2500;
128
- async function executeGatewayInterceptor(request, context, options) {
129
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
130
- const failMode = options.failMode ?? "closed";
131
- const signal = AbortSignal.timeout(timeoutMs);
132
- const frozenRequest = Object.freeze(structuredClone(request));
133
- let onAbortListener;
134
- const abortPromise = new Promise((_, reject) => {
135
- onAbortListener = () => reject(signal.reason);
136
- signal.addEventListener("abort", onAbortListener, { once: true });
137
- });
138
- try {
139
- const result = await Promise.race([
140
- Promise.resolve(
141
- options.interceptor(frozenRequest, { ...context, signal })
142
- ),
143
- abortPromise
144
- ]);
145
- return result;
146
- } catch (error) {
147
- const errorMessage = error instanceof Error ? error.message : String(error);
148
- if (failMode === "open") {
149
- log.warn(
150
- `[LIOP-Gateway] Interceptor error (fail-open, allowing request): ${errorMessage}`
151
- );
152
- return {
153
- allowed: true,
154
- metadata: { interceptorError: errorMessage, failMode: "open" }
155
- };
156
- }
157
- log.error(
158
- `[LIOP-Gateway] Interceptor error (fail-closed, rejecting request): ${errorMessage}`
159
- );
160
- return {
161
- allowed: false,
162
- reason: `Interceptor unavailable: ${errorMessage}`,
163
- errorCode: -32098,
164
- metadata: { interceptorError: errorMessage, failMode: "closed" }
165
- };
166
- } finally {
167
- if (onAbortListener) {
168
- signal.removeEventListener("abort", onAbortListener);
169
- }
170
- }
171
- }
172
-
173
- // src/gateway/rate-limiter.ts
174
- var InMemoryRateLimiter = class {
175
- windowMs;
176
- maxRequests;
177
- records = /* @__PURE__ */ new Map();
178
- cleanupTimer;
179
- constructor(options = {}) {
180
- this.windowMs = options.windowMs ?? 6e4;
181
- this.maxRequests = options.maxRequests ?? 120;
182
- this.cleanupTimer = setInterval(() => this.cleanup(), 3e4);
183
- if (typeof this.cleanupTimer.unref === "function") {
184
- this.cleanupTimer.unref();
185
- }
186
- }
187
- /**
188
- * Evaluates whether a request from the given key (IP, token, or clientId) is permitted.
189
- *
190
- * @param key Identifier for the client (e.g., remote IP or OAuth client ID)
191
- * @returns Status containing allowed boolean, remaining quota, and time until reset
192
- */
193
- check(key) {
194
- const now = Date.now();
195
- let record = this.records.get(key);
196
- if (!record || now >= record.resetTime) {
197
- record = {
198
- count: 1,
199
- resetTime: now + this.windowMs
200
- };
201
- this.records.set(key, record);
202
- return {
203
- allowed: true,
204
- remaining: this.maxRequests - 1,
205
- limit: this.maxRequests,
206
- resetMs: this.windowMs
207
- };
208
- }
209
- if (record.count >= this.maxRequests) {
210
- return {
211
- allowed: false,
212
- remaining: 0,
213
- limit: this.maxRequests,
214
- resetMs: Math.max(0, record.resetTime - now)
215
- };
216
- }
217
- record.count++;
218
- return {
219
- allowed: true,
220
- remaining: this.maxRequests - record.count,
221
- limit: this.maxRequests,
222
- resetMs: Math.max(0, record.resetTime - now)
223
- };
224
- }
225
- /**
226
- * Explicitly resets the rate limit bucket for a given key.
227
- */
228
- reset(key) {
229
- this.records.delete(key);
230
- }
231
- /**
232
- * Cleans up expired rate-limiting buckets to prevent memory leaks.
233
- */
234
- cleanup() {
235
- const now = Date.now();
236
- for (const [key, record] of this.records.entries()) {
237
- if (now >= record.resetTime) {
238
- this.records.delete(key);
239
- }
240
- }
241
- }
242
- /**
243
- * Destroys internal timers and clears records.
244
- */
245
- close() {
246
- if (this.cleanupTimer) {
247
- clearInterval(this.cleanupTimer);
248
- this.cleanupTimer = void 0;
249
- }
250
- this.records.clear();
251
- }
252
- };
253
-
254
- // src/gateway/hybrid.ts
255
- var LiopHybridGateway = class {
256
- constructor(liopServer, meshNode = null, rpcPort = 50051, rateLimiterOptions, interceptorOptions) {
257
- this.liopServer = liopServer;
258
- this.meshNode = meshNode;
259
- this.jwtValidator = this.liopServer.jwtValidator;
260
- this.oauthProvider = this.liopServer.oauthProvider;
261
- this.rateLimiter = new InMemoryRateLimiter(rateLimiterOptions);
262
- this.interceptorOptions = interceptorOptions;
263
- this.router = new LiopMcpRouter(this.liopServer, this.meshNode, rpcPort);
264
- this.h2Server = http2.createServer();
265
- this.setupH2Routes();
266
- this.h1Server = http.createServer();
267
- this.setupH1Routes();
268
- this.netServer = net.createServer((socket) => {
269
- socket.once("data", (buffer) => {
270
- const isHttp2 = buffer.toString().startsWith("PRI * HTTP/2.0");
271
- log.info(
272
- `[LIOP-Gateway] Incoming L4 Connection. Protocol: ${isHttp2 ? "HTTP/2 (gRPC)" : "HTTP/1.1 (MCP)"}`
273
- );
274
- if (isHttp2) {
275
- this.h2Server.emit("connection", socket);
276
- } else {
277
- this.h1Server.emit("connection", socket);
278
- }
279
- socket.unshift(buffer);
280
- });
281
- socket.on(
282
- "error",
283
- (err) => log.error(`[LIOP-Gateway] NetServer Socket Error: ${err.message}`)
284
- );
285
- });
286
- this.h1Server.on(
287
- "error",
288
- (err) => log.error(`[LIOP-Gateway] H1 Server Error: ${err.message}`)
289
- );
290
- this.h2Server.on(
291
- "error",
292
- (err) => log.error(`[LIOP-Gateway] H2 Server Error: ${err.message}`)
293
- );
294
- log.info("[LIOP-Gateway] Hybrid adapter initialized.");
295
- }
296
- liopServer;
297
- meshNode;
298
- netServer;
299
- h2Server;
300
- h1Server;
301
- router;
302
- jwtValidator;
303
- // biome-ignore lint/suspicious/noExplicitAny: oidc-provider is loaded in Phase C
304
- oauthProvider;
305
- rateLimiter;
306
- interceptorOptions;
307
- isDraining = false;
308
- activeRequests = 0;
309
- setupH2Routes() {
310
- this.h2Server.on("stream", (stream, headers) => {
311
- const contentType = headers["content-type"];
312
- const path = headers[":path"];
313
- if (contentType === "application/grpc") {
314
- this.handleGrpcStream(stream);
315
- } else if (path === "/mcp") {
316
- this.handleMcpH2Stream(stream, headers);
317
- }
318
- });
319
- }
320
- setupH1Routes() {
321
- this.h1Server.on("request", async (req, res) => {
322
- this.activeRequests += 1;
323
- const onDone = () => {
324
- res.removeListener("finish", onDone);
325
- res.removeListener("close", onDone);
326
- this.activeRequests = Math.max(0, this.activeRequests - 1);
327
- };
328
- res.on("finish", onDone);
329
- res.on("close", onDone);
330
- const url = req.url || "";
331
- const method = req.method;
332
- if (method === "GET" && url === "/metrics") {
333
- if (this.meshNode) {
334
- meshPeersConnected.set({}, this.meshNode.getPeers().length);
335
- }
336
- if (this.router) {
337
- manifestCacheSize.set({}, this.router.getManifestCacheSize());
338
- }
339
- res.writeHead(200, {
340
- "Content-Type": "text/plain; version=0.0.4; charset=utf-8"
341
- });
342
- res.end(protocolMetrics.exportPrometheusText());
343
- return;
344
- }
345
- if (method === "GET" && url === "/healthz") {
346
- if (this.isDraining) {
347
- res.writeHead(503, { "Content-Type": "application/json" });
348
- res.end(
349
- JSON.stringify({
350
- status: "draining",
351
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
352
- })
353
- );
354
- return;
355
- }
356
- res.writeHead(200, { "Content-Type": "application/json" });
357
- res.end(
358
- JSON.stringify({
359
- status: "ok",
360
- uptime: Math.floor(process.uptime()),
361
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
362
- })
363
- );
364
- return;
365
- }
366
- if (method === "GET" && url === "/readyz") {
367
- if (this.isDraining) {
368
- res.writeHead(503, { "Content-Type": "application/json" });
369
- res.end(
370
- JSON.stringify({
371
- status: "draining",
372
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
373
- })
374
- );
375
- return;
376
- }
377
- const meshStarted = this.meshNode ? this.meshNode.isStarted() : true;
378
- if (!meshStarted) {
379
- res.writeHead(503, { "Content-Type": "application/json" });
380
- res.end(
381
- JSON.stringify({
382
- status: "unready",
383
- meshStarted: false,
384
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
385
- })
386
- );
387
- return;
388
- }
389
- res.writeHead(200, { "Content-Type": "application/json" });
390
- res.end(
391
- JSON.stringify({
392
- status: "ready",
393
- meshStarted: true,
394
- peers: this.meshNode ? this.meshNode.getPeers().length : 0,
395
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
396
- })
397
- );
398
- return;
399
- }
400
- if (this.isDraining && url === "/mcp") {
401
- res.writeHead(503, { "Content-Type": "application/json" });
402
- res.end(
403
- JSON.stringify({
404
- jsonrpc: "2.0",
405
- error: {
406
- code: -32e3,
407
- message: "Gateway is draining connections for shutdown"
408
- }
409
- })
410
- );
411
- return;
412
- }
413
- const contentType = req.headers["content-type"];
414
- if (isGrpcWebRequest(contentType)) {
415
- await dispatchGrpcWebRequest(req, res, async (reqPath, payload) => {
416
- log.info(
417
- `[LIOP-Gateway] gRPC-Web fallback call: ${reqPath} (${payload.length} bytes)`
418
- );
419
- return {
420
- status: GRPC_WEB_CONSTANTS.STATUS_OK,
421
- message: "OK",
422
- data: Buffer.from("")
423
- };
424
- });
425
- return;
426
- }
427
- if (url.startsWith("/oidc") && this.oauthProvider) {
428
- const callback = typeof this.oauthProvider.callback === "function" ? this.oauthProvider.callback() : this.oauthProvider;
429
- const originalUrl = req.url;
430
- req.url = (originalUrl || "").slice(5) || "/";
431
- try {
432
- return callback(req, res);
433
- } finally {
434
- req.url = originalUrl;
435
- }
436
- }
437
- if (method === "GET" && url === "/.well-known/oauth-protected-resource") {
438
- if (this.jwtValidator) {
439
- const prm = buildProtectedResourceMetadata(
440
- this.jwtValidator.getIssuer(),
441
- this.jwtValidator.getAudience()
442
- );
443
- res.writeHead(200, { "Content-Type": "application/json" });
444
- res.end(JSON.stringify(prm));
445
- return;
446
- }
447
- res.writeHead(404);
448
- res.end("Not Found");
449
- return;
450
- }
451
- if (method === "GET" && (url === "/" || url === "/mcp" || url === "/health")) {
452
- if (url === "/health" && req.headers.accept?.includes("application/json")) {
453
- const meshInfo = this.meshNode ? {
454
- peerId: this.meshNode.getPeerId()?.toString() || "",
455
- multiaddrs: this.meshNode.getMultiaddrs().map((m) => m.toString())
456
- } : null;
457
- const issuer = this.jwtValidator?.getIssuer();
458
- const baseUrl = issuer ? issuer.endsWith("/oidc") ? issuer : `${issuer}/oidc` : "";
459
- const authInfoResponse = this.jwtValidator && issuer ? {
460
- issuer,
461
- jwks_uri: `${baseUrl}/jwks`,
462
- ...this.oauthProvider ? {
463
- token_endpoint: `${baseUrl}/token`
464
- } : {}
465
- } : void 0;
466
- const topologyInfo = {
467
- tier: this.meshNode?.isPrivateNetwork?.() ? 1 : 2,
468
- prmEndpoint: this.jwtValidator ? "/.well-known/oauth-protected-resource" : void 0,
469
- transportCapabilities: ["http-mcp", "grpc-lio", "p2p-dht"]
470
- };
471
- res.writeHead(200, { "Content-Type": "application/json" });
472
- res.end(
473
- JSON.stringify({
474
- status: "healthy",
475
- node: this.liopServer.getServerInfo(),
476
- mesh: meshInfo,
477
- tools: this.liopServer.listTools().map((t) => t.name),
478
- auth: authInfoResponse,
479
- topology: topologyInfo,
480
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
481
- })
482
- );
483
- return;
484
- }
485
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
486
- res.end(`
487
- <body style="background:#0f172a;color:#f8fafc;font-family:sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;margin:0">
488
- <div style="background:#1e293b;padding:40px;border-radius:16px;border:1px solid #38bdf8;text-align:center;box-shadow:0 20px 25px -5px rgba(0,0,0,0.1)">
489
- <h1 style="color:#38bdf8;margin-top:0">LIOP Protocol Transformer</h1>
490
- <p style="opacity:0.8;font-weight:600">L4/L7 Transcoding: JSON-RPC &harr; gRPC</p>
491
- <p style="opacity:0.6;font-size:14px">Active Protections: Kyber768 + AES-256-GCM + ZK-Proof Ready</p>
492
- <div style="background:#0f172a;padding:15px;border-radius:8px;margin-top:20px;border:1px dashed #334155">
493
- <code style="color:#10b981">Endpoint: http://localhost:3000/mcp</code>
494
- </div>
495
- </div>
496
- </body>
497
- `);
498
- return;
499
- }
500
- if (url === "/mcp" && method === "POST") {
501
- let authInfo = null;
502
- if (this.jwtValidator) {
503
- const authHeader = req.headers.authorization;
504
- if (!authHeader?.startsWith("Bearer ")) {
505
- res.writeHead(401, {
506
- "WWW-Authenticate": 'Bearer error="invalid_token", error_description="Missing or malformed Authorization header"',
507
- "Content-Type": "application/json"
508
- });
509
- res.end(JSON.stringify({ error: "Unauthorized" }));
510
- return;
511
- }
512
- try {
513
- authInfo = await this.jwtValidator.validate(authHeader.slice(7));
514
- } catch (e) {
515
- res.writeHead(401, {
516
- "WWW-Authenticate": `Bearer error="invalid_token", error_description="${e.message}"`,
517
- "Content-Type": "application/json"
518
- });
519
- res.end(JSON.stringify({ error: "Invalid token" }));
520
- return;
521
- }
522
- }
523
- let body = "";
524
- req.on("data", (chunk) => body += chunk.toString());
525
- req.on("end", async () => {
526
- const clientIp = req.socket.remoteAddress || "127.0.0.1";
527
- const rateLimitKey = authInfo?.clientId ? `client:${authInfo.clientId}` : `ip:${clientIp}`;
528
- const rateStatus = this.rateLimiter.check(rateLimitKey);
529
- if (!rateStatus.allowed) {
530
- res.writeHead(429, {
531
- "Content-Type": "application/json",
532
- "Retry-After": Math.ceil(rateStatus.resetMs / 1e3).toString()
533
- });
534
- res.end(
535
- JSON.stringify({
536
- jsonrpc: "2.0",
537
- id: null,
538
- error: {
539
- code: -32029,
540
- message: "Rate limit exceeded. Too many requests."
541
- }
542
- })
543
- );
544
- return;
545
- }
546
- try {
547
- const jsonRequest = JSON.parse(body);
548
- if (this.interceptorOptions?.interceptor) {
549
- const verdict = await executeGatewayInterceptor(
550
- jsonRequest,
551
- { clientIp, authInfo, protocol: "http1" },
552
- this.interceptorOptions
553
- );
554
- if (!verdict.allowed) {
555
- res.writeHead(403, { "Content-Type": "application/json" });
556
- res.end(
557
- JSON.stringify({
558
- jsonrpc: "2.0",
559
- id: jsonRequest.id ?? null,
560
- error: {
561
- code: verdict.errorCode ?? -32099,
562
- message: verdict.reason || "Forbidden: Rejected by Gateway Interceptor"
563
- }
564
- })
565
- );
566
- return;
567
- }
568
- }
569
- const mcpMethod = req.headers["mcp-method"];
570
- const mcpName = req.headers["mcp-name"];
571
- const mcpVersion = req.headers["mcp-protocol-version"];
572
- if (mcpMethod && mcpMethod !== jsonRequest.method) {
573
- res.writeHead(400, { "Content-Type": "application/json" });
574
- res.end(
575
- JSON.stringify({
576
- jsonrpc: "2.0",
577
- id: jsonRequest.id ?? null,
578
- error: {
579
- code: -32020,
580
- message: `HeaderMismatch: 'Mcp-Method' header (${mcpMethod}) does not match JSON-RPC method (${jsonRequest.method})`
581
- }
582
- })
583
- );
584
- return;
585
- }
586
- if (mcpName && jsonRequest.params?.name && mcpName !== jsonRequest.params.name) {
587
- res.writeHead(400, { "Content-Type": "application/json" });
588
- res.end(
589
- JSON.stringify({
590
- jsonrpc: "2.0",
591
- id: jsonRequest.id ?? null,
592
- error: {
593
- code: -32020,
594
- message: `HeaderMismatch: 'Mcp-Name' header (${mcpName}) does not match target name (${jsonRequest.params.name})`
595
- }
596
- })
597
- );
598
- return;
599
- }
600
- if (mcpVersion && jsonRequest.params) {
601
- if (!jsonRequest.params._meta) {
602
- jsonRequest.params._meta = {};
603
- }
604
- if (!jsonRequest.params._meta["io.modelcontextprotocol/protocolVersion"]) {
605
- jsonRequest.params._meta["io.modelcontextprotocol/protocolVersion"] = mcpVersion;
606
- }
607
- }
608
- const response = await this.router.dispatch(jsonRequest, authInfo);
609
- res.writeHead(200, { "Content-Type": "application/json" });
610
- res.end(JSON.stringify(response));
611
- } catch (e) {
612
- log.info(
613
- `[LIOP-Gateway] Error processing JSON-RPC payload: ${e.message}`
614
- );
615
- res.writeHead(400);
616
- res.end(
617
- JSON.stringify({
618
- jsonrpc: "2.0",
619
- error: { code: -32700, message: "Parse error" }
620
- })
621
- );
622
- }
623
- });
624
- } else {
625
- res.writeHead(404);
626
- res.end("Not Found");
627
- }
628
- });
629
- }
630
- handleGrpcStream(stream) {
631
- stream.on("data", (chunk) => {
632
- const data = chunk;
633
- if (data)
634
- log.info(
635
- `[LIOP-Gateway] Native gRPC Proxy passing ${data.length} bytes`
636
- );
637
- });
638
- stream.respond({ ":status": 200, "content-type": "application/grpc" });
639
- stream.end();
640
- }
641
- handleMcpH2Stream(stream, headers) {
642
- let body = "";
643
- stream.on("data", (chunk) => body += chunk.toString());
644
- stream.on("end", async () => {
645
- try {
646
- let authInfo = null;
647
- if (this.jwtValidator) {
648
- const authHeader = headers.authorization;
649
- if (!authHeader?.startsWith("Bearer ")) {
650
- stream.respond({
651
- ":status": 401,
652
- "www-authenticate": 'Bearer error="invalid_token", error_description="Missing or malformed Authorization header"',
653
- "content-type": "application/json"
654
- });
655
- stream.end(JSON.stringify({ error: "Unauthorized" }));
656
- return;
657
- }
658
- try {
659
- authInfo = await this.jwtValidator.validate(authHeader.slice(7));
660
- } catch (e) {
661
- stream.respond({
662
- ":status": 401,
663
- "www-authenticate": `Bearer error="invalid_token", error_description="${e.message}"`,
664
- "content-type": "application/json"
665
- });
666
- stream.end(JSON.stringify({ error: "Invalid token" }));
667
- return;
668
- }
669
- }
670
- const jsonRequest = JSON.parse(body);
671
- if (this.interceptorOptions?.interceptor) {
672
- const h2ClientIp = stream.session?.socket?.remoteAddress || "127.0.0.1";
673
- const verdict = await executeGatewayInterceptor(
674
- jsonRequest,
675
- { clientIp: h2ClientIp, authInfo, protocol: "http2" },
676
- this.interceptorOptions
677
- );
678
- if (!verdict.allowed) {
679
- stream.respond({
680
- ":status": 403,
681
- "content-type": "application/json"
682
- });
683
- stream.end(
684
- JSON.stringify({
685
- jsonrpc: "2.0",
686
- id: jsonRequest.id ?? null,
687
- error: {
688
- code: verdict.errorCode ?? -32099,
689
- message: verdict.reason || "Forbidden: Rejected by Gateway Interceptor"
690
- }
691
- })
692
- );
693
- return;
694
- }
695
- }
696
- const mcpMethod = headers["mcp-method"];
697
- const mcpName = headers["mcp-name"];
698
- const mcpVersion = headers["mcp-protocol-version"];
699
- if (mcpMethod && mcpMethod !== jsonRequest.method) {
700
- stream.respond({
701
- ":status": 400,
702
- "content-type": "application/json"
703
- });
704
- stream.end(
705
- JSON.stringify({
706
- jsonrpc: "2.0",
707
- id: jsonRequest.id ?? null,
708
- error: {
709
- code: -32020,
710
- message: `HeaderMismatch: 'Mcp-Method' header (${mcpMethod}) does not match JSON-RPC method (${jsonRequest.method})`
711
- }
712
- })
713
- );
714
- return;
715
- }
716
- if (mcpName && jsonRequest.params?.name && mcpName !== jsonRequest.params.name) {
717
- stream.respond({
718
- ":status": 400,
719
- "content-type": "application/json"
720
- });
721
- stream.end(
722
- JSON.stringify({
723
- jsonrpc: "2.0",
724
- id: jsonRequest.id ?? null,
725
- error: {
726
- code: -32020,
727
- message: `HeaderMismatch: 'Mcp-Name' header (${mcpName}) does not match target name (${jsonRequest.params.name})`
728
- }
729
- })
730
- );
731
- return;
732
- }
733
- if (mcpVersion && jsonRequest.params) {
734
- if (!jsonRequest.params._meta) {
735
- jsonRequest.params._meta = {};
736
- }
737
- if (!jsonRequest.params._meta["io.modelcontextprotocol/protocolVersion"]) {
738
- jsonRequest.params._meta["io.modelcontextprotocol/protocolVersion"] = mcpVersion;
739
- }
740
- }
741
- const response = await this.router.dispatch(jsonRequest, authInfo);
742
- if (response) {
743
- stream.respond({
744
- ":status": 200,
745
- "content-type": "application/json"
746
- });
747
- stream.end(JSON.stringify(response));
748
- } else stream.close();
749
- } catch (_e) {
750
- stream.respond({ ":status": 400 });
751
- stream.end();
752
- }
753
- });
754
- }
755
- async listen(port, host = "0.0.0.0") {
756
- if (this.meshNode) {
757
- await this.meshNode.start();
758
- const tools = this.liopServer.listTools();
759
- for (const tool of tools) {
760
- await this.meshNode.announceCapability(tool.name);
761
- log.info(
762
- `[LIOP-Gateway] \u{1F4E1} Announced local tool to Mesh: ${tool.name}`
763
- );
764
- }
765
- }
766
- return new Promise((resolve, reject) => {
767
- this.netServer.on("error", (err) => {
768
- if (err.code === "EADDRINUSE") {
769
- log.info(
770
- `[LIOP-Gateway] FATAL: Port ${port} is already in use by another process.`
771
- );
772
- } else {
773
- log.error(`[LIOP-Gateway] Binding Error: ${err.message}`);
774
- }
775
- reject(err);
776
- });
777
- this.netServer.listen(port, host, () => {
778
- const addr = this.netServer.address();
779
- const actualHost = typeof addr === "string" ? addr : addr?.address || host;
780
- const assignedPort = typeof addr === "string" ? port : addr?.port || port;
781
- log.info(
782
- `[LIOP-Gateway] \u2705 Transformer Mesh Gateway READY and listening on ${actualHost}:${assignedPort}`
783
- );
784
- resolve(assignedPort);
785
- });
786
- });
787
- }
788
- isDrained() {
789
- return this.isDraining;
790
- }
791
- async drain(timeoutMs = 5e3) {
792
- this.isDraining = true;
793
- log.info(
794
- `[LIOP-Gateway] Draining gateway connections (active: ${this.activeRequests}, timeout: ${timeoutMs}ms)...`
795
- );
796
- const start = Date.now();
797
- while (this.activeRequests > 0 && Date.now() - start < timeoutMs) {
798
- await new Promise((resolve) => setTimeout(resolve, 50));
799
- }
800
- await this.stop();
801
- }
802
- async stop() {
803
- if (this.meshNode) {
804
- await this.meshNode.stop();
805
- }
806
- this.rateLimiter.close();
807
- this.netServer.close();
808
- this.h2Server.close();
809
- this.h1Server.close();
810
- }
811
- getRouter() {
812
- return this.router;
813
- }
814
- };
815
-
816
- export { GRPC_WEB_CONSTANTS, InMemoryRateLimiter, LiopHybridGateway, buildProtectedResourceMetadata, decodeFrames, dispatchGrpcWebRequest, encodeDataFrame, encodeTrailerFrame, executeGatewayInterceptor, isGrpcWebRequest };
817
- //# sourceMappingURL=chunk-6MYJBM2E.js.map
818
- //# sourceMappingURL=chunk-6MYJBM2E.js.map