@gleanwork/mcp-server-tester 1.0.0-beta.8 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -1
- package/dist/cli/index.js +12 -1
- package/dist/fixtures/mcp.js +71 -14
- package/dist/fixtures/mcp.js.map +1 -1
- package/dist/index.cjs +124 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +118 -16
- package/dist/index.d.ts +118 -16
- package/dist/index.js +124 -21
- package/dist/index.js.map +1 -1
- package/dist/reporters/mcpReporter.cjs +34 -1
- package/dist/reporters/mcpReporter.cjs.map +1 -1
- package/dist/reporters/mcpReporter.d.cts +90 -0
- package/dist/reporters/mcpReporter.d.ts +90 -0
- package/dist/reporters/mcpReporter.js +34 -1
- package/dist/reporters/mcpReporter.js.map +1 -1
- package/package.json +1 -1
|
@@ -7,6 +7,34 @@ var url = require('url');
|
|
|
7
7
|
// node_modules/tsup/assets/cjs_shims.js
|
|
8
8
|
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
9
9
|
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
10
|
+
|
|
11
|
+
// src/utils/usageUtils.ts
|
|
12
|
+
function optionalSum(a, b) {
|
|
13
|
+
if (a === void 0 && b === void 0) return void 0;
|
|
14
|
+
return (a ?? 0) + (b ?? 0);
|
|
15
|
+
}
|
|
16
|
+
function sumUsage(a, b) {
|
|
17
|
+
if (!a && !b) return void 0;
|
|
18
|
+
if (!a) return b ? { ...b } : void 0;
|
|
19
|
+
if (!b) return { ...a };
|
|
20
|
+
return {
|
|
21
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
22
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
23
|
+
totalCostUsd: a.totalCostUsd + b.totalCostUsd,
|
|
24
|
+
durationMs: a.durationMs + b.durationMs,
|
|
25
|
+
durationApiMs: optionalSum(a.durationApiMs, b.durationApiMs),
|
|
26
|
+
cacheReadInputTokens: optionalSum(
|
|
27
|
+
a.cacheReadInputTokens,
|
|
28
|
+
b.cacheReadInputTokens
|
|
29
|
+
),
|
|
30
|
+
cacheCreationInputTokens: optionalSum(
|
|
31
|
+
a.cacheCreationInputTokens,
|
|
32
|
+
b.cacheCreationInputTokens
|
|
33
|
+
)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/reporters/mcpReporter.ts
|
|
10
38
|
var MCPReporter = class {
|
|
11
39
|
config;
|
|
12
40
|
startTime = 0;
|
|
@@ -235,6 +263,10 @@ var MCPReporter = class {
|
|
|
235
263
|
if (r.expectations.toolCallCount) expectationBreakdown.toolCallCount++;
|
|
236
264
|
}
|
|
237
265
|
const failed = total - passed;
|
|
266
|
+
const totalHostUsage = this.allResults.reduce(
|
|
267
|
+
(acc, r) => sumUsage(acc, r.hostUsage),
|
|
268
|
+
void 0
|
|
269
|
+
);
|
|
238
270
|
return {
|
|
239
271
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
240
272
|
durationMs,
|
|
@@ -249,7 +281,8 @@ var MCPReporter = class {
|
|
|
249
281
|
failed,
|
|
250
282
|
passRate: passed / total,
|
|
251
283
|
datasetBreakdown,
|
|
252
|
-
expectationBreakdown
|
|
284
|
+
expectationBreakdown,
|
|
285
|
+
totalHostUsage
|
|
253
286
|
},
|
|
254
287
|
results: this.allResults,
|
|
255
288
|
conformanceChecks: this.conformanceChecks.length > 0 ? this.conformanceChecks : void 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../node_modules/tsup/assets/cjs_shims.js","../../src/reporters/mcpReporter.ts"],"names":["readFile","mkdir","join","__filename","fileURLToPath","dirname","cp","writeFile","readdir","unlink","resolve"],"mappings":";;;;;;;AAKA,IAAM,gBAAA,GAAmB,MACvB,OAAO,QAAA,KAAa,WAAA,GAChB,IAAI,GAAA,CAAI,CAAA,KAAA,EAAQ,UAAU,CAAA,CAAE,CAAA,CAAE,IAAA,GAC7B,QAAA,CAAS,aAAA,IAAiB,QAAA,CAAS,aAAA,CAAc,OAAA,CAAQ,WAAA,EAAY,KAAM,QAAA,GAC1E,QAAA,CAAS,aAAA,CAAc,GAAA,GACvB,IAAI,GAAA,CAAI,SAAA,EAAW,QAAA,CAAS,OAAO,CAAA,CAAE,IAAA;AAEtC,IAAM,gCAAgC,gBAAA,EAAiB;AC4B9D,IAAqB,cAArB,MAAqD;AAAA,EAC3C,MAAA;AAAA,EACA,SAAA,GAAoB,CAAA;AAAA,EACpB,aAAoC,EAAC;AAAA,EACrC,oBAAqD,EAAC;AAAA,EACtD,qBAAuD,EAAC;AAAA,EAEhE,WAAA,CAAY,OAAA,GAAiC,EAAC,EAAG;AAC/C,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,SAAA,EAAW,QAAQ,SAAA,IAAa,mBAAA;AAAA,MAChC,QAAA,EAAU,QAAQ,QAAA,IAAY,KAAA;AAAA,MAC9B,YAAA,EAAc,QAAQ,YAAA,IAAgB,EAAA;AAAA,MACtC,KAAA,EAAO,QAAQ,KAAA,IAAS,KAAA;AAAA,MACxB,mBAAA,EAAqB,QAAQ,mBAAA,IAAuB;AAAA,KACtD;AAAA,EACF;AAAA,EAEQ,IAAI,OAAA,EAAuB;AACjC,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAO,KAAA,EAAO;AACtB,MAAA,OAAA,CAAQ,IAAI,OAAO,CAAA;AAAA,IACrB;AAAA,EACF;AAAA,EAEQ,QAAA,CAAS,SAAiB,KAAA,EAAuB;AACvD,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,CAAO,KAAA,EAAO;AACtB,MAAA,OAAA,CAAQ,KAAA,CAAM,OAAA,EAAS,KAAA,IAAS,EAAE,CAAA;AAAA,IACpC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,qBAAqB,UAAA,EAGR;AACzB,IAAA,IAAI,WAAW,IAAA,EAAM,OAAO,UAAA,CAAW,IAAA,CAAK,SAAS,OAAO,CAAA;AAC5D,IAAA,IAAI,WAAW,IAAA,EAAM;AACnB,MAAA,IAAI;AACF,QAAA,OAAO,MAAMA,iBAAA,CAAS,UAAA,CAAW,IAAA,EAAM,OAAO,CAAA;AAAA,MAChD,CAAA,CAAA,MAAQ;AACN,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAM,OAAA,CAAQ,OAAA,EAAqB,MAAA,EAA8B;AAC/D,IAAA,IAAA,CAAK,SAAA,GAAY,KAAK,GAAA,EAAI;AAC1B,IAAA,IAAA,CAAK,aAAa,EAAC;AACnB,IAAA,IAAA,CAAK,oBAAoB,EAAC;AAC1B,IAAA,IAAA,CAAK,qBAAqB,EAAC;AAG3B,IAAA,MAAMC,eAAM,IAAA,CAAK,MAAA,CAAO,WAAW,EAAE,SAAA,EAAW,MAAM,CAAA;AAAA,EACxD;AAAA,EAEA,MAAM,SAAA,CAAU,IAAA,EAAgB,MAAA,EAAmC;AAEjE,IAAA,MAAM,cAAA,GAAiB,OAAO,WAAA,CAAY,IAAA;AAAA,MACxC,CAAC,CAAA,KACC,CAAA,CAAE,IAAA,KAAS,kBAAA,IAAsB,EAAE,WAAA,KAAgB;AAAA,KACvD;AAEA,IAAA,IAAI,cAAA,GAAiB,KAAA;AAErB,IAAA,MAAM,cAAc,cAAA,GAChB,MAAM,IAAA,CAAK,oBAAA,CAAqB,cAAc,CAAA,GAC9C,IAAA;AACJ,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,IAAI;AACF,QAAA,MAAM,WAAA,GAAc,IAAA,CAAK,KAAA,CAAM,WAAW,CAAA;AAM1C,QAAA,IAAA,CAAK,UAAA,CAAW,IAAA,CAAK,GAAG,WAAA,CAAY,WAAW,CAAA;AAC/C,QAAA,cAAA,GAAiB,IAAA;AAAA,MACnB,SAAS,KAAA,EAAO;AACd,QAAA,IAAA,CAAK,QAAA;AAAA,UACH,CAAA,uDAAA,EAA0D,KAAK,KAAK,CAAA,EAAA,CAAA;AAAA,UACpE;AAAA,SACF;AAAA,MACF;AAAA,IACF;AAIA,IAAA,MAAM,qBAAA,GAAwB,OAAO,WAAA,CAAY,IAAA;AAAA,MAC/C,CAAC,CAAA,KACC,CAAA,CAAE,IAAA,KAAS,wBAAA,IACX,EAAE,WAAA,KAAgB;AAAA,KACtB;AAEA,IAAA,MAAM,qBAAqB,qBAAA,GACvB,MAAM,IAAA,CAAK,oBAAA,CAAqB,qBAAqB,CAAA,GACrD,IAAA;AACJ,IAAA,IAAI,kBAAA,EAAoB;AACtB,MAAA,IAAI;AACF,QAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,KAAA,CAAM,kBAAkB,CAAA;AAWrD,QAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,eAAA,CAAgB,MAAM,CAAA,EAAG;AACzC,UAAA,IAAA,CAAK,kBAAkB,IAAA,CAAK;AAAA,YAC1B,WAAW,IAAA,CAAK,KAAA;AAAA,YAChB,MAAM,eAAA,CAAgB,IAAA;AAAA,YACtB,QAAQ,eAAA,CAAgB,MAAA;AAAA,YACxB,YAAY,eAAA,CAAgB,UAAA;AAAA,YAC5B,WAAW,eAAA,CAAgB,SAAA;AAAA,YAC3B,UAAU,eAAA,CAAgB,QAAA;AAAA,YAC1B,SAAS,eAAA,CAAgB;AAAA,WAC1B,CAAA;AAAA,QACH;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,IAAA,CAAK,QAAA;AAAA,UACH,CAAA,iEAAA,EAAoE,KAAK,KAAK,CAAA,EAAA,CAAA;AAAA,UAC9E;AAAA,SACF;AAAA,MACF;AAAA,IACF;AAIA,IAAA,MAAM,mBAAA,GAAsB,OAAO,WAAA,CAAY,IAAA;AAAA,MAC7C,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,KAAS,gBAAA,IAAoB,EAAE,WAAA,KAAgB;AAAA,KAC1D;AAEA,IAAA,MAAM,mBAAmB,mBAAA,GACrB,MAAM,IAAA,CAAK,oBAAA,CAAqB,mBAAmB,CAAA,GACnD,IAAA;AACJ,IAAA,IAAI,gBAAA,EAAkB;AACpB,MAAA,IAAI;AACF,QAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,KAAA,CAAM,gBAAgB,CAAA;AAOjD,QAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,aAAA,CAAc,KAAK,CAAA,EAAG;AACtC,UAAA,IAAA,CAAK,mBAAmB,IAAA,CAAK;AAAA,YAC3B,WAAW,IAAA,CAAK,KAAA;AAAA,YAChB,OAAO,aAAA,CAAc,KAAA;AAAA,YACrB,WAAW,aAAA,CAAc;AAAA;AAAA;AAAA,WAG1B,CAAA;AAAA,QACH;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,IAAA,CAAK,QAAA;AAAA,UACH,CAAA,8DAAA,EAAiE,KAAK,KAAK,CAAA,EAAA,CAAA;AAAA,UAC3E;AAAA,SACF;AAAA,MACF;AAAA,IACF;AAOA,IAAA,IAAI,cAAA,IAAkB,CAAC,IAAA,CAAK,MAAA,CAAO,mBAAA,EAAqB;AACtD,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,kBAAA,GAAqB,OAAO,WAAA,CAAY,MAAA;AAAA,MAC5C,CAAC,CAAA,KACC,CAAA,CAAE,IAAA,IACF,CAAA,CAAE,KAAK,UAAA,CAAW,WAAW,CAAA,IAC7B,CAAA,CAAE,WAAA,KAAgB;AAAA,KACtB;AAEA,IAAA,KAAA,MAAW,cAAc,kBAAA,EAAoB;AAC3C,MAAA,MAAM,WAAA,GAAc,MAAM,IAAA,CAAK,oBAAA,CAAqB,UAAU,CAAA;AAC9D,MAAA,IAAI,CAAC,WAAA,EAAa;AAElB,MAAA,IAAI;AAEF,QAAA,MAAM,QAAA,GAAW,IAAA,CAAK,KAAA,CAAM,WAAW,CAAA;AAWvC,QAAA,MAAM,SAAA,GAAY,IAAA,CAAK,MAAA,EAAQ,KAAA,IAAS,qBAAA;AACxC,QAAA,MAAM,UAAA,GAAa,OAAO,MAAA,KAAW,QAAA;AAErC,QAAA,MAAM,eAAA,GAAkC;AAAA,UACtC,IAAI,IAAA,CAAK,KAAA;AAAA,UACT,WAAA,EAAa,SAAA;AAAA,UACb,UAAU,QAAA,CAAS,QAAA;AAAA,UACnB,MAAA,EAAQ,MAAA;AAAA,UACR,IAAA,EAAM,UAAA;AAAA,UACN,UAAU,QAAA,CAAS,MAAA;AAAA,UACnB,KAAA,EAAO,CAAC,UAAA,GAAa,aAAA,GAAgB,KAAA,CAAA;AAAA,UACrC,cAAc,EAAC;AAAA,UACf,UAAU,QAAA,CAAS,QAAA;AAAA,UACnB,SAAS,QAAA,CAAS,OAAA;AAAA,UAClB,YAAY,QAAA,CAAS;AAAA,SACvB;AAEA,QAAA,IAAA,CAAK,UAAA,CAAW,KAAK,eAAe,CAAA;AAAA,MACtC,SAAS,KAAA,EAAO;AACd,QAAA,IAAA,CAAK,QAAA;AAAA,UACH,CAAA,oDAAA,EAAuD,WAAW,IAAI,CAAA,EAAA,CAAA;AAAA,UACtE;AAAA,SACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,OAAA,EAAoC;AAC9C,IAAA,MAAM,OAAA,GAAU,KAAK,GAAA,EAAI;AACzB,IAAA,MAAM,UAAA,GAAa,UAAU,IAAA,CAAK,SAAA;AAGlC,IAAA,IAAI,IAAA,CAAK,UAAA,CAAW,MAAA,KAAW,CAAA,EAAG;AAChC,MAAA,IAAA,CAAK,IAAI,sDAAsD,CAAA;AAC/D,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,YAAA,CAAa,UAAU,CAAA;AAG5C,IAAA,MAAM,UAAA,GAAa,MAAM,IAAA,CAAK,kBAAA,EAAmB;AAGjD,IAAA,UAAA,CAAW,IAAA,CAAK;AAAA,MACd,WAAW,OAAA,CAAQ,SAAA;AAAA,MACnB,KAAA,EAAO,QAAQ,OAAA,CAAQ,KAAA;AAAA,MACvB,MAAA,EAAQ,QAAQ,OAAA,CAAQ,MAAA;AAAA,MACxB,MAAA,EAAQ,QAAQ,OAAA,CAAQ,MAAA;AAAA,MACxB,QAAA,EAAU,QAAQ,OAAA,CAAQ,QAAA;AAAA,MAC1B,YAAY,OAAA,CAAQ;AAAA,KACrB,CAAA;AAGD,IAAA,MAAM,IAAA,CAAK,YAAY,OAAO,CAAA;AAG9B,IAAA,MAAM,KAAK,cAAA,EAAe;AAG1B,IAAA,MAAM,SAAA,GAAYC,SAAA,CAAK,IAAA,CAAK,MAAA,CAAO,WAAW,QAAQ,CAAA;AACtD,IAAA,MAAM,IAAA,CAAK,cAAA,CAAe,OAAA,EAAS,UAAA,EAAY,SAAS,CAAA;AAExD,IAAA,MAAM,UAAA,GAAaA,SAAA,CAAK,SAAA,EAAW,YAAY,CAAA;AAC/C,IAAA,IAAA,CAAK,GAAA,CAAI;AAAA,iCAAA,EAAsC,UAAU,CAAA,CAAE,CAAA;AAC3D,IAAA,IAAA,CAAK,GAAA;AAAA,MACH,CAAA,wBAAA,EAA2B,OAAA,CAAQ,OAAA,CAAQ,MAAM,IAAI,OAAA,CAAQ,OAAA,CAAQ,KAAK,CAAA,SAAA,EAAA,CAAa,QAAQ,OAAA,CAAQ,QAAA,GAAW,GAAA,EAAK,OAAA,CAAQ,CAAC,CAAC,CAAA,EAAA;AAAA,KACnI;AAGA,IAAA,IAAI,KAAK,MAAA,CAAO,QAAA,IAAY,CAAC,OAAA,CAAQ,IAAI,EAAA,EAAI;AAC3C,MAAA,MAAM,IAAA,CAAK,WAAW,UAAU,CAAA;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,MAAc,cAAA,CACZ,OAAA,EACA,UAAA,EACA,SAAA,EACe;AAGf,IAAA,MAAMC,WAAAA,GAAaC,kBAAc,aAAe,CAAA;AAChD,IAAA,MAAM,SAAA,GAAYC,aAAQF,WAAU,CAAA;AACpC,IAAA,MAAM,UAAA,GAAaD,SAAA,CAAK,SAAA,EAAW,SAAS,CAAA;AAG5C,IAAA,MAAMD,cAAA,CAAM,SAAA,EAAW,EAAE,SAAA,EAAW,MAAM,CAAA;AAC1C,IAAA,MAAMK,WAAA,CAAG,YAAY,SAAA,EAAW,EAAE,WAAW,IAAA,EAAM,KAAA,EAAO,MAAM,CAAA;AAGhE,IAAA,MAAM,UAAA,GAAa,0BAA0B,IAAA,CAAK,SAAA;AAAA,MAChD;AAAA,QACE,OAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,IAAA;AAAA,MACA;AAAA,KACD,CAAA,CAAA,CAAA;AAED,IAAA,MAAMC,mBAAUL,SAAA,CAAK,SAAA,EAAW,SAAS,CAAA,EAAG,YAAY,OAAO,CAAA;AAAA,EACjE;AAAA,EAEQ,aAAa,UAAA,EAAoC;AACvD,IAAA,MAAM,KAAA,GAAQ,KAAK,UAAA,CAAW,MAAA;AAC9B,IAAA,MAAM,mBAA2C,EAAC;AAClD,IAAA,MAAM,oBAAA,GAAuB;AAAA,MAC3B,KAAA,EAAO,CAAA;AAAA,MACP,MAAA,EAAQ,CAAA;AAAA,MACR,YAAA,EAAc,CAAA;AAAA,MACd,KAAA,EAAO,CAAA;AAAA,MACP,QAAA,EAAU,CAAA;AAAA,MACV,KAAA,EAAO,CAAA;AAAA,MACP,KAAA,EAAO,CAAA;AAAA,MACP,IAAA,EAAM,CAAA;AAAA,MACN,cAAA,EAAgB,CAAA;AAAA,MAChB,aAAA,EAAe;AAAA,KACjB;AAEA,IAAA,IAAI,MAAA,GAAS,CAAA;AACb,IAAA,KAAA,MAAW,CAAA,IAAK,KAAK,UAAA,EAAY;AAC/B,MAAA,IAAI,EAAE,IAAA,EAAM,MAAA,EAAA;AAEZ,MAAA,MAAM,WAAA,GAAc,EAAE,WAAA,IAAe,iBAAA;AACrC,MAAA,gBAAA,CAAiB,WAAW,CAAA,GAAA,CAAK,gBAAA,CAAiB,WAAW,KAAK,CAAA,IAAK,CAAA;AAEvE,MAAA,IAAI,CAAA,CAAE,YAAA,CAAa,KAAA,EAAO,oBAAA,CAAqB,KAAA,EAAA;AAC/C,MAAA,IAAI,CAAA,CAAE,YAAA,CAAa,MAAA,EAAQ,oBAAA,CAAqB,MAAA,EAAA;AAChD,MAAA,IAAI,CAAA,CAAE,YAAA,CAAa,YAAA,EAAc,oBAAA,CAAqB,YAAA,EAAA;AACtD,MAAA,IAAI,CAAA,CAAE,YAAA,CAAa,KAAA,EAAO,oBAAA,CAAqB,KAAA,EAAA;AAC/C,MAAA,IAAI,CAAA,CAAE,YAAA,CAAa,QAAA,EAAU,oBAAA,CAAqB,QAAA,EAAA;AAClD,MAAA,IAAI,CAAA,CAAE,YAAA,CAAa,KAAA,EAAO,oBAAA,CAAqB,KAAA,EAAA;AAC/C,MAAA,IAAI,CAAA,CAAE,YAAA,CAAa,KAAA,EAAO,oBAAA,CAAqB,KAAA,EAAA;AAC/C,MAAA,IAAI,CAAA,CAAE,YAAA,CAAa,IAAA,EAAM,oBAAA,CAAqB,IAAA,EAAA;AAC9C,MAAA,IAAI,CAAA,CAAE,YAAA,CAAa,cAAA,EAAgB,oBAAA,CAAqB,cAAA,EAAA;AACxD,MAAA,IAAI,CAAA,CAAE,YAAA,CAAa,aAAA,EAAe,oBAAA,CAAqB,aAAA,EAAA;AAAA,IACzD;AAEA,IAAA,MAAM,SAAS,KAAA,GAAQ,MAAA;AAEvB,IAAA,OAAO;AAAA,MACL,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,MAClC,UAAA;AAAA,MACA,WAAA,EAAa;AAAA,QACX,EAAA,EAAI,CAAC,CAAC,OAAA,CAAQ,GAAA,CAAI,EAAA;AAAA,QAClB,MAAM,OAAA,CAAQ,OAAA;AAAA,QACd,UAAU,OAAA,CAAQ;AAAA,OACpB;AAAA,MACA,OAAA,EAAS;AAAA,QACP,KAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA;AAAA,QACA,UAAU,MAAA,GAAS,KAAA;AAAA,QACnB,gBAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,SAAS,IAAA,CAAK,UAAA;AAAA,MACd,mBACE,IAAA,CAAK,iBAAA,CAAkB,MAAA,GAAS,CAAA,GAAI,KAAK,iBAAA,GAAoB,MAAA;AAAA,MAC/D,oBACE,IAAA,CAAK,kBAAA,CAAmB,MAAA,GAAS,CAAA,GAC7B,KAAK,kBAAA,GACL;AAAA,KACR;AAAA,EACF;AAAA,EAEA,MAAc,kBAAA,GAA+D;AAC3E,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAQ,MAAMM,gBAAA,CAAQ,IAAA,CAAK,OAAO,SAAS,CAAA;AACjD,MAAA,MAAM,QAAA,GAAW,MACd,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,UAAA,CAAW,MAAM,CAAA,IAAK,CAAA,CAAE,SAAS,OAAO,CAAC,EACzD,IAAA,EAAK,CACL,MAAM,EAAE,IAAA,CAAK,MAAA,CAAO,YAAA,GAAe,CAAA,CAAE,CAAA;AAExC,MAAA,MAAM,aAA8C,EAAC;AAErD,MAAA,KAAA,MAAW,QAAQ,QAAA,EAAU;AAC3B,QAAA,IAAI;AACF,UAAA,MAAM,UAAU,MAAMR,iBAAA;AAAA,YACpBE,SAAA,CAAK,IAAA,CAAK,MAAA,CAAO,SAAA,EAAW,IAAI,CAAA;AAAA,YAChC;AAAA,WACF;AACA,UAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAElC,UAAA,UAAA,CAAW,IAAA,CAAK;AAAA,YACd,WAAW,OAAA,CAAQ,SAAA;AAAA,YACnB,KAAA,EAAO,QAAQ,OAAA,CAAQ,KAAA;AAAA,YACvB,MAAA,EAAQ,QAAQ,OAAA,CAAQ,MAAA;AAAA,YACxB,MAAA,EAAQ,QAAQ,OAAA,CAAQ,MAAA;AAAA,YACxB,QAAA,EAAU,QAAQ,OAAA,CAAQ,QAAA;AAAA,YAC1B,YAAY,OAAA,CAAQ;AAAA,WACrB,CAAA;AAAA,QACH,SAAS,KAAA,EAAO;AACd,UAAA,IAAA,CAAK,QAAA,CAAS,CAAA,8BAAA,EAAiC,IAAI,CAAA,CAAA,CAAA,EAAK,KAAK,CAAA;AAAA,QAC/D;AAAA,MACF;AAEA,MAAA,OAAO,UAAA;AAAA,IACT,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,EAAC;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAc,YAAY,OAAA,EAAwC;AAChE,IAAA,MAAM,WAAW,CAAA,IAAA,EAAO,OAAA,CAAQ,UAAU,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAC,CAAA,KAAA,CAAA;AAC5D,IAAA,MAAM,QAAA,GAAWA,SAAA,CAAK,IAAA,CAAK,MAAA,CAAO,WAAW,QAAQ,CAAA;AAErD,IAAA,MAAMK,kBAAA,CAAU,UAAU,IAAA,CAAK,SAAA,CAAU,SAAS,IAAA,EAAM,CAAC,GAAG,OAAO,CAAA;AAAA,EACrE;AAAA,EAEA,MAAc,cAAA,GAAgC;AAC5C,IAAA,IAAI;AACF,MAAA,MAAM,KAAA,GAAQ,MAAMC,gBAAA,CAAQ,IAAA,CAAK,OAAO,SAAS,CAAA;AACjD,MAAA,MAAM,WAAW,KAAA,CACd,MAAA,CAAO,CAAC,CAAA,KAAM,EAAE,UAAA,CAAW,MAAM,CAAA,IAAK,CAAA,CAAE,SAAS,OAAO,CAAC,CAAA,CACzD,IAAA,GACA,OAAA,EAAQ;AAGX,MAAA,MAAM,QAAA,GAAW,QAAA,CAAS,KAAA,CAAM,IAAA,CAAK,OAAO,YAAY,CAAA;AAExD,MAAA,KAAA,MAAW,QAAQ,QAAA,EAAU;AAC3B,QAAA,MAAMC,gBAAOP,SAAA,CAAK,IAAA,CAAK,MAAA,CAAO,SAAA,EAAW,IAAI,CAAC,CAAA;AAAA,MAChD;AAAA,IACF,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,QAAA,CAAS,8CAA8C,KAAK,CAAA;AAAA,IACnE;AAAA,EACF;AAAA,EAEA,MAAc,WAAW,UAAA,EAAmC;AAC1D,IAAA,IAAI;AAEF,MAAA,MAAM,EAAE,OAAA,EAAS,IAAA,EAAK,GAAI,MAAM,OAAO,MAAM,CAAA;AAC7C,MAAA,MAAM,YAAA,GAAeQ,aAAQ,UAAU,CAAA;AAEvC,MAAA,MAAM,KAAK,YAAY,CAAA;AACvB,MAAA,IAAA,CAAK,IAAI,yCAAyC,CAAA;AAAA,IACpD,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,QAAA,CAAS,yCAAyC,KAAK,CAAA;AAC5D,MAAA,IAAA,CAAK,GAAA,CAAI,CAAA,qCAAA,EAAwCA,YAAA,CAAQ,UAAU,CAAC,CAAA,CAAE,CAAA;AAAA,IACxE;AAAA,EACF;AACF","file":"mcpReporter.cjs","sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","import type {\n Reporter,\n FullConfig,\n Suite,\n TestCase,\n TestResult,\n FullResult,\n} from '@playwright/test/reporter';\nimport { mkdir, writeFile, readdir, readFile, unlink, cp } from 'fs/promises';\nimport { join, resolve, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport type { MCPEvalReporterConfig } from './types.js';\nimport type { AuthType } from '../types/index.js';\nimport type {\n MCPEvalRunData,\n MCPEvalHistoricalSummary,\n MCPConformanceResultData,\n MCPServerCapabilitiesData,\n EvalCaseResult,\n MCPConformanceCheck,\n} from '../types/reporter.js';\n\n/**\n * Custom Playwright reporter for MCP eval results\n *\n * Generates HTML reports with historical tracking\n *\n * @example\n * ```typescript\n * // playwright.config.ts\n * export default defineConfig({\n * reporter: [\n * ['@gleanwork/mcp-server-tester/reporters/mcpReporter', {\n * outputDir: '.mcp-test-results',\n * historyLimit: 10\n * }]\n * ]\n * });\n * ```\n */\nexport default class MCPReporter implements Reporter {\n private config: Required<MCPEvalReporterConfig>;\n private startTime: number = 0;\n private allResults: Array<EvalCaseResult> = [];\n private conformanceChecks: Array<MCPConformanceResultData> = [];\n private serverCapabilities: Array<MCPServerCapabilitiesData> = [];\n\n constructor(options: MCPEvalReporterConfig = {}) {\n this.config = {\n outputDir: options.outputDir ?? '.mcp-test-results',\n autoOpen: options.autoOpen ?? false,\n historyLimit: options.historyLimit ?? 10,\n quiet: options.quiet ?? false,\n includeAutoTracking: options.includeAutoTracking ?? true,\n };\n }\n\n private log(message: string): void {\n if (!this.config.quiet) {\n console.log(message);\n }\n }\n\n private logError(message: string, error?: unknown): void {\n if (!this.config.quiet) {\n console.error(message, error ?? '');\n }\n }\n\n /**\n * Reads attachment content from either in-memory body (Playwright < 1.43)\n * or from the file path on disk (Playwright ≥ 1.43, which writes large\n * attachments to disk and exposes path instead of body).\n */\n private async getAttachmentContent(attachment: {\n body?: Buffer;\n path?: string;\n }): Promise<string | null> {\n if (attachment.body) return attachment.body.toString('utf-8');\n if (attachment.path) {\n try {\n return await readFile(attachment.path, 'utf-8');\n } catch {\n return null;\n }\n }\n return null;\n }\n\n async onBegin(_config: FullConfig, _suite: Suite): Promise<void> {\n this.startTime = Date.now();\n this.allResults = [];\n this.conformanceChecks = [];\n this.serverCapabilities = [];\n\n // Ensure output directory exists\n await mkdir(this.config.outputDir, { recursive: true });\n }\n\n async onTestEnd(test: TestCase, result: TestResult): Promise<void> {\n // Strategy 1: Extract MCP eval results from runEvalDataset() attachments\n const evalAttachment = result.attachments.find(\n (a) =>\n a.name === 'mcp-test-results' && a.contentType === 'application/json'\n );\n\n let hasEvalDataset = false;\n\n const evalContent = evalAttachment\n ? await this.getAttachmentContent(evalAttachment)\n : null;\n if (evalContent) {\n try {\n const evalResults = JSON.parse(evalContent) as {\n caseResults: Array<EvalCaseResult>;\n };\n\n // Trust the data from the attachment - evalRunner now includes\n // authType and project from the mcp fixture (Playwright is source of truth)\n this.allResults.push(...evalResults.caseResults);\n hasEvalDataset = true;\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse eval results from test \"${test.title}\":`,\n error\n );\n }\n }\n\n // Strategy 2: Extract conformance check results\n // These are created by runConformanceChecks() when testInfo is passed\n const conformanceAttachment = result.attachments.find(\n (a) =>\n a.name === 'mcp-conformance-checks' &&\n a.contentType === 'application/json'\n );\n\n const conformanceContent = conformanceAttachment\n ? await this.getAttachmentContent(conformanceAttachment)\n : null;\n if (conformanceContent) {\n try {\n const conformanceData = JSON.parse(conformanceContent) as {\n operation: string;\n pass: boolean;\n checks: MCPConformanceCheck[];\n serverInfo?: { name?: string; version?: string };\n toolCount: number;\n authType?: AuthType;\n project?: string;\n };\n\n // Only push if checks array is valid\n if (Array.isArray(conformanceData.checks)) {\n this.conformanceChecks.push({\n testTitle: test.title,\n pass: conformanceData.pass,\n checks: conformanceData.checks,\n serverInfo: conformanceData.serverInfo,\n toolCount: conformanceData.toolCount,\n authType: conformanceData.authType,\n project: conformanceData.project,\n });\n }\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse conformance check attachment for \"${test.title}\":`,\n error\n );\n }\n }\n\n // Strategy 2b: Extract server capabilities from mcp-list-tools attachments\n // These are created by createMCPFixture().listTools()\n const listToolsAttachment = result.attachments.find(\n (a) => a.name === 'mcp-list-tools' && a.contentType === 'application/json'\n );\n\n const listToolsContent = listToolsAttachment\n ? await this.getAttachmentContent(listToolsAttachment)\n : null;\n if (listToolsContent) {\n try {\n const listToolsData = JSON.parse(listToolsContent) as {\n operation: string;\n toolCount: number;\n tools: Array<{ name: string; description?: string }>;\n };\n\n // Only push if tools array is valid\n if (Array.isArray(listToolsData.tools)) {\n this.serverCapabilities.push({\n testTitle: test.title,\n tools: listToolsData.tools,\n toolCount: listToolsData.toolCount,\n // Note: authType and project are available from the mcp fixture\n // but not currently included in the listTools attachment\n });\n }\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse mcp-list-tools attachment for \"${test.title}\":`,\n error\n );\n }\n }\n\n // Strategy 3: Extract MCP tool calls from auto-tracking attachments\n // These are created by createMCPFixture()\n // Skip if:\n // - This test already has eval dataset results (to avoid duplicates)\n // - Auto-tracking is disabled in config\n if (hasEvalDataset || !this.config.includeAutoTracking) {\n return;\n }\n\n const mcpCallAttachments = result.attachments.filter(\n (a) =>\n a.name &&\n a.name.startsWith('mcp-call-') &&\n a.contentType === 'application/json'\n );\n\n for (const attachment of mcpCallAttachments) {\n const callContent = await this.getAttachmentContent(attachment);\n if (!callContent) continue;\n\n try {\n // Attachment now includes authType and project from the mcp fixture\n const callData = JSON.parse(callContent) as {\n operation: string;\n toolName: string;\n args: Record<string, unknown>;\n result: unknown;\n durationMs: number;\n isError: boolean;\n authType?: AuthType;\n project?: string;\n };\n\n const suiteName = test.parent?.title || 'Uncategorized Tests';\n const testPassed = result.status === 'passed';\n\n const syntheticResult: EvalCaseResult = {\n id: test.title,\n datasetName: suiteName,\n toolName: callData.toolName,\n source: 'test',\n pass: testPassed,\n response: callData.result,\n error: !testPassed ? 'Test failed' : undefined,\n expectations: {},\n authType: callData.authType,\n project: callData.project,\n durationMs: callData.durationMs,\n };\n\n this.allResults.push(syntheticResult);\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse MCP call attachment \"${attachment.name}\":`,\n error\n );\n }\n }\n }\n\n async onEnd(_result: FullResult): Promise<void> {\n const endTime = Date.now();\n const durationMs = endTime - this.startTime;\n\n // Skip if no eval results collected\n if (this.allResults.length === 0) {\n this.log('[MCP Reporter] No MCP eval results found in test run');\n return;\n }\n\n // Build run data\n const runData = this.buildRunData(durationMs);\n\n // Load historical data\n const historical = await this.loadHistoricalData();\n\n // Add current run to historical\n historical.push({\n timestamp: runData.timestamp,\n total: runData.metrics.total,\n passed: runData.metrics.passed,\n failed: runData.metrics.failed,\n passRate: runData.metrics.passRate,\n durationMs: runData.durationMs,\n });\n\n // Save current run data\n await this.saveRunData(runData);\n\n // Clean up old runs\n await this.cleanupOldRuns();\n\n // Generate report using copy + inject pattern\n const reportDir = join(this.config.outputDir, 'latest');\n await this.generateReport(runData, historical, reportDir);\n\n const reportPath = join(reportDir, 'index.html');\n this.log(`\\n[MCP Reporter] Report generated: ${reportPath}`);\n this.log(\n `[MCP Reporter] Results: ${runData.metrics.passed}/${runData.metrics.total} passed (${(runData.metrics.passRate * 100).toFixed(1)}%)`\n );\n\n // Auto-open browser if configured and not in CI\n if (this.config.autoOpen && !process.env.CI) {\n await this.openReport(reportPath);\n }\n }\n\n private async generateReport(\n runData: MCPEvalRunData,\n historical: Array<MCPEvalHistoricalSummary>,\n outputDir: string\n ): Promise<void> {\n // Get the UI dist path (relative to this file)\n // In ESM, we need to use import.meta.url\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const uiDistPath = join(__dirname, 'ui-dist');\n\n // Step 1: Copy pre-built UI template\n await mkdir(outputDir, { recursive: true });\n await cp(uiDistPath, outputDir, { recursive: true, force: true });\n\n // Step 2: Inject test data as JavaScript\n const dataScript = `window.MCP_EVAL_DATA = ${JSON.stringify(\n {\n runData,\n historical,\n },\n null,\n 2\n )};`;\n\n await writeFile(join(outputDir, 'data.js'), dataScript, 'utf-8');\n }\n\n private buildRunData(durationMs: number): MCPEvalRunData {\n const total = this.allResults.length;\n const datasetBreakdown: Record<string, number> = {};\n const expectationBreakdown = {\n exact: 0,\n schema: 0,\n textContains: 0,\n regex: 0,\n snapshot: 0,\n judge: 0,\n error: 0,\n size: 0,\n toolsTriggered: 0,\n toolCallCount: 0,\n };\n\n let passed = 0;\n for (const r of this.allResults) {\n if (r.pass) passed++;\n\n const datasetName = r.datasetName || 'Unknown Dataset';\n datasetBreakdown[datasetName] = (datasetBreakdown[datasetName] || 0) + 1;\n\n if (r.expectations.exact) expectationBreakdown.exact++;\n if (r.expectations.schema) expectationBreakdown.schema++;\n if (r.expectations.textContains) expectationBreakdown.textContains++;\n if (r.expectations.regex) expectationBreakdown.regex++;\n if (r.expectations.snapshot) expectationBreakdown.snapshot++;\n if (r.expectations.judge) expectationBreakdown.judge++;\n if (r.expectations.error) expectationBreakdown.error++;\n if (r.expectations.size) expectationBreakdown.size++;\n if (r.expectations.toolsTriggered) expectationBreakdown.toolsTriggered++;\n if (r.expectations.toolCallCount) expectationBreakdown.toolCallCount++;\n }\n\n const failed = total - passed;\n\n return {\n timestamp: new Date().toISOString(),\n durationMs,\n environment: {\n ci: !!process.env.CI,\n node: process.version,\n platform: process.platform,\n },\n metrics: {\n total,\n passed,\n failed,\n passRate: passed / total,\n datasetBreakdown,\n expectationBreakdown,\n },\n results: this.allResults,\n conformanceChecks:\n this.conformanceChecks.length > 0 ? this.conformanceChecks : undefined,\n serverCapabilities:\n this.serverCapabilities.length > 0\n ? this.serverCapabilities\n : undefined,\n };\n }\n\n private async loadHistoricalData(): Promise<Array<MCPEvalHistoricalSummary>> {\n try {\n const files = await readdir(this.config.outputDir);\n const runFiles = files\n .filter((f) => f.startsWith('run-') && f.endsWith('.json'))\n .sort()\n .slice(-(this.config.historyLimit - 1)); // Keep most recent, leave room for current run\n\n const historical: Array<MCPEvalHistoricalSummary> = [];\n\n for (const file of runFiles) {\n try {\n const content = await readFile(\n join(this.config.outputDir, file),\n 'utf-8'\n );\n const runData = JSON.parse(content) as MCPEvalRunData;\n\n historical.push({\n timestamp: runData.timestamp,\n total: runData.metrics.total,\n passed: runData.metrics.passed,\n failed: runData.metrics.failed,\n passRate: runData.metrics.passRate,\n durationMs: runData.durationMs,\n });\n } catch (error) {\n this.logError(`[MCP Reporter] Failed to load ${file}:`, error);\n }\n }\n\n return historical;\n } catch {\n return [];\n }\n }\n\n private async saveRunData(runData: MCPEvalRunData): Promise<void> {\n const filename = `run-${runData.timestamp.replace(/:/g, '-')}.json`;\n const filepath = join(this.config.outputDir, filename);\n\n await writeFile(filepath, JSON.stringify(runData, null, 2), 'utf-8');\n }\n\n private async cleanupOldRuns(): Promise<void> {\n try {\n const files = await readdir(this.config.outputDir);\n const runFiles = files\n .filter((f) => f.startsWith('run-') && f.endsWith('.json'))\n .sort()\n .reverse();\n\n // Keep only historyLimit most recent runs\n const toDelete = runFiles.slice(this.config.historyLimit);\n\n for (const file of toDelete) {\n await unlink(join(this.config.outputDir, file));\n }\n } catch (error) {\n this.logError('[MCP Reporter] Failed to cleanup old runs:', error);\n }\n }\n\n private async openReport(reportPath: string): Promise<void> {\n try {\n // Dynamic import to avoid bundling issues\n const { default: open } = await import('open');\n const absolutePath = resolve(reportPath);\n\n await open(absolutePath);\n this.log('[MCP Reporter] Opened report in browser');\n } catch (error) {\n this.logError('[MCP Reporter] Failed to open report:', error);\n this.log(`[MCP Reporter] Open manually: file://${resolve(reportPath)}`);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../node_modules/tsup/assets/cjs_shims.js","../../src/utils/usageUtils.ts","../../src/reporters/mcpReporter.ts"],"names":["readFile","mkdir","join","__filename","fileURLToPath","dirname","cp","writeFile","readdir","unlink","resolve"],"mappings":"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","file":"mcpReporter.cjs","sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","import type { UsageMetrics } from '../types/index.js';\n\nfunction optionalSum(a?: number, b?: number): number | undefined {\n if (a === undefined && b === undefined) return undefined;\n return (a ?? 0) + (b ?? 0);\n}\n\nexport function sumUsage(\n a: UsageMetrics | undefined,\n b: UsageMetrics | undefined\n): UsageMetrics | undefined {\n if (!a && !b) return undefined;\n if (!a) return b ? { ...b } : undefined;\n if (!b) return { ...a };\n return {\n inputTokens: a.inputTokens + b.inputTokens,\n outputTokens: a.outputTokens + b.outputTokens,\n totalCostUsd: a.totalCostUsd + b.totalCostUsd,\n durationMs: a.durationMs + b.durationMs,\n durationApiMs: optionalSum(a.durationApiMs, b.durationApiMs),\n cacheReadInputTokens: optionalSum(\n a.cacheReadInputTokens,\n b.cacheReadInputTokens\n ),\n cacheCreationInputTokens: optionalSum(\n a.cacheCreationInputTokens,\n b.cacheCreationInputTokens\n ),\n };\n}\n","import type {\n Reporter,\n FullConfig,\n Suite,\n TestCase,\n TestResult,\n FullResult,\n} from '@playwright/test/reporter';\nimport { mkdir, writeFile, readdir, readFile, unlink, cp } from 'fs/promises';\nimport { join, resolve, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport type { MCPEvalReporterConfig } from './types.js';\nimport type { AuthType } from '../types/index.js';\nimport type {\n MCPEvalRunData,\n MCPEvalHistoricalSummary,\n MCPConformanceResultData,\n MCPServerCapabilitiesData,\n EvalCaseResult,\n MCPConformanceCheck,\n} from '../types/reporter.js';\nimport type { UsageMetrics } from '../types/index.js';\nimport { sumUsage } from '../utils/usageUtils.js';\n\n/**\n * Custom Playwright reporter for MCP eval results\n *\n * Generates HTML reports with historical tracking\n *\n * @example\n * ```typescript\n * // playwright.config.ts\n * export default defineConfig({\n * reporter: [\n * ['@gleanwork/mcp-server-tester/reporters/mcpReporter', {\n * outputDir: '.mcp-test-results',\n * historyLimit: 10\n * }]\n * ]\n * });\n * ```\n */\nexport default class MCPReporter implements Reporter {\n private config: Required<MCPEvalReporterConfig>;\n private startTime: number = 0;\n private allResults: Array<EvalCaseResult> = [];\n private conformanceChecks: Array<MCPConformanceResultData> = [];\n private serverCapabilities: Array<MCPServerCapabilitiesData> = [];\n\n constructor(options: MCPEvalReporterConfig = {}) {\n this.config = {\n outputDir: options.outputDir ?? '.mcp-test-results',\n autoOpen: options.autoOpen ?? false,\n historyLimit: options.historyLimit ?? 10,\n quiet: options.quiet ?? false,\n includeAutoTracking: options.includeAutoTracking ?? true,\n };\n }\n\n private log(message: string): void {\n if (!this.config.quiet) {\n console.log(message);\n }\n }\n\n private logError(message: string, error?: unknown): void {\n if (!this.config.quiet) {\n console.error(message, error ?? '');\n }\n }\n\n /**\n * Reads attachment content from either in-memory body (Playwright < 1.43)\n * or from the file path on disk (Playwright ≥ 1.43, which writes large\n * attachments to disk and exposes path instead of body).\n */\n private async getAttachmentContent(attachment: {\n body?: Buffer;\n path?: string;\n }): Promise<string | null> {\n if (attachment.body) return attachment.body.toString('utf-8');\n if (attachment.path) {\n try {\n return await readFile(attachment.path, 'utf-8');\n } catch {\n return null;\n }\n }\n return null;\n }\n\n async onBegin(_config: FullConfig, _suite: Suite): Promise<void> {\n this.startTime = Date.now();\n this.allResults = [];\n this.conformanceChecks = [];\n this.serverCapabilities = [];\n\n // Ensure output directory exists\n await mkdir(this.config.outputDir, { recursive: true });\n }\n\n async onTestEnd(test: TestCase, result: TestResult): Promise<void> {\n // Strategy 1: Extract MCP eval results from runEvalDataset() attachments\n const evalAttachment = result.attachments.find(\n (a) =>\n a.name === 'mcp-test-results' && a.contentType === 'application/json'\n );\n\n let hasEvalDataset = false;\n\n const evalContent = evalAttachment\n ? await this.getAttachmentContent(evalAttachment)\n : null;\n if (evalContent) {\n try {\n const evalResults = JSON.parse(evalContent) as {\n caseResults: Array<EvalCaseResult>;\n };\n\n // Trust the data from the attachment - evalRunner now includes\n // authType and project from the mcp fixture (Playwright is source of truth)\n this.allResults.push(...evalResults.caseResults);\n hasEvalDataset = true;\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse eval results from test \"${test.title}\":`,\n error\n );\n }\n }\n\n // Strategy 2: Extract conformance check results\n // These are created by runConformanceChecks() when testInfo is passed\n const conformanceAttachment = result.attachments.find(\n (a) =>\n a.name === 'mcp-conformance-checks' &&\n a.contentType === 'application/json'\n );\n\n const conformanceContent = conformanceAttachment\n ? await this.getAttachmentContent(conformanceAttachment)\n : null;\n if (conformanceContent) {\n try {\n const conformanceData = JSON.parse(conformanceContent) as {\n operation: string;\n pass: boolean;\n checks: MCPConformanceCheck[];\n serverInfo?: { name?: string; version?: string };\n toolCount: number;\n authType?: AuthType;\n project?: string;\n };\n\n // Only push if checks array is valid\n if (Array.isArray(conformanceData.checks)) {\n this.conformanceChecks.push({\n testTitle: test.title,\n pass: conformanceData.pass,\n checks: conformanceData.checks,\n serverInfo: conformanceData.serverInfo,\n toolCount: conformanceData.toolCount,\n authType: conformanceData.authType,\n project: conformanceData.project,\n });\n }\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse conformance check attachment for \"${test.title}\":`,\n error\n );\n }\n }\n\n // Strategy 2b: Extract server capabilities from mcp-list-tools attachments\n // These are created by createMCPFixture().listTools()\n const listToolsAttachment = result.attachments.find(\n (a) => a.name === 'mcp-list-tools' && a.contentType === 'application/json'\n );\n\n const listToolsContent = listToolsAttachment\n ? await this.getAttachmentContent(listToolsAttachment)\n : null;\n if (listToolsContent) {\n try {\n const listToolsData = JSON.parse(listToolsContent) as {\n operation: string;\n toolCount: number;\n tools: Array<{ name: string; description?: string }>;\n };\n\n // Only push if tools array is valid\n if (Array.isArray(listToolsData.tools)) {\n this.serverCapabilities.push({\n testTitle: test.title,\n tools: listToolsData.tools,\n toolCount: listToolsData.toolCount,\n // Note: authType and project are available from the mcp fixture\n // but not currently included in the listTools attachment\n });\n }\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse mcp-list-tools attachment for \"${test.title}\":`,\n error\n );\n }\n }\n\n // Strategy 3: Extract MCP tool calls from auto-tracking attachments\n // These are created by createMCPFixture()\n // Skip if:\n // - This test already has eval dataset results (to avoid duplicates)\n // - Auto-tracking is disabled in config\n if (hasEvalDataset || !this.config.includeAutoTracking) {\n return;\n }\n\n const mcpCallAttachments = result.attachments.filter(\n (a) =>\n a.name &&\n a.name.startsWith('mcp-call-') &&\n a.contentType === 'application/json'\n );\n\n for (const attachment of mcpCallAttachments) {\n const callContent = await this.getAttachmentContent(attachment);\n if (!callContent) continue;\n\n try {\n // Attachment now includes authType and project from the mcp fixture\n const callData = JSON.parse(callContent) as {\n operation: string;\n toolName: string;\n args: Record<string, unknown>;\n result: unknown;\n durationMs: number;\n isError: boolean;\n authType?: AuthType;\n project?: string;\n };\n\n const suiteName = test.parent?.title || 'Uncategorized Tests';\n const testPassed = result.status === 'passed';\n\n const syntheticResult: EvalCaseResult = {\n id: test.title,\n datasetName: suiteName,\n toolName: callData.toolName,\n source: 'test',\n pass: testPassed,\n response: callData.result,\n error: !testPassed ? 'Test failed' : undefined,\n expectations: {},\n authType: callData.authType,\n project: callData.project,\n durationMs: callData.durationMs,\n };\n\n this.allResults.push(syntheticResult);\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse MCP call attachment \"${attachment.name}\":`,\n error\n );\n }\n }\n }\n\n async onEnd(_result: FullResult): Promise<void> {\n const endTime = Date.now();\n const durationMs = endTime - this.startTime;\n\n // Skip if no eval results collected\n if (this.allResults.length === 0) {\n this.log('[MCP Reporter] No MCP eval results found in test run');\n return;\n }\n\n // Build run data\n const runData = this.buildRunData(durationMs);\n\n // Load historical data\n const historical = await this.loadHistoricalData();\n\n // Add current run to historical\n historical.push({\n timestamp: runData.timestamp,\n total: runData.metrics.total,\n passed: runData.metrics.passed,\n failed: runData.metrics.failed,\n passRate: runData.metrics.passRate,\n durationMs: runData.durationMs,\n });\n\n // Save current run data\n await this.saveRunData(runData);\n\n // Clean up old runs\n await this.cleanupOldRuns();\n\n // Generate report using copy + inject pattern\n const reportDir = join(this.config.outputDir, 'latest');\n await this.generateReport(runData, historical, reportDir);\n\n const reportPath = join(reportDir, 'index.html');\n this.log(`\\n[MCP Reporter] Report generated: ${reportPath}`);\n this.log(\n `[MCP Reporter] Results: ${runData.metrics.passed}/${runData.metrics.total} passed (${(runData.metrics.passRate * 100).toFixed(1)}%)`\n );\n\n // Auto-open browser if configured and not in CI\n if (this.config.autoOpen && !process.env.CI) {\n await this.openReport(reportPath);\n }\n }\n\n private async generateReport(\n runData: MCPEvalRunData,\n historical: Array<MCPEvalHistoricalSummary>,\n outputDir: string\n ): Promise<void> {\n // Get the UI dist path (relative to this file)\n // In ESM, we need to use import.meta.url\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const uiDistPath = join(__dirname, 'ui-dist');\n\n // Step 1: Copy pre-built UI template\n await mkdir(outputDir, { recursive: true });\n await cp(uiDistPath, outputDir, { recursive: true, force: true });\n\n // Step 2: Inject test data as JavaScript\n const dataScript = `window.MCP_EVAL_DATA = ${JSON.stringify(\n {\n runData,\n historical,\n },\n null,\n 2\n )};`;\n\n await writeFile(join(outputDir, 'data.js'), dataScript, 'utf-8');\n }\n\n private buildRunData(durationMs: number): MCPEvalRunData {\n const total = this.allResults.length;\n const datasetBreakdown: Record<string, number> = {};\n const expectationBreakdown = {\n exact: 0,\n schema: 0,\n textContains: 0,\n regex: 0,\n snapshot: 0,\n judge: 0,\n error: 0,\n size: 0,\n toolsTriggered: 0,\n toolCallCount: 0,\n };\n\n let passed = 0;\n for (const r of this.allResults) {\n if (r.pass) passed++;\n\n const datasetName = r.datasetName || 'Unknown Dataset';\n datasetBreakdown[datasetName] = (datasetBreakdown[datasetName] || 0) + 1;\n\n if (r.expectations.exact) expectationBreakdown.exact++;\n if (r.expectations.schema) expectationBreakdown.schema++;\n if (r.expectations.textContains) expectationBreakdown.textContains++;\n if (r.expectations.regex) expectationBreakdown.regex++;\n if (r.expectations.snapshot) expectationBreakdown.snapshot++;\n if (r.expectations.judge) expectationBreakdown.judge++;\n if (r.expectations.error) expectationBreakdown.error++;\n if (r.expectations.size) expectationBreakdown.size++;\n if (r.expectations.toolsTriggered) expectationBreakdown.toolsTriggered++;\n if (r.expectations.toolCallCount) expectationBreakdown.toolCallCount++;\n }\n\n const failed = total - passed;\n\n const totalHostUsage = this.allResults.reduce(\n (acc, r) => sumUsage(acc, r.hostUsage),\n undefined as UsageMetrics | undefined\n );\n\n return {\n timestamp: new Date().toISOString(),\n durationMs,\n environment: {\n ci: !!process.env.CI,\n node: process.version,\n platform: process.platform,\n },\n metrics: {\n total,\n passed,\n failed,\n passRate: passed / total,\n datasetBreakdown,\n expectationBreakdown,\n totalHostUsage,\n },\n results: this.allResults,\n conformanceChecks:\n this.conformanceChecks.length > 0 ? this.conformanceChecks : undefined,\n serverCapabilities:\n this.serverCapabilities.length > 0\n ? this.serverCapabilities\n : undefined,\n };\n }\n\n private async loadHistoricalData(): Promise<Array<MCPEvalHistoricalSummary>> {\n try {\n const files = await readdir(this.config.outputDir);\n const runFiles = files\n .filter((f) => f.startsWith('run-') && f.endsWith('.json'))\n .sort()\n .slice(-(this.config.historyLimit - 1)); // Keep most recent, leave room for current run\n\n const historical: Array<MCPEvalHistoricalSummary> = [];\n\n for (const file of runFiles) {\n try {\n const content = await readFile(\n join(this.config.outputDir, file),\n 'utf-8'\n );\n const runData = JSON.parse(content) as MCPEvalRunData;\n\n historical.push({\n timestamp: runData.timestamp,\n total: runData.metrics.total,\n passed: runData.metrics.passed,\n failed: runData.metrics.failed,\n passRate: runData.metrics.passRate,\n durationMs: runData.durationMs,\n });\n } catch (error) {\n this.logError(`[MCP Reporter] Failed to load ${file}:`, error);\n }\n }\n\n return historical;\n } catch {\n return [];\n }\n }\n\n private async saveRunData(runData: MCPEvalRunData): Promise<void> {\n const filename = `run-${runData.timestamp.replace(/:/g, '-')}.json`;\n const filepath = join(this.config.outputDir, filename);\n\n await writeFile(filepath, JSON.stringify(runData, null, 2), 'utf-8');\n }\n\n private async cleanupOldRuns(): Promise<void> {\n try {\n const files = await readdir(this.config.outputDir);\n const runFiles = files\n .filter((f) => f.startsWith('run-') && f.endsWith('.json'))\n .sort()\n .reverse();\n\n // Keep only historyLimit most recent runs\n const toDelete = runFiles.slice(this.config.historyLimit);\n\n for (const file of toDelete) {\n await unlink(join(this.config.outputDir, file));\n }\n } catch (error) {\n this.logError('[MCP Reporter] Failed to cleanup old runs:', error);\n }\n }\n\n private async openReport(reportPath: string): Promise<void> {\n try {\n // Dynamic import to avoid bundling issues\n const { default: open } = await import('open');\n const absolutePath = resolve(reportPath);\n\n await open(absolutePath);\n this.log('[MCP Reporter] Opened report in browser');\n } catch (error) {\n this.logError('[MCP Reporter] Failed to open report:', error);\n this.log(`[MCP Reporter] Open manually: file://${resolve(reportPath)}`);\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reporter types - re-exported from canonical source
|
|
5
|
+
*
|
|
6
|
+
* This module re-exports types from the canonical types module for backwards compatibility.
|
|
7
|
+
* All type definitions now live in src/types/.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Configuration options for MCP Eval Reporter
|
|
14
|
+
*/
|
|
15
|
+
interface MCPEvalReporterConfig {
|
|
16
|
+
/**
|
|
17
|
+
* Output directory for reports and historical data
|
|
18
|
+
* @default '.mcp-test-results'
|
|
19
|
+
*/
|
|
20
|
+
outputDir?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Auto-open report in browser after test run
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
autoOpen?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Number of historical runs to keep
|
|
28
|
+
* @default 10
|
|
29
|
+
*/
|
|
30
|
+
historyLimit?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Suppress console output (report still generated)
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
quiet?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Include auto-tracked MCP tool calls from tests without explicit eval results.
|
|
38
|
+
* When true, any test using the MCP fixture will have its tool calls
|
|
39
|
+
* included in the report, even without using runEvalCase/runEvalDataset.
|
|
40
|
+
* When false, only tests with explicit eval results are included.
|
|
41
|
+
* @default true
|
|
42
|
+
*/
|
|
43
|
+
includeAutoTracking?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Custom Playwright reporter for MCP eval results
|
|
48
|
+
*
|
|
49
|
+
* Generates HTML reports with historical tracking
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // playwright.config.ts
|
|
54
|
+
* export default defineConfig({
|
|
55
|
+
* reporter: [
|
|
56
|
+
* ['@gleanwork/mcp-server-tester/reporters/mcpReporter', {
|
|
57
|
+
* outputDir: '.mcp-test-results',
|
|
58
|
+
* historyLimit: 10
|
|
59
|
+
* }]
|
|
60
|
+
* ]
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare class MCPReporter implements Reporter {
|
|
65
|
+
private config;
|
|
66
|
+
private startTime;
|
|
67
|
+
private allResults;
|
|
68
|
+
private conformanceChecks;
|
|
69
|
+
private serverCapabilities;
|
|
70
|
+
constructor(options?: MCPEvalReporterConfig);
|
|
71
|
+
private log;
|
|
72
|
+
private logError;
|
|
73
|
+
/**
|
|
74
|
+
* Reads attachment content from either in-memory body (Playwright < 1.43)
|
|
75
|
+
* or from the file path on disk (Playwright ≥ 1.43, which writes large
|
|
76
|
+
* attachments to disk and exposes path instead of body).
|
|
77
|
+
*/
|
|
78
|
+
private getAttachmentContent;
|
|
79
|
+
onBegin(_config: FullConfig, _suite: Suite): Promise<void>;
|
|
80
|
+
onTestEnd(test: TestCase, result: TestResult): Promise<void>;
|
|
81
|
+
onEnd(_result: FullResult): Promise<void>;
|
|
82
|
+
private generateReport;
|
|
83
|
+
private buildRunData;
|
|
84
|
+
private loadHistoricalData;
|
|
85
|
+
private saveRunData;
|
|
86
|
+
private cleanupOldRuns;
|
|
87
|
+
private openReport;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { MCPReporter as default };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reporter types - re-exported from canonical source
|
|
5
|
+
*
|
|
6
|
+
* This module re-exports types from the canonical types module for backwards compatibility.
|
|
7
|
+
* All type definitions now live in src/types/.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Configuration options for MCP Eval Reporter
|
|
14
|
+
*/
|
|
15
|
+
interface MCPEvalReporterConfig {
|
|
16
|
+
/**
|
|
17
|
+
* Output directory for reports and historical data
|
|
18
|
+
* @default '.mcp-test-results'
|
|
19
|
+
*/
|
|
20
|
+
outputDir?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Auto-open report in browser after test run
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
autoOpen?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Number of historical runs to keep
|
|
28
|
+
* @default 10
|
|
29
|
+
*/
|
|
30
|
+
historyLimit?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Suppress console output (report still generated)
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
quiet?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Include auto-tracked MCP tool calls from tests without explicit eval results.
|
|
38
|
+
* When true, any test using the MCP fixture will have its tool calls
|
|
39
|
+
* included in the report, even without using runEvalCase/runEvalDataset.
|
|
40
|
+
* When false, only tests with explicit eval results are included.
|
|
41
|
+
* @default true
|
|
42
|
+
*/
|
|
43
|
+
includeAutoTracking?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Custom Playwright reporter for MCP eval results
|
|
48
|
+
*
|
|
49
|
+
* Generates HTML reports with historical tracking
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // playwright.config.ts
|
|
54
|
+
* export default defineConfig({
|
|
55
|
+
* reporter: [
|
|
56
|
+
* ['@gleanwork/mcp-server-tester/reporters/mcpReporter', {
|
|
57
|
+
* outputDir: '.mcp-test-results',
|
|
58
|
+
* historyLimit: 10
|
|
59
|
+
* }]
|
|
60
|
+
* ]
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare class MCPReporter implements Reporter {
|
|
65
|
+
private config;
|
|
66
|
+
private startTime;
|
|
67
|
+
private allResults;
|
|
68
|
+
private conformanceChecks;
|
|
69
|
+
private serverCapabilities;
|
|
70
|
+
constructor(options?: MCPEvalReporterConfig);
|
|
71
|
+
private log;
|
|
72
|
+
private logError;
|
|
73
|
+
/**
|
|
74
|
+
* Reads attachment content from either in-memory body (Playwright < 1.43)
|
|
75
|
+
* or from the file path on disk (Playwright ≥ 1.43, which writes large
|
|
76
|
+
* attachments to disk and exposes path instead of body).
|
|
77
|
+
*/
|
|
78
|
+
private getAttachmentContent;
|
|
79
|
+
onBegin(_config: FullConfig, _suite: Suite): Promise<void>;
|
|
80
|
+
onTestEnd(test: TestCase, result: TestResult): Promise<void>;
|
|
81
|
+
onEnd(_result: FullResult): Promise<void>;
|
|
82
|
+
private generateReport;
|
|
83
|
+
private buildRunData;
|
|
84
|
+
private loadHistoricalData;
|
|
85
|
+
private saveRunData;
|
|
86
|
+
private cleanupOldRuns;
|
|
87
|
+
private openReport;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { MCPReporter as default };
|
|
@@ -2,6 +2,34 @@ import { readFile, mkdir, cp, writeFile, readdir, unlink } from 'fs/promises';
|
|
|
2
2
|
import { join, dirname, resolve } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
|
|
5
|
+
// src/reporters/mcpReporter.ts
|
|
6
|
+
|
|
7
|
+
// src/utils/usageUtils.ts
|
|
8
|
+
function optionalSum(a, b) {
|
|
9
|
+
if (a === void 0 && b === void 0) return void 0;
|
|
10
|
+
return (a ?? 0) + (b ?? 0);
|
|
11
|
+
}
|
|
12
|
+
function sumUsage(a, b) {
|
|
13
|
+
if (!a && !b) return void 0;
|
|
14
|
+
if (!a) return b ? { ...b } : void 0;
|
|
15
|
+
if (!b) return { ...a };
|
|
16
|
+
return {
|
|
17
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
18
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
19
|
+
totalCostUsd: a.totalCostUsd + b.totalCostUsd,
|
|
20
|
+
durationMs: a.durationMs + b.durationMs,
|
|
21
|
+
durationApiMs: optionalSum(a.durationApiMs, b.durationApiMs),
|
|
22
|
+
cacheReadInputTokens: optionalSum(
|
|
23
|
+
a.cacheReadInputTokens,
|
|
24
|
+
b.cacheReadInputTokens
|
|
25
|
+
),
|
|
26
|
+
cacheCreationInputTokens: optionalSum(
|
|
27
|
+
a.cacheCreationInputTokens,
|
|
28
|
+
b.cacheCreationInputTokens
|
|
29
|
+
)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
5
33
|
// src/reporters/mcpReporter.ts
|
|
6
34
|
var MCPReporter = class {
|
|
7
35
|
config;
|
|
@@ -231,6 +259,10 @@ var MCPReporter = class {
|
|
|
231
259
|
if (r.expectations.toolCallCount) expectationBreakdown.toolCallCount++;
|
|
232
260
|
}
|
|
233
261
|
const failed = total - passed;
|
|
262
|
+
const totalHostUsage = this.allResults.reduce(
|
|
263
|
+
(acc, r) => sumUsage(acc, r.hostUsage),
|
|
264
|
+
void 0
|
|
265
|
+
);
|
|
234
266
|
return {
|
|
235
267
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
236
268
|
durationMs,
|
|
@@ -245,7 +277,8 @@ var MCPReporter = class {
|
|
|
245
277
|
failed,
|
|
246
278
|
passRate: passed / total,
|
|
247
279
|
datasetBreakdown,
|
|
248
|
-
expectationBreakdown
|
|
280
|
+
expectationBreakdown,
|
|
281
|
+
totalHostUsage
|
|
249
282
|
},
|
|
250
283
|
results: this.allResults,
|
|
251
284
|
conformanceChecks: this.conformanceChecks.length > 0 ? this.conformanceChecks : void 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/reporters/mcpReporter.ts"],"names":["__filename","__dirname"],"mappings":"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","file":"mcpReporter.js","sourcesContent":["import type {\n Reporter,\n FullConfig,\n Suite,\n TestCase,\n TestResult,\n FullResult,\n} from '@playwright/test/reporter';\nimport { mkdir, writeFile, readdir, readFile, unlink, cp } from 'fs/promises';\nimport { join, resolve, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport type { MCPEvalReporterConfig } from './types.js';\nimport type { AuthType } from '../types/index.js';\nimport type {\n MCPEvalRunData,\n MCPEvalHistoricalSummary,\n MCPConformanceResultData,\n MCPServerCapabilitiesData,\n EvalCaseResult,\n MCPConformanceCheck,\n} from '../types/reporter.js';\n\n/**\n * Custom Playwright reporter for MCP eval results\n *\n * Generates HTML reports with historical tracking\n *\n * @example\n * ```typescript\n * // playwright.config.ts\n * export default defineConfig({\n * reporter: [\n * ['@gleanwork/mcp-server-tester/reporters/mcpReporter', {\n * outputDir: '.mcp-test-results',\n * historyLimit: 10\n * }]\n * ]\n * });\n * ```\n */\nexport default class MCPReporter implements Reporter {\n private config: Required<MCPEvalReporterConfig>;\n private startTime: number = 0;\n private allResults: Array<EvalCaseResult> = [];\n private conformanceChecks: Array<MCPConformanceResultData> = [];\n private serverCapabilities: Array<MCPServerCapabilitiesData> = [];\n\n constructor(options: MCPEvalReporterConfig = {}) {\n this.config = {\n outputDir: options.outputDir ?? '.mcp-test-results',\n autoOpen: options.autoOpen ?? false,\n historyLimit: options.historyLimit ?? 10,\n quiet: options.quiet ?? false,\n includeAutoTracking: options.includeAutoTracking ?? true,\n };\n }\n\n private log(message: string): void {\n if (!this.config.quiet) {\n console.log(message);\n }\n }\n\n private logError(message: string, error?: unknown): void {\n if (!this.config.quiet) {\n console.error(message, error ?? '');\n }\n }\n\n /**\n * Reads attachment content from either in-memory body (Playwright < 1.43)\n * or from the file path on disk (Playwright ≥ 1.43, which writes large\n * attachments to disk and exposes path instead of body).\n */\n private async getAttachmentContent(attachment: {\n body?: Buffer;\n path?: string;\n }): Promise<string | null> {\n if (attachment.body) return attachment.body.toString('utf-8');\n if (attachment.path) {\n try {\n return await readFile(attachment.path, 'utf-8');\n } catch {\n return null;\n }\n }\n return null;\n }\n\n async onBegin(_config: FullConfig, _suite: Suite): Promise<void> {\n this.startTime = Date.now();\n this.allResults = [];\n this.conformanceChecks = [];\n this.serverCapabilities = [];\n\n // Ensure output directory exists\n await mkdir(this.config.outputDir, { recursive: true });\n }\n\n async onTestEnd(test: TestCase, result: TestResult): Promise<void> {\n // Strategy 1: Extract MCP eval results from runEvalDataset() attachments\n const evalAttachment = result.attachments.find(\n (a) =>\n a.name === 'mcp-test-results' && a.contentType === 'application/json'\n );\n\n let hasEvalDataset = false;\n\n const evalContent = evalAttachment\n ? await this.getAttachmentContent(evalAttachment)\n : null;\n if (evalContent) {\n try {\n const evalResults = JSON.parse(evalContent) as {\n caseResults: Array<EvalCaseResult>;\n };\n\n // Trust the data from the attachment - evalRunner now includes\n // authType and project from the mcp fixture (Playwright is source of truth)\n this.allResults.push(...evalResults.caseResults);\n hasEvalDataset = true;\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse eval results from test \"${test.title}\":`,\n error\n );\n }\n }\n\n // Strategy 2: Extract conformance check results\n // These are created by runConformanceChecks() when testInfo is passed\n const conformanceAttachment = result.attachments.find(\n (a) =>\n a.name === 'mcp-conformance-checks' &&\n a.contentType === 'application/json'\n );\n\n const conformanceContent = conformanceAttachment\n ? await this.getAttachmentContent(conformanceAttachment)\n : null;\n if (conformanceContent) {\n try {\n const conformanceData = JSON.parse(conformanceContent) as {\n operation: string;\n pass: boolean;\n checks: MCPConformanceCheck[];\n serverInfo?: { name?: string; version?: string };\n toolCount: number;\n authType?: AuthType;\n project?: string;\n };\n\n // Only push if checks array is valid\n if (Array.isArray(conformanceData.checks)) {\n this.conformanceChecks.push({\n testTitle: test.title,\n pass: conformanceData.pass,\n checks: conformanceData.checks,\n serverInfo: conformanceData.serverInfo,\n toolCount: conformanceData.toolCount,\n authType: conformanceData.authType,\n project: conformanceData.project,\n });\n }\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse conformance check attachment for \"${test.title}\":`,\n error\n );\n }\n }\n\n // Strategy 2b: Extract server capabilities from mcp-list-tools attachments\n // These are created by createMCPFixture().listTools()\n const listToolsAttachment = result.attachments.find(\n (a) => a.name === 'mcp-list-tools' && a.contentType === 'application/json'\n );\n\n const listToolsContent = listToolsAttachment\n ? await this.getAttachmentContent(listToolsAttachment)\n : null;\n if (listToolsContent) {\n try {\n const listToolsData = JSON.parse(listToolsContent) as {\n operation: string;\n toolCount: number;\n tools: Array<{ name: string; description?: string }>;\n };\n\n // Only push if tools array is valid\n if (Array.isArray(listToolsData.tools)) {\n this.serverCapabilities.push({\n testTitle: test.title,\n tools: listToolsData.tools,\n toolCount: listToolsData.toolCount,\n // Note: authType and project are available from the mcp fixture\n // but not currently included in the listTools attachment\n });\n }\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse mcp-list-tools attachment for \"${test.title}\":`,\n error\n );\n }\n }\n\n // Strategy 3: Extract MCP tool calls from auto-tracking attachments\n // These are created by createMCPFixture()\n // Skip if:\n // - This test already has eval dataset results (to avoid duplicates)\n // - Auto-tracking is disabled in config\n if (hasEvalDataset || !this.config.includeAutoTracking) {\n return;\n }\n\n const mcpCallAttachments = result.attachments.filter(\n (a) =>\n a.name &&\n a.name.startsWith('mcp-call-') &&\n a.contentType === 'application/json'\n );\n\n for (const attachment of mcpCallAttachments) {\n const callContent = await this.getAttachmentContent(attachment);\n if (!callContent) continue;\n\n try {\n // Attachment now includes authType and project from the mcp fixture\n const callData = JSON.parse(callContent) as {\n operation: string;\n toolName: string;\n args: Record<string, unknown>;\n result: unknown;\n durationMs: number;\n isError: boolean;\n authType?: AuthType;\n project?: string;\n };\n\n const suiteName = test.parent?.title || 'Uncategorized Tests';\n const testPassed = result.status === 'passed';\n\n const syntheticResult: EvalCaseResult = {\n id: test.title,\n datasetName: suiteName,\n toolName: callData.toolName,\n source: 'test',\n pass: testPassed,\n response: callData.result,\n error: !testPassed ? 'Test failed' : undefined,\n expectations: {},\n authType: callData.authType,\n project: callData.project,\n durationMs: callData.durationMs,\n };\n\n this.allResults.push(syntheticResult);\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse MCP call attachment \"${attachment.name}\":`,\n error\n );\n }\n }\n }\n\n async onEnd(_result: FullResult): Promise<void> {\n const endTime = Date.now();\n const durationMs = endTime - this.startTime;\n\n // Skip if no eval results collected\n if (this.allResults.length === 0) {\n this.log('[MCP Reporter] No MCP eval results found in test run');\n return;\n }\n\n // Build run data\n const runData = this.buildRunData(durationMs);\n\n // Load historical data\n const historical = await this.loadHistoricalData();\n\n // Add current run to historical\n historical.push({\n timestamp: runData.timestamp,\n total: runData.metrics.total,\n passed: runData.metrics.passed,\n failed: runData.metrics.failed,\n passRate: runData.metrics.passRate,\n durationMs: runData.durationMs,\n });\n\n // Save current run data\n await this.saveRunData(runData);\n\n // Clean up old runs\n await this.cleanupOldRuns();\n\n // Generate report using copy + inject pattern\n const reportDir = join(this.config.outputDir, 'latest');\n await this.generateReport(runData, historical, reportDir);\n\n const reportPath = join(reportDir, 'index.html');\n this.log(`\\n[MCP Reporter] Report generated: ${reportPath}`);\n this.log(\n `[MCP Reporter] Results: ${runData.metrics.passed}/${runData.metrics.total} passed (${(runData.metrics.passRate * 100).toFixed(1)}%)`\n );\n\n // Auto-open browser if configured and not in CI\n if (this.config.autoOpen && !process.env.CI) {\n await this.openReport(reportPath);\n }\n }\n\n private async generateReport(\n runData: MCPEvalRunData,\n historical: Array<MCPEvalHistoricalSummary>,\n outputDir: string\n ): Promise<void> {\n // Get the UI dist path (relative to this file)\n // In ESM, we need to use import.meta.url\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const uiDistPath = join(__dirname, 'ui-dist');\n\n // Step 1: Copy pre-built UI template\n await mkdir(outputDir, { recursive: true });\n await cp(uiDistPath, outputDir, { recursive: true, force: true });\n\n // Step 2: Inject test data as JavaScript\n const dataScript = `window.MCP_EVAL_DATA = ${JSON.stringify(\n {\n runData,\n historical,\n },\n null,\n 2\n )};`;\n\n await writeFile(join(outputDir, 'data.js'), dataScript, 'utf-8');\n }\n\n private buildRunData(durationMs: number): MCPEvalRunData {\n const total = this.allResults.length;\n const datasetBreakdown: Record<string, number> = {};\n const expectationBreakdown = {\n exact: 0,\n schema: 0,\n textContains: 0,\n regex: 0,\n snapshot: 0,\n judge: 0,\n error: 0,\n size: 0,\n toolsTriggered: 0,\n toolCallCount: 0,\n };\n\n let passed = 0;\n for (const r of this.allResults) {\n if (r.pass) passed++;\n\n const datasetName = r.datasetName || 'Unknown Dataset';\n datasetBreakdown[datasetName] = (datasetBreakdown[datasetName] || 0) + 1;\n\n if (r.expectations.exact) expectationBreakdown.exact++;\n if (r.expectations.schema) expectationBreakdown.schema++;\n if (r.expectations.textContains) expectationBreakdown.textContains++;\n if (r.expectations.regex) expectationBreakdown.regex++;\n if (r.expectations.snapshot) expectationBreakdown.snapshot++;\n if (r.expectations.judge) expectationBreakdown.judge++;\n if (r.expectations.error) expectationBreakdown.error++;\n if (r.expectations.size) expectationBreakdown.size++;\n if (r.expectations.toolsTriggered) expectationBreakdown.toolsTriggered++;\n if (r.expectations.toolCallCount) expectationBreakdown.toolCallCount++;\n }\n\n const failed = total - passed;\n\n return {\n timestamp: new Date().toISOString(),\n durationMs,\n environment: {\n ci: !!process.env.CI,\n node: process.version,\n platform: process.platform,\n },\n metrics: {\n total,\n passed,\n failed,\n passRate: passed / total,\n datasetBreakdown,\n expectationBreakdown,\n },\n results: this.allResults,\n conformanceChecks:\n this.conformanceChecks.length > 0 ? this.conformanceChecks : undefined,\n serverCapabilities:\n this.serverCapabilities.length > 0\n ? this.serverCapabilities\n : undefined,\n };\n }\n\n private async loadHistoricalData(): Promise<Array<MCPEvalHistoricalSummary>> {\n try {\n const files = await readdir(this.config.outputDir);\n const runFiles = files\n .filter((f) => f.startsWith('run-') && f.endsWith('.json'))\n .sort()\n .slice(-(this.config.historyLimit - 1)); // Keep most recent, leave room for current run\n\n const historical: Array<MCPEvalHistoricalSummary> = [];\n\n for (const file of runFiles) {\n try {\n const content = await readFile(\n join(this.config.outputDir, file),\n 'utf-8'\n );\n const runData = JSON.parse(content) as MCPEvalRunData;\n\n historical.push({\n timestamp: runData.timestamp,\n total: runData.metrics.total,\n passed: runData.metrics.passed,\n failed: runData.metrics.failed,\n passRate: runData.metrics.passRate,\n durationMs: runData.durationMs,\n });\n } catch (error) {\n this.logError(`[MCP Reporter] Failed to load ${file}:`, error);\n }\n }\n\n return historical;\n } catch {\n return [];\n }\n }\n\n private async saveRunData(runData: MCPEvalRunData): Promise<void> {\n const filename = `run-${runData.timestamp.replace(/:/g, '-')}.json`;\n const filepath = join(this.config.outputDir, filename);\n\n await writeFile(filepath, JSON.stringify(runData, null, 2), 'utf-8');\n }\n\n private async cleanupOldRuns(): Promise<void> {\n try {\n const files = await readdir(this.config.outputDir);\n const runFiles = files\n .filter((f) => f.startsWith('run-') && f.endsWith('.json'))\n .sort()\n .reverse();\n\n // Keep only historyLimit most recent runs\n const toDelete = runFiles.slice(this.config.historyLimit);\n\n for (const file of toDelete) {\n await unlink(join(this.config.outputDir, file));\n }\n } catch (error) {\n this.logError('[MCP Reporter] Failed to cleanup old runs:', error);\n }\n }\n\n private async openReport(reportPath: string): Promise<void> {\n try {\n // Dynamic import to avoid bundling issues\n const { default: open } = await import('open');\n const absolutePath = resolve(reportPath);\n\n await open(absolutePath);\n this.log('[MCP Reporter] Opened report in browser');\n } catch (error) {\n this.logError('[MCP Reporter] Failed to open report:', error);\n this.log(`[MCP Reporter] Open manually: file://${resolve(reportPath)}`);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils/usageUtils.ts","../../src/reporters/mcpReporter.ts"],"names":["__filename","__dirname"],"mappings":"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","file":"mcpReporter.js","sourcesContent":["import type { UsageMetrics } from '../types/index.js';\n\nfunction optionalSum(a?: number, b?: number): number | undefined {\n if (a === undefined && b === undefined) return undefined;\n return (a ?? 0) + (b ?? 0);\n}\n\nexport function sumUsage(\n a: UsageMetrics | undefined,\n b: UsageMetrics | undefined\n): UsageMetrics | undefined {\n if (!a && !b) return undefined;\n if (!a) return b ? { ...b } : undefined;\n if (!b) return { ...a };\n return {\n inputTokens: a.inputTokens + b.inputTokens,\n outputTokens: a.outputTokens + b.outputTokens,\n totalCostUsd: a.totalCostUsd + b.totalCostUsd,\n durationMs: a.durationMs + b.durationMs,\n durationApiMs: optionalSum(a.durationApiMs, b.durationApiMs),\n cacheReadInputTokens: optionalSum(\n a.cacheReadInputTokens,\n b.cacheReadInputTokens\n ),\n cacheCreationInputTokens: optionalSum(\n a.cacheCreationInputTokens,\n b.cacheCreationInputTokens\n ),\n };\n}\n","import type {\n Reporter,\n FullConfig,\n Suite,\n TestCase,\n TestResult,\n FullResult,\n} from '@playwright/test/reporter';\nimport { mkdir, writeFile, readdir, readFile, unlink, cp } from 'fs/promises';\nimport { join, resolve, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport type { MCPEvalReporterConfig } from './types.js';\nimport type { AuthType } from '../types/index.js';\nimport type {\n MCPEvalRunData,\n MCPEvalHistoricalSummary,\n MCPConformanceResultData,\n MCPServerCapabilitiesData,\n EvalCaseResult,\n MCPConformanceCheck,\n} from '../types/reporter.js';\nimport type { UsageMetrics } from '../types/index.js';\nimport { sumUsage } from '../utils/usageUtils.js';\n\n/**\n * Custom Playwright reporter for MCP eval results\n *\n * Generates HTML reports with historical tracking\n *\n * @example\n * ```typescript\n * // playwright.config.ts\n * export default defineConfig({\n * reporter: [\n * ['@gleanwork/mcp-server-tester/reporters/mcpReporter', {\n * outputDir: '.mcp-test-results',\n * historyLimit: 10\n * }]\n * ]\n * });\n * ```\n */\nexport default class MCPReporter implements Reporter {\n private config: Required<MCPEvalReporterConfig>;\n private startTime: number = 0;\n private allResults: Array<EvalCaseResult> = [];\n private conformanceChecks: Array<MCPConformanceResultData> = [];\n private serverCapabilities: Array<MCPServerCapabilitiesData> = [];\n\n constructor(options: MCPEvalReporterConfig = {}) {\n this.config = {\n outputDir: options.outputDir ?? '.mcp-test-results',\n autoOpen: options.autoOpen ?? false,\n historyLimit: options.historyLimit ?? 10,\n quiet: options.quiet ?? false,\n includeAutoTracking: options.includeAutoTracking ?? true,\n };\n }\n\n private log(message: string): void {\n if (!this.config.quiet) {\n console.log(message);\n }\n }\n\n private logError(message: string, error?: unknown): void {\n if (!this.config.quiet) {\n console.error(message, error ?? '');\n }\n }\n\n /**\n * Reads attachment content from either in-memory body (Playwright < 1.43)\n * or from the file path on disk (Playwright ≥ 1.43, which writes large\n * attachments to disk and exposes path instead of body).\n */\n private async getAttachmentContent(attachment: {\n body?: Buffer;\n path?: string;\n }): Promise<string | null> {\n if (attachment.body) return attachment.body.toString('utf-8');\n if (attachment.path) {\n try {\n return await readFile(attachment.path, 'utf-8');\n } catch {\n return null;\n }\n }\n return null;\n }\n\n async onBegin(_config: FullConfig, _suite: Suite): Promise<void> {\n this.startTime = Date.now();\n this.allResults = [];\n this.conformanceChecks = [];\n this.serverCapabilities = [];\n\n // Ensure output directory exists\n await mkdir(this.config.outputDir, { recursive: true });\n }\n\n async onTestEnd(test: TestCase, result: TestResult): Promise<void> {\n // Strategy 1: Extract MCP eval results from runEvalDataset() attachments\n const evalAttachment = result.attachments.find(\n (a) =>\n a.name === 'mcp-test-results' && a.contentType === 'application/json'\n );\n\n let hasEvalDataset = false;\n\n const evalContent = evalAttachment\n ? await this.getAttachmentContent(evalAttachment)\n : null;\n if (evalContent) {\n try {\n const evalResults = JSON.parse(evalContent) as {\n caseResults: Array<EvalCaseResult>;\n };\n\n // Trust the data from the attachment - evalRunner now includes\n // authType and project from the mcp fixture (Playwright is source of truth)\n this.allResults.push(...evalResults.caseResults);\n hasEvalDataset = true;\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse eval results from test \"${test.title}\":`,\n error\n );\n }\n }\n\n // Strategy 2: Extract conformance check results\n // These are created by runConformanceChecks() when testInfo is passed\n const conformanceAttachment = result.attachments.find(\n (a) =>\n a.name === 'mcp-conformance-checks' &&\n a.contentType === 'application/json'\n );\n\n const conformanceContent = conformanceAttachment\n ? await this.getAttachmentContent(conformanceAttachment)\n : null;\n if (conformanceContent) {\n try {\n const conformanceData = JSON.parse(conformanceContent) as {\n operation: string;\n pass: boolean;\n checks: MCPConformanceCheck[];\n serverInfo?: { name?: string; version?: string };\n toolCount: number;\n authType?: AuthType;\n project?: string;\n };\n\n // Only push if checks array is valid\n if (Array.isArray(conformanceData.checks)) {\n this.conformanceChecks.push({\n testTitle: test.title,\n pass: conformanceData.pass,\n checks: conformanceData.checks,\n serverInfo: conformanceData.serverInfo,\n toolCount: conformanceData.toolCount,\n authType: conformanceData.authType,\n project: conformanceData.project,\n });\n }\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse conformance check attachment for \"${test.title}\":`,\n error\n );\n }\n }\n\n // Strategy 2b: Extract server capabilities from mcp-list-tools attachments\n // These are created by createMCPFixture().listTools()\n const listToolsAttachment = result.attachments.find(\n (a) => a.name === 'mcp-list-tools' && a.contentType === 'application/json'\n );\n\n const listToolsContent = listToolsAttachment\n ? await this.getAttachmentContent(listToolsAttachment)\n : null;\n if (listToolsContent) {\n try {\n const listToolsData = JSON.parse(listToolsContent) as {\n operation: string;\n toolCount: number;\n tools: Array<{ name: string; description?: string }>;\n };\n\n // Only push if tools array is valid\n if (Array.isArray(listToolsData.tools)) {\n this.serverCapabilities.push({\n testTitle: test.title,\n tools: listToolsData.tools,\n toolCount: listToolsData.toolCount,\n // Note: authType and project are available from the mcp fixture\n // but not currently included in the listTools attachment\n });\n }\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse mcp-list-tools attachment for \"${test.title}\":`,\n error\n );\n }\n }\n\n // Strategy 3: Extract MCP tool calls from auto-tracking attachments\n // These are created by createMCPFixture()\n // Skip if:\n // - This test already has eval dataset results (to avoid duplicates)\n // - Auto-tracking is disabled in config\n if (hasEvalDataset || !this.config.includeAutoTracking) {\n return;\n }\n\n const mcpCallAttachments = result.attachments.filter(\n (a) =>\n a.name &&\n a.name.startsWith('mcp-call-') &&\n a.contentType === 'application/json'\n );\n\n for (const attachment of mcpCallAttachments) {\n const callContent = await this.getAttachmentContent(attachment);\n if (!callContent) continue;\n\n try {\n // Attachment now includes authType and project from the mcp fixture\n const callData = JSON.parse(callContent) as {\n operation: string;\n toolName: string;\n args: Record<string, unknown>;\n result: unknown;\n durationMs: number;\n isError: boolean;\n authType?: AuthType;\n project?: string;\n };\n\n const suiteName = test.parent?.title || 'Uncategorized Tests';\n const testPassed = result.status === 'passed';\n\n const syntheticResult: EvalCaseResult = {\n id: test.title,\n datasetName: suiteName,\n toolName: callData.toolName,\n source: 'test',\n pass: testPassed,\n response: callData.result,\n error: !testPassed ? 'Test failed' : undefined,\n expectations: {},\n authType: callData.authType,\n project: callData.project,\n durationMs: callData.durationMs,\n };\n\n this.allResults.push(syntheticResult);\n } catch (error) {\n this.logError(\n `[MCP Reporter] Failed to parse MCP call attachment \"${attachment.name}\":`,\n error\n );\n }\n }\n }\n\n async onEnd(_result: FullResult): Promise<void> {\n const endTime = Date.now();\n const durationMs = endTime - this.startTime;\n\n // Skip if no eval results collected\n if (this.allResults.length === 0) {\n this.log('[MCP Reporter] No MCP eval results found in test run');\n return;\n }\n\n // Build run data\n const runData = this.buildRunData(durationMs);\n\n // Load historical data\n const historical = await this.loadHistoricalData();\n\n // Add current run to historical\n historical.push({\n timestamp: runData.timestamp,\n total: runData.metrics.total,\n passed: runData.metrics.passed,\n failed: runData.metrics.failed,\n passRate: runData.metrics.passRate,\n durationMs: runData.durationMs,\n });\n\n // Save current run data\n await this.saveRunData(runData);\n\n // Clean up old runs\n await this.cleanupOldRuns();\n\n // Generate report using copy + inject pattern\n const reportDir = join(this.config.outputDir, 'latest');\n await this.generateReport(runData, historical, reportDir);\n\n const reportPath = join(reportDir, 'index.html');\n this.log(`\\n[MCP Reporter] Report generated: ${reportPath}`);\n this.log(\n `[MCP Reporter] Results: ${runData.metrics.passed}/${runData.metrics.total} passed (${(runData.metrics.passRate * 100).toFixed(1)}%)`\n );\n\n // Auto-open browser if configured and not in CI\n if (this.config.autoOpen && !process.env.CI) {\n await this.openReport(reportPath);\n }\n }\n\n private async generateReport(\n runData: MCPEvalRunData,\n historical: Array<MCPEvalHistoricalSummary>,\n outputDir: string\n ): Promise<void> {\n // Get the UI dist path (relative to this file)\n // In ESM, we need to use import.meta.url\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n const uiDistPath = join(__dirname, 'ui-dist');\n\n // Step 1: Copy pre-built UI template\n await mkdir(outputDir, { recursive: true });\n await cp(uiDistPath, outputDir, { recursive: true, force: true });\n\n // Step 2: Inject test data as JavaScript\n const dataScript = `window.MCP_EVAL_DATA = ${JSON.stringify(\n {\n runData,\n historical,\n },\n null,\n 2\n )};`;\n\n await writeFile(join(outputDir, 'data.js'), dataScript, 'utf-8');\n }\n\n private buildRunData(durationMs: number): MCPEvalRunData {\n const total = this.allResults.length;\n const datasetBreakdown: Record<string, number> = {};\n const expectationBreakdown = {\n exact: 0,\n schema: 0,\n textContains: 0,\n regex: 0,\n snapshot: 0,\n judge: 0,\n error: 0,\n size: 0,\n toolsTriggered: 0,\n toolCallCount: 0,\n };\n\n let passed = 0;\n for (const r of this.allResults) {\n if (r.pass) passed++;\n\n const datasetName = r.datasetName || 'Unknown Dataset';\n datasetBreakdown[datasetName] = (datasetBreakdown[datasetName] || 0) + 1;\n\n if (r.expectations.exact) expectationBreakdown.exact++;\n if (r.expectations.schema) expectationBreakdown.schema++;\n if (r.expectations.textContains) expectationBreakdown.textContains++;\n if (r.expectations.regex) expectationBreakdown.regex++;\n if (r.expectations.snapshot) expectationBreakdown.snapshot++;\n if (r.expectations.judge) expectationBreakdown.judge++;\n if (r.expectations.error) expectationBreakdown.error++;\n if (r.expectations.size) expectationBreakdown.size++;\n if (r.expectations.toolsTriggered) expectationBreakdown.toolsTriggered++;\n if (r.expectations.toolCallCount) expectationBreakdown.toolCallCount++;\n }\n\n const failed = total - passed;\n\n const totalHostUsage = this.allResults.reduce(\n (acc, r) => sumUsage(acc, r.hostUsage),\n undefined as UsageMetrics | undefined\n );\n\n return {\n timestamp: new Date().toISOString(),\n durationMs,\n environment: {\n ci: !!process.env.CI,\n node: process.version,\n platform: process.platform,\n },\n metrics: {\n total,\n passed,\n failed,\n passRate: passed / total,\n datasetBreakdown,\n expectationBreakdown,\n totalHostUsage,\n },\n results: this.allResults,\n conformanceChecks:\n this.conformanceChecks.length > 0 ? this.conformanceChecks : undefined,\n serverCapabilities:\n this.serverCapabilities.length > 0\n ? this.serverCapabilities\n : undefined,\n };\n }\n\n private async loadHistoricalData(): Promise<Array<MCPEvalHistoricalSummary>> {\n try {\n const files = await readdir(this.config.outputDir);\n const runFiles = files\n .filter((f) => f.startsWith('run-') && f.endsWith('.json'))\n .sort()\n .slice(-(this.config.historyLimit - 1)); // Keep most recent, leave room for current run\n\n const historical: Array<MCPEvalHistoricalSummary> = [];\n\n for (const file of runFiles) {\n try {\n const content = await readFile(\n join(this.config.outputDir, file),\n 'utf-8'\n );\n const runData = JSON.parse(content) as MCPEvalRunData;\n\n historical.push({\n timestamp: runData.timestamp,\n total: runData.metrics.total,\n passed: runData.metrics.passed,\n failed: runData.metrics.failed,\n passRate: runData.metrics.passRate,\n durationMs: runData.durationMs,\n });\n } catch (error) {\n this.logError(`[MCP Reporter] Failed to load ${file}:`, error);\n }\n }\n\n return historical;\n } catch {\n return [];\n }\n }\n\n private async saveRunData(runData: MCPEvalRunData): Promise<void> {\n const filename = `run-${runData.timestamp.replace(/:/g, '-')}.json`;\n const filepath = join(this.config.outputDir, filename);\n\n await writeFile(filepath, JSON.stringify(runData, null, 2), 'utf-8');\n }\n\n private async cleanupOldRuns(): Promise<void> {\n try {\n const files = await readdir(this.config.outputDir);\n const runFiles = files\n .filter((f) => f.startsWith('run-') && f.endsWith('.json'))\n .sort()\n .reverse();\n\n // Keep only historyLimit most recent runs\n const toDelete = runFiles.slice(this.config.historyLimit);\n\n for (const file of toDelete) {\n await unlink(join(this.config.outputDir, file));\n }\n } catch (error) {\n this.logError('[MCP Reporter] Failed to cleanup old runs:', error);\n }\n }\n\n private async openReport(reportPath: string): Promise<void> {\n try {\n // Dynamic import to avoid bundling issues\n const { default: open } = await import('open');\n const absolutePath = resolve(reportPath);\n\n await open(absolutePath);\n this.log('[MCP Reporter] Opened report in browser');\n } catch (error) {\n this.logError('[MCP Reporter] Failed to open report:', error);\n this.log(`[MCP Reporter] Open manually: file://${resolve(reportPath)}`);\n }\n }\n}\n"]}
|