imean-service-engine 1.3.0 → 1.4.1

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
@@ -13,6 +13,10 @@ var prettier = require('prettier');
13
13
  var crypto2 = require('crypto');
14
14
  var zlib = require('zlib');
15
15
  var nodeWs = require('@hono/node-ws');
16
+ var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
17
+ var types_js = require('@modelcontextprotocol/sdk/types.js');
18
+ var streaming = require('hono/streaming');
19
+ var ulid = require('ulid');
16
20
  var dayjs = require('dayjs');
17
21
 
18
22
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -85,7 +89,8 @@ function Action(options) {
85
89
  printError: options.printError ?? false,
86
90
  cache: options.cache,
87
91
  ttl: options.ttl,
88
- stream: options.stream
92
+ stream: options.stream,
93
+ mcp: options.mcp
89
94
  };
90
95
  prototype[ACTION_METADATA] = existingMetadata;
91
96
  });
@@ -121,6 +126,8 @@ var ScheduleMode = /* @__PURE__ */ ((ScheduleMode2) => {
121
126
  ScheduleMode2["FIXED_DELAY"] = "FIXED_DELAY";
122
127
  return ScheduleMode2;
123
128
  })(ScheduleMode || {});
129
+ var Plugin = class {
130
+ };
124
131
  var logger = winston__default.default.createLogger({
125
132
  level: "info",
126
133
  transports: [
@@ -520,6 +527,7 @@ var ActionHandler = class {
520
527
  const now = Date.now();
521
528
  if (cached !== null && (!this.metadata.ttl || cached?.expireAt > now)) {
522
529
  span?.setAttribute("cacheHit", true);
530
+ span?.setAttribute("cacheKey", cacheKey);
523
531
  return cached.data;
524
532
  }
525
533
  }
@@ -783,7 +791,16 @@ var Microservice = class {
783
791
  this.options = {
784
792
  prefix: "/api",
785
793
  name: "microservice",
794
+ version: "0.0.1",
795
+ env: "default",
796
+ printError: false,
797
+ disableCache: false,
798
+ generateClient: false,
799
+ etcd: false,
800
+ events: {},
801
+ websocket: { enabled: false },
786
802
  cacheAdapter: new MemoryCacheAdapter(),
803
+ plugins: [],
787
804
  ...options
788
805
  };
789
806
  this.cache = this.options.cacheAdapter;
@@ -802,6 +819,7 @@ var Microservice = class {
802
819
  this.initStatsEventManager();
803
820
  }
804
821
  await this.registerService(true);
822
+ await this.initPlugins();
805
823
  }
806
824
  async initModules() {
807
825
  for (const ModuleClass of this.options.modules) {
@@ -826,7 +844,7 @@ var Microservice = class {
826
844
  );
827
845
  this.actionHandlers.set(`${moduleName}.${actionName}`, handler);
828
846
  logger_default.info(
829
- `[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description}`
847
+ `[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description} ${actionMetadata.mcp ? "MCP:" + actionMetadata.mcp?.type : ""}`
830
848
  );
831
849
  }
832
850
  const schedules = getScheduleMetadata(ModuleClass.prototype);
@@ -1189,32 +1207,16 @@ Received SIGINT signal`);
1189
1207
  return ctx.json({ success: false, error: error.message });
1190
1208
  }
1191
1209
  };
1192
- async init() {
1193
- await this.waitingInitialization;
1194
- }
1195
- /**
1196
- * 获取所有注册的服务
1197
- */
1198
- async getServices() {
1199
- if (!this.etcdClient) {
1200
- throw new Error("etcd is not configured");
1210
+ async initPlugins() {
1211
+ for (const plugin of this.options.plugins) {
1212
+ logger_default.info(`[ \u521D\u59CB\u5316\u63D2\u4EF6 ] ${plugin.constructor.name}`);
1213
+ await plugin.initialize(this).catch((e) => {
1214
+ logger_default.error(`[ \u521D\u59CB\u5316\u63D2\u4EF6\u5931\u8D25 ] ${plugin.constructor.name}: ${e}`);
1215
+ });
1201
1216
  }
1202
- const namespace = this.options.etcd?.namespace ? `${this.options.etcd.namespace}/` : "";
1203
- const servicesKey = `${namespace}services`;
1204
- const services = await this.etcdClient.getAll().prefix(servicesKey);
1205
- return Object.values(services).map((value) => JSON.parse(value));
1206
1217
  }
1207
- /**
1208
- * 获取指定服务的实例
1209
- */
1210
- async getServiceInstances(serviceName) {
1211
- if (!this.etcdClient) {
1212
- throw new Error("etcd is not configured");
1213
- }
1214
- const namespace = this.options.etcd?.namespace ? `${this.options.etcd.namespace}/` : "";
1215
- const serviceKey = `${namespace}services/${serviceName}`;
1216
- const instances = await this.etcdClient.getAll().prefix(serviceKey);
1217
- return Object.values(instances).map((value) => JSON.parse(value));
1218
+ async init() {
1219
+ await this.waitingInitialization;
1218
1220
  }
1219
1221
  };
1220
1222
 
@@ -1239,6 +1241,122 @@ async function startCheck(checkers, pass) {
1239
1241
  logger_default.info("[ \u9884\u68C0\u5B8C\u6210 ]");
1240
1242
  if (pass) await pass();
1241
1243
  }
1244
+ var HonoTransport = class {
1245
+ constructor(url, stream, closeStream) {
1246
+ this.url = url;
1247
+ this.stream = stream;
1248
+ this.closeStream = closeStream;
1249
+ this.sessionId = ulid.ulid();
1250
+ }
1251
+ sessionId;
1252
+ onclose;
1253
+ onerror;
1254
+ onmessage;
1255
+ async start() {
1256
+ await this.stream.writeSSE({
1257
+ event: "endpoint",
1258
+ data: `${encodeURI(this.url)}?sessionId=${this.sessionId}`
1259
+ });
1260
+ this.stream.onAbort(() => {
1261
+ this.close();
1262
+ });
1263
+ }
1264
+ async handleMessage(message) {
1265
+ let parsedMessage;
1266
+ try {
1267
+ parsedMessage = types_js.JSONRPCMessageSchema.parse(message);
1268
+ } catch (error) {
1269
+ this.onerror?.(error);
1270
+ throw error;
1271
+ }
1272
+ this.onmessage?.(parsedMessage);
1273
+ }
1274
+ async send(message) {
1275
+ await this.stream.writeln(
1276
+ `event: message
1277
+ data: ${JSON.stringify(message)}
1278
+
1279
+ `
1280
+ );
1281
+ }
1282
+ async close() {
1283
+ this.onclose?.();
1284
+ this.closeStream();
1285
+ }
1286
+ };
1287
+ var ModelContextProtocolPlugin = class extends Plugin {
1288
+ mcpServer;
1289
+ transports = {};
1290
+ registerMcpTools(engine) {
1291
+ const modules = engine.getModules(true);
1292
+ for (const module of Object.values(modules)) {
1293
+ for (const action of Object.values(module.actions)) {
1294
+ if (action.mcp?.type !== "tool") {
1295
+ continue;
1296
+ }
1297
+ const args = {};
1298
+ const argsIndex = {};
1299
+ for (const [index, param] of (action.params ?? []).entries()) {
1300
+ args[param.description] = param;
1301
+ argsIndex[param.description] = index;
1302
+ }
1303
+ this.mcpServer.tool(
1304
+ `${module.name}.${action.name}`,
1305
+ action.description ?? "",
1306
+ args,
1307
+ async (params) => {
1308
+ const args2 = Object.values(params);
1309
+ const argsList = [];
1310
+ for (const [key, value] of Object.entries(args2)) {
1311
+ argsList[argsIndex[key]] = value;
1312
+ }
1313
+ const result = await engine.getActionHandler(module.name, action.name).handle(argsList);
1314
+ return {
1315
+ content: [{ type: "text", text: JSON.stringify(result) }]
1316
+ };
1317
+ }
1318
+ );
1319
+ }
1320
+ }
1321
+ }
1322
+ initialize = async (engine) => {
1323
+ const app = engine.getApp();
1324
+ this.mcpServer = new mcp_js.McpServer({
1325
+ name: engine.options.name,
1326
+ version: engine.options.version
1327
+ });
1328
+ this.registerMcpTools(engine);
1329
+ app.get(`${engine.options.prefix}/mcp_sse`, async (ctx) => {
1330
+ return streaming.streamSSE(ctx, async (stream) => {
1331
+ return new Promise(async (resolve) => {
1332
+ const transport = new HonoTransport(
1333
+ `${engine.options.prefix}/mcp_messages`,
1334
+ stream,
1335
+ () => {
1336
+ delete this.transports[transport.sessionId];
1337
+ resolve();
1338
+ }
1339
+ );
1340
+ this.transports[transport.sessionId] = transport;
1341
+ await this.mcpServer.connect(transport);
1342
+ });
1343
+ });
1344
+ });
1345
+ app.post(`${engine.options.prefix}/mcp_messages`, async (ctx) => {
1346
+ const sessionId = ctx.req.query("sessionId");
1347
+ if (!sessionId) {
1348
+ return ctx.text("No transport found for sessionId", 400);
1349
+ }
1350
+ const transport = this.transports[sessionId];
1351
+ const message = await ctx.req.json();
1352
+ await transport.handleMessage(message);
1353
+ return ctx.text("Accepted", 202);
1354
+ });
1355
+ logger_default.info(
1356
+ `ModelContextProtocolPlugin endpoint: ${engine.options.prefix}/mcp_sse`
1357
+ );
1358
+ };
1359
+ };
1242
1360
 
1243
1361
  Object.defineProperty(exports, "dayjs", {
1244
1362
  enumerable: true,
@@ -1248,7 +1366,9 @@ exports.Action = Action;
1248
1366
  exports.CacheAdapter = CacheAdapter;
1249
1367
  exports.MemoryCacheAdapter = MemoryCacheAdapter;
1250
1368
  exports.Microservice = Microservice;
1369
+ exports.ModelContextProtocolPlugin = ModelContextProtocolPlugin;
1251
1370
  exports.Module = Module;
1371
+ exports.Plugin = Plugin;
1252
1372
  exports.RedisCacheAdapter = RedisCacheAdapter;
1253
1373
  exports.Schedule = Schedule;
1254
1374
  exports.ScheduleMode = ScheduleMode;
package/dist/mod.d.cts CHANGED
@@ -25,6 +25,9 @@ declare class MemoryCacheAdapter extends CacheAdapter {
25
25
  }
26
26
 
27
27
  type CacheFn = (...args: any[]) => any;
28
+ type McpOptions = {
29
+ type: "tool";
30
+ };
28
31
  interface ActionOptions {
29
32
  params?: z.ZodType<any>[];
30
33
  returns?: z.ZodType<any>;
@@ -34,6 +37,7 @@ interface ActionOptions {
34
37
  cache?: boolean | CacheFn;
35
38
  ttl?: number;
36
39
  stream?: boolean;
40
+ mcp?: McpOptions;
37
41
  }
38
42
  interface ActionMetadata extends ActionOptions {
39
43
  name: string;
@@ -139,7 +143,7 @@ interface MicroserviceOptions {
139
143
  disableCache?: boolean;
140
144
  modules: (new () => any)[];
141
145
  generateClient?: URL | boolean;
142
- etcd?: EtcdConfig;
146
+ etcd?: EtcdConfig | false;
143
147
  events?: StatisticsEventOptions & {
144
148
  onStats?: (event: StatisticsEvent) => void | Promise<void>;
145
149
  onRegister?: (serviceInfo: ServiceInfo) => void | Promise<void>;
@@ -150,6 +154,7 @@ interface MicroserviceOptions {
150
154
  timeout?: number;
151
155
  };
152
156
  cacheAdapter?: CacheAdapter;
157
+ plugins?: Plugin[];
153
158
  }
154
159
  interface ModuleInfo extends ModuleOptions {
155
160
  actions: Record<string, ActionMetadata>;
@@ -160,6 +165,9 @@ interface StreamResponse<T = any> {
160
165
  done: boolean;
161
166
  error?: string;
162
167
  }
168
+ declare abstract class Plugin {
169
+ abstract initialize(engine: Microservice): Promise<void>;
170
+ }
163
171
 
164
172
  declare class ActionHandler {
165
173
  private moduleInstance;
@@ -195,7 +203,7 @@ declare class Microservice {
195
203
  private actionHandlers;
196
204
  modules: Map<string, ModuleInfo>;
197
205
  readonly fetch: typeof fetch;
198
- options: MicroserviceOptions;
206
+ options: Required<MicroserviceOptions>;
199
207
  cache: CacheAdapter;
200
208
  serviceId: string;
201
209
  constructor(options: MicroserviceOptions);
@@ -224,7 +232,7 @@ declare class Microservice {
224
232
  /**
225
233
  * 获取所有模块的元数据
226
234
  */
227
- private getModules;
235
+ getModules(withTypes?: boolean): Record<string, ModuleInfo>;
228
236
  /**
229
237
  * 停止服务
230
238
  */
@@ -241,15 +249,8 @@ declare class Microservice {
241
249
  private updateStats;
242
250
  getActionHandler(moduleName: string, actionName: string): ActionHandler;
243
251
  private handleRequest;
252
+ private initPlugins;
244
253
  init(): Promise<void>;
245
- /**
246
- * 获取所有注册的服务
247
- */
248
- getServices(): Promise<ServiceInfo[]>;
249
- /**
250
- * 获取指定服务的实例
251
- */
252
- getServiceInstances(serviceName: string): Promise<ServiceInfo[]>;
253
254
  }
254
255
 
255
256
  interface PreStartChecker {
@@ -259,6 +260,13 @@ interface PreStartChecker {
259
260
  }
260
261
  declare function startCheck(checkers: PreStartChecker[], pass?: () => void | Promise<void>): Promise<void>;
261
262
 
263
+ declare class ModelContextProtocolPlugin extends Plugin {
264
+ private mcpServer;
265
+ private transports;
266
+ private registerMcpTools;
267
+ initialize: (engine: Microservice) => Promise<void>;
268
+ }
269
+
262
270
  /**
263
271
  * 用于给微服务模块方法添加的注解
264
272
  */
@@ -278,4 +286,4 @@ declare function Schedule(options: ScheduleOptions): Function;
278
286
 
279
287
  declare const logger: winston.Logger;
280
288
 
281
- export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, CacheAdapter, type CacheFn, type EtcdConfig, type EventServiceInfo, MemoryCacheAdapter, Microservice, type MicroserviceOptions, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, type PreStartChecker, RedisCacheAdapter, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, type ServiceStats, type StatisticsEvent, type StreamResponse, logger, startCheck };
289
+ export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, CacheAdapter, type CacheFn, type EtcdConfig, type EventServiceInfo, type McpOptions, MemoryCacheAdapter, Microservice, type MicroserviceOptions, ModelContextProtocolPlugin, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Plugin, type PreStartChecker, RedisCacheAdapter, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, type ServiceStats, type StatisticsEvent, type StreamResponse, logger, startCheck };
package/dist/mod.d.ts CHANGED
@@ -25,6 +25,9 @@ declare class MemoryCacheAdapter extends CacheAdapter {
25
25
  }
26
26
 
27
27
  type CacheFn = (...args: any[]) => any;
28
+ type McpOptions = {
29
+ type: "tool";
30
+ };
28
31
  interface ActionOptions {
29
32
  params?: z.ZodType<any>[];
30
33
  returns?: z.ZodType<any>;
@@ -34,6 +37,7 @@ interface ActionOptions {
34
37
  cache?: boolean | CacheFn;
35
38
  ttl?: number;
36
39
  stream?: boolean;
40
+ mcp?: McpOptions;
37
41
  }
38
42
  interface ActionMetadata extends ActionOptions {
39
43
  name: string;
@@ -139,7 +143,7 @@ interface MicroserviceOptions {
139
143
  disableCache?: boolean;
140
144
  modules: (new () => any)[];
141
145
  generateClient?: URL | boolean;
142
- etcd?: EtcdConfig;
146
+ etcd?: EtcdConfig | false;
143
147
  events?: StatisticsEventOptions & {
144
148
  onStats?: (event: StatisticsEvent) => void | Promise<void>;
145
149
  onRegister?: (serviceInfo: ServiceInfo) => void | Promise<void>;
@@ -150,6 +154,7 @@ interface MicroserviceOptions {
150
154
  timeout?: number;
151
155
  };
152
156
  cacheAdapter?: CacheAdapter;
157
+ plugins?: Plugin[];
153
158
  }
154
159
  interface ModuleInfo extends ModuleOptions {
155
160
  actions: Record<string, ActionMetadata>;
@@ -160,6 +165,9 @@ interface StreamResponse<T = any> {
160
165
  done: boolean;
161
166
  error?: string;
162
167
  }
168
+ declare abstract class Plugin {
169
+ abstract initialize(engine: Microservice): Promise<void>;
170
+ }
163
171
 
164
172
  declare class ActionHandler {
165
173
  private moduleInstance;
@@ -195,7 +203,7 @@ declare class Microservice {
195
203
  private actionHandlers;
196
204
  modules: Map<string, ModuleInfo>;
197
205
  readonly fetch: typeof fetch;
198
- options: MicroserviceOptions;
206
+ options: Required<MicroserviceOptions>;
199
207
  cache: CacheAdapter;
200
208
  serviceId: string;
201
209
  constructor(options: MicroserviceOptions);
@@ -224,7 +232,7 @@ declare class Microservice {
224
232
  /**
225
233
  * 获取所有模块的元数据
226
234
  */
227
- private getModules;
235
+ getModules(withTypes?: boolean): Record<string, ModuleInfo>;
228
236
  /**
229
237
  * 停止服务
230
238
  */
@@ -241,15 +249,8 @@ declare class Microservice {
241
249
  private updateStats;
242
250
  getActionHandler(moduleName: string, actionName: string): ActionHandler;
243
251
  private handleRequest;
252
+ private initPlugins;
244
253
  init(): Promise<void>;
245
- /**
246
- * 获取所有注册的服务
247
- */
248
- getServices(): Promise<ServiceInfo[]>;
249
- /**
250
- * 获取指定服务的实例
251
- */
252
- getServiceInstances(serviceName: string): Promise<ServiceInfo[]>;
253
254
  }
254
255
 
255
256
  interface PreStartChecker {
@@ -259,6 +260,13 @@ interface PreStartChecker {
259
260
  }
260
261
  declare function startCheck(checkers: PreStartChecker[], pass?: () => void | Promise<void>): Promise<void>;
261
262
 
263
+ declare class ModelContextProtocolPlugin extends Plugin {
264
+ private mcpServer;
265
+ private transports;
266
+ private registerMcpTools;
267
+ initialize: (engine: Microservice) => Promise<void>;
268
+ }
269
+
262
270
  /**
263
271
  * 用于给微服务模块方法添加的注解
264
272
  */
@@ -278,4 +286,4 @@ declare function Schedule(options: ScheduleOptions): Function;
278
286
 
279
287
  declare const logger: winston.Logger;
280
288
 
281
- export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, CacheAdapter, type CacheFn, type EtcdConfig, type EventServiceInfo, MemoryCacheAdapter, Microservice, type MicroserviceOptions, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, type PreStartChecker, RedisCacheAdapter, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, type ServiceStats, type StatisticsEvent, type StreamResponse, logger, startCheck };
289
+ export { Action, type ActionErrorEvent, type ActionMetadata, type ActionOptions, CacheAdapter, type CacheFn, type EtcdConfig, type EventServiceInfo, type McpOptions, MemoryCacheAdapter, Microservice, type MicroserviceOptions, ModelContextProtocolPlugin, Module, type ModuleInfo, type ModuleMetadata, type ModuleOptions, Plugin, type PreStartChecker, RedisCacheAdapter, Schedule, type ScheduleMetadata, ScheduleMode, type ScheduleOptions, ServiceContext, type ServiceInfo, type ServiceStats, type StatisticsEvent, type StreamResponse, logger, startCheck };
package/dist/mod.js CHANGED
@@ -13,6 +13,10 @@ import crypto2 from 'node:crypto';
13
13
  import { brotliDecompress } from 'node:zlib';
14
14
  import { brotliCompress, constants } from 'zlib';
15
15
  import { createNodeWebSocket } from '@hono/node-ws';
16
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
17
+ import { JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
18
+ import { streamSSE } from 'hono/streaming';
19
+ import { ulid } from 'ulid';
16
20
  export { default as dayjs } from 'dayjs';
17
21
 
18
22
  // mod.ts
@@ -76,7 +80,8 @@ function Action(options) {
76
80
  printError: options.printError ?? false,
77
81
  cache: options.cache,
78
82
  ttl: options.ttl,
79
- stream: options.stream
83
+ stream: options.stream,
84
+ mcp: options.mcp
80
85
  };
81
86
  prototype[ACTION_METADATA] = existingMetadata;
82
87
  });
@@ -112,6 +117,8 @@ var ScheduleMode = /* @__PURE__ */ ((ScheduleMode2) => {
112
117
  ScheduleMode2["FIXED_DELAY"] = "FIXED_DELAY";
113
118
  return ScheduleMode2;
114
119
  })(ScheduleMode || {});
120
+ var Plugin = class {
121
+ };
115
122
  var logger = winston.createLogger({
116
123
  level: "info",
117
124
  transports: [
@@ -511,6 +518,7 @@ var ActionHandler = class {
511
518
  const now = Date.now();
512
519
  if (cached !== null && (!this.metadata.ttl || cached?.expireAt > now)) {
513
520
  span?.setAttribute("cacheHit", true);
521
+ span?.setAttribute("cacheKey", cacheKey);
514
522
  return cached.data;
515
523
  }
516
524
  }
@@ -774,7 +782,16 @@ var Microservice = class {
774
782
  this.options = {
775
783
  prefix: "/api",
776
784
  name: "microservice",
785
+ version: "0.0.1",
786
+ env: "default",
787
+ printError: false,
788
+ disableCache: false,
789
+ generateClient: false,
790
+ etcd: false,
791
+ events: {},
792
+ websocket: { enabled: false },
777
793
  cacheAdapter: new MemoryCacheAdapter(),
794
+ plugins: [],
778
795
  ...options
779
796
  };
780
797
  this.cache = this.options.cacheAdapter;
@@ -793,6 +810,7 @@ var Microservice = class {
793
810
  this.initStatsEventManager();
794
811
  }
795
812
  await this.registerService(true);
813
+ await this.initPlugins();
796
814
  }
797
815
  async initModules() {
798
816
  for (const ModuleClass of this.options.modules) {
@@ -817,7 +835,7 @@ var Microservice = class {
817
835
  );
818
836
  this.actionHandlers.set(`${moduleName}.${actionName}`, handler);
819
837
  logger_default.info(
820
- `[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description}`
838
+ `[ \u6CE8\u518C\u52A8\u4F5C ] ${moduleName}.${actionName} ${actionMetadata.description} ${actionMetadata.mcp ? "MCP:" + actionMetadata.mcp?.type : ""}`
821
839
  );
822
840
  }
823
841
  const schedules = getScheduleMetadata(ModuleClass.prototype);
@@ -1180,32 +1198,16 @@ Received SIGINT signal`);
1180
1198
  return ctx.json({ success: false, error: error.message });
1181
1199
  }
1182
1200
  };
1183
- async init() {
1184
- await this.waitingInitialization;
1185
- }
1186
- /**
1187
- * 获取所有注册的服务
1188
- */
1189
- async getServices() {
1190
- if (!this.etcdClient) {
1191
- throw new Error("etcd is not configured");
1201
+ async initPlugins() {
1202
+ for (const plugin of this.options.plugins) {
1203
+ logger_default.info(`[ \u521D\u59CB\u5316\u63D2\u4EF6 ] ${plugin.constructor.name}`);
1204
+ await plugin.initialize(this).catch((e) => {
1205
+ logger_default.error(`[ \u521D\u59CB\u5316\u63D2\u4EF6\u5931\u8D25 ] ${plugin.constructor.name}: ${e}`);
1206
+ });
1192
1207
  }
1193
- const namespace = this.options.etcd?.namespace ? `${this.options.etcd.namespace}/` : "";
1194
- const servicesKey = `${namespace}services`;
1195
- const services = await this.etcdClient.getAll().prefix(servicesKey);
1196
- return Object.values(services).map((value) => JSON.parse(value));
1197
1208
  }
1198
- /**
1199
- * 获取指定服务的实例
1200
- */
1201
- async getServiceInstances(serviceName) {
1202
- if (!this.etcdClient) {
1203
- throw new Error("etcd is not configured");
1204
- }
1205
- const namespace = this.options.etcd?.namespace ? `${this.options.etcd.namespace}/` : "";
1206
- const serviceKey = `${namespace}services/${serviceName}`;
1207
- const instances = await this.etcdClient.getAll().prefix(serviceKey);
1208
- return Object.values(instances).map((value) => JSON.parse(value));
1209
+ async init() {
1210
+ await this.waitingInitialization;
1209
1211
  }
1210
1212
  };
1211
1213
 
@@ -1230,5 +1232,121 @@ async function startCheck(checkers, pass) {
1230
1232
  logger_default.info("[ \u9884\u68C0\u5B8C\u6210 ]");
1231
1233
  if (pass) await pass();
1232
1234
  }
1235
+ var HonoTransport = class {
1236
+ constructor(url, stream, closeStream) {
1237
+ this.url = url;
1238
+ this.stream = stream;
1239
+ this.closeStream = closeStream;
1240
+ this.sessionId = ulid();
1241
+ }
1242
+ sessionId;
1243
+ onclose;
1244
+ onerror;
1245
+ onmessage;
1246
+ async start() {
1247
+ await this.stream.writeSSE({
1248
+ event: "endpoint",
1249
+ data: `${encodeURI(this.url)}?sessionId=${this.sessionId}`
1250
+ });
1251
+ this.stream.onAbort(() => {
1252
+ this.close();
1253
+ });
1254
+ }
1255
+ async handleMessage(message) {
1256
+ let parsedMessage;
1257
+ try {
1258
+ parsedMessage = JSONRPCMessageSchema.parse(message);
1259
+ } catch (error) {
1260
+ this.onerror?.(error);
1261
+ throw error;
1262
+ }
1263
+ this.onmessage?.(parsedMessage);
1264
+ }
1265
+ async send(message) {
1266
+ await this.stream.writeln(
1267
+ `event: message
1268
+ data: ${JSON.stringify(message)}
1269
+
1270
+ `
1271
+ );
1272
+ }
1273
+ async close() {
1274
+ this.onclose?.();
1275
+ this.closeStream();
1276
+ }
1277
+ };
1278
+ var ModelContextProtocolPlugin = class extends Plugin {
1279
+ mcpServer;
1280
+ transports = {};
1281
+ registerMcpTools(engine) {
1282
+ const modules = engine.getModules(true);
1283
+ for (const module of Object.values(modules)) {
1284
+ for (const action of Object.values(module.actions)) {
1285
+ if (action.mcp?.type !== "tool") {
1286
+ continue;
1287
+ }
1288
+ const args = {};
1289
+ const argsIndex = {};
1290
+ for (const [index, param] of (action.params ?? []).entries()) {
1291
+ args[param.description] = param;
1292
+ argsIndex[param.description] = index;
1293
+ }
1294
+ this.mcpServer.tool(
1295
+ `${module.name}.${action.name}`,
1296
+ action.description ?? "",
1297
+ args,
1298
+ async (params) => {
1299
+ const args2 = Object.values(params);
1300
+ const argsList = [];
1301
+ for (const [key, value] of Object.entries(args2)) {
1302
+ argsList[argsIndex[key]] = value;
1303
+ }
1304
+ const result = await engine.getActionHandler(module.name, action.name).handle(argsList);
1305
+ return {
1306
+ content: [{ type: "text", text: JSON.stringify(result) }]
1307
+ };
1308
+ }
1309
+ );
1310
+ }
1311
+ }
1312
+ }
1313
+ initialize = async (engine) => {
1314
+ const app = engine.getApp();
1315
+ this.mcpServer = new McpServer({
1316
+ name: engine.options.name,
1317
+ version: engine.options.version
1318
+ });
1319
+ this.registerMcpTools(engine);
1320
+ app.get(`${engine.options.prefix}/mcp_sse`, async (ctx) => {
1321
+ return streamSSE(ctx, async (stream) => {
1322
+ return new Promise(async (resolve) => {
1323
+ const transport = new HonoTransport(
1324
+ `${engine.options.prefix}/mcp_messages`,
1325
+ stream,
1326
+ () => {
1327
+ delete this.transports[transport.sessionId];
1328
+ resolve();
1329
+ }
1330
+ );
1331
+ this.transports[transport.sessionId] = transport;
1332
+ await this.mcpServer.connect(transport);
1333
+ });
1334
+ });
1335
+ });
1336
+ app.post(`${engine.options.prefix}/mcp_messages`, async (ctx) => {
1337
+ const sessionId = ctx.req.query("sessionId");
1338
+ if (!sessionId) {
1339
+ return ctx.text("No transport found for sessionId", 400);
1340
+ }
1341
+ const transport = this.transports[sessionId];
1342
+ const message = await ctx.req.json();
1343
+ await transport.handleMessage(message);
1344
+ return ctx.text("Accepted", 202);
1345
+ });
1346
+ logger_default.info(
1347
+ `ModelContextProtocolPlugin endpoint: ${engine.options.prefix}/mcp_sse`
1348
+ );
1349
+ };
1350
+ };
1233
1351
 
1234
- export { Action, CacheAdapter, MemoryCacheAdapter, Microservice, Module, RedisCacheAdapter, Schedule, ScheduleMode, ServiceContext, logger_default as logger, startCheck };
1352
+ export { Action, CacheAdapter, MemoryCacheAdapter, Microservice, ModelContextProtocolPlugin, Module, Plugin, RedisCacheAdapter, Schedule, ScheduleMode, ServiceContext, logger_default as logger, startCheck };
package/package.json CHANGED
@@ -1,85 +1,87 @@
1
- {
2
- "name": "imean-service-engine",
3
- "version": "1.3.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
- "winston": "^3.17.0",
50
- "zod": "^3.24.1"
51
- },
52
- "peerDependencies": {
53
- "@opentelemetry/api": "^1.x",
54
- "ioredis": "^5.6.0"
55
- },
56
- "devDependencies": {
57
- "@opentelemetry/auto-instrumentations-node": "^0.55.3",
58
- "@opentelemetry/exporter-logs-otlp-proto": "^0.57.1",
59
- "@opentelemetry/exporter-metrics-otlp-proto": "^0.57.1",
60
- "@opentelemetry/exporter-trace-otlp-proto": "^0.57.1",
61
- "@opentelemetry/instrumentation-winston": "^0.44.0",
62
- "@opentelemetry/sdk-logs": "^0.57.1",
63
- "@opentelemetry/sdk-metrics": "^1.30.1",
64
- "@opentelemetry/sdk-node": "^0.57.1",
65
- "@opentelemetry/sdk-trace-node": "^1.30.1",
66
- "@opentelemetry/winston-transport": "^0.10.0",
67
- "@types/ejson": "^2.2.2",
68
- "@types/fs-extra": "^11.0.4",
69
- "@types/ioredis-mock": "^8.2.5",
70
- "@types/node": "^20.0.0",
71
- "@vitest/coverage-v8": "^3.0.4",
72
- "imean-service-client": "^1.5.0",
73
- "ioredis-mock": "^8.9.0",
74
- "opentelemetry-instrumentation-fetch-node": "^1.2.3",
75
- "tslib": "^2.8.1",
76
- "tsup": "^8.0.1",
77
- "tsx": "^4.19.2",
78
- "typescript": "^5.3.3",
79
- "vite-tsconfig-paths": "^5.1.4",
80
- "vitest": "^3.0.3"
81
- },
82
- "engines": {
83
- "node": ">=20"
84
- }
85
- }
1
+ {
2
+ "name": "imean-service-engine",
3
+ "version": "1.4.1",
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
+ }