@gleanwork/mcp-server-tester 1.0.1 → 1.1.0

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.
@@ -1,10 +1,412 @@
1
1
  import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@playwright/test/reporter';
2
+ import { ZodType } from 'zod';
2
3
 
3
4
  /**
4
- * Reporter types - re-exported from canonical source
5
+ * Validator Types
5
6
  *
6
- * This module re-exports types from the canonical types module for backwards compatibility.
7
- * All type definitions now live in src/types/.
7
+ * Core types for the unified assertion architecture.
8
+ * These types are used by both Playwright matchers and the eval runner.
9
+ */
10
+
11
+ /**
12
+ * Options for text validation
13
+ */
14
+ interface TextValidatorOptions {
15
+ /** Whether to perform case-sensitive matching (default: true) */
16
+ caseSensitive?: boolean;
17
+ }
18
+ /**
19
+ * Options for response size validation
20
+ */
21
+ interface SizeValidatorOptions {
22
+ /** Maximum allowed size in bytes */
23
+ maxBytes?: number;
24
+ /** Minimum required size in bytes */
25
+ minBytes?: number;
26
+ }
27
+ /**
28
+ * Options for schema validation
29
+ */
30
+ interface SchemaValidatorOptions {
31
+ /** Whether to use strict mode (fail on extra properties) */
32
+ strict?: boolean;
33
+ }
34
+ /**
35
+ * Options for pattern validation
36
+ */
37
+ interface PatternValidatorOptions {
38
+ /** Whether to perform case-sensitive matching (default: true) */
39
+ caseSensitive?: boolean;
40
+ }
41
+ /**
42
+ * Built-in snapshot sanitizer names for use with toMatchToolSnapshot.
43
+ * Pass these values in the sanitizers array to replace non-deterministic
44
+ * values with stable placeholders before snapshot comparison.
45
+ *
46
+ * @example
47
+ * expect(result).toMatchToolSnapshot('my-snapshot', [
48
+ * SnapshotSanitizers.UUID,
49
+ * SnapshotSanitizers.ISO_DATE,
50
+ * ]);
51
+ */
52
+ declare const SnapshotSanitizers: {
53
+ /** Replaces Unix timestamps (seconds and milliseconds) with a stable placeholder */
54
+ readonly TIMESTAMP: "timestamp";
55
+ /** Replaces UUID v1-v5 strings with a stable placeholder */
56
+ readonly UUID: "uuid";
57
+ /** Replaces ISO 8601 date/datetime strings with a stable placeholder */
58
+ readonly ISO_DATE: "iso-date";
59
+ /** Replaces MongoDB ObjectId strings with a stable placeholder */
60
+ readonly OBJECT_ID: "objectId";
61
+ /** Replaces JWT tokens with a stable placeholder */
62
+ readonly JWT: "jwt";
63
+ };
64
+ /**
65
+ * Built-in sanitizer names for common variable patterns
66
+ */
67
+ type BuiltInSanitizer = (typeof SnapshotSanitizers)[keyof typeof SnapshotSanitizers];
68
+ /**
69
+ * Custom regex-based sanitizer
70
+ */
71
+ interface RegexSanitizer {
72
+ /** Regex pattern to match */
73
+ pattern: string | RegExp;
74
+ /** Replacement string (default: "[SANITIZED]") */
75
+ replacement?: string;
76
+ }
77
+ /**
78
+ * Field removal sanitizer - removes specified fields from objects
79
+ */
80
+ interface FieldRemovalSanitizer {
81
+ /** Field paths to remove (supports dot notation for nested fields) */
82
+ remove: string[];
83
+ }
84
+ /**
85
+ * Snapshot sanitizer configuration
86
+ *
87
+ * Sanitizers transform response data before snapshot comparison,
88
+ * allowing variable content (timestamps, IDs, etc.) to be normalized.
89
+ *
90
+ * Can be:
91
+ * - A built-in sanitizer name: 'timestamp', 'uuid', 'iso-date', 'objectId', 'jwt'
92
+ * - A regex sanitizer: { pattern: /regex/, replacement: '[REPLACED]' }
93
+ * - A field removal sanitizer: { remove: ['field1', 'nested.field'] }
94
+ */
95
+ type SnapshotSanitizer = BuiltInSanitizer | RegexSanitizer | FieldRemovalSanitizer;
96
+
97
+ /**
98
+ * Tool call validators for mcp_host simulation results.
99
+ *
100
+ * These validators extract the tool call trace from an MCPHostSimulationResult
101
+ * and apply assertions against expected call lists and counts.
102
+ */
103
+
104
+ interface ToolCallExpectation {
105
+ calls: Array<{
106
+ name: string;
107
+ arguments?: Record<string, unknown>;
108
+ required?: boolean;
109
+ }>;
110
+ order?: 'strict' | 'any';
111
+ exclusive?: boolean;
112
+ }
113
+ interface ToolCallCountOptions {
114
+ min?: number;
115
+ max?: number;
116
+ exact?: number;
117
+ }
118
+
119
+ /**
120
+ * Built-in judge rubrics matching Glean EvalV2's named judge types.
121
+ * Use these for consistent, standardized evaluations across teams.
122
+ *
123
+ * All built-in rubrics use a 5-point scale: 0.0 / 0.25 / 0.5 / 0.75 / 1.0
124
+ */
125
+ type BuiltInRubric = 'correctness' | 'completeness' | 'groundedness' | 'instruction-following' | 'conciseness';
126
+ /** A rubric specification: either a built-in named rubric or custom text. */
127
+ type RubricSpec = BuiltInRubric | {
128
+ text: string;
129
+ };
130
+
131
+ /** Valid LLM judge provider kinds. */
132
+ type ProviderKind = 'anthropic' | 'vertex-anthropic' | 'anthropic-agent-sdk' | 'openai' | 'google';
133
+
134
+ /**
135
+ * Matcher Types
136
+ *
137
+ * TypeScript declarations for custom Playwright matchers.
138
+ */
139
+
140
+ /**
141
+ * Options for the LLM judge matcher
142
+ */
143
+ interface JudgeMatcherOptions {
144
+ /** Reference response to compare against */
145
+ reference?: unknown;
146
+ /** Score threshold for passing (default: 0.7) */
147
+ passingThreshold?: number;
148
+ /** Number of judge evaluations (scores averaged) */
149
+ reps?: number;
150
+ /** Override the judge provider */
151
+ provider?: ProviderKind;
152
+ /** Override the judge model */
153
+ model?: string;
154
+ /**
155
+ * Name of a registered custom judge executor.
156
+ * When set, the named judge handles the entire evaluation pipeline
157
+ * and its `pass` result is authoritative.
158
+ */
159
+ judge?: string;
160
+ }
161
+ /**
162
+ * Declaration merging for Playwright matchers
163
+ */
164
+ declare global {
165
+ namespace PlaywrightTest {
166
+ interface Matchers<R, T = unknown> {
167
+ /**
168
+ * Validates that a response exactly matches the expected value
169
+ *
170
+ * @param expected - The expected response value
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * expect(result).toMatchToolResponse({ status: 'ok', count: 42 });
175
+ * ```
176
+ */
177
+ toMatchToolResponse(expected: unknown): R;
178
+ /**
179
+ * Validates that a response matches a Zod schema
180
+ *
181
+ * @param schema - The Zod schema to validate against
182
+ * @param options - Validation options
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * const WeatherSchema = z.object({
187
+ * temperature: z.number(),
188
+ * conditions: z.string(),
189
+ * });
190
+ * expect(result).toMatchToolSchema(WeatherSchema);
191
+ * ```
192
+ */
193
+ toMatchToolSchema(schema: ZodType, options?: SchemaValidatorOptions): R;
194
+ /**
195
+ * Validates that a response contains expected text substrings
196
+ *
197
+ * @param expected - Expected substring(s) to find
198
+ * @param options - Validation options
199
+ *
200
+ * @example
201
+ * ```typescript
202
+ * expect(result).toContainToolText('temperature');
203
+ * expect(result).toContainToolText(['temperature', 'conditions']);
204
+ * expect(result).toContainToolText('HELLO', { caseSensitive: false });
205
+ * ```
206
+ */
207
+ toContainToolText(expected: string | string[], options?: TextValidatorOptions): R;
208
+ /**
209
+ * Validates that a response matches regex patterns
210
+ *
211
+ * @param patterns - Expected pattern(s) to match
212
+ * @param options - Validation options
213
+ *
214
+ * @example
215
+ * ```typescript
216
+ * expect(result).toMatchToolPattern(/temperature: \d+/);
217
+ * expect(result).toMatchToolPattern(['temp: \\d+', 'humidity: \\d+%']);
218
+ * ```
219
+ */
220
+ toMatchToolPattern(patterns: string | RegExp | (string | RegExp)[], options?: PatternValidatorOptions): R;
221
+ /**
222
+ * Validates that a response matches a saved snapshot
223
+ *
224
+ * @param name - Snapshot name
225
+ * @param sanitizers - Optional sanitizers for non-deterministic values
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * expect(result).toMatchToolSnapshot('weather-response');
230
+ * expect(result).toMatchToolSnapshot('user-data', [
231
+ * { pattern: /\d{4}-\d{2}-\d{2}/, replacement: '[DATE]' },
232
+ * ]);
233
+ * ```
234
+ */
235
+ toMatchToolSnapshot(name: string, sanitizers?: SnapshotSanitizer[]): Promise<R>;
236
+ /**
237
+ * Validates that a response is (or is not) an error
238
+ *
239
+ * @param expected - What to expect (true for error, false for success, string for specific message)
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * expect(result).toBeToolError(); // Expects any error
244
+ * expect(result).not.toBeToolError(); // Expects success
245
+ * expect(result).toBeToolError('File not found'); // Expects specific error
246
+ * ```
247
+ */
248
+ toBeToolError(expected?: boolean | string | string[]): R;
249
+ /**
250
+ * Validates that a response passes LLM-as-judge evaluation.
251
+ *
252
+ * Two call signatures:
253
+ * - With rubric: `toPassToolJudge(rubric, options?)` — built-in LLM judge
254
+ * - With named judge: `toPassToolJudge({ judge: 'name' })` — custom judge executor
255
+ *
256
+ * @example
257
+ * ```typescript
258
+ * // Built-in LLM judge with rubric
259
+ * expect(result).toPassToolJudge('Response should be helpful and accurate');
260
+ * expect(result).toPassToolJudge('correctness', {
261
+ * reference: expectedOutput,
262
+ * passingThreshold: 0.8,
263
+ * });
264
+ *
265
+ * // Named custom judge (registered via registerJudge)
266
+ * expect(result).toPassToolJudge({ judge: 'glean-completeness' });
267
+ * ```
268
+ */
269
+ toPassToolJudge(rubric: RubricSpec, options?: JudgeMatcherOptions): Promise<R>;
270
+ toPassToolJudge(options: JudgeMatcherOptions): Promise<R>;
271
+ toPassToolJudge(judges: Array<JudgeMatcherOptions & {
272
+ rubric?: RubricSpec;
273
+ }>): Promise<R>;
274
+ /**
275
+ * Validates that a response meets size constraints
276
+ *
277
+ * @param options - Size constraints (maxBytes, minBytes)
278
+ *
279
+ * @example
280
+ * ```typescript
281
+ * expect(result).toHaveToolResponseSize({ maxBytes: 10000 });
282
+ * expect(result).toHaveToolResponseSize({ minBytes: 100, maxBytes: 50000 });
283
+ * ```
284
+ */
285
+ toHaveToolResponseSize(options: SizeValidatorOptions): R;
286
+ /**
287
+ * Validates that a response satisfies a custom predicate function
288
+ *
289
+ * Use this as an escape hatch when built-in matchers don't cover your use case.
290
+ * The predicate receives both the raw response and extracted text for convenience.
291
+ *
292
+ * @param predicate - Function that validates the response
293
+ * @param description - Optional description for error messages
294
+ *
295
+ * @example
296
+ * ```typescript
297
+ * // Simple boolean predicate
298
+ * expect(result).toSatisfyToolPredicate((response) => {
299
+ * return response.data?.items?.length > 0;
300
+ * });
301
+ *
302
+ * // Predicate with custom message
303
+ * expect(result).toSatisfyToolPredicate(
304
+ * (response, text) => ({
305
+ * pass: text.includes('success'),
306
+ * message: 'Expected response to contain "success"',
307
+ * }),
308
+ * 'success check'
309
+ * );
310
+ *
311
+ * // Async predicate
312
+ * expect(result).toSatisfyToolPredicate(async (response) => {
313
+ * return await validateWithExternalService(response);
314
+ * });
315
+ * ```
316
+ */
317
+ toSatisfyToolPredicate(predicate: ToolPredicate, description?: string): Promise<R>;
318
+ /**
319
+ * Validates which tools the LLM called during a mcp_host simulation.
320
+ *
321
+ * @example
322
+ * ```typescript
323
+ * expect(simulationResult).toHaveToolCalls({
324
+ * calls: [{ name: 'search', arguments: { query: 'hello' }, required: true }],
325
+ * order: 'any',
326
+ * });
327
+ * ```
328
+ */
329
+ toHaveToolCalls(expectation: ToolCallExpectation): R;
330
+ /**
331
+ * Validates the number of tool calls made during a mcp_host simulation.
332
+ *
333
+ * @example
334
+ * ```typescript
335
+ * expect(simulationResult).toHaveToolCallCount({ min: 1, max: 3 });
336
+ * expect(simulationResult).toHaveToolCallCount({ exact: 2 });
337
+ * ```
338
+ */
339
+ toHaveToolCallCount(options: ToolCallCountOptions): R;
340
+ }
341
+ }
342
+ }
343
+ /**
344
+ * Predicate result returned by the user's predicate function
345
+ */
346
+ interface PredicateResult {
347
+ /** Whether the predicate passed */
348
+ pass: boolean;
349
+ /** Message explaining the result (shown on failure) */
350
+ message?: string;
351
+ }
352
+ /**
353
+ * A predicate function that validates a response
354
+ */
355
+ type ToolPredicate = (response: unknown, text: string) => boolean | PredicateResult | Promise<boolean | PredicateResult>;
356
+
357
+ type StoredArtifactKind = 'eval-runner-result' | 'reporter-run' | 'eval-run-comparison' | 'server-comparison';
358
+ interface StoredEvalArtifactMetadata {
359
+ datasetName?: string;
360
+ gitHash?: string;
361
+ branch?: string;
362
+ runNumber?: string;
363
+ trigger?: string;
364
+ packageVersion?: string;
365
+ toolOverrideVariantId?: string;
366
+ mcpHostModel?: string;
367
+ judgeModel?: string;
368
+ labels?: Record<string, string>;
369
+ [key: string]: unknown;
370
+ }
371
+ interface StoredEvalArtifact<T> {
372
+ schemaVersion: 1;
373
+ kind: StoredArtifactKind;
374
+ id: string;
375
+ createdAt: string;
376
+ metadata: StoredEvalArtifactMetadata;
377
+ data: T;
378
+ }
379
+ interface StoredArtifactSummary {
380
+ kind: StoredArtifactKind;
381
+ id: string;
382
+ createdAt: string;
383
+ metadata: StoredEvalArtifactMetadata;
384
+ }
385
+ interface ListStoredArtifactsOptions {
386
+ limit?: number;
387
+ }
388
+ interface EvalResultStore {
389
+ saveArtifact<T>(artifact: StoredEvalArtifact<T>): Promise<void>;
390
+ loadArtifact<T>(kind: StoredArtifactKind, id: string): Promise<StoredEvalArtifact<T>>;
391
+ loadLatestArtifact<T>(kind: StoredArtifactKind): Promise<StoredEvalArtifact<T> | null>;
392
+ listArtifacts(kind: StoredArtifactKind, options?: ListStoredArtifactsOptions): Promise<StoredArtifactSummary[]>;
393
+ }
394
+ interface FileEvalResultStoreConfig {
395
+ provider: 'file';
396
+ dir: string;
397
+ }
398
+ interface GCSEvalResultStoreConfig {
399
+ provider: 'gcs';
400
+ bucket: string;
401
+ prefix?: string;
402
+ }
403
+ type EvalResultStoreConfig = FileEvalResultStoreConfig | GCSEvalResultStoreConfig;
404
+ type EvalResultStoreLike = EvalResultStore | EvalResultStoreConfig;
405
+
406
+ /**
407
+ * Reporter-specific type definitions
408
+ *
409
+ * These types are used by the MCP reporter and UI.
8
410
  *
9
411
  * @packageDocumentation
10
412
  */
@@ -41,6 +443,25 @@ interface MCPEvalReporterConfig {
41
443
  * @default true
42
444
  */
43
445
  includeAutoTracking?: boolean;
446
+ /**
447
+ * Optional external result store for durable reporter run history.
448
+ */
449
+ resultStore?: EvalResultStoreLike;
450
+ /**
451
+ * Optional run ID for externally stored reporter results.
452
+ * Defaults to a generated timestamp-based ID.
453
+ */
454
+ runId?: string;
455
+ /**
456
+ * Extra metadata to attach to externally stored reporter results.
457
+ */
458
+ runMetadata?: Record<string, unknown>;
459
+ /**
460
+ * When true, strips response payloads before storing reporter results externally.
461
+ * Local report output is unchanged.
462
+ * @default true
463
+ */
464
+ redactStoredResponses?: boolean;
44
465
  }
45
466
 
46
467
  /**
@@ -83,6 +504,8 @@ declare class MCPReporter implements Reporter {
83
504
  private buildRunData;
84
505
  private loadHistoricalData;
85
506
  private saveRunData;
507
+ private saveRunDataToStore;
508
+ private loadHistoricalDataFromStore;
86
509
  private cleanupOldRuns;
87
510
  private openReport;
88
511
  }