imean-service-engine 1.7.2 → 1.8.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.
package/dist/mod.cjs CHANGED
@@ -14,10 +14,6 @@ var prettier = require('prettier');
14
14
  var crypto2 = require('crypto');
15
15
  var zlib = require('zlib');
16
16
  var nodeWs = require('@hono/node-ws');
17
- var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
18
- var types_js = require('@modelcontextprotocol/sdk/types.js');
19
- var streaming = require('hono/streaming');
20
- var ulid = require('ulid');
21
17
  var html = require('hono/html');
22
18
  var jsxRuntime = require('hono/jsx/jsx-runtime');
23
19
  var dayjs = require('dayjs');
@@ -92,8 +88,7 @@ function Action(options) {
92
88
  printError: options.printError ?? false,
93
89
  cache: options.cache,
94
90
  ttl: options.ttl,
95
- stream: options.stream,
96
- mcp: options.mcp
91
+ stream: options.stream
97
92
  };
98
93
  prototype[ACTION_METADATA] = existingMetadata;
99
94
  });
@@ -471,14 +466,14 @@ var ActionHandler = class {
471
466
  this.microservice = microservice;
472
467
  this.moduleName = moduleName;
473
468
  }
474
- async handle(req) {
469
+ async handle(req, ctx) {
475
470
  return await tracer2.startActiveSpan(
476
471
  `handle ${this.moduleName}.${this.actionName}`,
477
472
  async (span) => {
478
473
  span.setAttribute("module", this.moduleName);
479
474
  span.setAttribute("action", this.actionName);
480
475
  try {
481
- return await this._handle(req);
476
+ return await this._handle(req, ctx);
482
477
  } catch (error) {
483
478
  span.recordException(error);
484
479
  span.setStatus({
@@ -523,7 +518,7 @@ var ActionHandler = class {
523
518
  span.end();
524
519
  }
525
520
  }
526
- async _handle(req) {
521
+ async _handle(req, ctx) {
527
522
  const span = api.trace.getActiveSpan();
528
523
  const { args, requestHash } = await this._validate(req);
529
524
  const cacheHash = typeof this.metadata.cache === "function" ? this.metadata.cache(...args) : requestHash;
@@ -541,7 +536,7 @@ var ActionHandler = class {
541
536
  try {
542
537
  const result = await this.moduleInstance[this.actionName].apply(
543
538
  this.moduleInstance,
544
- args
539
+ args.concat(ctx)
545
540
  );
546
541
  if (this.metadata.stream) {
547
542
  span?.setAttribute("stream", true);
@@ -610,9 +605,10 @@ function Page(options) {
610
605
  existingMetadata[methodName] = {
611
606
  name: methodName,
612
607
  description: options.description || "",
613
- method: options.method,
608
+ method: options.method || "get",
614
609
  path: normalizePath(options.path),
615
- absolutePath: options.absolutePath ?? false
610
+ absolutePath: options.absolutePath ?? false,
611
+ middlewares: options.middlewares ?? []
616
612
  };
617
613
  prototype[PAGE_METADATA] = existingMetadata;
618
614
  });
@@ -648,6 +644,9 @@ var PageHandler = class {
648
644
  this.moduleInstance,
649
645
  [ctx]
650
646
  );
647
+ if (result instanceof Response) {
648
+ return result;
649
+ }
651
650
  return ctx.html(result);
652
651
  } catch (error) {
653
652
  span.recordException(error);
@@ -664,8 +663,9 @@ var PageHandler = class {
664
663
  }
665
664
  };
666
665
  var WebSocketHandler = class {
667
- constructor(microservice, options) {
666
+ constructor(microservice, ctx, options) {
668
667
  this.microservice = microservice;
668
+ this.ctx = ctx;
669
669
  this.options = {
670
670
  timeout: options?.timeout || 3e4
671
671
  };
@@ -705,7 +705,7 @@ var WebSocketHandler = class {
705
705
  await this.sendMessage(ws, { type: "pong" });
706
706
  return;
707
707
  }
708
- const response = await this.handleRequest(ws, message);
708
+ const response = await this.handleRequest(ws, message, this.ctx);
709
709
  if (response) {
710
710
  await this.sendMessage(ws, response);
711
711
  }
@@ -732,7 +732,7 @@ var WebSocketHandler = class {
732
732
  onError(_error, ws) {
733
733
  this.onClose(ws);
734
734
  }
735
- async handleRequest(ws, message) {
735
+ async handleRequest(ws, message, ctx) {
736
736
  if (!message.id || !message.module || !message.action) {
737
737
  throw new Error("Invalid request message");
738
738
  }
@@ -768,7 +768,7 @@ var WebSocketHandler = class {
768
768
  message.action
769
769
  );
770
770
  const args = message.args ? Object.values(message.args) : [];
771
- const result = await handler.handle(args);
771
+ const result = await handler.handle(args, ctx);
772
772
  if (handler.metadata.stream) {
773
773
  try {
774
774
  for await (const value of result) {
@@ -929,20 +929,20 @@ var Microservice = class {
929
929
  );
930
930
  this.actionHandlers.set(`${moduleName}.${actionName}`, handler);
931
931
  logger_default.info(
932
- `[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description} ${actionMetadata.mcp ? "MCP:" + actionMetadata.mcp?.type : ""}`
932
+ `[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description}`
933
933
  );
934
934
  }
935
935
  for (const [_, page] of Object.entries(pages)) {
936
- const handler = new PageHandler(
937
- moduleInstance,
938
- page,
939
- moduleName
940
- );
936
+ const handler = new PageHandler(moduleInstance, page, moduleName);
941
937
  this.pageHandlers.set(`${moduleName}.${page.name}`, handler);
942
938
  const paths = Array.isArray(page.path) ? page.path : [page.path];
943
939
  for (let path of paths) {
944
940
  path = page.absolutePath ? path : `${this.options.prefix}${path}`;
945
- this.app[page.method](path, (ctx) => handler.handle(ctx));
941
+ this.app[page.method](
942
+ path,
943
+ ...page.middlewares ?? [],
944
+ (ctx) => handler.handle(ctx)
945
+ );
946
946
  logger_default.info(
947
947
  `[ \u6CE8\u518C\u9875\u9762 ] ${moduleName}.${page.name} ${page.method.toUpperCase()} ${page.path} ${page.description}`
948
948
  );
@@ -1004,11 +1004,10 @@ var Microservice = class {
1004
1004
  });
1005
1005
  this.app.post(`${prefix}/:moduleName/:actionName`, this.handleRequest);
1006
1006
  if (this.options.websocket?.enabled) {
1007
- this.wsHandler = new WebSocketHandler(this);
1008
1007
  this.app.get(
1009
1008
  `${prefix}/ws`,
1010
- this.nodeWebSocket.upgradeWebSocket((_ctx) => {
1011
- const wsHandler = new WebSocketHandler(this, {
1009
+ this.nodeWebSocket.upgradeWebSocket((ctx) => {
1010
+ const wsHandler = new WebSocketHandler(this, ctx, {
1012
1011
  timeout: this.options.websocket?.timeout
1013
1012
  });
1014
1013
  return {
@@ -1230,7 +1229,9 @@ Received SIGTERM signal`);
1230
1229
  async waitForActiveRequests() {
1231
1230
  const timeout = this.options.gracefulShutdown?.timeout || 3e4;
1232
1231
  const startTime = Date.now();
1233
- logger_default.info(`Waiting for ${this.activeRequests.size} active requests to complete...`);
1232
+ logger_default.info(
1233
+ `Waiting for ${this.activeRequests.size} active requests to complete...`
1234
+ );
1234
1235
  return new Promise((resolve) => {
1235
1236
  const checkInterval = setInterval(() => {
1236
1237
  const elapsed = Date.now() - startTime;
@@ -1240,10 +1241,14 @@ Received SIGTERM signal`);
1240
1241
  resolve();
1241
1242
  } else if (elapsed >= timeout) {
1242
1243
  clearInterval(checkInterval);
1243
- logger_default.warn(`Timeout waiting for requests to complete. ${this.activeRequests.size} requests still active`);
1244
+ logger_default.warn(
1245
+ `Timeout waiting for requests to complete. ${this.activeRequests.size} requests still active`
1246
+ );
1244
1247
  resolve();
1245
1248
  } else {
1246
- logger_default.info(`Still waiting for ${this.activeRequests.size} requests... (${elapsed}ms elapsed)`);
1249
+ logger_default.info(
1250
+ `Still waiting for ${this.activeRequests.size} requests... (${elapsed}ms elapsed)`
1251
+ );
1247
1252
  }
1248
1253
  }, 1e3);
1249
1254
  });
@@ -1265,7 +1270,10 @@ Received SIGTERM signal`);
1265
1270
  await Promise.race([
1266
1271
  Promise.resolve(hook.cleanup()),
1267
1272
  new Promise(
1268
- (_, reject) => setTimeout(() => reject(new Error(`Cleanup hook ${hook.name} timeout`)), timeout)
1273
+ (_, reject) => setTimeout(
1274
+ () => reject(new Error(`Cleanup hook ${hook.name} timeout`)),
1275
+ timeout
1276
+ )
1269
1277
  )
1270
1278
  ]);
1271
1279
  logger_default.info(`Cleanup hook ${hook.name} completed successfully`);
@@ -1346,10 +1354,13 @@ Received SIGTERM signal`);
1346
1354
  const requestId = crypto.randomUUID();
1347
1355
  const startTime = Date.now();
1348
1356
  if (this.status === "shutting_down") {
1349
- return ctx.json({
1350
- success: false,
1351
- error: "Service is shutting down"
1352
- }, 503);
1357
+ return ctx.json(
1358
+ {
1359
+ success: false,
1360
+ error: "Service is shutting down"
1361
+ },
1362
+ 503
1363
+ );
1353
1364
  }
1354
1365
  try {
1355
1366
  const paramsText = await ctx.req.text();
@@ -1360,7 +1371,7 @@ Received SIGTERM signal`);
1360
1371
  startTime,
1361
1372
  params: paramsText
1362
1373
  });
1363
- const result = await handler.handle(paramsText);
1374
+ const result = await handler.handle(paramsText, ctx);
1364
1375
  if (handler.metadata.stream) {
1365
1376
  const encoder = new TextEncoder();
1366
1377
  const microservice = this;
@@ -1419,121 +1430,6 @@ Received SIGTERM signal`);
1419
1430
  await this.waitingInitialization;
1420
1431
  }
1421
1432
  };
1422
- var HonoTransport = class {
1423
- constructor(url, stream, closeStream) {
1424
- this.url = url;
1425
- this.stream = stream;
1426
- this.closeStream = closeStream;
1427
- this.sessionId = ulid.ulid();
1428
- }
1429
- sessionId;
1430
- onclose;
1431
- onerror;
1432
- onmessage;
1433
- async start() {
1434
- await this.stream.writeSSE({
1435
- event: "endpoint",
1436
- data: `${encodeURI(this.url)}?sessionId=${this.sessionId}`
1437
- });
1438
- this.stream.onAbort(() => {
1439
- this.close();
1440
- });
1441
- }
1442
- async handleMessage(message) {
1443
- let parsedMessage;
1444
- try {
1445
- parsedMessage = types_js.JSONRPCMessageSchema.parse(message);
1446
- } catch (error) {
1447
- this.onerror?.(error);
1448
- throw error;
1449
- }
1450
- this.onmessage?.(parsedMessage);
1451
- }
1452
- async send(message) {
1453
- await this.stream.writeln(
1454
- `event: message
1455
- data: ${JSON.stringify(message)}
1456
-
1457
- `
1458
- );
1459
- }
1460
- async close() {
1461
- this.onclose?.();
1462
- this.closeStream();
1463
- }
1464
- };
1465
- var ModelContextProtocolPlugin = class extends Plugin {
1466
- mcpServer;
1467
- transports = {};
1468
- registerMcpTools(engine) {
1469
- const modules = engine.getModules(true);
1470
- for (const module of Object.values(modules)) {
1471
- for (const action of Object.values(module.actions)) {
1472
- if (action.mcp?.type !== "tool") {
1473
- continue;
1474
- }
1475
- const args = {};
1476
- const argsIndex = {};
1477
- for (const [index, param] of (action.params ?? []).entries()) {
1478
- args[param.description] = param;
1479
- argsIndex[param.description] = index;
1480
- }
1481
- this.mcpServer.tool(
1482
- `${module.name}.${action.name}`,
1483
- action.description ?? "",
1484
- args,
1485
- async (params) => {
1486
- const argsList = [];
1487
- for (const [key, value] of Object.entries(params)) {
1488
- argsList[argsIndex[key]] = value;
1489
- }
1490
- const result = await engine.getActionHandler(module.name, action.name).handle(argsList);
1491
- return {
1492
- content: [{ type: "text", text: JSON.stringify(result) }]
1493
- };
1494
- }
1495
- );
1496
- }
1497
- }
1498
- }
1499
- initialize = async (engine) => {
1500
- const app = engine.getApp();
1501
- this.mcpServer = new mcp_js.McpServer({
1502
- name: engine.options.name,
1503
- version: engine.options.version
1504
- });
1505
- this.registerMcpTools(engine);
1506
- app.get(`${engine.options.prefix}/mcp_sse`, async (ctx) => {
1507
- return streaming.streamSSE(ctx, async (stream) => {
1508
- return new Promise(async (resolve) => {
1509
- const transport = new HonoTransport(
1510
- `${engine.options.prefix}/mcp_messages`,
1511
- stream,
1512
- () => {
1513
- delete this.transports[transport.sessionId];
1514
- resolve();
1515
- }
1516
- );
1517
- this.transports[transport.sessionId] = transport;
1518
- await this.mcpServer.connect(transport);
1519
- });
1520
- });
1521
- });
1522
- app.post(`${engine.options.prefix}/mcp_messages`, async (ctx) => {
1523
- const sessionId = ctx.req.query("sessionId");
1524
- if (!sessionId) {
1525
- return ctx.text("No transport found for sessionId", 400);
1526
- }
1527
- const transport = this.transports[sessionId];
1528
- const message = await ctx.req.json();
1529
- await transport.handleMessage(message);
1530
- return ctx.text("Accepted", 202);
1531
- });
1532
- logger_default.info(
1533
- `ModelContextProtocolPlugin endpoint: ${engine.options.prefix}/mcp_sse`
1534
- );
1535
- };
1536
- };
1537
1433
  var DEFAULT_FAVICON = /* @__PURE__ */ jsxRuntime.jsx(
1538
1434
  "link",
1539
1435
  {
@@ -1746,7 +1642,6 @@ exports.CacheAdapter = CacheAdapter;
1746
1642
  exports.HtmxLayout = HtmxLayout;
1747
1643
  exports.MemoryCacheAdapter = MemoryCacheAdapter;
1748
1644
  exports.Microservice = Microservice;
1749
- exports.ModelContextProtocolPlugin = ModelContextProtocolPlugin;
1750
1645
  exports.Module = Module;
1751
1646
  exports.Page = Page;
1752
1647
  exports.PageRenderPlugin = PageRenderPlugin;
package/dist/mod.d.cts CHANGED
@@ -3,7 +3,7 @@ export * from 'zod';
3
3
  import { Redis, Cluster } from 'ioredis';
4
4
  import { LRUCache } from 'lru-cache';
5
5
  import { Lease, Etcd3 } from 'etcd3';
6
- import { Hono } from 'hono';
6
+ import { MiddlewareHandler, Context, Hono } from 'hono';
7
7
  export { default as dayjs } from 'dayjs';
8
8
  import winston from 'winston';
9
9
  import * as hono_utils_html from 'hono/utils/html';
@@ -27,9 +27,6 @@ declare class MemoryCacheAdapter extends CacheAdapter {
27
27
  }
28
28
 
29
29
  type CacheFn = (...args: any[]) => any;
30
- type McpOptions = {
31
- type: "tool";
32
- };
33
30
  interface ActionOptions {
34
31
  params?: z.ZodType<any>[];
35
32
  returns?: z.ZodType<any>;
@@ -39,16 +36,17 @@ interface ActionOptions {
39
36
  cache?: boolean | CacheFn;
40
37
  ttl?: number;
41
38
  stream?: boolean;
42
- mcp?: McpOptions;
43
39
  }
44
40
  interface PageOptions {
45
- method: "get" | "post" | "put" | "delete" | "patch" | "options";
41
+ method?: "get" | "post" | "put" | "delete" | "patch" | "options";
42
+ middlewares?: MiddlewareHandler[];
46
43
  path: string | string[];
47
44
  absolutePath?: boolean;
48
45
  description?: string;
49
46
  }
50
47
  interface PageMetadata extends PageOptions {
51
48
  name: string;
49
+ method: "get" | "post" | "put" | "delete" | "patch" | "options";
52
50
  }
53
51
  interface ActionMetadata extends ActionOptions {
54
52
  name: string;
@@ -213,7 +211,7 @@ declare class ActionHandler {
213
211
  private microservice;
214
212
  private moduleName;
215
213
  constructor(moduleInstance: any, actionName: string, metadata: ActionMetadata, microservice: Microservice, moduleName: string);
216
- handle(req: string | any[]): Promise<any>;
214
+ handle(req: string | any[], ctx: Context): Promise<any>;
217
215
  private _validate;
218
216
  private _handle;
219
217
  }
@@ -316,13 +314,6 @@ declare class Microservice {
316
314
  init(): Promise<void>;
317
315
  }
318
316
 
319
- declare class ModelContextProtocolPlugin extends Plugin {
320
- private mcpServer;
321
- private transports;
322
- private registerMcpTools;
323
- initialize: (engine: Microservice) => Promise<void>;
324
- }
325
-
326
317
  declare const BaseLayout: (props?: {
327
318
  children?: any;
328
319
  title?: string;
@@ -381,4 +372,4 @@ declare function Schedule(options: ScheduleOptions): Function;
381
372
 
382
373
  declare const logger: winston.Logger;
383
374
 
384
- export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, BaseLayout, CacheAdapter, type CacheFn, type CleanupHook, type EtcdConfig, type EventServiceInfo, HtmxLayout, type McpOptions, MemoryCacheAdapter, Microservice, type MicroserviceOptions, ModelContextProtocolPlugin, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Page, type PageMetadata, type PageOptions, PageRenderPlugin, type PageRenderPluginOptions, Plugin, type PreStartChecker, RedisCacheAdapter, type RequestInfo, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, ServiceInfoCards, type ServiceStats, ServiceStatusPage, type StatisticsEvent, type StreamResponse, logger, startCheck };
375
+ export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, BaseLayout, CacheAdapter, type CacheFn, type CleanupHook, type EtcdConfig, type EventServiceInfo, HtmxLayout, MemoryCacheAdapter, Microservice, type MicroserviceOptions, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Page, type PageMetadata, type PageOptions, PageRenderPlugin, type PageRenderPluginOptions, Plugin, type PreStartChecker, RedisCacheAdapter, type RequestInfo, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, ServiceInfoCards, type ServiceStats, ServiceStatusPage, type StatisticsEvent, type StreamResponse, logger, startCheck };
package/dist/mod.d.ts CHANGED
@@ -3,7 +3,7 @@ export * from 'zod';
3
3
  import { Redis, Cluster } from 'ioredis';
4
4
  import { LRUCache } from 'lru-cache';
5
5
  import { Lease, Etcd3 } from 'etcd3';
6
- import { Hono } from 'hono';
6
+ import { MiddlewareHandler, Context, Hono } from 'hono';
7
7
  export { default as dayjs } from 'dayjs';
8
8
  import winston from 'winston';
9
9
  import * as hono_utils_html from 'hono/utils/html';
@@ -27,9 +27,6 @@ declare class MemoryCacheAdapter extends CacheAdapter {
27
27
  }
28
28
 
29
29
  type CacheFn = (...args: any[]) => any;
30
- type McpOptions = {
31
- type: "tool";
32
- };
33
30
  interface ActionOptions {
34
31
  params?: z.ZodType<any>[];
35
32
  returns?: z.ZodType<any>;
@@ -39,16 +36,17 @@ interface ActionOptions {
39
36
  cache?: boolean | CacheFn;
40
37
  ttl?: number;
41
38
  stream?: boolean;
42
- mcp?: McpOptions;
43
39
  }
44
40
  interface PageOptions {
45
- method: "get" | "post" | "put" | "delete" | "patch" | "options";
41
+ method?: "get" | "post" | "put" | "delete" | "patch" | "options";
42
+ middlewares?: MiddlewareHandler[];
46
43
  path: string | string[];
47
44
  absolutePath?: boolean;
48
45
  description?: string;
49
46
  }
50
47
  interface PageMetadata extends PageOptions {
51
48
  name: string;
49
+ method: "get" | "post" | "put" | "delete" | "patch" | "options";
52
50
  }
53
51
  interface ActionMetadata extends ActionOptions {
54
52
  name: string;
@@ -213,7 +211,7 @@ declare class ActionHandler {
213
211
  private microservice;
214
212
  private moduleName;
215
213
  constructor(moduleInstance: any, actionName: string, metadata: ActionMetadata, microservice: Microservice, moduleName: string);
216
- handle(req: string | any[]): Promise<any>;
214
+ handle(req: string | any[], ctx: Context): Promise<any>;
217
215
  private _validate;
218
216
  private _handle;
219
217
  }
@@ -316,13 +314,6 @@ declare class Microservice {
316
314
  init(): Promise<void>;
317
315
  }
318
316
 
319
- declare class ModelContextProtocolPlugin extends Plugin {
320
- private mcpServer;
321
- private transports;
322
- private registerMcpTools;
323
- initialize: (engine: Microservice) => Promise<void>;
324
- }
325
-
326
317
  declare const BaseLayout: (props?: {
327
318
  children?: any;
328
319
  title?: string;
@@ -381,4 +372,4 @@ declare function Schedule(options: ScheduleOptions): Function;
381
372
 
382
373
  declare const logger: winston.Logger;
383
374
 
384
- export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, BaseLayout, CacheAdapter, type CacheFn, type CleanupHook, type EtcdConfig, type EventServiceInfo, HtmxLayout, type McpOptions, MemoryCacheAdapter, Microservice, type MicroserviceOptions, ModelContextProtocolPlugin, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Page, type PageMetadata, type PageOptions, PageRenderPlugin, type PageRenderPluginOptions, Plugin, type PreStartChecker, RedisCacheAdapter, type RequestInfo, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, ServiceInfoCards, type ServiceStats, ServiceStatusPage, type StatisticsEvent, type StreamResponse, logger, startCheck };
375
+ export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, BaseLayout, CacheAdapter, type CacheFn, type CleanupHook, type EtcdConfig, type EventServiceInfo, HtmxLayout, MemoryCacheAdapter, Microservice, type MicroserviceOptions, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Page, type PageMetadata, type PageOptions, PageRenderPlugin, type PageRenderPluginOptions, Plugin, type PreStartChecker, RedisCacheAdapter, type RequestInfo, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, ServiceInfoCards, type ServiceStats, ServiceStatusPage, type StatisticsEvent, type StreamResponse, logger, startCheck };
package/dist/mod.js CHANGED
@@ -14,10 +14,6 @@ import crypto2 from 'node:crypto';
14
14
  import { brotliDecompress } from 'node:zlib';
15
15
  import { brotliCompress, constants } from 'zlib';
16
16
  import { createNodeWebSocket } from '@hono/node-ws';
17
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
18
- import { JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
19
- import { streamSSE } from 'hono/streaming';
20
- import { ulid } from 'ulid';
21
17
  import { html } from 'hono/html';
22
18
  import { jsx, jsxs } from 'hono/jsx/jsx-runtime';
23
19
  export { default as dayjs } from 'dayjs';
@@ -83,8 +79,7 @@ function Action(options) {
83
79
  printError: options.printError ?? false,
84
80
  cache: options.cache,
85
81
  ttl: options.ttl,
86
- stream: options.stream,
87
- mcp: options.mcp
82
+ stream: options.stream
88
83
  };
89
84
  prototype[ACTION_METADATA] = existingMetadata;
90
85
  });
@@ -462,14 +457,14 @@ var ActionHandler = class {
462
457
  this.microservice = microservice;
463
458
  this.moduleName = moduleName;
464
459
  }
465
- async handle(req) {
460
+ async handle(req, ctx) {
466
461
  return await tracer2.startActiveSpan(
467
462
  `handle ${this.moduleName}.${this.actionName}`,
468
463
  async (span) => {
469
464
  span.setAttribute("module", this.moduleName);
470
465
  span.setAttribute("action", this.actionName);
471
466
  try {
472
- return await this._handle(req);
467
+ return await this._handle(req, ctx);
473
468
  } catch (error) {
474
469
  span.recordException(error);
475
470
  span.setStatus({
@@ -514,7 +509,7 @@ var ActionHandler = class {
514
509
  span.end();
515
510
  }
516
511
  }
517
- async _handle(req) {
512
+ async _handle(req, ctx) {
518
513
  const span = trace.getActiveSpan();
519
514
  const { args, requestHash } = await this._validate(req);
520
515
  const cacheHash = typeof this.metadata.cache === "function" ? this.metadata.cache(...args) : requestHash;
@@ -532,7 +527,7 @@ var ActionHandler = class {
532
527
  try {
533
528
  const result = await this.moduleInstance[this.actionName].apply(
534
529
  this.moduleInstance,
535
- args
530
+ args.concat(ctx)
536
531
  );
537
532
  if (this.metadata.stream) {
538
533
  span?.setAttribute("stream", true);
@@ -601,9 +596,10 @@ function Page(options) {
601
596
  existingMetadata[methodName] = {
602
597
  name: methodName,
603
598
  description: options.description || "",
604
- method: options.method,
599
+ method: options.method || "get",
605
600
  path: normalizePath(options.path),
606
- absolutePath: options.absolutePath ?? false
601
+ absolutePath: options.absolutePath ?? false,
602
+ middlewares: options.middlewares ?? []
607
603
  };
608
604
  prototype[PAGE_METADATA] = existingMetadata;
609
605
  });
@@ -639,6 +635,9 @@ var PageHandler = class {
639
635
  this.moduleInstance,
640
636
  [ctx]
641
637
  );
638
+ if (result instanceof Response) {
639
+ return result;
640
+ }
642
641
  return ctx.html(result);
643
642
  } catch (error) {
644
643
  span.recordException(error);
@@ -655,8 +654,9 @@ var PageHandler = class {
655
654
  }
656
655
  };
657
656
  var WebSocketHandler = class {
658
- constructor(microservice, options) {
657
+ constructor(microservice, ctx, options) {
659
658
  this.microservice = microservice;
659
+ this.ctx = ctx;
660
660
  this.options = {
661
661
  timeout: options?.timeout || 3e4
662
662
  };
@@ -696,7 +696,7 @@ var WebSocketHandler = class {
696
696
  await this.sendMessage(ws, { type: "pong" });
697
697
  return;
698
698
  }
699
- const response = await this.handleRequest(ws, message);
699
+ const response = await this.handleRequest(ws, message, this.ctx);
700
700
  if (response) {
701
701
  await this.sendMessage(ws, response);
702
702
  }
@@ -723,7 +723,7 @@ var WebSocketHandler = class {
723
723
  onError(_error, ws) {
724
724
  this.onClose(ws);
725
725
  }
726
- async handleRequest(ws, message) {
726
+ async handleRequest(ws, message, ctx) {
727
727
  if (!message.id || !message.module || !message.action) {
728
728
  throw new Error("Invalid request message");
729
729
  }
@@ -759,7 +759,7 @@ var WebSocketHandler = class {
759
759
  message.action
760
760
  );
761
761
  const args = message.args ? Object.values(message.args) : [];
762
- const result = await handler.handle(args);
762
+ const result = await handler.handle(args, ctx);
763
763
  if (handler.metadata.stream) {
764
764
  try {
765
765
  for await (const value of result) {
@@ -920,20 +920,20 @@ var Microservice = class {
920
920
  );
921
921
  this.actionHandlers.set(`${moduleName}.${actionName}`, handler);
922
922
  logger_default.info(
923
- `[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description} ${actionMetadata.mcp ? "MCP:" + actionMetadata.mcp?.type : ""}`
923
+ `[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description}`
924
924
  );
925
925
  }
926
926
  for (const [_, page] of Object.entries(pages)) {
927
- const handler = new PageHandler(
928
- moduleInstance,
929
- page,
930
- moduleName
931
- );
927
+ const handler = new PageHandler(moduleInstance, page, moduleName);
932
928
  this.pageHandlers.set(`${moduleName}.${page.name}`, handler);
933
929
  const paths = Array.isArray(page.path) ? page.path : [page.path];
934
930
  for (let path of paths) {
935
931
  path = page.absolutePath ? path : `${this.options.prefix}${path}`;
936
- this.app[page.method](path, (ctx) => handler.handle(ctx));
932
+ this.app[page.method](
933
+ path,
934
+ ...page.middlewares ?? [],
935
+ (ctx) => handler.handle(ctx)
936
+ );
937
937
  logger_default.info(
938
938
  `[ \u6CE8\u518C\u9875\u9762 ] ${moduleName}.${page.name} ${page.method.toUpperCase()} ${page.path} ${page.description}`
939
939
  );
@@ -995,11 +995,10 @@ var Microservice = class {
995
995
  });
996
996
  this.app.post(`${prefix}/:moduleName/:actionName`, this.handleRequest);
997
997
  if (this.options.websocket?.enabled) {
998
- this.wsHandler = new WebSocketHandler(this);
999
998
  this.app.get(
1000
999
  `${prefix}/ws`,
1001
- this.nodeWebSocket.upgradeWebSocket((_ctx) => {
1002
- const wsHandler = new WebSocketHandler(this, {
1000
+ this.nodeWebSocket.upgradeWebSocket((ctx) => {
1001
+ const wsHandler = new WebSocketHandler(this, ctx, {
1003
1002
  timeout: this.options.websocket?.timeout
1004
1003
  });
1005
1004
  return {
@@ -1221,7 +1220,9 @@ Received SIGTERM signal`);
1221
1220
  async waitForActiveRequests() {
1222
1221
  const timeout = this.options.gracefulShutdown?.timeout || 3e4;
1223
1222
  const startTime = Date.now();
1224
- logger_default.info(`Waiting for ${this.activeRequests.size} active requests to complete...`);
1223
+ logger_default.info(
1224
+ `Waiting for ${this.activeRequests.size} active requests to complete...`
1225
+ );
1225
1226
  return new Promise((resolve) => {
1226
1227
  const checkInterval = setInterval(() => {
1227
1228
  const elapsed = Date.now() - startTime;
@@ -1231,10 +1232,14 @@ Received SIGTERM signal`);
1231
1232
  resolve();
1232
1233
  } else if (elapsed >= timeout) {
1233
1234
  clearInterval(checkInterval);
1234
- logger_default.warn(`Timeout waiting for requests to complete. ${this.activeRequests.size} requests still active`);
1235
+ logger_default.warn(
1236
+ `Timeout waiting for requests to complete. ${this.activeRequests.size} requests still active`
1237
+ );
1235
1238
  resolve();
1236
1239
  } else {
1237
- logger_default.info(`Still waiting for ${this.activeRequests.size} requests... (${elapsed}ms elapsed)`);
1240
+ logger_default.info(
1241
+ `Still waiting for ${this.activeRequests.size} requests... (${elapsed}ms elapsed)`
1242
+ );
1238
1243
  }
1239
1244
  }, 1e3);
1240
1245
  });
@@ -1256,7 +1261,10 @@ Received SIGTERM signal`);
1256
1261
  await Promise.race([
1257
1262
  Promise.resolve(hook.cleanup()),
1258
1263
  new Promise(
1259
- (_, reject) => setTimeout(() => reject(new Error(`Cleanup hook ${hook.name} timeout`)), timeout)
1264
+ (_, reject) => setTimeout(
1265
+ () => reject(new Error(`Cleanup hook ${hook.name} timeout`)),
1266
+ timeout
1267
+ )
1260
1268
  )
1261
1269
  ]);
1262
1270
  logger_default.info(`Cleanup hook ${hook.name} completed successfully`);
@@ -1337,10 +1345,13 @@ Received SIGTERM signal`);
1337
1345
  const requestId = crypto.randomUUID();
1338
1346
  const startTime = Date.now();
1339
1347
  if (this.status === "shutting_down") {
1340
- return ctx.json({
1341
- success: false,
1342
- error: "Service is shutting down"
1343
- }, 503);
1348
+ return ctx.json(
1349
+ {
1350
+ success: false,
1351
+ error: "Service is shutting down"
1352
+ },
1353
+ 503
1354
+ );
1344
1355
  }
1345
1356
  try {
1346
1357
  const paramsText = await ctx.req.text();
@@ -1351,7 +1362,7 @@ Received SIGTERM signal`);
1351
1362
  startTime,
1352
1363
  params: paramsText
1353
1364
  });
1354
- const result = await handler.handle(paramsText);
1365
+ const result = await handler.handle(paramsText, ctx);
1355
1366
  if (handler.metadata.stream) {
1356
1367
  const encoder = new TextEncoder();
1357
1368
  const microservice = this;
@@ -1410,121 +1421,6 @@ Received SIGTERM signal`);
1410
1421
  await this.waitingInitialization;
1411
1422
  }
1412
1423
  };
1413
- var HonoTransport = class {
1414
- constructor(url, stream, closeStream) {
1415
- this.url = url;
1416
- this.stream = stream;
1417
- this.closeStream = closeStream;
1418
- this.sessionId = ulid();
1419
- }
1420
- sessionId;
1421
- onclose;
1422
- onerror;
1423
- onmessage;
1424
- async start() {
1425
- await this.stream.writeSSE({
1426
- event: "endpoint",
1427
- data: `${encodeURI(this.url)}?sessionId=${this.sessionId}`
1428
- });
1429
- this.stream.onAbort(() => {
1430
- this.close();
1431
- });
1432
- }
1433
- async handleMessage(message) {
1434
- let parsedMessage;
1435
- try {
1436
- parsedMessage = JSONRPCMessageSchema.parse(message);
1437
- } catch (error) {
1438
- this.onerror?.(error);
1439
- throw error;
1440
- }
1441
- this.onmessage?.(parsedMessage);
1442
- }
1443
- async send(message) {
1444
- await this.stream.writeln(
1445
- `event: message
1446
- data: ${JSON.stringify(message)}
1447
-
1448
- `
1449
- );
1450
- }
1451
- async close() {
1452
- this.onclose?.();
1453
- this.closeStream();
1454
- }
1455
- };
1456
- var ModelContextProtocolPlugin = class extends Plugin {
1457
- mcpServer;
1458
- transports = {};
1459
- registerMcpTools(engine) {
1460
- const modules = engine.getModules(true);
1461
- for (const module of Object.values(modules)) {
1462
- for (const action of Object.values(module.actions)) {
1463
- if (action.mcp?.type !== "tool") {
1464
- continue;
1465
- }
1466
- const args = {};
1467
- const argsIndex = {};
1468
- for (const [index, param] of (action.params ?? []).entries()) {
1469
- args[param.description] = param;
1470
- argsIndex[param.description] = index;
1471
- }
1472
- this.mcpServer.tool(
1473
- `${module.name}.${action.name}`,
1474
- action.description ?? "",
1475
- args,
1476
- async (params) => {
1477
- const argsList = [];
1478
- for (const [key, value] of Object.entries(params)) {
1479
- argsList[argsIndex[key]] = value;
1480
- }
1481
- const result = await engine.getActionHandler(module.name, action.name).handle(argsList);
1482
- return {
1483
- content: [{ type: "text", text: JSON.stringify(result) }]
1484
- };
1485
- }
1486
- );
1487
- }
1488
- }
1489
- }
1490
- initialize = async (engine) => {
1491
- const app = engine.getApp();
1492
- this.mcpServer = new McpServer({
1493
- name: engine.options.name,
1494
- version: engine.options.version
1495
- });
1496
- this.registerMcpTools(engine);
1497
- app.get(`${engine.options.prefix}/mcp_sse`, async (ctx) => {
1498
- return streamSSE(ctx, async (stream) => {
1499
- return new Promise(async (resolve) => {
1500
- const transport = new HonoTransport(
1501
- `${engine.options.prefix}/mcp_messages`,
1502
- stream,
1503
- () => {
1504
- delete this.transports[transport.sessionId];
1505
- resolve();
1506
- }
1507
- );
1508
- this.transports[transport.sessionId] = transport;
1509
- await this.mcpServer.connect(transport);
1510
- });
1511
- });
1512
- });
1513
- app.post(`${engine.options.prefix}/mcp_messages`, async (ctx) => {
1514
- const sessionId = ctx.req.query("sessionId");
1515
- if (!sessionId) {
1516
- return ctx.text("No transport found for sessionId", 400);
1517
- }
1518
- const transport = this.transports[sessionId];
1519
- const message = await ctx.req.json();
1520
- await transport.handleMessage(message);
1521
- return ctx.text("Accepted", 202);
1522
- });
1523
- logger_default.info(
1524
- `ModelContextProtocolPlugin endpoint: ${engine.options.prefix}/mcp_sse`
1525
- );
1526
- };
1527
- };
1528
1424
  var DEFAULT_FAVICON = /* @__PURE__ */ jsx(
1529
1425
  "link",
1530
1426
  {
@@ -1727,4 +1623,4 @@ async function startCheck(checkers, pass) {
1727
1623
  if (pass) await pass();
1728
1624
  }
1729
1625
 
1730
- export { Action, BaseLayout, CacheAdapter, HtmxLayout, MemoryCacheAdapter, Microservice, ModelContextProtocolPlugin, Module, Page, PageRenderPlugin, Plugin, RedisCacheAdapter, Schedule, ScheduleMode, ServiceContext, ServiceInfoCards, ServiceStatusPage, logger_default as logger, startCheck };
1626
+ export { Action, BaseLayout, CacheAdapter, HtmxLayout, MemoryCacheAdapter, Microservice, Module, Page, PageRenderPlugin, Plugin, RedisCacheAdapter, Schedule, ScheduleMode, ServiceContext, ServiceInfoCards, ServiceStatusPage, logger_default as logger, startCheck };
package/package.json CHANGED
@@ -1,87 +1,86 @@
1
- {
2
- "name": "imean-service-engine",
3
- "version": "1.7.2",
4
- "description": "microservice engine",
5
- "keywords": [
6
- "microservice",
7
- "websocket",
8
- "http",
9
- "node"
10
- ],
11
- "author": "imean",
12
- "type": "module",
13
- "license": "MIT",
14
- "repository": {
15
- "type": "git",
16
- "url": "git+https://git.imean.tech/imean/imean-microservice-framework.git"
17
- },
18
- "main": "dist/mod.js",
19
- "module": "dist/mod.js",
20
- "types": "dist/mod.d.ts",
21
- "exports": {
22
- ".": {
23
- "types": "./dist/mod.d.ts",
24
- "import": "./dist/mod.js",
25
- "require": "./dist/mod.cjs"
26
- }
27
- },
28
- "files": [
29
- "dist",
30
- "README.md",
31
- "LICENSE"
32
- ],
33
- "scripts": {
34
- "dev": "tsx watch dev/index.ts",
35
- "build": "tsup",
36
- "test": "vitest run",
37
- "prepublishOnly": "npm run build && npm run test"
38
- },
39
- "dependencies": {
40
- "@hono/node-server": "^1.13.7",
41
- "@hono/node-ws": "^1.0.6",
42
- "@modelcontextprotocol/sdk": "^1.8.0",
43
- "dayjs": "^1.11.13",
44
- "ejson": "^2.2.3",
45
- "etcd3": "^1.1.2",
46
- "fs-extra": "^11.3.0",
47
- "hono": "^4.6.17",
48
- "lru-cache": "^11.0.2",
49
- "prettier": "^3.4.2",
50
- "ulid": "^3.0.0",
51
- "winston": "^3.17.0",
52
- "zod": "^3.24.1"
53
- },
54
- "peerDependencies": {
55
- "@opentelemetry/api": "^1.x",
56
- "ioredis": "^5.6.0"
57
- },
58
- "devDependencies": {
59
- "@opentelemetry/auto-instrumentations-node": "^0.55.3",
60
- "@opentelemetry/exporter-logs-otlp-proto": "^0.57.1",
61
- "@opentelemetry/exporter-metrics-otlp-proto": "^0.57.1",
62
- "@opentelemetry/exporter-trace-otlp-proto": "^0.57.1",
63
- "@opentelemetry/instrumentation-winston": "^0.44.0",
64
- "@opentelemetry/sdk-logs": "^0.57.1",
65
- "@opentelemetry/sdk-metrics": "^1.30.1",
66
- "@opentelemetry/sdk-node": "^0.57.1",
67
- "@opentelemetry/sdk-trace-node": "^1.30.1",
68
- "@opentelemetry/winston-transport": "^0.10.0",
69
- "@types/ejson": "^2.2.2",
70
- "@types/fs-extra": "^11.0.4",
71
- "@types/ioredis-mock": "^8.2.5",
72
- "@types/node": "^20.0.0",
73
- "@vitest/coverage-v8": "^3.0.4",
74
- "imean-service-client": "^1.5.0",
75
- "ioredis-mock": "^8.9.0",
76
- "opentelemetry-instrumentation-fetch-node": "^1.2.3",
77
- "tslib": "^2.8.1",
78
- "tsup": "^8.0.1",
79
- "tsx": "^4.19.2",
80
- "typescript": "^5.3.3",
81
- "vite-tsconfig-paths": "^5.1.4",
82
- "vitest": "^3.0.3"
83
- },
84
- "engines": {
85
- "node": ">=20"
86
- }
87
- }
1
+ {
2
+ "name": "imean-service-engine",
3
+ "version": "1.8.0",
4
+ "description": "microservice engine",
5
+ "keywords": [
6
+ "microservice",
7
+ "websocket",
8
+ "http",
9
+ "node"
10
+ ],
11
+ "author": "imean",
12
+ "type": "module",
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://git.imean.tech/imean/imean-microservice-framework.git"
17
+ },
18
+ "main": "dist/mod.js",
19
+ "module": "dist/mod.js",
20
+ "types": "dist/mod.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/mod.d.ts",
24
+ "import": "./dist/mod.js",
25
+ "require": "./dist/mod.cjs"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "scripts": {
34
+ "dev": "tsx watch dev/index.ts",
35
+ "build": "tsup",
36
+ "test": "vitest run",
37
+ "prepublishOnly": "npm run build && npm run test"
38
+ },
39
+ "dependencies": {
40
+ "@hono/node-server": "^1.13.7",
41
+ "@hono/node-ws": "^1.0.6",
42
+ "dayjs": "^1.11.13",
43
+ "ejson": "^2.2.3",
44
+ "etcd3": "^1.1.2",
45
+ "fs-extra": "^11.3.0",
46
+ "hono": "^4.6.17",
47
+ "lru-cache": "^11.0.2",
48
+ "prettier": "^3.4.2",
49
+ "ulid": "^3.0.0",
50
+ "winston": "^3.17.0",
51
+ "zod": "^3.24.1"
52
+ },
53
+ "peerDependencies": {
54
+ "@opentelemetry/api": "^1.x",
55
+ "ioredis": "^5.6.0"
56
+ },
57
+ "devDependencies": {
58
+ "@opentelemetry/auto-instrumentations-node": "^0.55.3",
59
+ "@opentelemetry/exporter-logs-otlp-proto": "^0.57.1",
60
+ "@opentelemetry/exporter-metrics-otlp-proto": "^0.57.1",
61
+ "@opentelemetry/exporter-trace-otlp-proto": "^0.57.1",
62
+ "@opentelemetry/instrumentation-winston": "^0.44.0",
63
+ "@opentelemetry/sdk-logs": "^0.57.1",
64
+ "@opentelemetry/sdk-metrics": "^1.30.1",
65
+ "@opentelemetry/sdk-node": "^0.57.1",
66
+ "@opentelemetry/sdk-trace-node": "^1.30.1",
67
+ "@opentelemetry/winston-transport": "^0.10.0",
68
+ "@types/ejson": "^2.2.2",
69
+ "@types/fs-extra": "^11.0.4",
70
+ "@types/ioredis-mock": "^8.2.5",
71
+ "@types/node": "^20.0.0",
72
+ "@vitest/coverage-v8": "^3.0.4",
73
+ "imean-service-client": "^1.5.0",
74
+ "ioredis-mock": "^8.9.0",
75
+ "opentelemetry-instrumentation-fetch-node": "^1.2.3",
76
+ "tslib": "^2.8.1",
77
+ "tsup": "^8.0.1",
78
+ "tsx": "^4.19.2",
79
+ "typescript": "^5.3.3",
80
+ "vite-tsconfig-paths": "^5.1.4",
81
+ "vitest": "^3.0.3"
82
+ },
83
+ "engines": {
84
+ "node": ">=20"
85
+ }
86
+ }