@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/findMcpServerDef.ts"],"sourcesContent":["import { getAllMcpServerHandlerDefs, type McpServerHandlerDef } from './McpServerHandlerDef';\n// Import all server definitions to ensure they are registered\nimport './prometheus/def';\nimport './tencent-cls/def';\nimport './sql/def';\nimport './relay/def';\n\n/**\n * Find MCP server definitions matching a predicate\n */\nexport function findMcpServerDef(predicate?: (def: McpServerHandlerDef) => boolean): McpServerHandlerDef[] {\n\tconst all = getAllMcpServerHandlerDefs();\n\tif (!predicate) {\n\t\treturn all;\n\t}\n\treturn all.filter(predicate);\n}\n\n/**\n * Resolve a single MCP server definition by name\n */\nexport function resolveMcpServerDef(name: string): McpServerHandlerDef | undefined {\n\treturn findMcpServerDef((v) => v.name === name)[0];\n}\n\n/**\n * Get total count of registered MCP server types\n */\nexport function getMcpServerDefCount(): number {\n\treturn getAllMcpServerHandlerDefs().length;\n}\n"],"names":["getAllMcpServerHandlerDefs","findMcpServerDef","predicate","all","filter","resolveMcpServerDef","name","v","getMcpServerDefCount","length"],"mappings":"AAAA,SAASA,0BAA0B,QAAkC,wBAAwB;AAC7F,8DAA8D;AAC9D,OAAO,mBAAmB;AAC1B,OAAO,oBAAoB;AAC3B,OAAO,YAAY;AACnB,OAAO,cAAc;
|
|
1
|
+
{"version":3,"sources":["../../src/providers/findMcpServerDef.ts"],"sourcesContent":["import { getAllMcpServerHandlerDefs, type McpServerHandlerDef } from './McpServerHandlerDef';\n// Import all server definitions to ensure they are registered\nimport './prometheus/def';\nimport './tencent-cls/def';\nimport './sql/def';\nimport './relay/def';\nimport './feishu/def';\n\n/**\n * Find MCP server definitions matching a predicate\n */\nexport function findMcpServerDef(predicate?: (def: McpServerHandlerDef) => boolean): McpServerHandlerDef[] {\n\tconst all = getAllMcpServerHandlerDefs();\n\tif (!predicate) {\n\t\treturn all;\n\t}\n\treturn all.filter(predicate);\n}\n\n/**\n * Resolve a single MCP server definition by name\n */\nexport function resolveMcpServerDef(name: string): McpServerHandlerDef | undefined {\n\treturn findMcpServerDef((v) => v.name === name)[0];\n}\n\n/**\n * Get total count of registered MCP server types\n */\nexport function getMcpServerDefCount(): number {\n\treturn getAllMcpServerHandlerDefs().length;\n}\n"],"names":["getAllMcpServerHandlerDefs","findMcpServerDef","predicate","all","filter","resolveMcpServerDef","name","v","getMcpServerDefCount","length"],"mappings":"AAAA,SAASA,0BAA0B,QAAkC,wBAAwB;AAC7F,8DAA8D;AAC9D,OAAO,mBAAmB;AAC1B,OAAO,oBAAoB;AAC3B,OAAO,YAAY;AACnB,OAAO,cAAc;AACrB,OAAO,eAAe;AAEtB;;CAEC,GACD,OAAO,SAASC,iBAAiBC,SAAiD;IACjF,MAAMC,MAAMH;IACZ,IAAI,CAACE,WAAW;QACf,OAAOC;IACR;IACA,OAAOA,IAAIC,MAAM,CAACF;AACnB;AAEA;;CAEC,GACD,OAAO,SAASG,oBAAoBC,IAAY;IAC/C,OAAOL,iBAAiB,CAACM,IAAMA,EAAED,IAAI,KAAKA,KAAK,CAAC,EAAE;AACnD;AAEA;;CAEC,GACD,OAAO,SAASE;IACf,OAAOR,6BAA6BS,MAAM;AAC3C"}
|
package/lib/scripts/bundle.js
CHANGED
|
@@ -40,9 +40,15 @@ const commonOptions = {
|
|
|
40
40
|
// External native modules
|
|
41
41
|
external: [
|
|
42
42
|
'better-sqlite3',
|
|
43
|
+
'bun:sqlite',
|
|
44
|
+
'kysely-bun-sqlite',
|
|
43
45
|
'oracledb',
|
|
44
46
|
'mariadb/callback',
|
|
45
|
-
'mysql'
|
|
47
|
+
'mysql',
|
|
48
|
+
'@nestjs/websockets',
|
|
49
|
+
'@nestjs/microservices',
|
|
50
|
+
'@nestjs/platform-express',
|
|
51
|
+
'@larksuiteoapi/node-sdk'
|
|
46
52
|
]
|
|
47
53
|
};
|
|
48
54
|
// Ensure dist directory exists
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/scripts/bundle.ts"],"sourcesContent":["#!/usr/bin/env node\nimport fs from 'node:fs';\nimport * as esbuild from 'esbuild';\n\nconst pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));\n\nconst banner = `// Bundled with esbuild\n// ${pkg.name}@${pkg.version}\n\nvar require,__filename,__dirname;\n{\n const {createRequire} = await import('node:module');\n require ||= createRequire(import.meta.url);\n}\n{\n const {fileURLToPath} = await import('node:url');\n const {dirname} = await import('node:path');\n __filename ||= fileURLToPath(import.meta.url);\n __dirname ||= dirname(__filename)\n};\n`;\n\nconst commonOptions: esbuild.BuildOptions = {\n\tbundle: true,\n\tlogLevel: 'info',\n\tbanner: { js: banner },\n\tdefine: {\n\t\tNODE_ENV: JSON.stringify('production'),\n\t\t__DEV__: JSON.stringify(false),\n\t\t'process.env.NODE_ENV': JSON.stringify('production'),\n\t},\n\tkeepNames: true,\n\ttreeShaking: true,\n\tminifySyntax: true,\n\tformat: 'esm',\n\tplatform: 'node',\n\tcharset: 'utf8',\n\ttarget: 'node18',\n\tsourcemap: false,\n\tlegalComments: 'none',\n\t// External native modules\n\texternal: ['better-sqlite3'
|
|
1
|
+
{"version":3,"sources":["../../src/scripts/bundle.ts"],"sourcesContent":["#!/usr/bin/env node\nimport fs from 'node:fs';\nimport * as esbuild from 'esbuild';\n\nconst pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));\n\nconst banner = `// Bundled with esbuild\n// ${pkg.name}@${pkg.version}\n\nvar require,__filename,__dirname;\n{\n const {createRequire} = await import('node:module');\n require ||= createRequire(import.meta.url);\n}\n{\n const {fileURLToPath} = await import('node:url');\n const {dirname} = await import('node:path');\n __filename ||= fileURLToPath(import.meta.url);\n __dirname ||= dirname(__filename)\n};\n`;\n\nconst commonOptions: esbuild.BuildOptions = {\n\tbundle: true,\n\tlogLevel: 'info',\n\tbanner: { js: banner },\n\tdefine: {\n\t\tNODE_ENV: JSON.stringify('production'),\n\t\t__DEV__: JSON.stringify(false),\n\t\t'process.env.NODE_ENV': JSON.stringify('production'),\n\t},\n\tkeepNames: true,\n\ttreeShaking: true,\n\tminifySyntax: true,\n\tformat: 'esm',\n\tplatform: 'node',\n\tcharset: 'utf8',\n\ttarget: 'node18',\n\tsourcemap: false,\n\tlegalComments: 'none',\n\t// External native modules\n\texternal: [\n\t\t'better-sqlite3',\n\t\t'bun:sqlite',\n\t\t'kysely-bun-sqlite',\n\t\t'oracledb',\n\t\t'mariadb/callback',\n\t\t'mysql',\n\t\t'@nestjs/websockets',\n\t\t'@nestjs/microservices',\n\t\t'@nestjs/platform-express',\n\t\t'@larksuiteoapi/node-sdk',\n\t],\n};\n\n// Ensure dist directory exists\nfs.mkdirSync('dist', { recursive: true });\n\nconsole.log('Building MCPS...');\n\n// Build library entry (index.ts)\nconst libResult = await esbuild.build({\n\t...commonOptions,\n\tentryPoints: ['src/index.ts'],\n\toutfile: 'dist/index.mjs',\n});\n\n// Build CLI entry (mcps-cli.ts)\nconst cliResult = await esbuild.build({\n\t...commonOptions,\n\tentryPoints: ['src/mcps-cli.ts'],\n\toutfile: 'dist/mcps-cli.mjs',\n});\n\nif (libResult.errors.length === 0 && cliResult.errors.length === 0) {\n\t// Process CLI output - add shebang\n\tconst cliOutfile = 'dist/mcps-cli.mjs';\n\tlet content = fs.readFileSync(cliOutfile, 'utf-8');\n\tcontent = content.replace(/^#!.*\\n/gm, '');\n\tfs.writeFileSync(cliOutfile, `#!/usr/bin/env node\\n${content}`);\n\tfs.chmodSync(cliOutfile, 0o755);\n\n\tconst libStats = fs.statSync('dist/index.mjs');\n\tconst cliStats = fs.statSync(cliOutfile);\n\n\tconsole.log(`✅ Build successful!`);\n\tconsole.log(`📦 Library: dist/index.mjs (${(libStats.size / 1024).toFixed(1)}KB)`);\n\tconsole.log(`📦 CLI: ${cliOutfile} (${(cliStats.size / 1024).toFixed(1)}KB)`);\n\tconsole.log(`🚀 Ready for npm publish and npx usage`);\n} else {\n\tconsole.error('❌ Build failed:', [...libResult.errors, ...cliResult.errors]);\n\tprocess.exit(1);\n}\n"],"names":["fs","esbuild","pkg","JSON","parse","readFileSync","banner","name","version","commonOptions","bundle","logLevel","js","define","NODE_ENV","stringify","__DEV__","keepNames","treeShaking","minifySyntax","format","platform","charset","target","sourcemap","legalComments","external","mkdirSync","recursive","console","log","libResult","build","entryPoints","outfile","cliResult","errors","length","cliOutfile","content","replace","writeFileSync","chmodSync","libStats","statSync","cliStats","size","toFixed","error","process","exit"],"mappings":";AACA,OAAOA,QAAQ,UAAU;AACzB,YAAYC,aAAa,UAAU;AAEnC,MAAMC,MAAMC,KAAKC,KAAK,CAACJ,GAAGK,YAAY,CAAC,gBAAgB;AAEvD,MAAMC,SAAS,CAAC;GACb,EAAEJ,IAAIK,IAAI,CAAC,CAAC,EAAEL,IAAIM,OAAO,CAAC;;;;;;;;;;;;;AAa7B,CAAC;AAED,MAAMC,gBAAsC;IAC3CC,QAAQ;IACRC,UAAU;IACVL,QAAQ;QAAEM,IAAIN;IAAO;IACrBO,QAAQ;QACPC,UAAUX,KAAKY,SAAS,CAAC;QACzBC,SAASb,KAAKY,SAAS,CAAC;QACxB,wBAAwBZ,KAAKY,SAAS,CAAC;IACxC;IACAE,WAAW;IACXC,aAAa;IACbC,cAAc;IACdC,QAAQ;IACRC,UAAU;IACVC,SAAS;IACTC,QAAQ;IACRC,WAAW;IACXC,eAAe;IACf,0BAA0B;IAC1BC,UAAU;QACT;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;KACA;AACF;AAEA,+BAA+B;AAC/B1B,GAAG2B,SAAS,CAAC,QAAQ;IAAEC,WAAW;AAAK;AAEvCC,QAAQC,GAAG,CAAC;AAEZ,iCAAiC;AACjC,MAAMC,YAAY,MAAM9B,QAAQ+B,KAAK,CAAC;IACrC,GAAGvB,aAAa;IAChBwB,aAAa;QAAC;KAAe;IAC7BC,SAAS;AACV;AAEA,gCAAgC;AAChC,MAAMC,YAAY,MAAMlC,QAAQ+B,KAAK,CAAC;IACrC,GAAGvB,aAAa;IAChBwB,aAAa;QAAC;KAAkB;IAChCC,SAAS;AACV;AAEA,IAAIH,UAAUK,MAAM,CAACC,MAAM,KAAK,KAAKF,UAAUC,MAAM,CAACC,MAAM,KAAK,GAAG;IACnE,mCAAmC;IACnC,MAAMC,aAAa;IACnB,IAAIC,UAAUvC,GAAGK,YAAY,CAACiC,YAAY;IAC1CC,UAAUA,QAAQC,OAAO,CAAC,aAAa;IACvCxC,GAAGyC,aAAa,CAACH,YAAY,CAAC,qBAAqB,EAAEC,SAAS;IAC9DvC,GAAG0C,SAAS,CAACJ,YAAY;IAEzB,MAAMK,WAAW3C,GAAG4C,QAAQ,CAAC;IAC7B,MAAMC,WAAW7C,GAAG4C,QAAQ,CAACN;IAE7BT,QAAQC,GAAG,CAAC,CAAC,mBAAmB,CAAC;IACjCD,QAAQC,GAAG,CAAC,CAAC,4BAA4B,EAAE,AAACa,CAAAA,SAASG,IAAI,GAAG,IAAG,EAAGC,OAAO,CAAC,GAAG,GAAG,CAAC;IACjFlB,QAAQC,GAAG,CAAC,CAAC,QAAQ,EAAEQ,WAAW,EAAE,EAAE,AAACO,CAAAA,SAASC,IAAI,GAAG,IAAG,EAAGC,OAAO,CAAC,GAAG,GAAG,CAAC;IAC5ElB,QAAQC,GAAG,CAAC,CAAC,sCAAsC,CAAC;AACrD,OAAO;IACND,QAAQmB,KAAK,CAAC,mBAAmB;WAAIjB,UAAUK,MAAM;WAAKD,UAAUC,MAAM;KAAC;IAC3Ea,QAAQC,IAAI,CAAC;AACd"}
|
package/lib/server/api-routes.js
CHANGED
|
@@ -5,19 +5,18 @@ import { RPCHandler } from "@orpc/server/fetch";
|
|
|
5
5
|
import { CORSPlugin } from "@orpc/server/plugins";
|
|
6
6
|
import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4";
|
|
7
7
|
import { html } from "hono/html";
|
|
8
|
-
import { AuditRouter } from "./audit.js";
|
|
9
8
|
import { createMcpsRouter } from "./mcps-router.js";
|
|
10
9
|
/**
|
|
11
|
-
* Register oRPC API routes for
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
* Register oRPC API routes for MCPS.
|
|
11
|
+
* Audit router is not included by default - use setupAudit() plugin to add it.
|
|
12
|
+
*/ export function registerApiRoutes({ app, config, apiRouters, statsProvider }) {
|
|
14
13
|
const McpsRouter = createMcpsRouter({
|
|
15
|
-
config
|
|
14
|
+
config,
|
|
15
|
+
statsProvider
|
|
16
16
|
});
|
|
17
|
-
// Combined router for all APIs
|
|
18
17
|
const combinedRouter = {
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
mcps: McpsRouter,
|
|
19
|
+
...apiRouters
|
|
21
20
|
};
|
|
22
21
|
const handleByRpc = new RPCHandler(combinedRouter);
|
|
23
22
|
const handleByOpenAPI = new OpenAPIHandler(combinedRouter, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/server/api-routes.ts"],"sourcesContent":["import { SmartCoercionPlugin } from '@orpc/json-schema';\nimport { OpenAPIGenerator } from '@orpc/openapi';\nimport { OpenAPIHandler } from '@orpc/openapi/fetch';\nimport { RPCHandler } from '@orpc/server/fetch';\nimport { CORSPlugin } from '@orpc/server/plugins';\nimport { ZodToJsonSchemaConverter } from '@orpc/zod/zod4';\nimport type { Hono } from 'hono';\nimport { html } from 'hono/html';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/server/api-routes.ts"],"sourcesContent":["import { SmartCoercionPlugin } from '@orpc/json-schema';\nimport { OpenAPIGenerator } from '@orpc/openapi';\nimport { OpenAPIHandler } from '@orpc/openapi/fetch';\nimport { RPCHandler } from '@orpc/server/fetch';\nimport { CORSPlugin } from '@orpc/server/plugins';\nimport { ZodToJsonSchemaConverter } from '@orpc/zod/zod4';\nimport type { Hono } from 'hono';\nimport { html } from 'hono/html';\nimport { createMcpsRouter } from './mcps-router';\nimport type { McpsConfig } from './schema';\nimport type { StatsProvider } from './server';\n\nexport interface RegisterApiRoutesOptions {\n\tapp: Hono;\n\tconfig: McpsConfig;\n\t/** Additional oRPC routers registered by plugins (e.g. audit) */\n\tapiRouters?: Record<string, any>;\n\t/** Optional stats provider from audit plugin */\n\tstatsProvider?: StatsProvider;\n}\n\n/**\n * Register oRPC API routes for MCPS.\n * Audit router is not included by default - use setupAudit() plugin to add it.\n */\nexport function registerApiRoutes({ app, config, apiRouters, statsProvider }: RegisterApiRoutesOptions) {\n\tconst McpsRouter = createMcpsRouter({ config, statsProvider });\n\n\tconst combinedRouter: Record<string, any> = {\n\t\tmcps: McpsRouter,\n\t\t...apiRouters,\n\t};\n\n\tconst handleByRpc = new RPCHandler(combinedRouter);\n\tconst handleByOpenAPI = new OpenAPIHandler(combinedRouter, {\n\t\tplugins: [\n\t\t\tnew SmartCoercionPlugin({\n\t\t\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t\t\t}),\n\t\t\tnew CORSPlugin({\n\t\t\t\texposeHeaders: ['Content-Disposition'],\n\t\t\t}),\n\t\t],\n\t});\n\n\tapp.use('/api/rpc/*', async (c, next) => {\n\t\tconst { matched, response } = await handleByRpc.handle(c.req.raw, {\n\t\t\tprefix: '/api/rpc',\n\t\t\tcontext: {},\n\t\t});\n\t\tif (matched) {\n\t\t\treturn c.newResponse(response.body, response);\n\t\t}\n\t\treturn next();\n\t});\n\n\tapp.use('/api/*', async (c, next) => {\n\t\tconst { matched, response } = await handleByOpenAPI.handle(c.req.raw, {\n\t\t\tprefix: '/api',\n\t\t\tcontext: {},\n\t\t});\n\t\tif (matched) {\n\t\t\treturn c.newResponse(response.body, response);\n\t\t}\n\t\treturn next();\n\t});\n\n\t// OpenAPI spec\n\tconst openAPIGenerator = new OpenAPIGenerator({\n\t\tschemaConverters: [new ZodToJsonSchemaConverter()],\n\t});\n\tlet specCache: unknown;\n\n\tapp.get('/api/spec.json', async (c) => {\n\t\tif (!specCache) {\n\t\t\tspecCache = await openAPIGenerator.generate(combinedRouter, {\n\t\t\t\tinfo: { title: 'MCPS API', version: '1.0.0' },\n\t\t\t\tservers: [{ url: '/api' }],\n\t\t\t});\n\t\t}\n\t\treturn c.json(specCache);\n\t});\n\n\t// Swagger UI\n\tapp.get('/api/docs', (c) => {\n\t\treturn c.html(\n\t\t\thtml`<!doctype html>\n\t\t\t\t<html lang=\"en\">\n\t\t\t\t\t<head>\n\t\t\t\t\t\t<title>MCPS API</title>\n\t\t\t\t\t\t<meta charset=\"utf-8\" />\n\t\t\t\t\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t\t\t\t</head>\n\t\t\t\t\t<body>\n\t\t\t\t\t\t<script id=\"api-reference\" data-url=\"/api/spec.json\"></script>\n\t\t\t\t\t\t<script src=\"https://cdn.jsdelivr.net/npm/@scalar/api-reference\"></script>\n\t\t\t\t\t</body>\n\t\t\t\t</html>`,\n\t\t);\n\t});\n}\n"],"names":["SmartCoercionPlugin","OpenAPIGenerator","OpenAPIHandler","RPCHandler","CORSPlugin","ZodToJsonSchemaConverter","html","createMcpsRouter","registerApiRoutes","app","config","apiRouters","statsProvider","McpsRouter","combinedRouter","mcps","handleByRpc","handleByOpenAPI","plugins","schemaConverters","exposeHeaders","use","c","next","matched","response","handle","req","raw","prefix","context","newResponse","body","openAPIGenerator","specCache","get","generate","info","title","version","servers","url","json"],"mappings":"AAAA,SAASA,mBAAmB,QAAQ,oBAAoB;AACxD,SAASC,gBAAgB,QAAQ,gBAAgB;AACjD,SAASC,cAAc,QAAQ,sBAAsB;AACrD,SAASC,UAAU,QAAQ,qBAAqB;AAChD,SAASC,UAAU,QAAQ,uBAAuB;AAClD,SAASC,wBAAwB,QAAQ,iBAAiB;AAE1D,SAASC,IAAI,QAAQ,YAAY;AACjC,SAASC,gBAAgB,QAAQ,gBAAgB;AAajD;;;CAGC,GACD,OAAO,SAASC,kBAAkB,EAAEC,GAAG,EAAEC,MAAM,EAAEC,UAAU,EAAEC,aAAa,EAA4B;IACrG,MAAMC,aAAaN,iBAAiB;QAAEG;QAAQE;IAAc;IAE5D,MAAME,iBAAsC;QAC3CC,MAAMF;QACN,GAAGF,UAAU;IACd;IAEA,MAAMK,cAAc,IAAIb,WAAWW;IACnC,MAAMG,kBAAkB,IAAIf,eAAeY,gBAAgB;QAC1DI,SAAS;YACR,IAAIlB,oBAAoB;gBACvBmB,kBAAkB;oBAAC,IAAId;iBAA2B;YACnD;YACA,IAAID,WAAW;gBACdgB,eAAe;oBAAC;iBAAsB;YACvC;SACA;IACF;IAEAX,IAAIY,GAAG,CAAC,cAAc,OAAOC,GAAGC;QAC/B,MAAM,EAAEC,OAAO,EAAEC,QAAQ,EAAE,GAAG,MAAMT,YAAYU,MAAM,CAACJ,EAAEK,GAAG,CAACC,GAAG,EAAE;YACjEC,QAAQ;YACRC,SAAS,CAAC;QACX;QACA,IAAIN,SAAS;YACZ,OAAOF,EAAES,WAAW,CAACN,SAASO,IAAI,EAAEP;QACrC;QACA,OAAOF;IACR;IAEAd,IAAIY,GAAG,CAAC,UAAU,OAAOC,GAAGC;QAC3B,MAAM,EAAEC,OAAO,EAAEC,QAAQ,EAAE,GAAG,MAAMR,gBAAgBS,MAAM,CAACJ,EAAEK,GAAG,CAACC,GAAG,EAAE;YACrEC,QAAQ;YACRC,SAAS,CAAC;QACX;QACA,IAAIN,SAAS;YACZ,OAAOF,EAAES,WAAW,CAACN,SAASO,IAAI,EAAEP;QACrC;QACA,OAAOF;IACR;IAEA,eAAe;IACf,MAAMU,mBAAmB,IAAIhC,iBAAiB;QAC7CkB,kBAAkB;YAAC,IAAId;SAA2B;IACnD;IACA,IAAI6B;IAEJzB,IAAI0B,GAAG,CAAC,kBAAkB,OAAOb;QAChC,IAAI,CAACY,WAAW;YACfA,YAAY,MAAMD,iBAAiBG,QAAQ,CAACtB,gBAAgB;gBAC3DuB,MAAM;oBAAEC,OAAO;oBAAYC,SAAS;gBAAQ;gBAC5CC,SAAS;oBAAC;wBAAEC,KAAK;oBAAO;iBAAE;YAC3B;QACD;QACA,OAAOnB,EAAEoB,IAAI,CAACR;IACf;IAEA,aAAa;IACbzB,IAAI0B,GAAG,CAAC,aAAa,CAACb;QACrB,OAAOA,EAAEhB,IAAI,CACZA,IAAI,CAAC;;;;;;;;;;;WAWG,CAAC;IAEX;AACD"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { MikroORM } from "@mikro-orm/core";
|
|
2
|
+
import { SqliteDriver } from "@mikro-orm/sql";
|
|
3
|
+
import { createSqliteDialect } from "@wener/server/mikro-orm";
|
|
4
|
+
import { ChatRequestEntity } from "../entities/ChatRequestEntity.js";
|
|
5
|
+
import { McpRequestEntity } from "../entities/McpRequestEntity.js";
|
|
6
|
+
import { RequestLogEntity } from "../entities/RequestLogEntity.js";
|
|
7
|
+
import { ResponseEntity } from "../entities/ResponseEntity.js";
|
|
8
|
+
export { RequestLogEntity };
|
|
9
|
+
let orm = null;
|
|
10
|
+
let initPromise = null;
|
|
11
|
+
async function getOrmConfig(dbConfig) {
|
|
12
|
+
const dbPath = dbConfig?.path || ".mcps.db";
|
|
13
|
+
return {
|
|
14
|
+
driver: SqliteDriver,
|
|
15
|
+
dbName: dbPath,
|
|
16
|
+
entities: [
|
|
17
|
+
ChatRequestEntity,
|
|
18
|
+
McpRequestEntity,
|
|
19
|
+
RequestLogEntity,
|
|
20
|
+
ResponseEntity
|
|
21
|
+
],
|
|
22
|
+
driverOptions: await createSqliteDialect(dbPath),
|
|
23
|
+
debug: process.env.NODE_ENV === "development",
|
|
24
|
+
allowGlobalContext: true
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export async function ensureDbInitialized(dbConfig) {
|
|
28
|
+
if (orm)
|
|
29
|
+
return orm;
|
|
30
|
+
if (initPromise)
|
|
31
|
+
return initPromise;
|
|
32
|
+
initPromise = (async () => {
|
|
33
|
+
const config = await getOrmConfig(dbConfig);
|
|
34
|
+
orm = await MikroORM.init(config);
|
|
35
|
+
await orm.schema.update();
|
|
36
|
+
return orm;
|
|
37
|
+
})();
|
|
38
|
+
try {
|
|
39
|
+
return await initPromise;
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
initPromise = null;
|
|
43
|
+
throw e;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function getOrm() {
|
|
47
|
+
if (!orm)
|
|
48
|
+
throw new Error("Database not initialized");
|
|
49
|
+
return orm;
|
|
50
|
+
}
|
|
51
|
+
export function getEntityManager() {
|
|
52
|
+
return getOrm().em;
|
|
53
|
+
}
|
|
54
|
+
export async function closeDb() {
|
|
55
|
+
if (orm) {
|
|
56
|
+
await orm.close();
|
|
57
|
+
orm = null;
|
|
58
|
+
initPromise = null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function isDbInitialized() {
|
|
62
|
+
return orm !== null;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=audit-db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/audit-db.ts"],"sourcesContent":["import { MikroORM, type Options } from '@mikro-orm/core';\nimport { SqliteDriver } from '@mikro-orm/sql';\nimport { createSqliteDialect } from '@wener/server/mikro-orm';\nimport { ChatRequestEntity } from '../entities/ChatRequestEntity';\nimport { McpRequestEntity } from '../entities/McpRequestEntity';\nimport { RequestLogEntity } from '../entities/RequestLogEntity';\nimport { ResponseEntity } from '../entities/ResponseEntity';\nimport type { DbConfig } from './schema';\n\nexport { RequestLogEntity };\n\nlet orm: MikroORM<SqliteDriver> | null = null;\nlet initPromise: Promise<MikroORM<SqliteDriver>> | null = null;\n\nasync function getOrmConfig(dbConfig?: DbConfig): Promise<Options<SqliteDriver>> {\n\tconst dbPath = dbConfig?.path || '.mcps.db';\n\treturn {\n\t\tdriver: SqliteDriver,\n\t\tdbName: dbPath,\n\t\tentities: [ChatRequestEntity, McpRequestEntity, RequestLogEntity, ResponseEntity],\n\t\tdriverOptions: await createSqliteDialect(dbPath),\n\t\tdebug: process.env.NODE_ENV === 'development',\n\t\tallowGlobalContext: true,\n\t};\n}\n\nexport async function ensureDbInitialized(dbConfig?: DbConfig): Promise<MikroORM<SqliteDriver>> {\n\tif (orm) return orm;\n\tif (initPromise) return initPromise;\n\n\tinitPromise = (async () => {\n\t\tconst config = await getOrmConfig(dbConfig);\n\t\torm = await MikroORM.init(config);\n\t\tawait orm.schema.update();\n\t\treturn orm;\n\t})();\n\n\ttry {\n\t\treturn await initPromise;\n\t} catch (e) {\n\t\tinitPromise = null;\n\t\tthrow e;\n\t}\n}\n\nexport function getOrm(): MikroORM<SqliteDriver> {\n\tif (!orm) throw new Error('Database not initialized');\n\treturn orm;\n}\n\nexport function getEntityManager() {\n\treturn getOrm().em;\n}\n\nexport async function closeDb(): Promise<void> {\n\tif (orm) {\n\t\tawait orm.close();\n\t\torm = null;\n\t\tinitPromise = null;\n\t}\n}\n\nexport function isDbInitialized(): boolean {\n\treturn orm !== null;\n}\n"],"names":["MikroORM","SqliteDriver","createSqliteDialect","ChatRequestEntity","McpRequestEntity","RequestLogEntity","ResponseEntity","orm","initPromise","getOrmConfig","dbConfig","dbPath","path","driver","dbName","entities","driverOptions","debug","process","env","NODE_ENV","allowGlobalContext","ensureDbInitialized","config","init","schema","update","e","getOrm","Error","getEntityManager","em","closeDb","close","isDbInitialized"],"mappings":"AAAA,SAASA,QAAQ,QAAsB,kBAAkB;AACzD,SAASC,YAAY,QAAQ,iBAAiB;AAC9C,SAASC,mBAAmB,QAAQ,0BAA0B;AAC9D,SAASC,iBAAiB,QAAQ,gCAAgC;AAClE,SAASC,gBAAgB,QAAQ,+BAA+B;AAChE,SAASC,gBAAgB,QAAQ,+BAA+B;AAChE,SAASC,cAAc,QAAQ,6BAA6B;AAG5D,SAASD,gBAAgB,GAAG;AAE5B,IAAIE,MAAqC;AACzC,IAAIC,cAAsD;AAE1D,eAAeC,aAAaC,QAAmB;IAC9C,MAAMC,SAASD,UAAUE,QAAQ;IACjC,OAAO;QACNC,QAAQZ;QACRa,QAAQH;QACRI,UAAU;YAACZ;YAAmBC;YAAkBC;YAAkBC;SAAe;QACjFU,eAAe,MAAMd,oBAAoBS;QACzCM,OAAOC,QAAQC,GAAG,CAACC,QAAQ,KAAK;QAChCC,oBAAoB;IACrB;AACD;AAEA,OAAO,eAAeC,oBAAoBZ,QAAmB;IAC5D,IAAIH,KAAK,OAAOA;IAChB,IAAIC,aAAa,OAAOA;IAExBA,cAAc,AAAC,CAAA;QACd,MAAMe,SAAS,MAAMd,aAAaC;QAClCH,MAAM,MAAMP,SAASwB,IAAI,CAACD;QAC1B,MAAMhB,IAAIkB,MAAM,CAACC,MAAM;QACvB,OAAOnB;IACR,CAAA;IAEA,IAAI;QACH,OAAO,MAAMC;IACd,EAAE,OAAOmB,GAAG;QACXnB,cAAc;QACd,MAAMmB;IACP;AACD;AAEA,OAAO,SAASC;IACf,IAAI,CAACrB,KAAK,MAAM,IAAIsB,MAAM;IAC1B,OAAOtB;AACR;AAEA,OAAO,SAASuB;IACf,OAAOF,SAASG,EAAE;AACnB;AAEA,OAAO,eAAeC;IACrB,IAAIzB,KAAK;QACR,MAAMA,IAAI0B,KAAK;QACf1B,MAAM;QACNC,cAAc;IACf;AACD;AAEA,OAAO,SAAS0B;IACf,OAAO3B,QAAQ;AAChB"}
|
|
@@ -1,57 +1,28 @@
|
|
|
1
1
|
import { implement } from "@orpc/server";
|
|
2
2
|
import { LRUCache } from "lru-cache";
|
|
3
3
|
import { AuditContract } from "../contracts/index.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Convert Headers to a plain record
|
|
8
|
-
*/ function headersToRecord(headers) {
|
|
4
|
+
import { McpsEventType } from "./events.js";
|
|
5
|
+
function headersToRecord(headers) {
|
|
9
6
|
const record = {};
|
|
10
7
|
headers.forEach((value, key) => {
|
|
11
8
|
record[key] = value;
|
|
12
9
|
});
|
|
13
10
|
return record;
|
|
14
11
|
}
|
|
15
|
-
// In-memory audit store using LRU cache
|
|
16
12
|
const auditStore = new LRUCache({
|
|
17
13
|
max: 10000,
|
|
18
14
|
ttl: 1000 * 60 * 60 * 24
|
|
19
15
|
});
|
|
20
|
-
// Counter for IDs
|
|
21
16
|
let eventCounter = 0;
|
|
22
|
-
// Audit configuration state
|
|
23
|
-
let auditEnabled = true; // default to enabled
|
|
24
17
|
let dbConfigured = false;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* @param auditConfig - Audit config section
|
|
30
|
-
* @param fallbackDbConfig - Fallback db config from root config
|
|
31
|
-
*/ export function configureAudit(auditConfig, fallbackDbConfig) {
|
|
32
|
-
// Determine if audit is enabled (default: true)
|
|
33
|
-
auditEnabled = auditConfig?.enabled !== false;
|
|
34
|
-
if (auditEnabled) {
|
|
35
|
-
// Use audit.db config if present, otherwise fallback to root db config
|
|
36
|
-
const dbConfig = auditConfig?.db ?? fallbackDbConfig;
|
|
37
|
-
configureDb(dbConfig);
|
|
38
|
-
dbConfigured = true;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Check if audit is enabled
|
|
43
|
-
*/ export function isAuditEnabled() {
|
|
44
|
-
return auditEnabled;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Persist audit event to database (lazy init)
|
|
48
|
-
*/ async function persistToDb(event, id) {
|
|
49
|
-
if (!auditEnabled || !dbConfigured) {
|
|
18
|
+
let storedAuditConfig;
|
|
19
|
+
let storedDbConfig;
|
|
20
|
+
async function persistToDb(event, id) {
|
|
21
|
+
if (!dbConfigured)
|
|
50
22
|
return;
|
|
51
|
-
}
|
|
52
23
|
try {
|
|
53
|
-
|
|
54
|
-
const orm = await ensureDbInitialized();
|
|
24
|
+
const { ensureDbInitialized, RequestLogEntity } = await import("./audit-db.js");
|
|
25
|
+
const orm = await ensureDbInitialized(storedDbConfig);
|
|
55
26
|
const em = orm.em.fork();
|
|
56
27
|
const logEntry = new RequestLogEntity();
|
|
57
28
|
logEntry.requestId = id;
|
|
@@ -64,7 +35,6 @@ let dbConfigured = false;
|
|
|
64
35
|
logEntry.durationMs = event.durationMs ?? undefined;
|
|
65
36
|
logEntry.error = event.error ?? undefined;
|
|
66
37
|
logEntry.requestHeaders = event.requestHeaders ?? undefined;
|
|
67
|
-
// Determine request type
|
|
68
38
|
if (event.path.startsWith("/mcp/")) {
|
|
69
39
|
logEntry.requestType = "mcp";
|
|
70
40
|
}
|
|
@@ -78,46 +48,32 @@ let dbConfigured = false;
|
|
|
78
48
|
await em.flush();
|
|
79
49
|
}
|
|
80
50
|
catch (e) {
|
|
81
|
-
// Log persistence errors but don't throw - in-memory store is the primary
|
|
82
51
|
console.error("Failed to persist audit log:", e);
|
|
83
52
|
}
|
|
84
53
|
}
|
|
85
|
-
|
|
86
|
-
* Add an audit event
|
|
87
|
-
*/ export function addAuditEvent(event) {
|
|
54
|
+
export function addAuditEvent(event) {
|
|
88
55
|
const id = `${Date.now()}-${++eventCounter}`;
|
|
89
56
|
const fullEvent = {
|
|
90
57
|
...event,
|
|
91
58
|
id
|
|
92
59
|
};
|
|
93
60
|
auditStore.set(id, fullEvent);
|
|
94
|
-
|
|
95
|
-
persistToDb(fullEvent, id).catch(() => {
|
|
96
|
-
// Already logged in persistToDb
|
|
97
|
-
});
|
|
61
|
+
persistToDb(fullEvent, id).catch(() => { });
|
|
98
62
|
return fullEvent;
|
|
99
63
|
}
|
|
100
|
-
|
|
101
|
-
* Query audit events
|
|
102
|
-
*/ export function queryAuditEvents(options) {
|
|
64
|
+
export function queryAuditEvents(options) {
|
|
103
65
|
const { limit = 50, offset = 0, serverName, serverType, method, from, to } = options;
|
|
104
|
-
// Get all events as array
|
|
105
66
|
let events = [];
|
|
106
67
|
for (const [, event] of auditStore.entries()) {
|
|
107
68
|
events.push(event);
|
|
108
69
|
}
|
|
109
|
-
// Sort by timestamp desc
|
|
110
70
|
events.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
111
|
-
|
|
112
|
-
if (serverName) {
|
|
71
|
+
if (serverName)
|
|
113
72
|
events = events.filter((e) => e.serverName === serverName);
|
|
114
|
-
|
|
115
|
-
if (serverType) {
|
|
73
|
+
if (serverType)
|
|
116
74
|
events = events.filter((e) => e.serverType === serverType);
|
|
117
|
-
|
|
118
|
-
if (method) {
|
|
75
|
+
if (method)
|
|
119
76
|
events = events.filter((e) => e.method === method);
|
|
120
|
-
}
|
|
121
77
|
if (from) {
|
|
122
78
|
const fromTime = new Date(from).getTime();
|
|
123
79
|
events = events.filter((e) => new Date(e.timestamp).getTime() >= fromTime);
|
|
@@ -127,21 +83,17 @@ let dbConfigured = false;
|
|
|
127
83
|
events = events.filter((e) => new Date(e.timestamp).getTime() <= toTime);
|
|
128
84
|
}
|
|
129
85
|
const total = events.length;
|
|
130
|
-
// Paginate
|
|
131
86
|
events = events.slice(offset, offset + limit);
|
|
132
87
|
return {
|
|
133
88
|
events,
|
|
134
89
|
total
|
|
135
90
|
};
|
|
136
91
|
}
|
|
137
|
-
|
|
138
|
-
* Get audit statistics
|
|
139
|
-
*/ export function getAuditStats(options) {
|
|
92
|
+
export function getAuditStats(options) {
|
|
140
93
|
let events = [];
|
|
141
94
|
for (const [, event] of auditStore.entries()) {
|
|
142
95
|
events.push(event);
|
|
143
96
|
}
|
|
144
|
-
// Apply time filters
|
|
145
97
|
if (options.from) {
|
|
146
98
|
const fromTime = new Date(options.from).getTime();
|
|
147
99
|
events = events.filter((e) => new Date(e.timestamp).getTime() >= fromTime);
|
|
@@ -150,12 +102,10 @@ let dbConfigured = false;
|
|
|
150
102
|
const toTime = new Date(options.to).getTime();
|
|
151
103
|
events = events.filter((e) => new Date(e.timestamp).getTime() <= toTime);
|
|
152
104
|
}
|
|
153
|
-
// Calculate stats
|
|
154
105
|
const totalRequests = events.length;
|
|
155
106
|
const totalErrors = events.filter((e) => e.error || e.status && e.status >= 400).length;
|
|
156
107
|
const durations = events.map((e) => e.durationMs).filter((d) => d != null);
|
|
157
108
|
const avgDurationMs = durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : 0;
|
|
158
|
-
// Group by server
|
|
159
109
|
const serverCounts = new Map();
|
|
160
110
|
for (const event of events) {
|
|
161
111
|
const name = event.serverName || "unknown";
|
|
@@ -165,11 +115,10 @@ let dbConfigured = false;
|
|
|
165
115
|
name,
|
|
166
116
|
count
|
|
167
117
|
})).sort((a, b) => b.count - a.count);
|
|
168
|
-
// Group by method
|
|
169
118
|
const methodCounts = new Map();
|
|
170
119
|
for (const event of events) {
|
|
171
|
-
const
|
|
172
|
-
methodCounts.set(
|
|
120
|
+
const m = event.method || "unknown";
|
|
121
|
+
methodCounts.set(m, (methodCounts.get(m) || 0) + 1);
|
|
173
122
|
}
|
|
174
123
|
const byMethod = Array.from(methodCounts.entries()).map(([method, count]) => ({
|
|
175
124
|
method,
|
|
@@ -183,9 +132,7 @@ let dbConfigured = false;
|
|
|
183
132
|
byMethod
|
|
184
133
|
};
|
|
185
134
|
}
|
|
186
|
-
|
|
187
|
-
* Clear audit events before a timestamp
|
|
188
|
-
*/ export function clearAuditEvents(before) {
|
|
135
|
+
export function clearAuditEvents(before) {
|
|
189
136
|
const beforeTime = new Date(before).getTime();
|
|
190
137
|
let deleted = 0;
|
|
191
138
|
for (const [id, event] of auditStore.entries()) {
|
|
@@ -196,9 +143,7 @@ let dbConfigured = false;
|
|
|
196
143
|
}
|
|
197
144
|
return deleted;
|
|
198
145
|
}
|
|
199
|
-
|
|
200
|
-
* Audit Router implementation
|
|
201
|
-
*/ export const AuditRouter = implement(AuditContract).router({
|
|
146
|
+
export const AuditRouter = implement(AuditContract).router({
|
|
202
147
|
list: implement(AuditContract.list).handler(async ({ input }) => {
|
|
203
148
|
return queryAuditEvents(input);
|
|
204
149
|
}),
|
|
@@ -216,59 +161,60 @@ let dbConfigured = false;
|
|
|
216
161
|
})
|
|
217
162
|
});
|
|
218
163
|
/**
|
|
219
|
-
*
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
164
|
+
* Set up audit by subscribing to the server emitter.
|
|
165
|
+
* Call this from the `setup` callback of `createServer` to opt in to audit.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* createServer({
|
|
170
|
+
* setup: (ctx) => {
|
|
171
|
+
* setupAudit(ctx);
|
|
172
|
+
* },
|
|
173
|
+
* });
|
|
174
|
+
* ```
|
|
175
|
+
*/ export function setupAudit(ctx, options) {
|
|
176
|
+
const auditConfig = options?.auditConfig ?? ctx.config.audit;
|
|
177
|
+
const dbConfig = options?.dbConfig ?? ctx.config.db;
|
|
178
|
+
const enabled = auditConfig?.enabled !== false;
|
|
179
|
+
if (!enabled)
|
|
180
|
+
return;
|
|
181
|
+
const auditDbConfig = auditConfig?.db ?? dbConfig;
|
|
182
|
+
if (auditDbConfig) {
|
|
183
|
+
storedDbConfig = auditDbConfig;
|
|
184
|
+
dbConfigured = true;
|
|
185
|
+
}
|
|
186
|
+
storedAuditConfig = auditConfig;
|
|
187
|
+
// Subscribe to request events
|
|
188
|
+
ctx.emitter.on(McpsEventType.Request, (event) => {
|
|
189
|
+
const shouldAudit = event.path.startsWith("/mcp/") || event.path.startsWith("/v1/") || event.path.startsWith("/api/") && event.method !== "GET";
|
|
190
|
+
if (shouldAudit) {
|
|
191
|
+
addAuditEvent({
|
|
192
|
+
timestamp: event.timestamp,
|
|
193
|
+
method: event.method,
|
|
194
|
+
path: event.path,
|
|
195
|
+
serverName: event.serverName,
|
|
196
|
+
serverType: event.serverType,
|
|
197
|
+
status: event.status,
|
|
198
|
+
durationMs: event.durationMs,
|
|
199
|
+
error: event.error,
|
|
200
|
+
requestHeaders: event.requestHeaders
|
|
201
|
+
});
|
|
253
202
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
requestHeaders: headersToRecord(c.req.raw.headers)
|
|
269
|
-
});
|
|
270
|
-
}
|
|
203
|
+
});
|
|
204
|
+
// Register audit API router
|
|
205
|
+
ctx.apiRouters.audit = AuditRouter;
|
|
206
|
+
// Register stats provider so mcps-router can access stats
|
|
207
|
+
ctx.statsProvider = {
|
|
208
|
+
getStats: getAuditStats,
|
|
209
|
+
queryEvents: (opts) => {
|
|
210
|
+
const result = queryAuditEvents(opts);
|
|
211
|
+
return {
|
|
212
|
+
events: result.events.map((e) => ({
|
|
213
|
+
path: e.path
|
|
214
|
+
})),
|
|
215
|
+
total: result.total
|
|
216
|
+
};
|
|
271
217
|
}
|
|
272
218
|
};
|
|
273
219
|
}
|
|
274
|
-
//# sourceMappingURL=audit.js.map
|
|
220
|
+
//# sourceMappingURL=audit-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/audit-plugin.ts"],"sourcesContent":["import { implement } from '@orpc/server';\nimport { LRUCache } from 'lru-cache';\nimport { AuditContract, type AuditEvent } from '../contracts';\nimport { McpsEventType, type McpsEmitter } from './events';\nimport type { AuditConfig, DbConfig } from './schema';\nimport type { McpsServerContext } from './server';\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\nconst auditStore = new LRUCache<string, AuditEvent>({\n\tmax: 10000,\n\tttl: 1000 * 60 * 60 * 24,\n});\n\nlet eventCounter = 0;\nlet dbConfigured = false;\nlet storedAuditConfig: AuditConfig | undefined;\nlet storedDbConfig: DbConfig | undefined;\n\nasync function persistToDb(event: AuditEvent, id: string): Promise<void> {\n\tif (!dbConfigured) return;\n\n\ttry {\n\t\tconst { ensureDbInitialized, RequestLogEntity } = await import('./audit-db.js');\n\t\tconst orm = await ensureDbInitialized(storedDbConfig);\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\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\tconsole.error('Failed to persist audit log:', e);\n\t}\n}\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\tpersistToDb(fullEvent, id).catch(() => {});\n\n\treturn fullEvent;\n}\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\tlet events: AuditEvent[] = [];\n\tfor (const [, event] of auditStore.entries()) {\n\t\tevents.push(event);\n\t}\n\n\tevents.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());\n\n\tif (serverName) events = events.filter((e) => e.serverName === serverName);\n\tif (serverType) events = events.filter((e) => e.serverType === serverType);\n\tif (method) events = events.filter((e) => e.method === method);\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\tevents = events.slice(offset, offset + limit);\n\n\treturn { events, total };\n}\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\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\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\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\tconst methodCounts = new Map<string, number>();\n\tfor (const event of events) {\n\t\tconst m = event.method || 'unknown';\n\t\tmethodCounts.set(m, (methodCounts.get(m) || 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\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\nexport const AuditRouter = implement(AuditContract).router({\n\tlist: implement(AuditContract.list).handler(async ({ input }) => {\n\t\treturn queryAuditEvents(input);\n\t}),\n\tget: implement(AuditContract.get).handler(async ({ input }) => {\n\t\treturn auditStore.get(input.id) ?? null;\n\t}),\n\tstats: implement(AuditContract.stats).handler(async ({ input }) => {\n\t\treturn getAuditStats(input);\n\t}),\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 * Set up audit by subscribing to the server emitter.\n * Call this from the `setup` callback of `createServer` to opt in to audit.\n *\n * @example\n * ```ts\n * createServer({\n * setup: (ctx) => {\n * setupAudit(ctx);\n * },\n * });\n * ```\n */\nexport function setupAudit(ctx: McpsServerContext, options?: { auditConfig?: AuditConfig; dbConfig?: DbConfig }) {\n\tconst auditConfig = options?.auditConfig ?? ctx.config.audit;\n\tconst dbConfig = options?.dbConfig ?? ctx.config.db;\n\n\tconst enabled = auditConfig?.enabled !== false;\n\tif (!enabled) return;\n\n\tconst auditDbConfig = auditConfig?.db ?? dbConfig;\n\tif (auditDbConfig) {\n\t\tstoredDbConfig = auditDbConfig;\n\t\tdbConfigured = true;\n\t}\n\tstoredAuditConfig = auditConfig;\n\n\t// Subscribe to request events\n\tctx.emitter.on(McpsEventType.Request, (event) => {\n\t\tconst shouldAudit =\n\t\t\tevent.path.startsWith('/mcp/') ||\n\t\t\tevent.path.startsWith('/v1/') ||\n\t\t\t(event.path.startsWith('/api/') && event.method !== 'GET');\n\n\t\tif (shouldAudit) {\n\t\t\taddAuditEvent({\n\t\t\t\ttimestamp: event.timestamp,\n\t\t\t\tmethod: event.method,\n\t\t\t\tpath: event.path,\n\t\t\t\tserverName: event.serverName,\n\t\t\t\tserverType: event.serverType,\n\t\t\t\tstatus: event.status,\n\t\t\t\tdurationMs: event.durationMs,\n\t\t\t\terror: event.error,\n\t\t\t\trequestHeaders: event.requestHeaders,\n\t\t\t});\n\t\t}\n\t});\n\n\t// Register audit API router\n\tctx.apiRouters.audit = AuditRouter;\n\n\t// Register stats provider so mcps-router can access stats\n\tctx.statsProvider = {\n\t\tgetStats: getAuditStats,\n\t\tqueryEvents: (opts) => {\n\t\t\tconst result = queryAuditEvents(opts);\n\t\t\treturn { events: result.events.map((e) => ({ path: e.path })), total: result.total };\n\t\t},\n\t};\n}\n"],"names":["implement","LRUCache","AuditContract","McpsEventType","headersToRecord","headers","record","forEach","value","key","auditStore","max","ttl","eventCounter","dbConfigured","storedAuditConfig","storedDbConfig","persistToDb","event","id","ensureDbInitialized","RequestLogEntity","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","m","byMethod","clearAuditEvents","before","beforeTime","deleted","delete","AuditRouter","router","list","handler","input","stats","clear","setupAudit","ctx","auditConfig","config","audit","dbConfig","db","enabled","auditDbConfig","emitter","on","Request","shouldAudit","apiRouters","statsProvider","getStats","queryEvents","opts","result"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/events.ts"],"sourcesContent":["import Emittery from 'emittery';\n\nexport const McpsEventType = {\n\tRequest: 'Mcps:Request',\n} as const;\n\nexport type McpsRequestEvent = {\n\ttimestamp: string;\n\tmethod: string;\n\tpath: string;\n\tserverName?: string;\n\tserverType?: string;\n\tstatus?: number;\n\tdurationMs?: number;\n\terror?: string;\n\trequestHeaders?: Record<string, string>;\n};\n\nexport type McpsEventData = {\n\t[McpsEventType.Request]: McpsRequestEvent;\n};\n\nexport type McpsEmitter = Emittery<McpsEventData>;\n\nexport function createMcpsEmitter(): McpsEmitter {\n\treturn new Emittery<McpsEventData>({\n\t\tdebug: { name: 'McpsEmitter' },\n\t});\n}\n"],"names":["Emittery","McpsEventType","Request","createMcpsEmitter","debug","name"],"mappings":"AAAA,OAAOA,cAAc,WAAW;AAEhC,OAAO,MAAMC,gBAAgB;IAC5BC,SAAS;AACV,EAAW;AAoBX,OAAO,SAASC;IACf,OAAO,IAAIH,SAAwB;QAClCI,OAAO;YAAEC,MAAM;QAAc;IAC9B;AACD"}
|