@wener/mcps 1.0.1 → 1.0.4

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.

Potentially problematic release.


This version of @wener/mcps might be problematic. Click here for more details.

Files changed (59) hide show
  1. package/README.md +144 -0
  2. package/dist/index.mjs +213076 -1
  3. package/dist/mcps-cli.mjs +102547 -59344
  4. package/lib/chat/handler.js +2 -2
  5. package/lib/chat/handler.js.map +1 -1
  6. package/lib/cli-start.js +36 -0
  7. package/lib/cli-start.js.map +1 -0
  8. package/lib/cli.js +19 -0
  9. package/lib/cli.js.map +1 -0
  10. package/lib/dev.server.js +7 -1
  11. package/lib/dev.server.js.map +1 -1
  12. package/lib/index.js +21 -3
  13. package/lib/index.js.map +1 -1
  14. package/lib/mcps-cli.js +6 -35
  15. package/lib/mcps-cli.js.map +1 -1
  16. package/lib/providers/feishu/def.js +35 -0
  17. package/lib/providers/feishu/def.js.map +1 -0
  18. package/lib/providers/findMcpServerDef.js +1 -0
  19. package/lib/providers/findMcpServerDef.js.map +1 -1
  20. package/lib/scripts/bundle.js +7 -1
  21. package/lib/scripts/bundle.js.map +1 -1
  22. package/lib/server/api-routes.js +7 -8
  23. package/lib/server/api-routes.js.map +1 -1
  24. package/lib/server/audit-db.js +64 -0
  25. package/lib/server/audit-db.js.map +1 -0
  26. package/lib/server/{audit.js → audit-plugin.js} +72 -126
  27. package/lib/server/audit-plugin.js.map +1 -0
  28. package/lib/server/events.js +13 -0
  29. package/lib/server/events.js.map +1 -0
  30. package/lib/server/mcp-routes.js +31 -60
  31. package/lib/server/mcp-routes.js.map +1 -1
  32. package/lib/server/mcps-router.js +19 -24
  33. package/lib/server/mcps-router.js.map +1 -1
  34. package/lib/server/schema.js +22 -2
  35. package/lib/server/schema.js.map +1 -1
  36. package/lib/server/server.js +142 -87
  37. package/lib/server/server.js.map +1 -1
  38. package/package.json +35 -5
  39. package/src/chat/handler.ts +2 -2
  40. package/src/cli-start.ts +43 -0
  41. package/src/cli.ts +45 -0
  42. package/src/dev.server.ts +8 -1
  43. package/src/index.ts +47 -1
  44. package/src/mcps-cli.ts +6 -48
  45. package/src/providers/feishu/def.ts +37 -0
  46. package/src/providers/findMcpServerDef.ts +1 -0
  47. package/src/scripts/bundle.ts +12 -1
  48. package/src/server/api-routes.ts +11 -8
  49. package/src/server/audit-db.ts +65 -0
  50. package/src/server/{audit.ts → audit-plugin.ts} +69 -142
  51. package/src/server/events.ts +29 -0
  52. package/src/server/mcp-routes.ts +30 -58
  53. package/src/server/mcps-router.ts +21 -29
  54. package/src/server/schema.ts +23 -2
  55. package/src/server/server.ts +149 -81
  56. package/lib/server/audit.js.map +0 -1
  57. package/lib/server/db.js +0 -97
  58. package/lib/server/db.js.map +0 -1
  59. package/src/server/db.ts +0 -115
@@ -5,38 +5,32 @@ import { LRUCache } from "lru-cache";
5
5
  import { isDevelopment } from "std-env";
6
6
  import { findMcpServerDef } from "../providers/findMcpServerDef.js";
7
7
  import { registerApiRoutes } from "./api-routes.js";
8
- import { auditMiddleware, configureAudit } from "./audit.js";
9
8
  import { registerChatRoutes } from "./chat-routes.js";
10
9
  import { loadConfig, loadEnvFiles, substituteEnvVars } from "./config.js";
10
+ import { createMcpsEmitter, McpsEventType } from "./events.js";
11
11
  import { registerMcpRoutes } from "./mcp-routes.js";
12
12
  const log = consola.withTag("mcps");
13
13
  export function createServer(options = {}) {
14
- const { cwd = process.cwd(), discoveryConfig: discoveryConfigOption } = options;
14
+ const { cwd = process.cwd(), discoveryConfig: discoveryConfigOption, setup } = options;
15
15
  const app = new Hono();
16
+ const emitter = createMcpsEmitter();
17
+ const apiRouters = {};
16
18
  // Request logging
17
19
  app.use(logger((v) => {
18
20
  log.debug(v);
19
21
  }));
20
- // Audit middleware
21
- app.use(auditMiddleware());
22
+ // Request event middleware - emits events for subscribers (audit, monitoring, etc.)
23
+ app.use(requestEventMiddleware(emitter));
22
24
  // Load .env files first
23
25
  loadEnvFiles(cwd);
24
26
  // Load config (with env var substitution)
25
27
  const config = substituteEnvVars(loadConfig(cwd));
26
- // discoveryConfig: CLI option overrides config file, defaults to false
27
28
  const discoveryConfig = discoveryConfigOption ?? config.discoveryConfig ?? false;
28
29
  // Log available server types from registry
29
30
  const serverDefs = findMcpServerDef();
30
31
  log.info(`Available server types: ${serverDefs.map((d) => d.name).join(", ")}`);
31
32
  log.info(`Loaded ${Object.keys(config.servers).length} servers from config (discoveryConfig: ${discoveryConfig})`);
32
- // Configure audit with lazy DB initialization
33
- // DB will only be initialized when the first audit event needs persistence
34
- configureAudit(config.audit, config.db);
35
- const auditEnabled = config.audit?.enabled !== false;
36
- const auditDbPath = config.audit?.db?.path ?? config.db?.path ?? ".mcps.db";
37
- log.info(`Audit configured: enabled=${auditEnabled}, db=${auditDbPath} (lazy init)`);
38
- // Unified cache for all MCP servers (both pre-configured and dynamic)
39
- // Keyed by cache key from def.getCacheKey() or `config::${name}` for pre-configured
33
+ // Unified cache for all MCP servers
40
34
  const serverCache = new LRUCache({
41
35
  max: 100,
42
36
  dispose: (value, key) => {
@@ -44,76 +38,81 @@ export function createServer(options = {}) {
44
38
  value.close?.().catch((e) => log.error("Failed to close server", e));
45
39
  }
46
40
  });
47
- // =========================================================================
48
- // Register MCP routes (pre-configured and dynamic endpoints)
49
- // =========================================================================
50
- registerMcpRoutes({
41
+ const ctx = {
51
42
  app,
52
43
  config,
53
- serverCache
54
- });
55
- // =========================================================================
56
- // Register Chat/LLM Gateway routes
57
- // =========================================================================
58
- registerChatRoutes({
59
- app,
60
- config
61
- });
62
- // =========================================================================
63
- // Health and info endpoints
64
- // =========================================================================
65
- app.get("/health", (c) => c.json({
66
- status: "ok"
67
- }));
68
- app.get("/", (c) => {
69
- return c.json({
70
- message: "hello"
44
+ emitter,
45
+ serverCache,
46
+ apiRouters
47
+ };
48
+ const finalize = async () => {
49
+ // Allow plugins to set up before routes are registered
50
+ await setup?.(ctx);
51
+ // =========================================================================
52
+ // Register MCP routes (pre-configured and dynamic endpoints)
53
+ // =========================================================================
54
+ registerMcpRoutes({
55
+ app,
56
+ config,
57
+ serverCache
71
58
  });
72
- });
73
- // Server info - only available in dev mode or when discoveryConfig is enabled
74
- if (isDevelopment || discoveryConfig) {
75
- app.get("/info", (c) => {
76
- // Dynamically get routes from Hono app
77
- const routes = app.routes.map((r) => ({
78
- method: r.method,
79
- path: r.path
80
- })).filter((r) => r.method !== "ALL" || r.path.startsWith("/mcp/")).sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
81
- // Group routes by category
82
- const mcpRoutes = routes.filter((r) => r.path.startsWith("/mcp/")).map((r) => r.path);
83
- const apiRoutes = routes.filter((r) => r.path.startsWith("/api/")).map((r) => `${r.method} ${r.path}`);
84
- const chatRoutes = routes.filter((r) => r.path.startsWith("/v1/")).map((r) => `${r.method} ${r.path}`);
85
- // Get unique MCP paths
86
- const uniqueMcpPaths = [
87
- ...new Set(mcpRoutes)
88
- ];
89
- return c.json({
90
- name: "@wener/mcps",
91
- version: "0.1.0",
92
- servers: Object.keys(config.servers),
93
- serverTypes: findMcpServerDef().map((d) => d.name),
94
- models: config.models ? config.models.map((m) => m.name) : [],
95
- endpoints: {
96
- mcp: uniqueMcpPaths,
97
- api: [
98
- ...new Set(apiRoutes)
99
- ],
100
- chat: [
101
- ...new Set(chatRoutes)
102
- ]
103
- }
59
+ // =========================================================================
60
+ // Register Chat/LLM Gateway routes
61
+ // =========================================================================
62
+ registerChatRoutes({
63
+ app,
64
+ config
65
+ });
66
+ // =========================================================================
67
+ // Health and info endpoints
68
+ // =========================================================================
69
+ app.get("/health", (c) => c.json({
70
+ status: "ok"
71
+ }));
72
+ app.get("/", (c) => c.json({
73
+ message: "hello"
74
+ }));
75
+ if (isDevelopment || discoveryConfig) {
76
+ app.get("/info", (c) => {
77
+ const routes = app.routes.map((r) => ({
78
+ method: r.method,
79
+ path: r.path
80
+ })).filter((r) => r.method !== "ALL" || r.path.startsWith("/mcp/")).sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
81
+ const mcpRoutes = routes.filter((r) => r.path.startsWith("/mcp/")).map((r) => r.path);
82
+ const apiRoutes = routes.filter((r) => r.path.startsWith("/api/")).map((r) => `${r.method} ${r.path}`);
83
+ const chatRoutes = routes.filter((r) => r.path.startsWith("/v1/")).map((r) => `${r.method} ${r.path}`);
84
+ const uniqueMcpPaths = [
85
+ ...new Set(mcpRoutes)
86
+ ];
87
+ return c.json({
88
+ name: "@wener/mcps",
89
+ version: "0.1.0",
90
+ servers: Object.keys(config.servers),
91
+ serverTypes: findMcpServerDef().map((d) => d.name),
92
+ models: config.models ? config.models.map((m) => m.name) : [],
93
+ endpoints: {
94
+ mcp: uniqueMcpPaths,
95
+ api: [
96
+ ...new Set(apiRoutes)
97
+ ],
98
+ chat: [
99
+ ...new Set(chatRoutes)
100
+ ]
101
+ }
102
+ });
104
103
  });
104
+ }
105
+ // =========================================================================
106
+ // Register oRPC API routes (with any plugin-provided routers)
107
+ // =========================================================================
108
+ registerApiRoutes({
109
+ app,
110
+ config,
111
+ apiRouters,
112
+ statsProvider: ctx.statsProvider
105
113
  });
106
- }
107
- // =========================================================================
108
- // Register oRPC API routes
109
- // =========================================================================
110
- registerApiRoutes({
111
- app,
112
- config
113
- });
114
- /**
115
- * Print available endpoints grouped by category
116
- */ const printEndpoints = () => {
114
+ };
115
+ const printEndpoints = () => {
117
116
  const routes = app.routes;
118
117
  const mcpPaths = new Set();
119
118
  const apiPaths = new Set();
@@ -135,32 +134,88 @@ export function createServer(options = {}) {
135
134
  }
136
135
  }
137
136
  log.info("Available endpoints:");
138
- if (mcpPaths.size > 0) {
137
+ if (mcpPaths.size > 0)
139
138
  log.info(` MCP: ${[
140
139
  ...mcpPaths
141
140
  ].sort().join(", ")}`);
142
- }
143
- if (chatPaths.size > 0) {
141
+ if (chatPaths.size > 0)
144
142
  log.info(` Chat: ${[
145
143
  ...chatPaths
146
144
  ].sort().join(", ")}`);
147
- }
148
- if (apiPaths.size > 0) {
145
+ if (apiPaths.size > 0)
149
146
  log.info(` API: ${[
150
147
  ...apiPaths
151
148
  ].sort().join(", ")}`);
152
- }
153
- if (otherPaths.size > 0) {
149
+ if (otherPaths.size > 0)
154
150
  log.info(` Other: ${[
155
151
  ...otherPaths
156
152
  ].sort().join(", ")}`);
157
- }
158
153
  };
159
154
  return {
160
155
  app,
161
156
  config,
157
+ emitter,
162
158
  serverCache,
163
- printEndpoints
159
+ printEndpoints,
160
+ finalize
161
+ };
162
+ }
163
+ function headersToRecord(headers) {
164
+ const record = {};
165
+ headers.forEach((value, key) => {
166
+ record[key] = value;
167
+ });
168
+ return record;
169
+ }
170
+ /**
171
+ * Middleware that emits request events via the emitter.
172
+ * Subscribers (like audit plugin) can listen and handle these events.
173
+ */ function requestEventMiddleware(emitter) {
174
+ return async (c, next) => {
175
+ const startTime = Date.now();
176
+ const path = c.req.path;
177
+ let serverName;
178
+ let serverType;
179
+ const mcpMatch = path.match(/^\/mcp\/([^/]+)/);
180
+ if (mcpMatch) {
181
+ serverName = mcpMatch[1];
182
+ if ([
183
+ "tencent-cls",
184
+ "sql",
185
+ "prometheus",
186
+ "relay"
187
+ ].includes(serverName)) {
188
+ serverType = serverName;
189
+ }
190
+ else {
191
+ serverType = "custom";
192
+ }
193
+ }
194
+ if (path.startsWith("/v1/")) {
195
+ serverType = "chat";
196
+ }
197
+ let error;
198
+ try {
199
+ await next();
200
+ }
201
+ catch (e) {
202
+ error = e instanceof Error ? e.message : String(e);
203
+ throw e;
204
+ }
205
+ finally {
206
+ const durationMs = Date.now() - startTime;
207
+ emitter.emit(McpsEventType.Request, {
208
+ timestamp: new Date().toISOString(),
209
+ method: c.req.method,
210
+ path,
211
+ serverName,
212
+ serverType,
213
+ status: c.res.status,
214
+ durationMs,
215
+ error,
216
+ requestHeaders: headersToRecord(c.req.raw.headers)
217
+ });
218
+ }
164
219
  };
165
220
  }
166
221
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/server.ts"],"sourcesContent":["import consola from 'consola';\nimport { Hono } from 'hono';\nimport { logger } from 'hono/logger';\nimport { LRUCache } from 'lru-cache';\nimport { isDevelopment } from 'std-env';\nimport type { McpServerInstance } from '@wener/ai/mcp';\nimport { findMcpServerDef } from '../providers/findMcpServerDef';\nimport { registerApiRoutes } from './api-routes';\nimport { auditMiddleware, configureAudit } from './audit';\nimport { registerChatRoutes } from './chat-routes';\nimport { loadConfig, loadEnvFiles, substituteEnvVars } from './config';\nimport { registerMcpRoutes } from './mcp-routes';\n\nconst log = consola.withTag('mcps');\n\nexport interface CreateServerOptions {\n\tcwd?: string;\n\tport?: number;\n\t/** Enable server config discovery endpoints (default: false) */\n\tdiscoveryConfig?: boolean;\n}\n\nexport function createServer(options: CreateServerOptions = {}) {\n\tconst { cwd = process.cwd(), discoveryConfig: discoveryConfigOption } = options;\n\n\tconst app = new Hono();\n\n\t// Request logging\n\tapp.use(\n\t\tlogger((v) => {\n\t\t\tlog.debug(v);\n\t\t}),\n\t);\n\n\t// Audit middleware\n\tapp.use(auditMiddleware());\n\n\t// Load .env files first\n\tloadEnvFiles(cwd);\n\n\t// Load config (with env var substitution)\n\tconst config = substituteEnvVars(loadConfig(cwd));\n\t// discoveryConfig: CLI option overrides config file, defaults to false\n\tconst discoveryConfig = discoveryConfigOption ?? config.discoveryConfig ?? false;\n\n\t// Log available server types from registry\n\tconst serverDefs = findMcpServerDef();\n\tlog.info(`Available server types: ${serverDefs.map((d) => d.name).join(', ')}`);\n\tlog.info(`Loaded ${Object.keys(config.servers).length} servers from config (discoveryConfig: ${discoveryConfig})`);\n\n\t// Configure audit with lazy DB initialization\n\t// DB will only be initialized when the first audit event needs persistence\n\tconfigureAudit(config.audit, config.db);\n\tconst auditEnabled = config.audit?.enabled !== false;\n\tconst auditDbPath = config.audit?.db?.path ?? config.db?.path ?? '.mcps.db';\n\tlog.info(`Audit configured: enabled=${auditEnabled}, db=${auditDbPath} (lazy init)`);\n\n\t// Unified cache for all MCP servers (both pre-configured and dynamic)\n\t// Keyed by cache key from def.getCacheKey() or `config::${name}` for pre-configured\n\tconst serverCache = new LRUCache<string, McpServerInstance>({\n\t\tmax: 100,\n\t\tdispose: (value, key) => {\n\t\t\tlog.info(`Closing expired MCP server: ${key}`);\n\t\t\tvalue.close?.().catch((e: unknown) => log.error('Failed to close server', e));\n\t\t},\n\t});\n\n\t// =========================================================================\n\t// Register MCP routes (pre-configured and dynamic endpoints)\n\t// =========================================================================\n\tregisterMcpRoutes({ app, config, serverCache });\n\n\t// =========================================================================\n\t// Register Chat/LLM Gateway routes\n\t// =========================================================================\n\tregisterChatRoutes({ app, config });\n\n\t// =========================================================================\n\t// Health and info endpoints\n\t// =========================================================================\n\tapp.get('/health', (c) => c.json({ status: 'ok' }));\n\n\tapp.get('/', (c) => {\n\t\treturn c.json({ message: 'hello' });\n\t});\n\n\t// Server info - only available in dev mode or when discoveryConfig is enabled\n\tif (isDevelopment || discoveryConfig) {\n\t\tapp.get('/info', (c) => {\n\t\t\t// Dynamically get routes from Hono app\n\t\t\tconst routes = app.routes\n\t\t\t\t.map((r) => ({ method: r.method, path: r.path }))\n\t\t\t\t.filter((r) => r.method !== 'ALL' || r.path.startsWith('/mcp/'))\n\t\t\t\t.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));\n\n\t\t\t// Group routes by category\n\t\t\tconst mcpRoutes = routes.filter((r) => r.path.startsWith('/mcp/')).map((r) => r.path);\n\t\t\tconst apiRoutes = routes.filter((r) => r.path.startsWith('/api/')).map((r) => `${r.method} ${r.path}`);\n\t\t\tconst chatRoutes = routes.filter((r) => r.path.startsWith('/v1/')).map((r) => `${r.method} ${r.path}`);\n\n\t\t\t// Get unique MCP paths\n\t\t\tconst uniqueMcpPaths = [...new Set(mcpRoutes)];\n\n\t\t\treturn c.json({\n\t\t\t\tname: '@wener/mcps',\n\t\t\t\tversion: '0.1.0',\n\t\t\t\tservers: Object.keys(config.servers),\n\t\t\t\tserverTypes: findMcpServerDef().map((d) => d.name),\n\t\t\t\tmodels: config.models ? config.models.map((m) => m.name) : [],\n\t\t\t\tendpoints: {\n\t\t\t\t\tmcp: uniqueMcpPaths,\n\t\t\t\t\tapi: [...new Set(apiRoutes)],\n\t\t\t\t\tchat: [...new Set(chatRoutes)],\n\t\t\t\t},\n\t\t\t});\n\t\t});\n\t}\n\n\t// =========================================================================\n\t// Register oRPC API routes\n\t// =========================================================================\n\tregisterApiRoutes({ app, config });\n\n\t/**\n\t * Print available endpoints grouped by category\n\t */\n\tconst printEndpoints = () => {\n\t\tconst routes = app.routes;\n\t\tconst mcpPaths = new Set<string>();\n\t\tconst apiPaths = new Set<string>();\n\t\tconst chatPaths = new Set<string>();\n\t\tconst otherPaths = new Set<string>();\n\n\t\tfor (const route of routes) {\n\t\t\tconst path = route.path;\n\t\t\tif (path.startsWith('/mcp/')) {\n\t\t\t\tmcpPaths.add(path);\n\t\t\t} else if (path.startsWith('/api/')) {\n\t\t\t\tapiPaths.add(`${route.method} ${path}`);\n\t\t\t} else if (path.startsWith('/v1/')) {\n\t\t\t\tchatPaths.add(`${route.method} ${path}`);\n\t\t\t} else if (route.method !== 'ALL') {\n\t\t\t\totherPaths.add(`${route.method} ${path}`);\n\t\t\t}\n\t\t}\n\n\t\tlog.info('Available endpoints:');\n\t\tif (mcpPaths.size > 0) {\n\t\t\tlog.info(` MCP: ${[...mcpPaths].sort().join(', ')}`);\n\t\t}\n\t\tif (chatPaths.size > 0) {\n\t\t\tlog.info(` Chat: ${[...chatPaths].sort().join(', ')}`);\n\t\t}\n\t\tif (apiPaths.size > 0) {\n\t\t\tlog.info(` API: ${[...apiPaths].sort().join(', ')}`);\n\t\t}\n\t\tif (otherPaths.size > 0) {\n\t\t\tlog.info(` Other: ${[...otherPaths].sort().join(', ')}`);\n\t\t}\n\t};\n\n\treturn { app, config, serverCache, printEndpoints };\n}\n"],"names":["consola","Hono","logger","LRUCache","isDevelopment","findMcpServerDef","registerApiRoutes","auditMiddleware","configureAudit","registerChatRoutes","loadConfig","loadEnvFiles","substituteEnvVars","registerMcpRoutes","log","withTag","createServer","options","cwd","process","discoveryConfig","discoveryConfigOption","app","use","v","debug","config","serverDefs","info","map","d","name","join","Object","keys","servers","length","audit","db","auditEnabled","enabled","auditDbPath","path","serverCache","max","dispose","value","key","close","catch","e","error","get","c","json","status","message","routes","r","method","filter","startsWith","sort","a","b","localeCompare","mcpRoutes","apiRoutes","chatRoutes","uniqueMcpPaths","Set","version","serverTypes","models","m","endpoints","mcp","api","chat","printEndpoints","mcpPaths","apiPaths","chatPaths","otherPaths","route","add","size"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/server/server.ts"],"sourcesContent":["import consola from 'consola';\nimport type { Context, Next } from 'hono';\nimport { Hono } from 'hono';\nimport { logger } from 'hono/logger';\nimport { LRUCache } from 'lru-cache';\nimport { isDevelopment } from 'std-env';\nimport type { McpServerInstance } from '@wener/ai/mcp';\nimport { findMcpServerDef } from '../providers/findMcpServerDef';\nimport { registerApiRoutes } from './api-routes';\nimport { registerChatRoutes } from './chat-routes';\nimport { loadConfig, loadEnvFiles, substituteEnvVars } from './config';\nimport { createMcpsEmitter, McpsEventType, type McpsEmitter } from './events';\nimport { registerMcpRoutes } from './mcp-routes';\n\nconst log = consola.withTag('mcps');\n\nexport interface McpsServerContext {\n\tapp: Hono;\n\tconfig: import('./schema').McpsConfig;\n\temitter: McpsEmitter;\n\tserverCache: LRUCache<string, McpServerInstance>;\n\t/** Plugins can register additional oRPC routers here */\n\tapiRouters: Record<string, any>;\n\t/** Plugins can register stats providers here */\n\tstatsProvider?: StatsProvider;\n}\n\nexport interface StatsProvider {\n\tgetStats(options: { from?: string | null; to?: string | null }): {\n\t\ttotalRequests: number;\n\t\ttotalErrors: number;\n\t\tavgDurationMs: number;\n\t\tbyServer: Array<{ name: string; count: number }>;\n\t\tbyMethod: Array<{ method: string; count: number }>;\n\t};\n\tqueryEvents(options: { limit?: number }): { events: Array<{ path: string }>; total: number };\n}\n\nexport interface CreateServerOptions {\n\tcwd?: string;\n\tport?: number;\n\t/** Enable server config discovery endpoints (default: false) */\n\tdiscoveryConfig?: boolean;\n\t/**\n\t * Called after core server is created but before routes are registered.\n\t * Use this to set up optional plugins like audit via the emitter.\n\t */\n\tsetup?: (ctx: McpsServerContext) => void | Promise<void>;\n}\n\nexport function createServer(options: CreateServerOptions = {}) {\n\tconst { cwd = process.cwd(), discoveryConfig: discoveryConfigOption, setup } = options;\n\n\tconst app = new Hono();\n\tconst emitter = createMcpsEmitter();\n\tconst apiRouters: Record<string, any> = {};\n\n\t// Request logging\n\tapp.use(\n\t\tlogger((v) => {\n\t\t\tlog.debug(v);\n\t\t}),\n\t);\n\n\t// Request event middleware - emits events for subscribers (audit, monitoring, etc.)\n\tapp.use(requestEventMiddleware(emitter));\n\n\t// Load .env files first\n\tloadEnvFiles(cwd);\n\n\t// Load config (with env var substitution)\n\tconst config = substituteEnvVars(loadConfig(cwd));\n\tconst discoveryConfig = discoveryConfigOption ?? config.discoveryConfig ?? false;\n\n\t// Log available server types from registry\n\tconst serverDefs = findMcpServerDef();\n\tlog.info(`Available server types: ${serverDefs.map((d) => d.name).join(', ')}`);\n\tlog.info(`Loaded ${Object.keys(config.servers).length} servers from config (discoveryConfig: ${discoveryConfig})`);\n\n\t// Unified cache for all MCP servers\n\tconst serverCache = new LRUCache<string, McpServerInstance>({\n\t\tmax: 100,\n\t\tdispose: (value, key) => {\n\t\t\tlog.info(`Closing expired MCP server: ${key}`);\n\t\t\tvalue.close?.().catch((e: unknown) => log.error('Failed to close server', e));\n\t\t},\n\t});\n\n\tconst ctx: McpsServerContext = { app, config, emitter, serverCache, apiRouters };\n\n\tconst finalize = async () => {\n\t\t// Allow plugins to set up before routes are registered\n\t\tawait setup?.(ctx);\n\n\t\t// =========================================================================\n\t\t// Register MCP routes (pre-configured and dynamic endpoints)\n\t\t// =========================================================================\n\t\tregisterMcpRoutes({ app, config, serverCache });\n\n\t\t// =========================================================================\n\t\t// Register Chat/LLM Gateway routes\n\t\t// =========================================================================\n\t\tregisterChatRoutes({ app, config });\n\n\t\t// =========================================================================\n\t\t// Health and info endpoints\n\t\t// =========================================================================\n\t\tapp.get('/health', (c) => c.json({ status: 'ok' }));\n\t\tapp.get('/', (c) => c.json({ message: 'hello' }));\n\n\t\tif (isDevelopment || discoveryConfig) {\n\t\t\tapp.get('/info', (c) => {\n\t\t\t\tconst routes = app.routes\n\t\t\t\t\t.map((r) => ({ method: r.method, path: r.path }))\n\t\t\t\t\t.filter((r) => r.method !== 'ALL' || r.path.startsWith('/mcp/'))\n\t\t\t\t\t.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));\n\n\t\t\t\tconst mcpRoutes = routes.filter((r) => r.path.startsWith('/mcp/')).map((r) => r.path);\n\t\t\t\tconst apiRoutes = routes.filter((r) => r.path.startsWith('/api/')).map((r) => `${r.method} ${r.path}`);\n\t\t\t\tconst chatRoutes = routes.filter((r) => r.path.startsWith('/v1/')).map((r) => `${r.method} ${r.path}`);\n\t\t\t\tconst uniqueMcpPaths = [...new Set(mcpRoutes)];\n\n\t\t\t\treturn c.json({\n\t\t\t\t\tname: '@wener/mcps',\n\t\t\t\t\tversion: '0.1.0',\n\t\t\t\t\tservers: Object.keys(config.servers),\n\t\t\t\t\tserverTypes: findMcpServerDef().map((d) => d.name),\n\t\t\t\t\tmodels: config.models ? config.models.map((m) => m.name) : [],\n\t\t\t\t\tendpoints: {\n\t\t\t\t\t\tmcp: uniqueMcpPaths,\n\t\t\t\t\t\tapi: [...new Set(apiRoutes)],\n\t\t\t\t\t\tchat: [...new Set(chatRoutes)],\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t// =========================================================================\n\t\t// Register oRPC API routes (with any plugin-provided routers)\n\t\t// =========================================================================\n\t\tregisterApiRoutes({ app, config, apiRouters, statsProvider: ctx.statsProvider });\n\t};\n\n\tconst printEndpoints = () => {\n\t\tconst routes = app.routes;\n\t\tconst mcpPaths = new Set<string>();\n\t\tconst apiPaths = new Set<string>();\n\t\tconst chatPaths = new Set<string>();\n\t\tconst otherPaths = new Set<string>();\n\n\t\tfor (const route of routes) {\n\t\t\tconst path = route.path;\n\t\t\tif (path.startsWith('/mcp/')) {\n\t\t\t\tmcpPaths.add(path);\n\t\t\t} else if (path.startsWith('/api/')) {\n\t\t\t\tapiPaths.add(`${route.method} ${path}`);\n\t\t\t} else if (path.startsWith('/v1/')) {\n\t\t\t\tchatPaths.add(`${route.method} ${path}`);\n\t\t\t} else if (route.method !== 'ALL') {\n\t\t\t\totherPaths.add(`${route.method} ${path}`);\n\t\t\t}\n\t\t}\n\n\t\tlog.info('Available endpoints:');\n\t\tif (mcpPaths.size > 0) log.info(` MCP: ${[...mcpPaths].sort().join(', ')}`);\n\t\tif (chatPaths.size > 0) log.info(` Chat: ${[...chatPaths].sort().join(', ')}`);\n\t\tif (apiPaths.size > 0) log.info(` API: ${[...apiPaths].sort().join(', ')}`);\n\t\tif (otherPaths.size > 0) log.info(` Other: ${[...otherPaths].sort().join(', ')}`);\n\t};\n\n\treturn { app, config, emitter, serverCache, printEndpoints, finalize };\n}\n\nfunction headersToRecord(headers: Headers): Record<string, string> {\n\tconst record: Record<string, string> = {};\n\theaders.forEach((value, key) => {\n\t\trecord[key] = value;\n\t});\n\treturn record;\n}\n\n/**\n * Middleware that emits request events via the emitter.\n * Subscribers (like audit plugin) can listen and handle these events.\n */\nfunction requestEventMiddleware(emitter: McpsEmitter) {\n\treturn async (c: Context, next: Next) => {\n\t\tconst startTime = Date.now();\n\t\tconst path = c.req.path;\n\n\t\tlet serverName: string | undefined;\n\t\tlet serverType: string | undefined;\n\n\t\tconst mcpMatch = path.match(/^\\/mcp\\/([^/]+)/);\n\t\tif (mcpMatch) {\n\t\t\tserverName = mcpMatch[1];\n\t\t\tif (['tencent-cls', 'sql', 'prometheus', 'relay'].includes(serverName)) {\n\t\t\t\tserverType = serverName;\n\t\t\t} else {\n\t\t\t\tserverType = 'custom';\n\t\t\t}\n\t\t}\n\n\t\tif (path.startsWith('/v1/')) {\n\t\t\tserverType = 'chat';\n\t\t}\n\n\t\tlet error: string | undefined;\n\n\t\ttry {\n\t\t\tawait next();\n\t\t} catch (e) {\n\t\t\terror = e instanceof Error ? e.message : String(e);\n\t\t\tthrow e;\n\t\t} finally {\n\t\t\tconst durationMs = Date.now() - startTime;\n\n\t\t\temitter.emit(McpsEventType.Request, {\n\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\tmethod: c.req.method,\n\t\t\t\tpath,\n\t\t\t\tserverName,\n\t\t\t\tserverType,\n\t\t\t\tstatus: c.res.status,\n\t\t\t\tdurationMs,\n\t\t\t\terror,\n\t\t\t\trequestHeaders: headersToRecord(c.req.raw.headers),\n\t\t\t});\n\t\t}\n\t};\n}\n"],"names":["consola","Hono","logger","LRUCache","isDevelopment","findMcpServerDef","registerApiRoutes","registerChatRoutes","loadConfig","loadEnvFiles","substituteEnvVars","createMcpsEmitter","McpsEventType","registerMcpRoutes","log","withTag","createServer","options","cwd","process","discoveryConfig","discoveryConfigOption","setup","app","emitter","apiRouters","use","v","debug","requestEventMiddleware","config","serverDefs","info","map","d","name","join","Object","keys","servers","length","serverCache","max","dispose","value","key","close","catch","e","error","ctx","finalize","get","c","json","status","message","routes","r","method","path","filter","startsWith","sort","a","b","localeCompare","mcpRoutes","apiRoutes","chatRoutes","uniqueMcpPaths","Set","version","serverTypes","models","m","endpoints","mcp","api","chat","statsProvider","printEndpoints","mcpPaths","apiPaths","chatPaths","otherPaths","route","add","size","headersToRecord","headers","record","forEach","next","startTime","Date","now","req","serverName","serverType","mcpMatch","match","includes","Error","String","durationMs","emit","Request","timestamp","toISOString","res","requestHeaders","raw"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@wener/mcps",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
+ "type": "module",
4
5
  "description": "",
5
6
  "author": "",
6
7
  "bin": {
@@ -18,6 +19,30 @@
18
19
  "./server": {
19
20
  "types": "./src/server/index.ts",
20
21
  "default": "./lib/server/index.js"
22
+ },
23
+ "./entities": {
24
+ "types": "./src/entities/index.ts",
25
+ "default": "./lib/entities/index.js"
26
+ },
27
+ "./contracts": {
28
+ "types": "./src/contracts/index.ts",
29
+ "default": "./lib/contracts/index.js"
30
+ },
31
+ "./chat": {
32
+ "types": "./src/chat/index.ts",
33
+ "default": "./lib/chat/index.js"
34
+ },
35
+ "./providers": {
36
+ "types": "./src/providers/McpServerHandlerDef.ts",
37
+ "default": "./lib/providers/McpServerHandlerDef.js"
38
+ },
39
+ "./cli": {
40
+ "types": "./src/cli.ts",
41
+ "default": "./lib/cli.js"
42
+ },
43
+ "./audit": {
44
+ "types": "./src/server/audit-plugin.ts",
45
+ "default": "./lib/server/audit-plugin.js"
21
46
  }
22
47
  },
23
48
  "files": [
@@ -27,17 +52,21 @@
27
52
  "README.md"
28
53
  ],
29
54
  "keywords": [],
55
+ "dependencies": {
56
+ "emittery": "^1.2.0"
57
+ },
30
58
  "devDependencies": {
31
59
  "@ai-sdk/mcp": "^1.0.18",
32
60
  "@ai-sdk/openai-compatible": "^2.0.26",
33
61
  "@ai-sdk/react": "^3.0.71",
34
62
  "@base-ui/react": "^1.1.0",
35
63
  "@hono/mcp": "^0.2.3",
64
+ "@larksuiteoapi/node-sdk": "^1.59.0",
36
65
  "@hono/node-server": "^1.19.9",
37
66
  "@hono/vite-dev-server": "^0.24.1",
38
- "@mikro-orm/core": "^7.0.0-rc.0",
39
- "@mikro-orm/decorators": "^7.0.0-rc.0",
40
- "@mikro-orm/sqlite": "^7.0.0-rc.0",
67
+ "@mikro-orm/core": "^7.0.0-rc.2",
68
+ "@mikro-orm/decorators": "^7.0.0-rc.2",
69
+ "@mikro-orm/sql": "^7.0.0-rc.2",
41
70
  "@modelcontextprotocol/sdk": "^1.25.3",
42
71
  "@orpc/client": "^1.13.4",
43
72
  "@orpc/contract": "^1.13.4",
@@ -71,7 +100,8 @@
71
100
  "tedious": "^19.2.0",
72
101
  "yaml": "^2.8.2",
73
102
  "zod": "^4.3.6",
74
- "@wener/ai": "0.1.0"
103
+ "@wener/ai": "0.1.2",
104
+ "@wener/server": "2.0.5"
75
105
  },
76
106
  "scripts": {
77
107
  "build": "bun run ./src/scripts/bundle.ts",
@@ -747,7 +747,7 @@ export function createChatHandler(options: ChatHandlerOptions = {}) {
747
747
  let previousContext: { input: unknown; output: unknown[] } | null = null;
748
748
  if (request.previous_response_id) {
749
749
  try {
750
- const { isDbInitialized, getEntityManager } = await import('../server/db');
750
+ const { isDbInitialized, getEntityManager } = await import('../server/audit-db');
751
751
  const { ResponseEntity } = await import('../entities');
752
752
  if (isDbInitialized()) {
753
753
  const em = getEntityManager().fork();
@@ -840,7 +840,7 @@ export function createChatHandler(options: ChatHandlerOptions = {}) {
840
840
 
841
841
  // Store response for future previous_response_id lookups
842
842
  try {
843
- const { isDbInitialized, getEntityManager } = await import('../server/db');
843
+ const { isDbInitialized, getEntityManager } = await import('../server/audit-db');
844
844
  const { ResponseEntity } = await import('../entities');
845
845
  if (isDbInitialized()) {
846
846
  const em = getEntityManager().fork();
@@ -0,0 +1,43 @@
1
+ import { serve } from '@hono/node-server';
2
+ import consola from 'consola';
3
+ import { createServer, type CreateServerOptions } from './server/server';
4
+
5
+ const log = consola.withTag('mcps');
6
+
7
+ let serverHandle: ReturnType<typeof serve> | undefined;
8
+
9
+ function shutdown() {
10
+ log.info('Shutting down...');
11
+ if (serverHandle) {
12
+ serverHandle.close(() => process.exit(0));
13
+ setTimeout(() => process.exit(1), 3000);
14
+ } else {
15
+ process.exit(0);
16
+ }
17
+ }
18
+
19
+ process.on('SIGINT', shutdown);
20
+ process.on('SIGTERM', shutdown);
21
+
22
+ export async function startServer(options: { port: string; cwd: string; discoveryConfig: boolean; setup?: CreateServerOptions['setup'] }) {
23
+ const port = Number.parseInt(options.port, 10);
24
+ const { app, printEndpoints, finalize } = createServer({
25
+ cwd: options.cwd,
26
+ port,
27
+ discoveryConfig: options.discoveryConfig,
28
+ setup: options.setup,
29
+ });
30
+
31
+ await finalize();
32
+
33
+ log.info(`Starting MCPS server on port ${port}`);
34
+
35
+ serverHandle = serve({
36
+ fetch: app.fetch,
37
+ port,
38
+ hostname: '0.0.0.0',
39
+ });
40
+
41
+ log.success(`MCPS server running at http://localhost:${port}`);
42
+ printEndpoints();
43
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { Command } from 'commander';
2
+ import type { CreateServerOptions } from './server/server';
3
+
4
+ export interface CreateProgramOptions {
5
+ name?: string;
6
+ description?: string;
7
+ version?: string;
8
+ defaultPort?: string;
9
+ /** Called before the server starts, allows registering additional providers via side-effect imports */
10
+ beforeStart?: () => void | Promise<void>;
11
+ /** Server setup callback, passed to createServer. Use to register plugins like audit. */
12
+ setup?: CreateServerOptions['setup'];
13
+ }
14
+
15
+ /**
16
+ * Create a reusable Commander program for MCPS server.
17
+ * Consumers can register providers before calling `program.parseAsync()`.
18
+ */
19
+ export function createProgram(options: CreateProgramOptions = {}): Command {
20
+ const {
21
+ name = 'mcps',
22
+ description = 'MCP Proxy Server - Unified MCP service with relay, SQL, CLS, and Prometheus support',
23
+ version = '0.1.0',
24
+ defaultPort = '8036',
25
+ beforeStart,
26
+ setup,
27
+ } = options;
28
+
29
+ const program = new Command();
30
+
31
+ program
32
+ .name(name)
33
+ .description(description)
34
+ .version(version)
35
+ .option('-p, --port <port>', 'Port to listen on', defaultPort)
36
+ .option('-c, --cwd <path>', 'Working directory for config files', process.cwd())
37
+ .option('--discovery-config', 'Enable server config discovery endpoints', false)
38
+ .action(async (opts) => {
39
+ await beforeStart?.();
40
+ const { startServer } = await import('./cli-start.js');
41
+ await startServer({ ...opts, setup });
42
+ });
43
+
44
+ return program;
45
+ }
package/src/dev.server.ts CHANGED
@@ -1,6 +1,13 @@
1
+ import { setupAudit } from '#/server/audit-plugin';
1
2
  import { createServer } from '#/server/server';
2
3
 
3
- const { app } = createServer({});
4
+ const { app, finalize } = createServer({
5
+ setup: (ctx) => {
6
+ setupAudit(ctx);
7
+ },
8
+ });
9
+
10
+ await finalize();
4
11
 
5
12
  export default {
6
13
  fetch: app.fetch,
package/src/index.ts CHANGED
@@ -1 +1,47 @@
1
- export {};
1
+ // Core server
2
+ export { createServer, type CreateServerOptions, type McpsServerContext, type StatsProvider } from './server/server';
3
+
4
+ // Events (emittery-based decoupling)
5
+ export { McpsEventType, createMcpsEmitter, type McpsEmitter, type McpsEventData, type McpsRequestEvent } from './server/events';
6
+
7
+ // Configuration
8
+ export { loadConfig, substituteEnvVars, loadEnvFiles } from './server/config';
9
+ export {
10
+ type McpsConfig,
11
+ type ServerConfig,
12
+ type ModelConfig,
13
+ type AuditConfig,
14
+ type DbConfig,
15
+ McpsConfigSchema,
16
+ ServerConfigSchema,
17
+ ModelConfigSchema,
18
+ AuditConfigSchema,
19
+ DbConfigSchema,
20
+ HeaderNames,
21
+ } from './server/schema';
22
+
23
+ // MCP routes and handler
24
+ export { registerMcpRoutes } from './server/mcp-routes';
25
+ export { registerChatRoutes } from './server/chat-routes';
26
+ export { registerApiRoutes } from './server/api-routes';
27
+ export { createMcpLoggingHandler } from './server/mcp-handler';
28
+ export { createMcpsRouter } from './server/mcps-router';
29
+
30
+ // Providers
31
+ export {
32
+ type McpServerHandlerDef,
33
+ type McpServerHandlerDef as McpServerDef,
34
+ type HeaderMapping,
35
+ type DefineMcpServerHandlerOptions,
36
+ defineMcpServerHandler,
37
+ registerMcpServerHandler,
38
+ getAllMcpServerHandlerDefs,
39
+ getMcpServerHandlerDef,
40
+ } from './providers/McpServerHandlerDef';
41
+ export { findMcpServerDef, resolveMcpServerDef, getMcpServerDefCount } from './providers/findMcpServerDef';
42
+
43
+ // Contracts
44
+ export * from './contracts';
45
+
46
+ // Chat
47
+ export { createChatHandler, type ChatHandlerOptions } from './chat';