@wener/mcps 1.0.2 → 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.
- package/README.md +144 -0
- package/dist/index.mjs +213076 -1
- package/dist/mcps-cli.mjs +102547 -59344
- package/lib/chat/handler.js +2 -2
- package/lib/chat/handler.js.map +1 -1
- package/lib/cli-start.js +36 -0
- package/lib/cli-start.js.map +1 -0
- package/lib/cli.js +19 -0
- package/lib/cli.js.map +1 -0
- package/lib/dev.server.js +7 -1
- package/lib/dev.server.js.map +1 -1
- package/lib/index.js +21 -3
- package/lib/index.js.map +1 -1
- package/lib/mcps-cli.js +6 -35
- package/lib/mcps-cli.js.map +1 -1
- package/lib/providers/feishu/def.js +35 -0
- package/lib/providers/feishu/def.js.map +1 -0
- package/lib/providers/findMcpServerDef.js +1 -0
- package/lib/providers/findMcpServerDef.js.map +1 -1
- package/lib/scripts/bundle.js +7 -1
- package/lib/scripts/bundle.js.map +1 -1
- package/lib/server/api-routes.js +7 -8
- package/lib/server/api-routes.js.map +1 -1
- package/lib/server/audit-db.js +64 -0
- package/lib/server/audit-db.js.map +1 -0
- package/lib/server/{audit.js → audit-plugin.js} +72 -126
- package/lib/server/audit-plugin.js.map +1 -0
- package/lib/server/events.js +13 -0
- package/lib/server/events.js.map +1 -0
- package/lib/server/mcp-routes.js +31 -60
- package/lib/server/mcp-routes.js.map +1 -1
- package/lib/server/mcps-router.js +19 -24
- package/lib/server/mcps-router.js.map +1 -1
- package/lib/server/schema.js +22 -2
- package/lib/server/schema.js.map +1 -1
- package/lib/server/server.js +142 -87
- package/lib/server/server.js.map +1 -1
- package/package.json +33 -6
- package/src/chat/handler.ts +2 -2
- package/src/cli-start.ts +43 -0
- package/src/cli.ts +45 -0
- package/src/dev.server.ts +8 -1
- package/src/index.ts +47 -1
- package/src/mcps-cli.ts +6 -48
- package/src/providers/feishu/def.ts +37 -0
- package/src/providers/findMcpServerDef.ts +1 -0
- package/src/scripts/bundle.ts +12 -1
- package/src/server/api-routes.ts +11 -8
- package/src/server/audit-db.ts +65 -0
- package/src/server/{audit.ts → audit-plugin.ts} +69 -142
- package/src/server/events.ts +29 -0
- package/src/server/mcp-routes.ts +30 -58
- package/src/server/mcps-router.ts +21 -29
- package/src/server/schema.ts +23 -2
- package/src/server/server.ts +149 -81
- package/lib/server/audit.js.map +0 -1
- package/lib/server/db.js +0 -97
- package/lib/server/db.js.map +0 -1
- package/src/server/db.ts +0 -115
package/src/server/mcp-routes.ts
CHANGED
|
@@ -17,13 +17,15 @@ export interface RegisterMcpRoutesOptions {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* Register MCP routes for both pre-configured and dynamic endpoints
|
|
20
|
+
* Register MCP routes for both pre-configured and dynamic endpoints.
|
|
21
|
+
*
|
|
22
|
+
* McpServer only supports one transport connection at a time, so we create
|
|
23
|
+
* a fresh server instance per request instead of caching stateful servers.
|
|
21
24
|
*/
|
|
22
|
-
export function registerMcpRoutes({ app, config, serverCache }: RegisterMcpRoutesOptions) {
|
|
25
|
+
export function registerMcpRoutes({ app, config, serverCache: _serverCache }: RegisterMcpRoutesOptions) {
|
|
23
26
|
const serverDefs = findMcpServerDef();
|
|
24
27
|
|
|
25
28
|
// Register pre-configured servers from config
|
|
26
|
-
// These are named endpoints like /mcp/my-sql that use config from file
|
|
27
29
|
for (const [name, serverConfig] of Object.entries(config.servers)) {
|
|
28
30
|
const def = getMcpServerHandlerDef(serverConfig.type);
|
|
29
31
|
if (!def) {
|
|
@@ -31,7 +33,6 @@ export function registerMcpRoutes({ app, config, serverCache }: RegisterMcpRoute
|
|
|
31
33
|
continue;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
// Resolve config using def (config comes from file, not headers)
|
|
35
36
|
const options = def.resolveConfig(serverConfig);
|
|
36
37
|
if (!options) {
|
|
37
38
|
log.warn(`Failed to resolve config for ${name}`);
|
|
@@ -44,48 +45,23 @@ export function registerMcpRoutes({ app, config, serverCache }: RegisterMcpRoute
|
|
|
44
45
|
continue;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
// Create and cache the server instance at startup
|
|
48
|
-
const cacheKey = `config::${name}`;
|
|
49
|
-
const item = def.create(options);
|
|
50
|
-
if (!item) {
|
|
51
|
-
log.warn(`Failed to create server: ${name}`);
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
serverCache.set(cacheKey, item);
|
|
55
|
-
|
|
56
48
|
const path = `/mcp/${name}`;
|
|
57
49
|
log.info(`Registered MCP server: ${path} (${serverConfig.type})`);
|
|
58
50
|
|
|
59
51
|
app.all(path, async (c) => {
|
|
60
|
-
|
|
61
|
-
if (!serverItem) {
|
|
62
|
-
return c.text('Server not found', 404);
|
|
63
|
-
}
|
|
64
|
-
// Create a new transport for each request to avoid "Transport already started" error
|
|
65
|
-
const transport = new StreamableHTTPTransport();
|
|
66
|
-
try {
|
|
67
|
-
await serverItem.server.connect(transport);
|
|
68
|
-
const handleRequest = createMcpLoggingHandler(transport, name);
|
|
69
|
-
return await handleRequest(c);
|
|
70
|
-
} catch (e) {
|
|
71
|
-
log.error(`[${name}] Request error:`, e);
|
|
72
|
-
return c.text(`Internal server error: ${e instanceof Error ? e.message : 'Unknown error'}`, 500);
|
|
73
|
-
}
|
|
52
|
+
return handleMcpRequest(c, def, options, name);
|
|
74
53
|
});
|
|
75
54
|
}
|
|
76
55
|
|
|
77
|
-
// Register dynamic endpoints for all server types
|
|
78
|
-
// These endpoints accept config via HTTP headers
|
|
56
|
+
// Register dynamic endpoints for all server types (header-based config)
|
|
79
57
|
for (const def of serverDefs) {
|
|
80
58
|
const path = `/mcp/${def.name}`;
|
|
81
59
|
log.debug(`Registering dynamic endpoint: ${path}`);
|
|
82
60
|
|
|
83
61
|
app.all(path, async (c) => {
|
|
84
|
-
// Use def.resolveConfig to parse config from headers
|
|
85
62
|
const options = def.resolveConfig({ type: def.name } as any, c.req.raw.headers);
|
|
86
63
|
|
|
87
64
|
if (!options) {
|
|
88
|
-
// Build error message from headerMappings
|
|
89
65
|
const requiredHeaders =
|
|
90
66
|
def.headerMappings
|
|
91
67
|
?.filter((m) => m.required)
|
|
@@ -94,40 +70,36 @@ export function registerMcpRoutes({ app, config, serverCache }: RegisterMcpRoute
|
|
|
94
70
|
return c.text(`Missing ${requiredHeaders}`, 400);
|
|
95
71
|
}
|
|
96
72
|
|
|
97
|
-
// Validate options
|
|
98
73
|
const validation = def.validateOptions(options);
|
|
99
74
|
if (!validation.valid) {
|
|
100
75
|
return c.text(validation.error || `Invalid configuration for ${def.name}`, 400);
|
|
101
76
|
}
|
|
102
77
|
|
|
103
|
-
|
|
104
|
-
const key = def.getCacheKey(options);
|
|
105
|
-
let item = serverCache.get(key);
|
|
106
|
-
if (!item) {
|
|
107
|
-
log.info(`Creating new ${def.title} server: ${key}`);
|
|
108
|
-
try {
|
|
109
|
-
const newItem = def.create(options);
|
|
110
|
-
if (!newItem) return c.text(`Failed to create ${def.name} server`, 500);
|
|
111
|
-
item = newItem;
|
|
112
|
-
serverCache.set(key, item);
|
|
113
|
-
} catch (e) {
|
|
114
|
-
log.error(`Failed to create ${def.name} server:`, e);
|
|
115
|
-
return c.text(`Failed to create ${def.name} server`, 500);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Create a new transport for each request
|
|
120
|
-
const transport = new StreamableHTTPTransport();
|
|
121
|
-
try {
|
|
122
|
-
await item.server.connect(transport);
|
|
123
|
-
const handleRequest = createMcpLoggingHandler(transport, def.name);
|
|
124
|
-
return await handleRequest(c);
|
|
125
|
-
} catch (e) {
|
|
126
|
-
log.error(`[${def.name}] Request error:`, e);
|
|
127
|
-
return c.text(`Internal server error: ${e instanceof Error ? e.message : 'Unknown error'}`, 500);
|
|
128
|
-
}
|
|
78
|
+
return handleMcpRequest(c, def, options, def.name);
|
|
129
79
|
});
|
|
130
80
|
}
|
|
131
81
|
|
|
132
82
|
return { serverDefs };
|
|
133
83
|
}
|
|
84
|
+
|
|
85
|
+
async function handleMcpRequest(c: any, def: McpServerHandlerDef, options: any, name: string) {
|
|
86
|
+
let item: McpServerInstance | undefined;
|
|
87
|
+
try {
|
|
88
|
+
item = def.create(options);
|
|
89
|
+
if (!item) return c.text(`Failed to create ${name} server`, 500);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
log.error(`Failed to create ${name} server:`, e);
|
|
92
|
+
return c.text(`Failed to create ${name} server`, 500);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const transport = new StreamableHTTPTransport();
|
|
96
|
+
try {
|
|
97
|
+
await item.server.connect(transport);
|
|
98
|
+
const handleRequest = createMcpLoggingHandler(transport, name);
|
|
99
|
+
return await handleRequest(c);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
log.error(`[${name}] Request error:`, e);
|
|
102
|
+
item.close?.().catch(() => {});
|
|
103
|
+
return c.text(`Internal server error: ${e instanceof Error ? e.message : 'Unknown error'}`, 500);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import { implement } from '@orpc/server';
|
|
2
2
|
import { McpsContract, type ModelInfo, type ServerInfo, type ServerTypeInfo, type ToolInfo } from '../contracts';
|
|
3
3
|
import { findMcpServerDef } from '../providers/findMcpServerDef';
|
|
4
|
-
import { getAuditStats, queryAuditEvents } from './audit';
|
|
5
4
|
import type { McpsConfig } from './schema';
|
|
5
|
+
import type { StatsProvider } from './server';
|
|
6
6
|
|
|
7
|
-
// Simple glob pattern matching
|
|
8
7
|
function matchGlob(pattern: string, text: string): boolean {
|
|
9
8
|
const regexPattern = pattern
|
|
10
|
-
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
9
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
11
10
|
.replace(/\*/g, '.*')
|
|
12
11
|
.replace(/\?/g, '.');
|
|
13
12
|
return new RegExp(`^${regexPattern}$`, 'i').test(text);
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
// Build server types from registry
|
|
17
15
|
function getServerTypes(): ServerTypeInfo[] {
|
|
18
16
|
return findMcpServerDef().map((def) => ({
|
|
19
17
|
type: def.name,
|
|
@@ -22,7 +20,6 @@ function getServerTypes(): ServerTypeInfo[] {
|
|
|
22
20
|
}));
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
// Build endpoints from server types
|
|
26
23
|
function getEndpoints(): string[] {
|
|
27
24
|
const mcpEndpoints = findMcpServerDef().map((def) => `/mcp/${def.name}`);
|
|
28
25
|
const chatEndpoints = [
|
|
@@ -38,22 +35,27 @@ function getEndpoints(): string[] {
|
|
|
38
35
|
|
|
39
36
|
export interface McpsRouterContext {
|
|
40
37
|
config: McpsConfig;
|
|
38
|
+
/** Optional stats provider (e.g. from audit plugin) */
|
|
39
|
+
statsProvider?: StatsProvider;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
const emptyStats = {
|
|
43
|
+
totalRequests: 0,
|
|
44
|
+
totalErrors: 0,
|
|
45
|
+
avgDurationMs: 0,
|
|
46
|
+
byServer: [] as Array<{ name: string; count: number }>,
|
|
47
|
+
byMethod: [] as Array<{ method: string; count: number }>,
|
|
48
|
+
};
|
|
49
|
+
|
|
46
50
|
export function createMcpsRouter(ctx: McpsRouterContext) {
|
|
47
|
-
const { config } = ctx;
|
|
51
|
+
const { config, statsProvider } = ctx;
|
|
48
52
|
|
|
49
|
-
// Build server info list
|
|
50
53
|
const servers: ServerInfo[] = Object.entries(config.servers).map(([name, serverConfig]) => ({
|
|
51
54
|
name,
|
|
52
55
|
type: serverConfig.type,
|
|
53
56
|
disabled: serverConfig.disabled,
|
|
54
57
|
}));
|
|
55
58
|
|
|
56
|
-
// Build model info list
|
|
57
59
|
const models: ModelInfo[] = (config.models ?? []).map((model) => ({
|
|
58
60
|
name: model.name,
|
|
59
61
|
adapter: model.adapter,
|
|
@@ -76,10 +78,13 @@ export function createMcpsRouter(ctx: McpsRouterContext) {
|
|
|
76
78
|
}),
|
|
77
79
|
|
|
78
80
|
stats: implement(McpsContract.stats).handler(async ({ input }) => {
|
|
79
|
-
|
|
81
|
+
if (!statsProvider) {
|
|
82
|
+
return { ...emptyStats, byEndpoint: [] };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const auditStats = statsProvider.getStats(input);
|
|
86
|
+
const { events } = statsProvider.queryEvents({ limit: 10000 });
|
|
80
87
|
|
|
81
|
-
// Build endpoint stats from audit events
|
|
82
|
-
const { events } = queryAuditEvents({ limit: 10000 });
|
|
83
88
|
const endpointCounts = new Map<string, number>();
|
|
84
89
|
for (const event of events) {
|
|
85
90
|
const endpoint = event.path || 'unknown';
|
|
@@ -88,12 +93,9 @@ export function createMcpsRouter(ctx: McpsRouterContext) {
|
|
|
88
93
|
const byEndpoint = Array.from(endpointCounts.entries())
|
|
89
94
|
.map(([endpoint, count]) => ({ endpoint, count }))
|
|
90
95
|
.sort((a, b) => b.count - a.count)
|
|
91
|
-
.slice(0, 20);
|
|
96
|
+
.slice(0, 20);
|
|
92
97
|
|
|
93
|
-
return {
|
|
94
|
-
...auditStats,
|
|
95
|
-
byEndpoint,
|
|
96
|
-
};
|
|
98
|
+
return { ...auditStats, byEndpoint };
|
|
97
99
|
}),
|
|
98
100
|
|
|
99
101
|
servers: implement(McpsContract.servers).handler(async () => {
|
|
@@ -105,17 +107,7 @@ export function createMcpsRouter(ctx: McpsRouterContext) {
|
|
|
105
107
|
}),
|
|
106
108
|
|
|
107
109
|
tools: implement(McpsContract.tools).handler(async ({ input }) => {
|
|
108
|
-
// TODO: This is a placeholder that returns empty tools list.
|
|
109
|
-
// Full implementation would require connecting to each MCP server
|
|
110
|
-
// and calling tools/list. Consider caching tool lists and
|
|
111
|
-
// refreshing periodically or on demand.
|
|
112
110
|
const tools: ToolInfo[] = [];
|
|
113
|
-
|
|
114
|
-
// For now, return an empty list.
|
|
115
|
-
// When MCP servers support persistent connections or when we
|
|
116
|
-
// implement a tool registry, this will be populated.
|
|
117
|
-
|
|
118
|
-
// Apply filters if provided
|
|
119
111
|
let filteredTools = tools;
|
|
120
112
|
|
|
121
113
|
if (input.server) {
|
package/src/server/schema.ts
CHANGED
|
@@ -14,6 +14,9 @@ export const HeaderNames = {
|
|
|
14
14
|
MCP_URL: 'X-MCP-URL',
|
|
15
15
|
MCP_TYPE: 'X-MCP-TYPE',
|
|
16
16
|
MCP_COMMAND: 'X-MCP-COMMAND',
|
|
17
|
+
FEISHU_APP_ID: 'X-FEISHU-APP-ID',
|
|
18
|
+
FEISHU_APP_SECRET: 'X-FEISHU-APP-SECRET',
|
|
19
|
+
FEISHU_DOMAIN: 'X-FEISHU-DOMAIN',
|
|
17
20
|
// Tool filtering headers
|
|
18
21
|
MCP_READONLY: 'X-MCP-Readonly',
|
|
19
22
|
MCP_INCLUDE: 'X-MCP-Include',
|
|
@@ -53,6 +56,15 @@ export const PrometheusConfigSchema = BaseServerConfigSchema.extend({
|
|
|
53
56
|
});
|
|
54
57
|
export type PrometheusConfig = z.infer<typeof PrometheusConfigSchema>;
|
|
55
58
|
|
|
59
|
+
// Feishu/Lark config
|
|
60
|
+
export const FeishuConfigSchema = BaseServerConfigSchema.extend({
|
|
61
|
+
type: z.literal('feishu'),
|
|
62
|
+
appId: z.string().optional().describe('Feishu App ID'),
|
|
63
|
+
appSecret: z.string().optional().describe('Feishu App Secret'),
|
|
64
|
+
domain: z.string().optional().describe('feishu (China) or lark (International)'),
|
|
65
|
+
});
|
|
66
|
+
export type FeishuConfig = z.infer<typeof FeishuConfigSchema>;
|
|
67
|
+
|
|
56
68
|
// Relay config for proxying to other MCP servers
|
|
57
69
|
export const RelayConfigSchema = BaseServerConfigSchema.extend({
|
|
58
70
|
type: z.literal('relay'),
|
|
@@ -63,13 +75,22 @@ export const RelayConfigSchema = BaseServerConfigSchema.extend({
|
|
|
63
75
|
});
|
|
64
76
|
export type RelayConfig = z.infer<typeof RelayConfigSchema>;
|
|
65
77
|
|
|
66
|
-
//
|
|
67
|
-
|
|
78
|
+
// Known server config schemas
|
|
79
|
+
const KnownServerConfigSchema = z.discriminatedUnion('type', [
|
|
68
80
|
TencentClsConfigSchema,
|
|
69
81
|
SqlConfigSchema,
|
|
70
82
|
PrometheusConfigSchema,
|
|
83
|
+
FeishuConfigSchema,
|
|
71
84
|
RelayConfigSchema,
|
|
72
85
|
]);
|
|
86
|
+
|
|
87
|
+
// Catch-all for custom/extension server types (e.g. platform-admin, fusionops-admin)
|
|
88
|
+
const GenericServerConfigSchema = BaseServerConfigSchema.extend({
|
|
89
|
+
type: z.string(),
|
|
90
|
+
}).passthrough();
|
|
91
|
+
|
|
92
|
+
// Union of known types with generic fallback for extensibility
|
|
93
|
+
export const ServerConfigSchema = z.union([KnownServerConfigSchema, GenericServerConfigSchema]);
|
|
73
94
|
export type ServerConfig = z.infer<typeof ServerConfigSchema>;
|
|
74
95
|
|
|
75
96
|
/**
|
package/src/server/server.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import consola from 'consola';
|
|
2
|
+
import type { Context, Next } from 'hono';
|
|
2
3
|
import { Hono } from 'hono';
|
|
3
4
|
import { logger } from 'hono/logger';
|
|
4
5
|
import { LRUCache } from 'lru-cache';
|
|
@@ -6,24 +7,53 @@ import { isDevelopment } from 'std-env';
|
|
|
6
7
|
import type { McpServerInstance } from '@wener/ai/mcp';
|
|
7
8
|
import { findMcpServerDef } from '../providers/findMcpServerDef';
|
|
8
9
|
import { registerApiRoutes } from './api-routes';
|
|
9
|
-
import { auditMiddleware, configureAudit } from './audit';
|
|
10
10
|
import { registerChatRoutes } from './chat-routes';
|
|
11
11
|
import { loadConfig, loadEnvFiles, substituteEnvVars } from './config';
|
|
12
|
+
import { createMcpsEmitter, McpsEventType, type McpsEmitter } from './events';
|
|
12
13
|
import { registerMcpRoutes } from './mcp-routes';
|
|
13
14
|
|
|
14
15
|
const log = consola.withTag('mcps');
|
|
15
16
|
|
|
17
|
+
export interface McpsServerContext {
|
|
18
|
+
app: Hono;
|
|
19
|
+
config: import('./schema').McpsConfig;
|
|
20
|
+
emitter: McpsEmitter;
|
|
21
|
+
serverCache: LRUCache<string, McpServerInstance>;
|
|
22
|
+
/** Plugins can register additional oRPC routers here */
|
|
23
|
+
apiRouters: Record<string, any>;
|
|
24
|
+
/** Plugins can register stats providers here */
|
|
25
|
+
statsProvider?: StatsProvider;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface StatsProvider {
|
|
29
|
+
getStats(options: { from?: string | null; to?: string | null }): {
|
|
30
|
+
totalRequests: number;
|
|
31
|
+
totalErrors: number;
|
|
32
|
+
avgDurationMs: number;
|
|
33
|
+
byServer: Array<{ name: string; count: number }>;
|
|
34
|
+
byMethod: Array<{ method: string; count: number }>;
|
|
35
|
+
};
|
|
36
|
+
queryEvents(options: { limit?: number }): { events: Array<{ path: string }>; total: number };
|
|
37
|
+
}
|
|
38
|
+
|
|
16
39
|
export interface CreateServerOptions {
|
|
17
40
|
cwd?: string;
|
|
18
41
|
port?: number;
|
|
19
42
|
/** Enable server config discovery endpoints (default: false) */
|
|
20
43
|
discoveryConfig?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Called after core server is created but before routes are registered.
|
|
46
|
+
* Use this to set up optional plugins like audit via the emitter.
|
|
47
|
+
*/
|
|
48
|
+
setup?: (ctx: McpsServerContext) => void | Promise<void>;
|
|
21
49
|
}
|
|
22
50
|
|
|
23
51
|
export function createServer(options: CreateServerOptions = {}) {
|
|
24
|
-
const { cwd = process.cwd(), discoveryConfig: discoveryConfigOption } = options;
|
|
52
|
+
const { cwd = process.cwd(), discoveryConfig: discoveryConfigOption, setup } = options;
|
|
25
53
|
|
|
26
54
|
const app = new Hono();
|
|
55
|
+
const emitter = createMcpsEmitter();
|
|
56
|
+
const apiRouters: Record<string, any> = {};
|
|
27
57
|
|
|
28
58
|
// Request logging
|
|
29
59
|
app.use(
|
|
@@ -32,15 +62,14 @@ export function createServer(options: CreateServerOptions = {}) {
|
|
|
32
62
|
}),
|
|
33
63
|
);
|
|
34
64
|
|
|
35
|
-
//
|
|
36
|
-
app.use(
|
|
65
|
+
// Request event middleware - emits events for subscribers (audit, monitoring, etc.)
|
|
66
|
+
app.use(requestEventMiddleware(emitter));
|
|
37
67
|
|
|
38
68
|
// Load .env files first
|
|
39
69
|
loadEnvFiles(cwd);
|
|
40
70
|
|
|
41
71
|
// Load config (with env var substitution)
|
|
42
72
|
const config = substituteEnvVars(loadConfig(cwd));
|
|
43
|
-
// discoveryConfig: CLI option overrides config file, defaults to false
|
|
44
73
|
const discoveryConfig = discoveryConfigOption ?? config.discoveryConfig ?? false;
|
|
45
74
|
|
|
46
75
|
// Log available server types from registry
|
|
@@ -48,15 +77,7 @@ export function createServer(options: CreateServerOptions = {}) {
|
|
|
48
77
|
log.info(`Available server types: ${serverDefs.map((d) => d.name).join(', ')}`);
|
|
49
78
|
log.info(`Loaded ${Object.keys(config.servers).length} servers from config (discoveryConfig: ${discoveryConfig})`);
|
|
50
79
|
|
|
51
|
-
//
|
|
52
|
-
// DB will only be initialized when the first audit event needs persistence
|
|
53
|
-
configureAudit(config.audit, config.db);
|
|
54
|
-
const auditEnabled = config.audit?.enabled !== false;
|
|
55
|
-
const auditDbPath = config.audit?.db?.path ?? config.db?.path ?? '.mcps.db';
|
|
56
|
-
log.info(`Audit configured: enabled=${auditEnabled}, db=${auditDbPath} (lazy init)`);
|
|
57
|
-
|
|
58
|
-
// Unified cache for all MCP servers (both pre-configured and dynamic)
|
|
59
|
-
// Keyed by cache key from def.getCacheKey() or `config::${name}` for pre-configured
|
|
80
|
+
// Unified cache for all MCP servers
|
|
60
81
|
const serverCache = new LRUCache<string, McpServerInstance>({
|
|
61
82
|
max: 100,
|
|
62
83
|
dispose: (value, key) => {
|
|
@@ -65,65 +86,61 @@ export function createServer(options: CreateServerOptions = {}) {
|
|
|
65
86
|
},
|
|
66
87
|
});
|
|
67
88
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
},
|
|
89
|
+
const ctx: McpsServerContext = { app, config, emitter, serverCache, apiRouters };
|
|
90
|
+
|
|
91
|
+
const finalize = async () => {
|
|
92
|
+
// Allow plugins to set up before routes are registered
|
|
93
|
+
await setup?.(ctx);
|
|
94
|
+
|
|
95
|
+
// =========================================================================
|
|
96
|
+
// Register MCP routes (pre-configured and dynamic endpoints)
|
|
97
|
+
// =========================================================================
|
|
98
|
+
registerMcpRoutes({ app, config, serverCache });
|
|
99
|
+
|
|
100
|
+
// =========================================================================
|
|
101
|
+
// Register Chat/LLM Gateway routes
|
|
102
|
+
// =========================================================================
|
|
103
|
+
registerChatRoutes({ app, config });
|
|
104
|
+
|
|
105
|
+
// =========================================================================
|
|
106
|
+
// Health and info endpoints
|
|
107
|
+
// =========================================================================
|
|
108
|
+
app.get('/health', (c) => c.json({ status: 'ok' }));
|
|
109
|
+
app.get('/', (c) => c.json({ message: 'hello' }));
|
|
110
|
+
|
|
111
|
+
if (isDevelopment || discoveryConfig) {
|
|
112
|
+
app.get('/info', (c) => {
|
|
113
|
+
const routes = app.routes
|
|
114
|
+
.map((r) => ({ method: r.method, path: r.path }))
|
|
115
|
+
.filter((r) => r.method !== 'ALL' || r.path.startsWith('/mcp/'))
|
|
116
|
+
.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
|
|
117
|
+
|
|
118
|
+
const mcpRoutes = routes.filter((r) => r.path.startsWith('/mcp/')).map((r) => r.path);
|
|
119
|
+
const apiRoutes = routes.filter((r) => r.path.startsWith('/api/')).map((r) => `${r.method} ${r.path}`);
|
|
120
|
+
const chatRoutes = routes.filter((r) => r.path.startsWith('/v1/')).map((r) => `${r.method} ${r.path}`);
|
|
121
|
+
const uniqueMcpPaths = [...new Set(mcpRoutes)];
|
|
122
|
+
|
|
123
|
+
return c.json({
|
|
124
|
+
name: '@wener/mcps',
|
|
125
|
+
version: '0.1.0',
|
|
126
|
+
servers: Object.keys(config.servers),
|
|
127
|
+
serverTypes: findMcpServerDef().map((d) => d.name),
|
|
128
|
+
models: config.models ? config.models.map((m) => m.name) : [],
|
|
129
|
+
endpoints: {
|
|
130
|
+
mcp: uniqueMcpPaths,
|
|
131
|
+
api: [...new Set(apiRoutes)],
|
|
132
|
+
chat: [...new Set(chatRoutes)],
|
|
133
|
+
},
|
|
134
|
+
});
|
|
115
135
|
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
136
|
+
}
|
|
118
137
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
138
|
+
// =========================================================================
|
|
139
|
+
// Register oRPC API routes (with any plugin-provided routers)
|
|
140
|
+
// =========================================================================
|
|
141
|
+
registerApiRoutes({ app, config, apiRouters, statsProvider: ctx.statsProvider });
|
|
142
|
+
};
|
|
123
143
|
|
|
124
|
-
/**
|
|
125
|
-
* Print available endpoints grouped by category
|
|
126
|
-
*/
|
|
127
144
|
const printEndpoints = () => {
|
|
128
145
|
const routes = app.routes;
|
|
129
146
|
const mcpPaths = new Set<string>();
|
|
@@ -145,19 +162,70 @@ export function createServer(options: CreateServerOptions = {}) {
|
|
|
145
162
|
}
|
|
146
163
|
|
|
147
164
|
log.info('Available endpoints:');
|
|
148
|
-
if (mcpPaths.size > 0) {
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
if (
|
|
152
|
-
|
|
165
|
+
if (mcpPaths.size > 0) log.info(` MCP: ${[...mcpPaths].sort().join(', ')}`);
|
|
166
|
+
if (chatPaths.size > 0) log.info(` Chat: ${[...chatPaths].sort().join(', ')}`);
|
|
167
|
+
if (apiPaths.size > 0) log.info(` API: ${[...apiPaths].sort().join(', ')}`);
|
|
168
|
+
if (otherPaths.size > 0) log.info(` Other: ${[...otherPaths].sort().join(', ')}`);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
return { app, config, emitter, serverCache, printEndpoints, finalize };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function headersToRecord(headers: Headers): Record<string, string> {
|
|
175
|
+
const record: Record<string, string> = {};
|
|
176
|
+
headers.forEach((value, key) => {
|
|
177
|
+
record[key] = value;
|
|
178
|
+
});
|
|
179
|
+
return record;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Middleware that emits request events via the emitter.
|
|
184
|
+
* Subscribers (like audit plugin) can listen and handle these events.
|
|
185
|
+
*/
|
|
186
|
+
function requestEventMiddleware(emitter: McpsEmitter) {
|
|
187
|
+
return async (c: Context, next: Next) => {
|
|
188
|
+
const startTime = Date.now();
|
|
189
|
+
const path = c.req.path;
|
|
190
|
+
|
|
191
|
+
let serverName: string | undefined;
|
|
192
|
+
let serverType: string | undefined;
|
|
193
|
+
|
|
194
|
+
const mcpMatch = path.match(/^\/mcp\/([^/]+)/);
|
|
195
|
+
if (mcpMatch) {
|
|
196
|
+
serverName = mcpMatch[1];
|
|
197
|
+
if (['tencent-cls', 'sql', 'prometheus', 'relay'].includes(serverName)) {
|
|
198
|
+
serverType = serverName;
|
|
199
|
+
} else {
|
|
200
|
+
serverType = 'custom';
|
|
201
|
+
}
|
|
153
202
|
}
|
|
154
|
-
|
|
155
|
-
|
|
203
|
+
|
|
204
|
+
if (path.startsWith('/v1/')) {
|
|
205
|
+
serverType = 'chat';
|
|
156
206
|
}
|
|
157
|
-
|
|
158
|
-
|
|
207
|
+
|
|
208
|
+
let error: string | undefined;
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
await next();
|
|
212
|
+
} catch (e) {
|
|
213
|
+
error = e instanceof Error ? e.message : String(e);
|
|
214
|
+
throw e;
|
|
215
|
+
} finally {
|
|
216
|
+
const durationMs = Date.now() - startTime;
|
|
217
|
+
|
|
218
|
+
emitter.emit(McpsEventType.Request, {
|
|
219
|
+
timestamp: new Date().toISOString(),
|
|
220
|
+
method: c.req.method,
|
|
221
|
+
path,
|
|
222
|
+
serverName,
|
|
223
|
+
serverType,
|
|
224
|
+
status: c.res.status,
|
|
225
|
+
durationMs,
|
|
226
|
+
error,
|
|
227
|
+
requestHeaders: headersToRecord(c.req.raw.headers),
|
|
228
|
+
});
|
|
159
229
|
}
|
|
160
230
|
};
|
|
161
|
-
|
|
162
|
-
return { app, config, serverCache, printEndpoints };
|
|
163
231
|
}
|
package/lib/server/audit.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/audit.ts"],"sourcesContent":["import { implement } from '@orpc/server';\nimport type { Context, Next } from 'hono';\nimport { LRUCache } from 'lru-cache';\nimport { AuditContract, type AuditEvent } from '../contracts';\nimport { RequestLogEntity } from '../entities';\nimport { ensureDbInitialized, configureDb } from './db';\nimport type { AuditConfig, DbConfig } from './schema';\n\n/**\n * Convert Headers to a plain record\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// In-memory audit store using LRU cache\nconst auditStore = new LRUCache<string, AuditEvent>({\n\tmax: 10000, // Keep last 10k events\n\tttl: 1000 * 60 * 60 * 24, // 24 hours\n});\n\n// Counter for IDs\nlet eventCounter = 0;\n\n// Audit configuration state\nlet auditEnabled = true; // default to enabled\nlet dbConfigured = false;\n\n/**\n * Configure audit module with settings\n * Call this before using audit features\n *\n * @param auditConfig - Audit config section\n * @param fallbackDbConfig - Fallback db config from root config\n */\nexport function configureAudit(auditConfig?: AuditConfig, fallbackDbConfig?: DbConfig): void {\n\t// Determine if audit is enabled (default: true)\n\tauditEnabled = auditConfig?.enabled !== false;\n\n\tif (auditEnabled) {\n\t\t// Use audit.db config if present, otherwise fallback to root db config\n\t\tconst dbConfig = auditConfig?.db ?? fallbackDbConfig;\n\t\tconfigureDb(dbConfig);\n\t\tdbConfigured = true;\n\t}\n}\n\n/**\n * Check if audit is enabled\n */\nexport function isAuditEnabled(): boolean {\n\treturn auditEnabled;\n}\n\n/**\n * Persist audit event to database (lazy init)\n */\nasync function persistToDb(event: AuditEvent, id: string): Promise<void> {\n\tif (!auditEnabled || !dbConfigured) {\n\t\treturn;\n\t}\n\n\ttry {\n\t\t// Lazy initialize DB on first persist\n\t\tconst orm = await ensureDbInitialized();\n\t\tconst em = orm.em.fork();\n\n\t\tconst logEntry = new RequestLogEntity();\n\t\tlogEntry.requestId = id;\n\t\tlogEntry.timestamp = new Date(event.timestamp);\n\t\tlogEntry.method = event.method;\n\t\tlogEntry.path = event.path;\n\t\tlogEntry.serverName = event.serverName ?? undefined;\n\t\tlogEntry.serverType = event.serverType ?? undefined;\n\t\tlogEntry.status = event.status ?? undefined;\n\t\tlogEntry.durationMs = event.durationMs ?? undefined;\n\t\tlogEntry.error = event.error ?? undefined;\n\t\tlogEntry.requestHeaders = event.requestHeaders ?? undefined;\n\t\t// Determine request type\n\t\tif (event.path.startsWith('/mcp/')) {\n\t\t\tlogEntry.requestType = 'mcp';\n\t\t} else if (event.path.startsWith('/v1/')) {\n\t\t\tlogEntry.requestType = 'chat';\n\t\t} else {\n\t\t\tlogEntry.requestType = 'api';\n\t\t}\n\t\tem.persist(logEntry);\n\t\tawait em.flush();\n\t} catch (e) {\n\t\t// Log persistence errors but don't throw - in-memory store is the primary\n\t\tconsole.error('Failed to persist audit log:', e);\n\t}\n}\n\n/**\n * Add an audit event\n */\nexport function addAuditEvent(event: Omit<AuditEvent, 'id'>): AuditEvent {\n\tconst id = `${Date.now()}-${++eventCounter}`;\n\tconst fullEvent: AuditEvent = { ...event, id };\n\tauditStore.set(id, fullEvent);\n\n\t// Persist to database asynchronously (lazy init)\n\tpersistToDb(fullEvent, id).catch(() => {\n\t\t// Already logged in persistToDb\n\t});\n\n\treturn fullEvent;\n}\n\n/**\n * Query audit events\n */\nexport function queryAuditEvents(options: {\n\tlimit?: number;\n\toffset?: number;\n\tserverName?: string | null;\n\tserverType?: string | null;\n\tmethod?: string | null;\n\tfrom?: string | null;\n\tto?: string | null;\n}): { events: AuditEvent[]; total: number } {\n\tconst { limit = 50, offset = 0, serverName, serverType, method, from, to } = options;\n\n\t// Get all events as array\n\tlet events: AuditEvent[] = [];\n\tfor (const [, event] of auditStore.entries()) {\n\t\tevents.push(event);\n\t}\n\n\t// Sort by timestamp desc\n\tevents.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());\n\n\t// Apply filters\n\tif (serverName) {\n\t\tevents = events.filter((e) => e.serverName === serverName);\n\t}\n\tif (serverType) {\n\t\tevents = events.filter((e) => e.serverType === serverType);\n\t}\n\tif (method) {\n\t\tevents = events.filter((e) => e.method === method);\n\t}\n\tif (from) {\n\t\tconst fromTime = new Date(from).getTime();\n\t\tevents = events.filter((e) => new Date(e.timestamp).getTime() >= fromTime);\n\t}\n\tif (to) {\n\t\tconst toTime = new Date(to).getTime();\n\t\tevents = events.filter((e) => new Date(e.timestamp).getTime() <= toTime);\n\t}\n\n\tconst total = events.length;\n\n\t// Paginate\n\tevents = events.slice(offset, offset + limit);\n\n\treturn { events, total };\n}\n\n/**\n * Get audit statistics\n */\nexport function getAuditStats(options: { from?: string | null; to?: string | null }) {\n\tlet events: AuditEvent[] = [];\n\tfor (const [, event] of auditStore.entries()) {\n\t\tevents.push(event);\n\t}\n\n\t// Apply time filters\n\tif (options.from) {\n\t\tconst fromTime = new Date(options.from).getTime();\n\t\tevents = events.filter((e) => new Date(e.timestamp).getTime() >= fromTime);\n\t}\n\tif (options.to) {\n\t\tconst toTime = new Date(options.to).getTime();\n\t\tevents = events.filter((e) => new Date(e.timestamp).getTime() <= toTime);\n\t}\n\n\t// Calculate stats\n\tconst totalRequests = events.length;\n\tconst totalErrors = events.filter((e) => e.error || (e.status && e.status >= 400)).length;\n\n\tconst durations = events.map((e) => e.durationMs).filter((d): d is number => d != null);\n\tconst avgDurationMs = durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : 0;\n\n\t// Group by server\n\tconst serverCounts = new Map<string, number>();\n\tfor (const event of events) {\n\t\tconst name = event.serverName || 'unknown';\n\t\tserverCounts.set(name, (serverCounts.get(name) || 0) + 1);\n\t}\n\tconst byServer = Array.from(serverCounts.entries())\n\t\t.map(([name, count]) => ({ name, count }))\n\t\t.sort((a, b) => b.count - a.count);\n\n\t// Group by method\n\tconst methodCounts = new Map<string, number>();\n\tfor (const event of events) {\n\t\tconst method = event.method || 'unknown';\n\t\tmethodCounts.set(method, (methodCounts.get(method) || 0) + 1);\n\t}\n\tconst byMethod = Array.from(methodCounts.entries())\n\t\t.map(([method, count]) => ({ method, count }))\n\t\t.sort((a, b) => b.count - a.count);\n\n\treturn { totalRequests, totalErrors, avgDurationMs, byServer, byMethod };\n}\n\n/**\n * Clear audit events before a timestamp\n */\nexport function clearAuditEvents(before: string): number {\n\tconst beforeTime = new Date(before).getTime();\n\tlet deleted = 0;\n\n\tfor (const [id, event] of auditStore.entries()) {\n\t\tif (new Date(event.timestamp).getTime() < beforeTime) {\n\t\t\tauditStore.delete(id);\n\t\t\tdeleted++;\n\t\t}\n\t}\n\n\treturn deleted;\n}\n\n/**\n * Audit Router implementation\n */\nexport const AuditRouter = implement(AuditContract).router({\n\tlist: implement(AuditContract.list).handler(async ({ input }) => {\n\t\treturn queryAuditEvents(input);\n\t}),\n\n\tget: implement(AuditContract.get).handler(async ({ input }) => {\n\t\treturn auditStore.get(input.id) ?? null;\n\t}),\n\n\tstats: implement(AuditContract.stats).handler(async ({ input }) => {\n\t\treturn getAuditStats(input);\n\t}),\n\n\tclear: implement(AuditContract.clear).handler(async ({ input }) => {\n\t\tconst deleted = clearAuditEvents(input.before);\n\t\treturn { deleted };\n\t}),\n});\n\n/**\n * Hono middleware for audit logging\n */\nexport function auditMiddleware() {\n\treturn async (c: Context, next: Next) => {\n\t\tconst startTime = Date.now();\n\t\tconst path = c.req.path;\n\n\t\t// Extract server info from path\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\t// Infer type from well-known paths\n\t\t\tif (serverName === 'tencent-cls') serverType = 'tencent-cls';\n\t\t\telse if (serverName === 'sql') serverType = 'sql';\n\t\t\telse if (serverName === 'prometheus') serverType = 'prometheus';\n\t\t\telse if (serverName === 'relay') serverType = 'relay';\n\t\t\telse serverType = 'custom';\n\t\t}\n\n\t\t// Extract model info from chat requests\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\t// Audit MCP requests, Chat API requests, and other API requests\n\t\t\tconst shouldAudit =\n\t\t\t\tpath.startsWith('/mcp/') || path.startsWith('/v1/') || (path.startsWith('/api/') && c.req.method !== 'GET');\n\n\t\t\tif (shouldAudit) {\n\t\t\t\taddAuditEvent({\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\tmethod: c.req.method,\n\t\t\t\t\tpath,\n\t\t\t\t\tserverName,\n\t\t\t\t\tserverType,\n\t\t\t\t\tstatus: c.res.status,\n\t\t\t\t\tdurationMs,\n\t\t\t\t\terror,\n\t\t\t\t\trequestHeaders: headersToRecord(c.req.raw.headers),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t};\n}\n"],"names":["implement","LRUCache","AuditContract","RequestLogEntity","ensureDbInitialized","configureDb","headersToRecord","headers","record","forEach","value","key","auditStore","max","ttl","eventCounter","auditEnabled","dbConfigured","configureAudit","auditConfig","fallbackDbConfig","enabled","dbConfig","db","isAuditEnabled","persistToDb","event","id","orm","em","fork","logEntry","requestId","timestamp","Date","method","path","serverName","undefined","serverType","status","durationMs","error","requestHeaders","startsWith","requestType","persist","flush","e","console","addAuditEvent","now","fullEvent","set","catch","queryAuditEvents","options","limit","offset","from","to","events","entries","push","sort","a","b","getTime","filter","fromTime","toTime","total","length","slice","getAuditStats","totalRequests","totalErrors","durations","map","d","avgDurationMs","reduce","serverCounts","Map","name","get","byServer","Array","count","methodCounts","byMethod","clearAuditEvents","before","beforeTime","deleted","delete","AuditRouter","router","list","handler","input","stats","clear","auditMiddleware","c","next","startTime","req","mcpMatch","match","Error","message","String","shouldAudit","toISOString","res","raw"],"mappings":"AAAA,SAASA,SAAS,QAAQ,eAAe;AAEzC,SAASC,QAAQ,QAAQ,YAAY;AACrC,SAASC,aAAa,QAAyB,eAAe;AAC9D,SAASC,gBAAgB,QAAQ,cAAc;AAC/C,SAASC,mBAAmB,EAAEC,WAAW,QAAQ,OAAO;AAGxD;;CAEC,GACD,SAASC,gBAAgBC,OAAgB;IACxC,MAAMC,SAAiC,CAAC;IACxCD,QAAQE,OAAO,CAAC,CAACC,OAAOC;QACvBH,MAAM,CAACG,IAAI,GAAGD;IACf;IACA,OAAOF;AACR;AAEA,wCAAwC;AACxC,MAAMI,aAAa,IAAIX,SAA6B;IACnDY,KAAK;IACLC,KAAK,OAAO,KAAK,KAAK;AACvB;AAEA,kBAAkB;AAClB,IAAIC,eAAe;AAEnB,4BAA4B;AAC5B,IAAIC,eAAe,MAAM,qBAAqB;AAC9C,IAAIC,eAAe;AAEnB;;;;;;CAMC,GACD,OAAO,SAASC,eAAeC,WAAyB,EAAEC,gBAA2B;IACpF,gDAAgD;IAChDJ,eAAeG,aAAaE,YAAY;IAExC,IAAIL,cAAc;QACjB,uEAAuE;QACvE,MAAMM,WAAWH,aAAaI,MAAMH;QACpCf,YAAYiB;QACZL,eAAe;IAChB;AACD;AAEA;;CAEC,GACD,OAAO,SAASO;IACf,OAAOR;AACR;AAEA;;CAEC,GACD,eAAeS,YAAYC,KAAiB,EAAEC,EAAU;IACvD,IAAI,CAACX,gBAAgB,CAACC,cAAc;QACnC;IACD;IAEA,IAAI;QACH,sCAAsC;QACtC,MAAMW,MAAM,MAAMxB;QAClB,MAAMyB,KAAKD,IAAIC,EAAE,CAACC,IAAI;QAEtB,MAAMC,WAAW,IAAI5B;QACrB4B,SAASC,SAAS,GAAGL;QACrBI,SAASE,SAAS,GAAG,IAAIC,KAAKR,MAAMO,SAAS;QAC7CF,SAASI,MAAM,GAAGT,MAAMS,MAAM;QAC9BJ,SAASK,IAAI,GAAGV,MAAMU,IAAI;QAC1BL,SAASM,UAAU,GAAGX,MAAMW,UAAU,IAAIC;QAC1CP,SAASQ,UAAU,GAAGb,MAAMa,UAAU,IAAID;QAC1CP,SAASS,MAAM,GAAGd,MAAMc,MAAM,IAAIF;QAClCP,SAASU,UAAU,GAAGf,MAAMe,UAAU,IAAIH;QAC1CP,SAASW,KAAK,GAAGhB,MAAMgB,KAAK,IAAIJ;QAChCP,SAASY,cAAc,GAAGjB,MAAMiB,cAAc,IAAIL;QAClD,yBAAyB;QACzB,IAAIZ,MAAMU,IAAI,CAACQ,UAAU,CAAC,UAAU;YACnCb,SAASc,WAAW,GAAG;QACxB,OAAO,IAAInB,MAAMU,IAAI,CAACQ,UAAU,CAAC,SAAS;YACzCb,SAASc,WAAW,GAAG;QACxB,OAAO;YACNd,SAASc,WAAW,GAAG;QACxB;QACAhB,GAAGiB,OAAO,CAACf;QACX,MAAMF,GAAGkB,KAAK;IACf,EAAE,OAAOC,GAAG;QACX,0EAA0E;QAC1EC,QAAQP,KAAK,CAAC,gCAAgCM;IAC/C;AACD;AAEA;;CAEC,GACD,OAAO,SAASE,cAAcxB,KAA6B;IAC1D,MAAMC,KAAK,GAAGO,KAAKiB,GAAG,GAAG,CAAC,EAAE,EAAEpC,cAAc;IAC5C,MAAMqC,YAAwB;QAAE,GAAG1B,KAAK;QAAEC;IAAG;IAC7Cf,WAAWyC,GAAG,CAAC1B,IAAIyB;IAEnB,iDAAiD;IACjD3B,YAAY2B,WAAWzB,IAAI2B,KAAK,CAAC;IAChC,gCAAgC;IACjC;IAEA,OAAOF;AACR;AAEA;;CAEC,GACD,OAAO,SAASG,iBAAiBC,OAQhC;IACA,MAAM,EAAEC,QAAQ,EAAE,EAAEC,SAAS,CAAC,EAAErB,UAAU,EAAEE,UAAU,EAAEJ,MAAM,EAAEwB,IAAI,EAAEC,EAAE,EAAE,GAAGJ;IAE7E,0BAA0B;IAC1B,IAAIK,SAAuB,EAAE;IAC7B,KAAK,MAAM,GAAGnC,MAAM,IAAId,WAAWkD,OAAO,GAAI;QAC7CD,OAAOE,IAAI,CAACrC;IACb;IAEA,yBAAyB;IACzBmC,OAAOG,IAAI,CAAC,CAACC,GAAGC,IAAM,IAAIhC,KAAKgC,EAAEjC,SAAS,EAAEkC,OAAO,KAAK,IAAIjC,KAAK+B,EAAEhC,SAAS,EAAEkC,OAAO;IAErF,gBAAgB;IAChB,IAAI9B,YAAY;QACfwB,SAASA,OAAOO,MAAM,CAAC,CAACpB,IAAMA,EAAEX,UAAU,KAAKA;IAChD;IACA,IAAIE,YAAY;QACfsB,SAASA,OAAOO,MAAM,CAAC,CAACpB,IAAMA,EAAET,UAAU,KAAKA;IAChD;IACA,IAAIJ,QAAQ;QACX0B,SAASA,OAAOO,MAAM,CAAC,CAACpB,IAAMA,EAAEb,MAAM,KAAKA;IAC5C;IACA,IAAIwB,MAAM;QACT,MAAMU,WAAW,IAAInC,KAAKyB,MAAMQ,OAAO;QACvCN,SAASA,OAAOO,MAAM,CAAC,CAACpB,IAAM,IAAId,KAAKc,EAAEf,SAAS,EAAEkC,OAAO,MAAME;IAClE;IACA,IAAIT,IAAI;QACP,MAAMU,SAAS,IAAIpC,KAAK0B,IAAIO,OAAO;QACnCN,SAASA,OAAOO,MAAM,CAAC,CAACpB,IAAM,IAAId,KAAKc,EAAEf,SAAS,EAAEkC,OAAO,MAAMG;IAClE;IAEA,MAAMC,QAAQV,OAAOW,MAAM;IAE3B,WAAW;IACXX,SAASA,OAAOY,KAAK,CAACf,QAAQA,SAASD;IAEvC,OAAO;QAAEI;QAAQU;IAAM;AACxB;AAEA;;CAEC,GACD,OAAO,SAASG,cAAclB,OAAqD;IAClF,IAAIK,SAAuB,EAAE;IAC7B,KAAK,MAAM,GAAGnC,MAAM,IAAId,WAAWkD,OAAO,GAAI;QAC7CD,OAAOE,IAAI,CAACrC;IACb;IAEA,qBAAqB;IACrB,IAAI8B,QAAQG,IAAI,EAAE;QACjB,MAAMU,WAAW,IAAInC,KAAKsB,QAAQG,IAAI,EAAEQ,OAAO;QAC/CN,SAASA,OAAOO,MAAM,CAAC,CAACpB,IAAM,IAAId,KAAKc,EAAEf,SAAS,EAAEkC,OAAO,MAAME;IAClE;IACA,IAAIb,QAAQI,EAAE,EAAE;QACf,MAAMU,SAAS,IAAIpC,KAAKsB,QAAQI,EAAE,EAAEO,OAAO;QAC3CN,SAASA,OAAOO,MAAM,CAAC,CAACpB,IAAM,IAAId,KAAKc,EAAEf,SAAS,EAAEkC,OAAO,MAAMG;IAClE;IAEA,kBAAkB;IAClB,MAAMK,gBAAgBd,OAAOW,MAAM;IACnC,MAAMI,cAAcf,OAAOO,MAAM,CAAC,CAACpB,IAAMA,EAAEN,KAAK,IAAKM,EAAER,MAAM,IAAIQ,EAAER,MAAM,IAAI,KAAMgC,MAAM;IAEzF,MAAMK,YAAYhB,OAAOiB,GAAG,CAAC,CAAC9B,IAAMA,EAAEP,UAAU,EAAE2B,MAAM,CAAC,CAACW,IAAmBA,KAAK;IAClF,MAAMC,gBAAgBH,UAAUL,MAAM,GAAG,IAAIK,UAAUI,MAAM,CAAC,CAAChB,GAAGC,IAAMD,IAAIC,GAAG,KAAKW,UAAUL,MAAM,GAAG;IAEvG,kBAAkB;IAClB,MAAMU,eAAe,IAAIC;IACzB,KAAK,MAAMzD,SAASmC,OAAQ;QAC3B,MAAMuB,OAAO1D,MAAMW,UAAU,IAAI;QACjC6C,aAAa7B,GAAG,CAAC+B,MAAM,AAACF,CAAAA,aAAaG,GAAG,CAACD,SAAS,CAAA,IAAK;IACxD;IACA,MAAME,WAAWC,MAAM5B,IAAI,CAACuB,aAAapB,OAAO,IAC9CgB,GAAG,CAAC,CAAC,CAACM,MAAMI,MAAM,GAAM,CAAA;YAAEJ;YAAMI;QAAM,CAAA,GACtCxB,IAAI,CAAC,CAACC,GAAGC,IAAMA,EAAEsB,KAAK,GAAGvB,EAAEuB,KAAK;IAElC,kBAAkB;IAClB,MAAMC,eAAe,IAAIN;IACzB,KAAK,MAAMzD,SAASmC,OAAQ;QAC3B,MAAM1B,SAAST,MAAMS,MAAM,IAAI;QAC/BsD,aAAapC,GAAG,CAAClB,QAAQ,AAACsD,CAAAA,aAAaJ,GAAG,CAAClD,WAAW,CAAA,IAAK;IAC5D;IACA,MAAMuD,WAAWH,MAAM5B,IAAI,CAAC8B,aAAa3B,OAAO,IAC9CgB,GAAG,CAAC,CAAC,CAAC3C,QAAQqD,MAAM,GAAM,CAAA;YAAErD;YAAQqD;QAAM,CAAA,GAC1CxB,IAAI,CAAC,CAACC,GAAGC,IAAMA,EAAEsB,KAAK,GAAGvB,EAAEuB,KAAK;IAElC,OAAO;QAAEb;QAAeC;QAAaI;QAAeM;QAAUI;IAAS;AACxE;AAEA;;CAEC,GACD,OAAO,SAASC,iBAAiBC,MAAc;IAC9C,MAAMC,aAAa,IAAI3D,KAAK0D,QAAQzB,OAAO;IAC3C,IAAI2B,UAAU;IAEd,KAAK,MAAM,CAACnE,IAAID,MAAM,IAAId,WAAWkD,OAAO,GAAI;QAC/C,IAAI,IAAI5B,KAAKR,MAAMO,SAAS,EAAEkC,OAAO,KAAK0B,YAAY;YACrDjF,WAAWmF,MAAM,CAACpE;YAClBmE;QACD;IACD;IAEA,OAAOA;AACR;AAEA;;CAEC,GACD,OAAO,MAAME,cAAchG,UAAUE,eAAe+F,MAAM,CAAC;IAC1DC,MAAMlG,UAAUE,cAAcgG,IAAI,EAAEC,OAAO,CAAC,OAAO,EAAEC,KAAK,EAAE;QAC3D,OAAO7C,iBAAiB6C;IACzB;IAEAf,KAAKrF,UAAUE,cAAcmF,GAAG,EAAEc,OAAO,CAAC,OAAO,EAAEC,KAAK,EAAE;QACzD,OAAOxF,WAAWyE,GAAG,CAACe,MAAMzE,EAAE,KAAK;IACpC;IAEA0E,OAAOrG,UAAUE,cAAcmG,KAAK,EAAEF,OAAO,CAAC,OAAO,EAAEC,KAAK,EAAE;QAC7D,OAAO1B,cAAc0B;IACtB;IAEAE,OAAOtG,UAAUE,cAAcoG,KAAK,EAAEH,OAAO,CAAC,OAAO,EAAEC,KAAK,EAAE;QAC7D,MAAMN,UAAUH,iBAAiBS,MAAMR,MAAM;QAC7C,OAAO;YAAEE;QAAQ;IAClB;AACD,GAAG;AAEH;;CAEC,GACD,OAAO,SAASS;IACf,OAAO,OAAOC,GAAYC;QACzB,MAAMC,YAAYxE,KAAKiB,GAAG;QAC1B,MAAMf,OAAOoE,EAAEG,GAAG,CAACvE,IAAI;QAEvB,gCAAgC;QAChC,IAAIC;QACJ,IAAIE;QAEJ,MAAMqE,WAAWxE,KAAKyE,KAAK,CAAC;QAC5B,IAAID,UAAU;YACbvE,aAAauE,QAAQ,CAAC,EAAE;YACxB,mCAAmC;YACnC,IAAIvE,eAAe,eAAeE,aAAa;iBAC1C,IAAIF,eAAe,OAAOE,aAAa;iBACvC,IAAIF,eAAe,cAAcE,aAAa;iBAC9C,IAAIF,eAAe,SAASE,aAAa;iBACzCA,aAAa;QACnB;QAEA,wCAAwC;QACxC,IAAIH,KAAKQ,UAAU,CAAC,SAAS;YAC5BL,aAAa;QACd;QAEA,IAAIG;QAEJ,IAAI;YACH,MAAM+D;QACP,EAAE,OAAOzD,GAAG;YACXN,QAAQM,aAAa8D,QAAQ9D,EAAE+D,OAAO,GAAGC,OAAOhE;YAChD,MAAMA;QACP,SAAU;YACT,MAAMP,aAAaP,KAAKiB,GAAG,KAAKuD;YAEhC,gEAAgE;YAChE,MAAMO,cACL7E,KAAKQ,UAAU,CAAC,YAAYR,KAAKQ,UAAU,CAAC,WAAYR,KAAKQ,UAAU,CAAC,YAAY4D,EAAEG,GAAG,CAACxE,MAAM,KAAK;YAEtG,IAAI8E,aAAa;gBAChB/D,cAAc;oBACbjB,WAAW,IAAIC,OAAOgF,WAAW;oBACjC/E,QAAQqE,EAAEG,GAAG,CAACxE,MAAM;oBACpBC;oBACAC;oBACAE;oBACAC,QAAQgE,EAAEW,GAAG,CAAC3E,MAAM;oBACpBC;oBACAC;oBACAC,gBAAgBrC,gBAAgBkG,EAAEG,GAAG,CAACS,GAAG,CAAC7G,OAAO;gBAClD;YACD;QACD;IACD;AACD"}
|