@gleanwork/mcp-server-tester 1.0.1-beta.0 → 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.
- package/dist/cli/index.js +1 -1
- package/dist/fixtures/mcp.js +1 -1
- package/dist/fixtures/mcp.js.map +1 -1
- package/dist/index-BcUWzQCx.d.cts +4282 -0
- package/dist/index-BcUWzQCx.d.ts +4282 -0
- package/dist/index.cjs +677 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +551 -4365
- package/dist/index.d.ts +551 -4365
- package/dist/index.js +667 -29
- package/dist/index.js.map +1 -1
- package/dist/reporters/mcpReporter.cjs +294 -1
- package/dist/reporters/mcpReporter.cjs.map +1 -1
- package/dist/reporters/mcpReporter.d.cts +426 -3
- package/dist/reporters/mcpReporter.d.ts +426 -3
- package/dist/reporters/mcpReporter.js +294 -1
- package/dist/reporters/mcpReporter.js.map +1 -1
- package/dist/reporters/ui-dist/app.js +1 -1
- package/dist/types/index.cjs +19 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +9 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.js +17 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +8 -1
|
@@ -0,0 +1,4282 @@
|
|
|
1
|
+
import { ZodType, z } from 'zod';
|
|
2
|
+
import { Page, TestInfo, Expect } from '@playwright/test';
|
|
3
|
+
import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
4
|
+
import { OAuthClientMetadata, OAuthClientInformationFull, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
5
|
+
import * as oauth from 'oauth4webapi';
|
|
6
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
7
|
+
import { Tool, CallToolResult, Implementation, ServerCapabilities, Resource, Prompt } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import * as playwright_test from 'playwright/test';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validator Types
|
|
12
|
+
*
|
|
13
|
+
* Core types for the unified assertion architecture.
|
|
14
|
+
* These types are used by both Playwright matchers and the eval runner.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Result of a validation operation
|
|
19
|
+
*/
|
|
20
|
+
interface ValidationResult {
|
|
21
|
+
/** Whether the validation passed */
|
|
22
|
+
pass: boolean;
|
|
23
|
+
/** Human-readable message explaining the result */
|
|
24
|
+
message: string;
|
|
25
|
+
/** Additional structured details about the validation */
|
|
26
|
+
details?: Record<string, unknown>;
|
|
27
|
+
/**
|
|
28
|
+
* Optional quantitative metrics from the validation.
|
|
29
|
+
* Populated by validateToolCalls for precision/recall.
|
|
30
|
+
*/
|
|
31
|
+
metrics?: {
|
|
32
|
+
precision?: number;
|
|
33
|
+
recall?: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Options for text validation
|
|
38
|
+
*/
|
|
39
|
+
interface TextValidatorOptions {
|
|
40
|
+
/** Whether to perform case-sensitive matching (default: true) */
|
|
41
|
+
caseSensitive?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Options for response size validation
|
|
45
|
+
*/
|
|
46
|
+
interface SizeValidatorOptions {
|
|
47
|
+
/** Maximum allowed size in bytes */
|
|
48
|
+
maxBytes?: number;
|
|
49
|
+
/** Minimum required size in bytes */
|
|
50
|
+
minBytes?: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Options for schema validation
|
|
54
|
+
*/
|
|
55
|
+
interface SchemaValidatorOptions {
|
|
56
|
+
/** Whether to use strict mode (fail on extra properties) */
|
|
57
|
+
strict?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Options for pattern validation
|
|
61
|
+
*/
|
|
62
|
+
interface PatternValidatorOptions {
|
|
63
|
+
/** Whether to perform case-sensitive matching (default: true) */
|
|
64
|
+
caseSensitive?: boolean;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Built-in snapshot sanitizer names for use with toMatchToolSnapshot.
|
|
68
|
+
* Pass these values in the sanitizers array to replace non-deterministic
|
|
69
|
+
* values with stable placeholders before snapshot comparison.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* expect(result).toMatchToolSnapshot('my-snapshot', [
|
|
73
|
+
* SnapshotSanitizers.UUID,
|
|
74
|
+
* SnapshotSanitizers.ISO_DATE,
|
|
75
|
+
* ]);
|
|
76
|
+
*/
|
|
77
|
+
declare const SnapshotSanitizers: {
|
|
78
|
+
/** Replaces Unix timestamps (seconds and milliseconds) with a stable placeholder */
|
|
79
|
+
readonly TIMESTAMP: "timestamp";
|
|
80
|
+
/** Replaces UUID v1-v5 strings with a stable placeholder */
|
|
81
|
+
readonly UUID: "uuid";
|
|
82
|
+
/** Replaces ISO 8601 date/datetime strings with a stable placeholder */
|
|
83
|
+
readonly ISO_DATE: "iso-date";
|
|
84
|
+
/** Replaces MongoDB ObjectId strings with a stable placeholder */
|
|
85
|
+
readonly OBJECT_ID: "objectId";
|
|
86
|
+
/** Replaces JWT tokens with a stable placeholder */
|
|
87
|
+
readonly JWT: "jwt";
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Built-in sanitizer names for common variable patterns
|
|
91
|
+
*/
|
|
92
|
+
type BuiltInSanitizer = (typeof SnapshotSanitizers)[keyof typeof SnapshotSanitizers];
|
|
93
|
+
/**
|
|
94
|
+
* Custom regex-based sanitizer
|
|
95
|
+
*/
|
|
96
|
+
interface RegexSanitizer {
|
|
97
|
+
/** Regex pattern to match */
|
|
98
|
+
pattern: string | RegExp;
|
|
99
|
+
/** Replacement string (default: "[SANITIZED]") */
|
|
100
|
+
replacement?: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Field removal sanitizer - removes specified fields from objects
|
|
104
|
+
*/
|
|
105
|
+
interface FieldRemovalSanitizer {
|
|
106
|
+
/** Field paths to remove (supports dot notation for nested fields) */
|
|
107
|
+
remove: string[];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Snapshot sanitizer configuration
|
|
111
|
+
*
|
|
112
|
+
* Sanitizers transform response data before snapshot comparison,
|
|
113
|
+
* allowing variable content (timestamps, IDs, etc.) to be normalized.
|
|
114
|
+
*
|
|
115
|
+
* Can be:
|
|
116
|
+
* - A built-in sanitizer name: 'timestamp', 'uuid', 'iso-date', 'objectId', 'jwt'
|
|
117
|
+
* - A regex sanitizer: { pattern: /regex/, replacement: '[REPLACED]' }
|
|
118
|
+
* - A field removal sanitizer: { remove: ['field1', 'nested.field'] }
|
|
119
|
+
*/
|
|
120
|
+
type SnapshotSanitizer = BuiltInSanitizer | RegexSanitizer | FieldRemovalSanitizer;
|
|
121
|
+
/**
|
|
122
|
+
* Schema registry for named schemas in datasets
|
|
123
|
+
*/
|
|
124
|
+
type SchemaRegistry = Record<string, ZodType>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Tool call validators for mcp_host simulation results.
|
|
128
|
+
*
|
|
129
|
+
* These validators extract the tool call trace from an MCPHostSimulationResult
|
|
130
|
+
* and apply assertions against expected call lists and counts.
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
interface ToolCallExpectation {
|
|
134
|
+
calls: Array<{
|
|
135
|
+
name: string;
|
|
136
|
+
arguments?: Record<string, unknown>;
|
|
137
|
+
required?: boolean;
|
|
138
|
+
}>;
|
|
139
|
+
order?: 'strict' | 'any';
|
|
140
|
+
exclusive?: boolean;
|
|
141
|
+
}
|
|
142
|
+
interface ToolCallCountOptions {
|
|
143
|
+
min?: number;
|
|
144
|
+
max?: number;
|
|
145
|
+
exact?: number;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Validates tool calls made during an MCP host simulation.
|
|
149
|
+
*
|
|
150
|
+
* @param response - Must be an MCPHostSimulationResult (from mcp_host mode)
|
|
151
|
+
* @param expectation - Expected tool call specification
|
|
152
|
+
*/
|
|
153
|
+
declare function validateToolCalls(response: unknown, expectation: ToolCallExpectation): ValidationResult;
|
|
154
|
+
/**
|
|
155
|
+
* Validates the number of tool calls made during an MCP host simulation.
|
|
156
|
+
*
|
|
157
|
+
* @param response - Must be an MCPHostSimulationResult (from mcp_host mode)
|
|
158
|
+
* @param options - Count constraints (min, max, exact)
|
|
159
|
+
*/
|
|
160
|
+
declare function validateToolCallCount(response: unknown, options: ToolCallCountOptions): ValidationResult;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Built-in judge rubrics matching Glean EvalV2's named judge types.
|
|
164
|
+
* Use these for consistent, standardized evaluations across teams.
|
|
165
|
+
*
|
|
166
|
+
* All built-in rubrics use a 5-point scale: 0.0 / 0.25 / 0.5 / 0.75 / 1.0
|
|
167
|
+
*/
|
|
168
|
+
type BuiltInRubric = 'correctness' | 'completeness' | 'groundedness' | 'instruction-following' | 'conciseness';
|
|
169
|
+
declare const BUILT_IN_RUBRICS: Record<BuiltInRubric, string>;
|
|
170
|
+
/** A rubric specification: either a built-in named rubric or custom text. */
|
|
171
|
+
type RubricSpec = BuiltInRubric | {
|
|
172
|
+
text: string;
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Returns true if `s` is a built-in rubric name.
|
|
176
|
+
*/
|
|
177
|
+
declare function isBuiltInRubric(s: unknown): s is BuiltInRubric;
|
|
178
|
+
/**
|
|
179
|
+
* Resolves a RubricSpec to its full rubric text.
|
|
180
|
+
* - Built-in name → returns the expanded rubric text from BUILT_IN_RUBRICS
|
|
181
|
+
* - Custom object → returns rubric.text as-is
|
|
182
|
+
*/
|
|
183
|
+
declare function resolveRubric(rubric: RubricSpec): string;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Usage metrics from Claude Agent SDK response
|
|
187
|
+
*/
|
|
188
|
+
interface UsageMetrics {
|
|
189
|
+
/**
|
|
190
|
+
* Number of input tokens consumed
|
|
191
|
+
*/
|
|
192
|
+
inputTokens: number;
|
|
193
|
+
/**
|
|
194
|
+
* Number of output tokens generated
|
|
195
|
+
*/
|
|
196
|
+
outputTokens: number;
|
|
197
|
+
/**
|
|
198
|
+
* Total cost in USD
|
|
199
|
+
*/
|
|
200
|
+
totalCostUsd: number;
|
|
201
|
+
/**
|
|
202
|
+
* Execution duration in milliseconds
|
|
203
|
+
*/
|
|
204
|
+
durationMs: number;
|
|
205
|
+
/**
|
|
206
|
+
* API call duration in milliseconds (excluding network overhead)
|
|
207
|
+
*/
|
|
208
|
+
durationApiMs?: number;
|
|
209
|
+
/**
|
|
210
|
+
* Number of tokens read from cache
|
|
211
|
+
*/
|
|
212
|
+
cacheReadInputTokens?: number;
|
|
213
|
+
/**
|
|
214
|
+
* Number of tokens written to cache
|
|
215
|
+
*/
|
|
216
|
+
cacheCreationInputTokens?: number;
|
|
217
|
+
}
|
|
218
|
+
/** Valid LLM judge provider kinds. */
|
|
219
|
+
type ProviderKind = 'anthropic' | 'vertex-anthropic' | 'anthropic-agent-sdk' | 'openai' | 'google';
|
|
220
|
+
/**
|
|
221
|
+
* Configuration for an LLM judge
|
|
222
|
+
*/
|
|
223
|
+
interface JudgeConfig {
|
|
224
|
+
/**
|
|
225
|
+
* LLM provider to use
|
|
226
|
+
* @default 'anthropic'
|
|
227
|
+
*/
|
|
228
|
+
provider?: ProviderKind;
|
|
229
|
+
/**
|
|
230
|
+
* Environment variable name containing the API key
|
|
231
|
+
* @default 'ANTHROPIC_API_KEY'
|
|
232
|
+
*/
|
|
233
|
+
apiKeyEnvVar?: string;
|
|
234
|
+
/**
|
|
235
|
+
* Model to use for judging
|
|
236
|
+
* @default 'claude-sonnet-4-20250514'
|
|
237
|
+
*/
|
|
238
|
+
model?: string;
|
|
239
|
+
/**
|
|
240
|
+
* Maximum tokens for response
|
|
241
|
+
* @default 1000
|
|
242
|
+
*/
|
|
243
|
+
maxTokens?: number;
|
|
244
|
+
/**
|
|
245
|
+
* Temperature (0-1, lower is more deterministic)
|
|
246
|
+
* @default 0.0
|
|
247
|
+
*/
|
|
248
|
+
temperature?: number;
|
|
249
|
+
/**
|
|
250
|
+
* Maximum budget in USD for the judge evaluation
|
|
251
|
+
* @default 0.10
|
|
252
|
+
*/
|
|
253
|
+
maxBudgetUsd?: number;
|
|
254
|
+
/**
|
|
255
|
+
* Maximum size (in bytes) for tool output before failing the test
|
|
256
|
+
* When set, the judge will fail if the candidate response exceeds this size
|
|
257
|
+
*/
|
|
258
|
+
maxToolOutputSize?: number;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Result from LLM judge evaluation
|
|
262
|
+
*/
|
|
263
|
+
interface JudgeResult {
|
|
264
|
+
/**
|
|
265
|
+
* Whether the evaluation passed
|
|
266
|
+
*/
|
|
267
|
+
pass: boolean;
|
|
268
|
+
/**
|
|
269
|
+
* Numeric score (0-1, where 1 is best)
|
|
270
|
+
*/
|
|
271
|
+
score?: number;
|
|
272
|
+
/**
|
|
273
|
+
* Reasoning/explanation from the judge
|
|
274
|
+
*/
|
|
275
|
+
reasoning?: string;
|
|
276
|
+
/**
|
|
277
|
+
* Usage metrics from the Claude Agent SDK
|
|
278
|
+
*/
|
|
279
|
+
usage?: UsageMetrics;
|
|
280
|
+
/**
|
|
281
|
+
* Size of the candidate response in bytes (for maxToolOutputSize tracking)
|
|
282
|
+
*/
|
|
283
|
+
candidateSizeBytes?: number;
|
|
284
|
+
/**
|
|
285
|
+
* Whether the candidate exceeded maxToolOutputSize
|
|
286
|
+
*/
|
|
287
|
+
exceedsMaxToolOutputSize?: boolean;
|
|
288
|
+
/**
|
|
289
|
+
* Standard deviation of individual rep scores.
|
|
290
|
+
* Only populated when the judge was run with reps > 1.
|
|
291
|
+
*/
|
|
292
|
+
scoreStdDev?: number;
|
|
293
|
+
/**
|
|
294
|
+
* True when the standard deviation across reps exceeds 0.2, indicating
|
|
295
|
+
* that the rubric may be ambiguous or the judge is non-deterministic.
|
|
296
|
+
* Only populated when the judge was run with reps > 1.
|
|
297
|
+
*/
|
|
298
|
+
highVariance?: boolean;
|
|
299
|
+
/**
|
|
300
|
+
* Individual scores from each judge rep.
|
|
301
|
+
* Only populated when the judge was run with reps > 1.
|
|
302
|
+
*/
|
|
303
|
+
scores?: number[];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* LLM judge client interface
|
|
308
|
+
*/
|
|
309
|
+
interface Judge {
|
|
310
|
+
/**
|
|
311
|
+
* Evaluates a candidate response against a reference
|
|
312
|
+
*
|
|
313
|
+
* @param candidate - The actual response to evaluate
|
|
314
|
+
* @param reference - The expected/reference response (or null if not applicable)
|
|
315
|
+
* @param rubric - The evaluation rubric/criteria
|
|
316
|
+
* @returns Evaluation result with usage metrics
|
|
317
|
+
*/
|
|
318
|
+
evaluate(candidate: unknown, reference: unknown, rubric: string): Promise<JudgeResult>;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Judge Validator
|
|
323
|
+
*
|
|
324
|
+
* Validates a response using an LLM-as-a-judge evaluation.
|
|
325
|
+
*/
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Configuration for the judge validator
|
|
329
|
+
*/
|
|
330
|
+
interface JudgeValidatorConfig {
|
|
331
|
+
/**
|
|
332
|
+
* The evaluation rubric: a built-in name or custom { text: string }.
|
|
333
|
+
* Required when no named `judge` is specified.
|
|
334
|
+
*/
|
|
335
|
+
rubric?: RubricSpec;
|
|
336
|
+
/** Optional reference response to compare against */
|
|
337
|
+
reference?: unknown;
|
|
338
|
+
/** Minimum score required to pass (0-1, default: 0.7) */
|
|
339
|
+
threshold?: number;
|
|
340
|
+
/** Number of judge evaluations to run. Scores averaged. @default 1 */
|
|
341
|
+
reps?: number;
|
|
342
|
+
/** Judge provider. @default 'claude' */
|
|
343
|
+
provider?: ProviderKind;
|
|
344
|
+
/** Model override (e.g., 'claude-opus-4-20250514') */
|
|
345
|
+
model?: string;
|
|
346
|
+
/** Environment variable name for API key */
|
|
347
|
+
apiKeyEnvVar?: string;
|
|
348
|
+
/** Max tokens for judge response */
|
|
349
|
+
maxTokens?: number;
|
|
350
|
+
/** Temperature for judge LLM (0–1) */
|
|
351
|
+
temperature?: number;
|
|
352
|
+
/** Max budget in USD per evaluation */
|
|
353
|
+
maxBudgetUsd?: number;
|
|
354
|
+
/** Fail if response exceeds this size in bytes before judging */
|
|
355
|
+
maxToolOutputSize?: number;
|
|
356
|
+
/**
|
|
357
|
+
* Name of a registered custom judge executor.
|
|
358
|
+
* When set, the named judge handles the entire evaluation pipeline
|
|
359
|
+
* and returns a normalized score. The `threshold` determines pass/fail.
|
|
360
|
+
* Register judges with `registerJudge()` before tests run.
|
|
361
|
+
*/
|
|
362
|
+
judge?: string;
|
|
363
|
+
}
|
|
364
|
+
declare function validateJudge(response: unknown, config: JudgeValidatorConfig): Promise<ValidationResult>;
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Matcher Types
|
|
368
|
+
*
|
|
369
|
+
* TypeScript declarations for custom Playwright matchers.
|
|
370
|
+
*/
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Options for the LLM judge matcher
|
|
374
|
+
*/
|
|
375
|
+
interface JudgeMatcherOptions {
|
|
376
|
+
/** Reference response to compare against */
|
|
377
|
+
reference?: unknown;
|
|
378
|
+
/** Score threshold for passing (default: 0.7) */
|
|
379
|
+
passingThreshold?: number;
|
|
380
|
+
/** Number of judge evaluations (scores averaged) */
|
|
381
|
+
reps?: number;
|
|
382
|
+
/** Override the judge provider */
|
|
383
|
+
provider?: ProviderKind;
|
|
384
|
+
/** Override the judge model */
|
|
385
|
+
model?: string;
|
|
386
|
+
/**
|
|
387
|
+
* Name of a registered custom judge executor.
|
|
388
|
+
* When set, the named judge handles the entire evaluation pipeline
|
|
389
|
+
* and its `pass` result is authoritative.
|
|
390
|
+
*/
|
|
391
|
+
judge?: string;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Declaration merging for Playwright matchers
|
|
395
|
+
*/
|
|
396
|
+
declare global {
|
|
397
|
+
namespace PlaywrightTest {
|
|
398
|
+
interface Matchers<R, T = unknown> {
|
|
399
|
+
/**
|
|
400
|
+
* Validates that a response exactly matches the expected value
|
|
401
|
+
*
|
|
402
|
+
* @param expected - The expected response value
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* ```typescript
|
|
406
|
+
* expect(result).toMatchToolResponse({ status: 'ok', count: 42 });
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
409
|
+
toMatchToolResponse(expected: unknown): R;
|
|
410
|
+
/**
|
|
411
|
+
* Validates that a response matches a Zod schema
|
|
412
|
+
*
|
|
413
|
+
* @param schema - The Zod schema to validate against
|
|
414
|
+
* @param options - Validation options
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```typescript
|
|
418
|
+
* const WeatherSchema = z.object({
|
|
419
|
+
* temperature: z.number(),
|
|
420
|
+
* conditions: z.string(),
|
|
421
|
+
* });
|
|
422
|
+
* expect(result).toMatchToolSchema(WeatherSchema);
|
|
423
|
+
* ```
|
|
424
|
+
*/
|
|
425
|
+
toMatchToolSchema(schema: ZodType, options?: SchemaValidatorOptions): R;
|
|
426
|
+
/**
|
|
427
|
+
* Validates that a response contains expected text substrings
|
|
428
|
+
*
|
|
429
|
+
* @param expected - Expected substring(s) to find
|
|
430
|
+
* @param options - Validation options
|
|
431
|
+
*
|
|
432
|
+
* @example
|
|
433
|
+
* ```typescript
|
|
434
|
+
* expect(result).toContainToolText('temperature');
|
|
435
|
+
* expect(result).toContainToolText(['temperature', 'conditions']);
|
|
436
|
+
* expect(result).toContainToolText('HELLO', { caseSensitive: false });
|
|
437
|
+
* ```
|
|
438
|
+
*/
|
|
439
|
+
toContainToolText(expected: string | string[], options?: TextValidatorOptions): R;
|
|
440
|
+
/**
|
|
441
|
+
* Validates that a response matches regex patterns
|
|
442
|
+
*
|
|
443
|
+
* @param patterns - Expected pattern(s) to match
|
|
444
|
+
* @param options - Validation options
|
|
445
|
+
*
|
|
446
|
+
* @example
|
|
447
|
+
* ```typescript
|
|
448
|
+
* expect(result).toMatchToolPattern(/temperature: \d+/);
|
|
449
|
+
* expect(result).toMatchToolPattern(['temp: \\d+', 'humidity: \\d+%']);
|
|
450
|
+
* ```
|
|
451
|
+
*/
|
|
452
|
+
toMatchToolPattern(patterns: string | RegExp | (string | RegExp)[], options?: PatternValidatorOptions): R;
|
|
453
|
+
/**
|
|
454
|
+
* Validates that a response matches a saved snapshot
|
|
455
|
+
*
|
|
456
|
+
* @param name - Snapshot name
|
|
457
|
+
* @param sanitizers - Optional sanitizers for non-deterministic values
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```typescript
|
|
461
|
+
* expect(result).toMatchToolSnapshot('weather-response');
|
|
462
|
+
* expect(result).toMatchToolSnapshot('user-data', [
|
|
463
|
+
* { pattern: /\d{4}-\d{2}-\d{2}/, replacement: '[DATE]' },
|
|
464
|
+
* ]);
|
|
465
|
+
* ```
|
|
466
|
+
*/
|
|
467
|
+
toMatchToolSnapshot(name: string, sanitizers?: SnapshotSanitizer[]): Promise<R>;
|
|
468
|
+
/**
|
|
469
|
+
* Validates that a response is (or is not) an error
|
|
470
|
+
*
|
|
471
|
+
* @param expected - What to expect (true for error, false for success, string for specific message)
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* ```typescript
|
|
475
|
+
* expect(result).toBeToolError(); // Expects any error
|
|
476
|
+
* expect(result).not.toBeToolError(); // Expects success
|
|
477
|
+
* expect(result).toBeToolError('File not found'); // Expects specific error
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
480
|
+
toBeToolError(expected?: boolean | string | string[]): R;
|
|
481
|
+
/**
|
|
482
|
+
* Validates that a response passes LLM-as-judge evaluation.
|
|
483
|
+
*
|
|
484
|
+
* Two call signatures:
|
|
485
|
+
* - With rubric: `toPassToolJudge(rubric, options?)` — built-in LLM judge
|
|
486
|
+
* - With named judge: `toPassToolJudge({ judge: 'name' })` — custom judge executor
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```typescript
|
|
490
|
+
* // Built-in LLM judge with rubric
|
|
491
|
+
* expect(result).toPassToolJudge('Response should be helpful and accurate');
|
|
492
|
+
* expect(result).toPassToolJudge('correctness', {
|
|
493
|
+
* reference: expectedOutput,
|
|
494
|
+
* passingThreshold: 0.8,
|
|
495
|
+
* });
|
|
496
|
+
*
|
|
497
|
+
* // Named custom judge (registered via registerJudge)
|
|
498
|
+
* expect(result).toPassToolJudge({ judge: 'glean-completeness' });
|
|
499
|
+
* ```
|
|
500
|
+
*/
|
|
501
|
+
toPassToolJudge(rubric: RubricSpec, options?: JudgeMatcherOptions): Promise<R>;
|
|
502
|
+
toPassToolJudge(options: JudgeMatcherOptions): Promise<R>;
|
|
503
|
+
toPassToolJudge(judges: Array<JudgeMatcherOptions & {
|
|
504
|
+
rubric?: RubricSpec;
|
|
505
|
+
}>): Promise<R>;
|
|
506
|
+
/**
|
|
507
|
+
* Validates that a response meets size constraints
|
|
508
|
+
*
|
|
509
|
+
* @param options - Size constraints (maxBytes, minBytes)
|
|
510
|
+
*
|
|
511
|
+
* @example
|
|
512
|
+
* ```typescript
|
|
513
|
+
* expect(result).toHaveToolResponseSize({ maxBytes: 10000 });
|
|
514
|
+
* expect(result).toHaveToolResponseSize({ minBytes: 100, maxBytes: 50000 });
|
|
515
|
+
* ```
|
|
516
|
+
*/
|
|
517
|
+
toHaveToolResponseSize(options: SizeValidatorOptions): R;
|
|
518
|
+
/**
|
|
519
|
+
* Validates that a response satisfies a custom predicate function
|
|
520
|
+
*
|
|
521
|
+
* Use this as an escape hatch when built-in matchers don't cover your use case.
|
|
522
|
+
* The predicate receives both the raw response and extracted text for convenience.
|
|
523
|
+
*
|
|
524
|
+
* @param predicate - Function that validates the response
|
|
525
|
+
* @param description - Optional description for error messages
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* ```typescript
|
|
529
|
+
* // Simple boolean predicate
|
|
530
|
+
* expect(result).toSatisfyToolPredicate((response) => {
|
|
531
|
+
* return response.data?.items?.length > 0;
|
|
532
|
+
* });
|
|
533
|
+
*
|
|
534
|
+
* // Predicate with custom message
|
|
535
|
+
* expect(result).toSatisfyToolPredicate(
|
|
536
|
+
* (response, text) => ({
|
|
537
|
+
* pass: text.includes('success'),
|
|
538
|
+
* message: 'Expected response to contain "success"',
|
|
539
|
+
* }),
|
|
540
|
+
* 'success check'
|
|
541
|
+
* );
|
|
542
|
+
*
|
|
543
|
+
* // Async predicate
|
|
544
|
+
* expect(result).toSatisfyToolPredicate(async (response) => {
|
|
545
|
+
* return await validateWithExternalService(response);
|
|
546
|
+
* });
|
|
547
|
+
* ```
|
|
548
|
+
*/
|
|
549
|
+
toSatisfyToolPredicate(predicate: ToolPredicate, description?: string): Promise<R>;
|
|
550
|
+
/**
|
|
551
|
+
* Validates which tools the LLM called during a mcp_host simulation.
|
|
552
|
+
*
|
|
553
|
+
* @example
|
|
554
|
+
* ```typescript
|
|
555
|
+
* expect(simulationResult).toHaveToolCalls({
|
|
556
|
+
* calls: [{ name: 'search', arguments: { query: 'hello' }, required: true }],
|
|
557
|
+
* order: 'any',
|
|
558
|
+
* });
|
|
559
|
+
* ```
|
|
560
|
+
*/
|
|
561
|
+
toHaveToolCalls(expectation: ToolCallExpectation): R;
|
|
562
|
+
/**
|
|
563
|
+
* Validates the number of tool calls made during a mcp_host simulation.
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* ```typescript
|
|
567
|
+
* expect(simulationResult).toHaveToolCallCount({ min: 1, max: 3 });
|
|
568
|
+
* expect(simulationResult).toHaveToolCallCount({ exact: 2 });
|
|
569
|
+
* ```
|
|
570
|
+
*/
|
|
571
|
+
toHaveToolCallCount(options: ToolCallCountOptions): R;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Predicate result returned by the user's predicate function
|
|
577
|
+
*/
|
|
578
|
+
interface PredicateResult {
|
|
579
|
+
/** Whether the predicate passed */
|
|
580
|
+
pass: boolean;
|
|
581
|
+
/** Message explaining the result (shown on failure) */
|
|
582
|
+
message?: string;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* A predicate function that validates a response
|
|
586
|
+
*/
|
|
587
|
+
type ToolPredicate = (response: unknown, text: string) => boolean | PredicateResult | Promise<boolean | PredicateResult>;
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* OAuth configuration for MCP authentication
|
|
591
|
+
*/
|
|
592
|
+
interface MCPOAuthConfig {
|
|
593
|
+
/**
|
|
594
|
+
* OAuth authorization server metadata URL
|
|
595
|
+
* (e.g., https://auth.example.com/.well-known/oauth-authorization-server)
|
|
596
|
+
*/
|
|
597
|
+
serverUrl: string;
|
|
598
|
+
/**
|
|
599
|
+
* Scopes to request during authorization
|
|
600
|
+
*/
|
|
601
|
+
scopes?: Array<string>;
|
|
602
|
+
/**
|
|
603
|
+
* Resource indicator (RFC 8707, required by MCP 2025-06-18 spec)
|
|
604
|
+
*/
|
|
605
|
+
resource?: string;
|
|
606
|
+
/**
|
|
607
|
+
* Path to Playwright auth state file
|
|
608
|
+
* (e.g., playwright/.auth/oauth-state.json)
|
|
609
|
+
*/
|
|
610
|
+
authStatePath?: string;
|
|
611
|
+
/**
|
|
612
|
+
* Client ID (if pre-registered; otherwise uses Dynamic Client Registration)
|
|
613
|
+
*/
|
|
614
|
+
clientId?: string;
|
|
615
|
+
/**
|
|
616
|
+
* Client secret (for confidential clients)
|
|
617
|
+
*/
|
|
618
|
+
clientSecret?: string;
|
|
619
|
+
/**
|
|
620
|
+
* Redirect URI for OAuth callback
|
|
621
|
+
*/
|
|
622
|
+
redirectUri?: string;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* OAuth 2.1 client credentials configuration for machine-to-machine (CI/CD) authentication.
|
|
626
|
+
* Credentials can be provided here or via MCP_CLIENT_ID/MCP_CLIENT_SECRET environment variables.
|
|
627
|
+
*/
|
|
628
|
+
interface MCPClientCredentialsConfig {
|
|
629
|
+
/**
|
|
630
|
+
* OAuth client ID (falls back to MCP_CLIENT_ID env var)
|
|
631
|
+
*/
|
|
632
|
+
clientId?: string;
|
|
633
|
+
/**
|
|
634
|
+
* OAuth client secret (falls back to MCP_CLIENT_SECRET env var)
|
|
635
|
+
*/
|
|
636
|
+
clientSecret?: string;
|
|
637
|
+
/**
|
|
638
|
+
* Token endpoint URL (required)
|
|
639
|
+
*/
|
|
640
|
+
tokenEndpoint?: string;
|
|
641
|
+
/**
|
|
642
|
+
* Scopes to request
|
|
643
|
+
*/
|
|
644
|
+
scopes?: string[];
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Authentication configuration for MCP connections
|
|
648
|
+
*/
|
|
649
|
+
interface MCPAuthConfig {
|
|
650
|
+
/**
|
|
651
|
+
* Pre-acquired access token (simplest authentication mode)
|
|
652
|
+
*/
|
|
653
|
+
accessToken?: string;
|
|
654
|
+
/**
|
|
655
|
+
* Full OAuth configuration for browser-based authentication
|
|
656
|
+
*/
|
|
657
|
+
oauth?: MCPOAuthConfig;
|
|
658
|
+
/**
|
|
659
|
+
* OAuth 2.1 client credentials grant for machine-to-machine authentication
|
|
660
|
+
*/
|
|
661
|
+
clientCredentials?: MCPClientCredentialsConfig;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* MCP host capabilities that can be registered with the server
|
|
665
|
+
*/
|
|
666
|
+
interface MCPHostCapabilities {
|
|
667
|
+
/**
|
|
668
|
+
* Sampling capabilities (for LLM sampling)
|
|
669
|
+
*/
|
|
670
|
+
sampling?: Record<string, unknown>;
|
|
671
|
+
/**
|
|
672
|
+
* Roots capabilities (for file system roots)
|
|
673
|
+
*/
|
|
674
|
+
roots?: {
|
|
675
|
+
/**
|
|
676
|
+
* Whether the client can notify the server when roots change
|
|
677
|
+
*/
|
|
678
|
+
listChanged: boolean;
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Configuration for MCP client connection via stdio transport (local process)
|
|
683
|
+
*/
|
|
684
|
+
interface StdioMCPConfig {
|
|
685
|
+
/**
|
|
686
|
+
* Transport type discriminant
|
|
687
|
+
*/
|
|
688
|
+
transport: 'stdio';
|
|
689
|
+
/**
|
|
690
|
+
* Command to execute (required for stdio transport)
|
|
691
|
+
*/
|
|
692
|
+
command: string;
|
|
693
|
+
/**
|
|
694
|
+
* Command arguments
|
|
695
|
+
*/
|
|
696
|
+
args?: Array<string>;
|
|
697
|
+
/**
|
|
698
|
+
* Working directory for the command
|
|
699
|
+
*/
|
|
700
|
+
cwd?: string;
|
|
701
|
+
/**
|
|
702
|
+
* Environment variables to pass to the subprocess.
|
|
703
|
+
* Merged with the current process environment.
|
|
704
|
+
*/
|
|
705
|
+
env?: Record<string, string>;
|
|
706
|
+
/**
|
|
707
|
+
* Suppress stderr output from the server process.
|
|
708
|
+
* When true, server stderr is ignored instead of inherited.
|
|
709
|
+
*/
|
|
710
|
+
quiet?: boolean;
|
|
711
|
+
/**
|
|
712
|
+
* Host capabilities to register with the server
|
|
713
|
+
*/
|
|
714
|
+
capabilities?: MCPHostCapabilities;
|
|
715
|
+
/**
|
|
716
|
+
* Connection timeout in milliseconds
|
|
717
|
+
*/
|
|
718
|
+
connectTimeoutMs?: number;
|
|
719
|
+
/**
|
|
720
|
+
* Request timeout in milliseconds
|
|
721
|
+
*/
|
|
722
|
+
requestTimeoutMs?: number;
|
|
723
|
+
/**
|
|
724
|
+
* Timeout in milliseconds for MCP tool/list operations. Default: 30000
|
|
725
|
+
*/
|
|
726
|
+
callTimeoutMs?: number;
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* Configuration for MCP client connection via HTTP transport (remote server)
|
|
730
|
+
*/
|
|
731
|
+
interface HttpMCPConfig {
|
|
732
|
+
/**
|
|
733
|
+
* Transport type discriminant
|
|
734
|
+
*/
|
|
735
|
+
transport: 'http';
|
|
736
|
+
/**
|
|
737
|
+
* Server URL (required for http transport)
|
|
738
|
+
*/
|
|
739
|
+
serverUrl: string;
|
|
740
|
+
/**
|
|
741
|
+
* HTTP headers (e.g., Authorization)
|
|
742
|
+
*/
|
|
743
|
+
headers?: Record<string, string>;
|
|
744
|
+
/**
|
|
745
|
+
* Authentication configuration
|
|
746
|
+
*/
|
|
747
|
+
auth?: MCPAuthConfig;
|
|
748
|
+
/**
|
|
749
|
+
* Host capabilities to register with the server
|
|
750
|
+
*/
|
|
751
|
+
capabilities?: MCPHostCapabilities;
|
|
752
|
+
/**
|
|
753
|
+
* Connection timeout in milliseconds
|
|
754
|
+
*/
|
|
755
|
+
connectTimeoutMs?: number;
|
|
756
|
+
/**
|
|
757
|
+
* Request timeout in milliseconds
|
|
758
|
+
*/
|
|
759
|
+
requestTimeoutMs?: number;
|
|
760
|
+
/**
|
|
761
|
+
* Timeout in milliseconds for MCP tool/list operations. Default: 30000
|
|
762
|
+
*/
|
|
763
|
+
callTimeoutMs?: number;
|
|
764
|
+
/**
|
|
765
|
+
* HTTP proxy configuration. Falls back to HTTPS_PROXY/HTTP_PROXY environment variables.
|
|
766
|
+
*/
|
|
767
|
+
proxy?: {
|
|
768
|
+
/**
|
|
769
|
+
* Proxy URL. Credentials can be embedded directly if required:
|
|
770
|
+
* `http://user:pass@proxy.example.com:8080`
|
|
771
|
+
*/
|
|
772
|
+
url: string;
|
|
773
|
+
};
|
|
774
|
+
/**
|
|
775
|
+
* Number of retry attempts for transient connection failures and 429 rate limit responses.
|
|
776
|
+
* Uses exponential backoff with Retry-After header awareness. Defaults to 0 (no retries).
|
|
777
|
+
*/
|
|
778
|
+
retryAttempts?: number;
|
|
779
|
+
/**
|
|
780
|
+
* TLS/mTLS configuration for custom certificates or disabling cert validation.
|
|
781
|
+
* File paths should point to PEM-encoded certificate files.
|
|
782
|
+
*/
|
|
783
|
+
tls?: {
|
|
784
|
+
/**
|
|
785
|
+
* Path to CA certificate PEM file (for custom/self-signed CAs)
|
|
786
|
+
*/
|
|
787
|
+
ca?: string;
|
|
788
|
+
/**
|
|
789
|
+
* Path to client certificate PEM file (for mutual TLS)
|
|
790
|
+
*/
|
|
791
|
+
cert?: string;
|
|
792
|
+
/**
|
|
793
|
+
* Path to client private key PEM file (for mutual TLS)
|
|
794
|
+
*/
|
|
795
|
+
key?: string;
|
|
796
|
+
/**
|
|
797
|
+
* Whether to reject unauthorized certificates. Defaults to true.
|
|
798
|
+
* Set to false to disable certificate validation (not recommended for production).
|
|
799
|
+
*/
|
|
800
|
+
rejectUnauthorized?: boolean;
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Configuration for MCP client connection.
|
|
805
|
+
*
|
|
806
|
+
* This is a discriminated union — narrow with `isStdioConfig()` or `isHttpConfig()`
|
|
807
|
+
* before accessing transport-specific fields.
|
|
808
|
+
*
|
|
809
|
+
* Supports both stdio (local) and HTTP (remote) transports.
|
|
810
|
+
*/
|
|
811
|
+
type MCPConfig = StdioMCPConfig | HttpMCPConfig;
|
|
812
|
+
/**
|
|
813
|
+
* Union schema for MCPConfig (validates based on transport type)
|
|
814
|
+
*/
|
|
815
|
+
declare const MCPConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
816
|
+
transport: z.ZodLiteral<"stdio">;
|
|
817
|
+
command: z.ZodString;
|
|
818
|
+
args: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
819
|
+
cwd: z.ZodOptional<z.ZodString>;
|
|
820
|
+
env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
821
|
+
capabilities: z.ZodOptional<z.ZodObject<{
|
|
822
|
+
sampling: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
823
|
+
roots: z.ZodOptional<z.ZodObject<{
|
|
824
|
+
listChanged: z.ZodBoolean;
|
|
825
|
+
}, z.core.$strip>>;
|
|
826
|
+
}, z.core.$strip>>;
|
|
827
|
+
connectTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
828
|
+
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
829
|
+
callTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
830
|
+
quiet: z.ZodOptional<z.ZodBoolean>;
|
|
831
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
832
|
+
transport: z.ZodLiteral<"http">;
|
|
833
|
+
serverUrl: z.ZodString;
|
|
834
|
+
headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
835
|
+
capabilities: z.ZodOptional<z.ZodObject<{
|
|
836
|
+
sampling: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
837
|
+
roots: z.ZodOptional<z.ZodObject<{
|
|
838
|
+
listChanged: z.ZodBoolean;
|
|
839
|
+
}, z.core.$strip>>;
|
|
840
|
+
}, z.core.$strip>>;
|
|
841
|
+
connectTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
842
|
+
requestTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
843
|
+
callTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
844
|
+
auth: z.ZodOptional<z.ZodObject<{
|
|
845
|
+
accessToken: z.ZodOptional<z.ZodString>;
|
|
846
|
+
oauth: z.ZodOptional<z.ZodObject<{
|
|
847
|
+
serverUrl: z.ZodString;
|
|
848
|
+
scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
849
|
+
resource: z.ZodOptional<z.ZodString>;
|
|
850
|
+
authStatePath: z.ZodOptional<z.ZodString>;
|
|
851
|
+
clientId: z.ZodOptional<z.ZodString>;
|
|
852
|
+
clientSecret: z.ZodOptional<z.ZodString>;
|
|
853
|
+
redirectUri: z.ZodOptional<z.ZodString>;
|
|
854
|
+
}, z.core.$strip>>;
|
|
855
|
+
clientCredentials: z.ZodOptional<z.ZodObject<{
|
|
856
|
+
clientId: z.ZodOptional<z.ZodString>;
|
|
857
|
+
clientSecret: z.ZodOptional<z.ZodString>;
|
|
858
|
+
tokenEndpoint: z.ZodOptional<z.ZodString>;
|
|
859
|
+
scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
860
|
+
}, z.core.$strip>>;
|
|
861
|
+
}, z.core.$strip>>;
|
|
862
|
+
proxy: z.ZodOptional<z.ZodObject<{
|
|
863
|
+
url: z.ZodString;
|
|
864
|
+
}, z.core.$strip>>;
|
|
865
|
+
retryAttempts: z.ZodOptional<z.ZodNumber>;
|
|
866
|
+
tls: z.ZodOptional<z.ZodObject<{
|
|
867
|
+
ca: z.ZodOptional<z.ZodString>;
|
|
868
|
+
cert: z.ZodOptional<z.ZodString>;
|
|
869
|
+
key: z.ZodOptional<z.ZodString>;
|
|
870
|
+
rejectUnauthorized: z.ZodOptional<z.ZodBoolean>;
|
|
871
|
+
}, z.core.$strip>>;
|
|
872
|
+
}, z.core.$strip>], "transport">;
|
|
873
|
+
/**
|
|
874
|
+
* Validates an MCPConfig object
|
|
875
|
+
*
|
|
876
|
+
* @param config - The config to validate
|
|
877
|
+
* @returns The validated config
|
|
878
|
+
* @throws {z.ZodError} If validation fails
|
|
879
|
+
*/
|
|
880
|
+
declare function validateMCPConfig(config: unknown): MCPConfig;
|
|
881
|
+
/**
|
|
882
|
+
* Type guard to check if a config is for stdio transport
|
|
883
|
+
*/
|
|
884
|
+
declare function isStdioConfig(config: MCPConfig): config is StdioMCPConfig;
|
|
885
|
+
/**
|
|
886
|
+
* Type guard to check if a config is for HTTP transport
|
|
887
|
+
*/
|
|
888
|
+
declare function isHttpConfig(config: MCPConfig): config is HttpMCPConfig;
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Auth types for MCP OAuth integration
|
|
892
|
+
*/
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Stored OAuth tokens
|
|
896
|
+
*/
|
|
897
|
+
interface StoredTokens {
|
|
898
|
+
/**
|
|
899
|
+
* OAuth access token
|
|
900
|
+
*/
|
|
901
|
+
accessToken: string;
|
|
902
|
+
/**
|
|
903
|
+
* OAuth refresh token (if provided)
|
|
904
|
+
*/
|
|
905
|
+
refreshToken?: string;
|
|
906
|
+
/**
|
|
907
|
+
* Token expiration timestamp (Unix milliseconds)
|
|
908
|
+
*/
|
|
909
|
+
expiresAt?: number;
|
|
910
|
+
/**
|
|
911
|
+
* Token type (typically "Bearer")
|
|
912
|
+
*/
|
|
913
|
+
tokenType: string;
|
|
914
|
+
/**
|
|
915
|
+
* Client ID that was used to obtain these tokens.
|
|
916
|
+
* Required for token refresh since refresh tokens are bound to the client.
|
|
917
|
+
*/
|
|
918
|
+
clientId?: string;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Stored client information from Dynamic Client Registration
|
|
922
|
+
*/
|
|
923
|
+
interface StoredClientInfo {
|
|
924
|
+
/**
|
|
925
|
+
* Client ID from DCR
|
|
926
|
+
*/
|
|
927
|
+
clientId: string;
|
|
928
|
+
/**
|
|
929
|
+
* Client secret from DCR (for confidential clients)
|
|
930
|
+
*/
|
|
931
|
+
clientSecret?: string;
|
|
932
|
+
/**
|
|
933
|
+
* Client ID issued at timestamp
|
|
934
|
+
*/
|
|
935
|
+
clientIdIssuedAt?: number;
|
|
936
|
+
/**
|
|
937
|
+
* Client secret expiration timestamp
|
|
938
|
+
*/
|
|
939
|
+
clientSecretExpiresAt?: number;
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Complete OAuth state persisted to disk for Playwright auth state pattern
|
|
943
|
+
*/
|
|
944
|
+
interface StoredOAuthState {
|
|
945
|
+
/**
|
|
946
|
+
* OAuth tokens
|
|
947
|
+
*/
|
|
948
|
+
tokens?: StoredTokens;
|
|
949
|
+
/**
|
|
950
|
+
* DCR client information
|
|
951
|
+
*/
|
|
952
|
+
clientInfo?: StoredClientInfo;
|
|
953
|
+
/**
|
|
954
|
+
* PKCE code verifier (used during authorization flow)
|
|
955
|
+
*/
|
|
956
|
+
codeVerifier?: string;
|
|
957
|
+
/**
|
|
958
|
+
* OAuth state parameter (for CSRF protection)
|
|
959
|
+
*/
|
|
960
|
+
state?: string;
|
|
961
|
+
/**
|
|
962
|
+
* Timestamp when this state was saved
|
|
963
|
+
*/
|
|
964
|
+
savedAt: number;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Login form selectors for standard OAuth login automation
|
|
968
|
+
*/
|
|
969
|
+
interface OAuthLoginSelectors {
|
|
970
|
+
/** Selector for username/email input field */
|
|
971
|
+
usernameInput: string;
|
|
972
|
+
/** Selector for password input field */
|
|
973
|
+
passwordInput: string;
|
|
974
|
+
/** Selector for login submit button */
|
|
975
|
+
submitButton: string;
|
|
976
|
+
/** Selector for consent/authorize button (optional) */
|
|
977
|
+
consentButton?: string;
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Base configuration shared by all OAuth setup strategies
|
|
981
|
+
*/
|
|
982
|
+
interface OAuthSetupBaseConfig {
|
|
983
|
+
/** OAuth authorization server metadata URL */
|
|
984
|
+
authServerUrl: string;
|
|
985
|
+
/** Scopes to request */
|
|
986
|
+
scopes: Array<string>;
|
|
987
|
+
/** Path to save OAuth state file */
|
|
988
|
+
outputPath: string;
|
|
989
|
+
/** Pre-registered client ID (optional, uses DCR if not provided) */
|
|
990
|
+
clientId?: string;
|
|
991
|
+
/** Pre-registered client secret (optional) */
|
|
992
|
+
clientSecret?: string;
|
|
993
|
+
/** Redirect URI for OAuth callback */
|
|
994
|
+
redirectUri?: string;
|
|
995
|
+
/** Resource indicator (RFC 8707) */
|
|
996
|
+
resource?: string;
|
|
997
|
+
/** Timeout for login flow in milliseconds (default: 30000) */
|
|
998
|
+
timeoutMs?: number;
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Standard login strategy: automates a form with username, password, and submit button.
|
|
1002
|
+
* Use when the IdP presents all login fields on a single page.
|
|
1003
|
+
*/
|
|
1004
|
+
interface StandardLoginConfig {
|
|
1005
|
+
/** Login form selectors for Playwright automation */
|
|
1006
|
+
loginSelectors: OAuthLoginSelectors;
|
|
1007
|
+
/** Test user credentials */
|
|
1008
|
+
credentials: {
|
|
1009
|
+
username: string;
|
|
1010
|
+
password: string;
|
|
1011
|
+
};
|
|
1012
|
+
customLoginFlow?: never;
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Custom login strategy: full control over the browser-based login flow.
|
|
1016
|
+
* Use for multi-step logins, MFA, custom consent screens, or any flow
|
|
1017
|
+
* that doesn't fit the standard username/password/submit pattern.
|
|
1018
|
+
*
|
|
1019
|
+
* The callback receives a Playwright Page already navigated to the OAuth
|
|
1020
|
+
* authorization URL. Complete the login so the IdP redirects to the
|
|
1021
|
+
* callback URL — `performOAuthSetup` handles PKCE, token exchange,
|
|
1022
|
+
* and state persistence automatically.
|
|
1023
|
+
*/
|
|
1024
|
+
interface CustomLoginConfig {
|
|
1025
|
+
/**
|
|
1026
|
+
* Custom Playwright automation for the IdP login flow.
|
|
1027
|
+
*
|
|
1028
|
+
* @param page - Playwright Page already navigated to the OAuth authorization URL
|
|
1029
|
+
*
|
|
1030
|
+
* @example
|
|
1031
|
+
* ```typescript
|
|
1032
|
+
* customLoginFlow: async (page) => {
|
|
1033
|
+
* await page.fill('#username', process.env.TEST_USER!);
|
|
1034
|
+
* await page.click('#continue');
|
|
1035
|
+
* await page.fill('#password', process.env.TEST_PASS!);
|
|
1036
|
+
* await page.click('#submit');
|
|
1037
|
+
* }
|
|
1038
|
+
* ```
|
|
1039
|
+
*/
|
|
1040
|
+
customLoginFlow: (page: Page) => Promise<void>;
|
|
1041
|
+
loginSelectors?: never;
|
|
1042
|
+
credentials?: never;
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Configuration for OAuth setup flow.
|
|
1046
|
+
*
|
|
1047
|
+
* Provide either `loginSelectors` + `credentials` for standard form-based login,
|
|
1048
|
+
* or `customLoginFlow` for full control over the browser automation.
|
|
1049
|
+
*/
|
|
1050
|
+
type OAuthSetupConfig = OAuthSetupBaseConfig & (StandardLoginConfig | CustomLoginConfig);
|
|
1051
|
+
/**
|
|
1052
|
+
* Result of token exchange or refresh
|
|
1053
|
+
*/
|
|
1054
|
+
interface TokenResult {
|
|
1055
|
+
/**
|
|
1056
|
+
* Access token
|
|
1057
|
+
*/
|
|
1058
|
+
accessToken: string;
|
|
1059
|
+
/**
|
|
1060
|
+
* Token type (typically "Bearer")
|
|
1061
|
+
*/
|
|
1062
|
+
tokenType: string;
|
|
1063
|
+
/**
|
|
1064
|
+
* Expires in seconds
|
|
1065
|
+
*/
|
|
1066
|
+
expiresIn?: number;
|
|
1067
|
+
/**
|
|
1068
|
+
* Refresh token (if provided)
|
|
1069
|
+
*/
|
|
1070
|
+
refreshToken?: string;
|
|
1071
|
+
/**
|
|
1072
|
+
* Granted scopes (space-separated)
|
|
1073
|
+
*/
|
|
1074
|
+
scope?: string;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* OAuth client provider implementation for MCP SDK
|
|
1079
|
+
*
|
|
1080
|
+
* Implements the MCP SDK's OAuthClientProvider interface using file-based storage
|
|
1081
|
+
* for integration with Playwright's auth state pattern.
|
|
1082
|
+
*/
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Configuration for the Playwright OAuth client provider
|
|
1086
|
+
*/
|
|
1087
|
+
interface PlaywrightOAuthClientProviderConfig {
|
|
1088
|
+
/**
|
|
1089
|
+
* Path to the auth state file (e.g., playwright/.auth/oauth-state.json)
|
|
1090
|
+
*/
|
|
1091
|
+
storagePath: string;
|
|
1092
|
+
/**
|
|
1093
|
+
* OAuth redirect URI for callback
|
|
1094
|
+
*/
|
|
1095
|
+
redirectUri: string;
|
|
1096
|
+
/**
|
|
1097
|
+
* Client metadata for DCR or display
|
|
1098
|
+
*/
|
|
1099
|
+
clientMetadata?: Partial<OAuthClientMetadata>;
|
|
1100
|
+
/**
|
|
1101
|
+
* Pre-registered client ID (if not using DCR)
|
|
1102
|
+
*/
|
|
1103
|
+
clientId?: string;
|
|
1104
|
+
/**
|
|
1105
|
+
* Pre-registered client secret (if not using DCR)
|
|
1106
|
+
*/
|
|
1107
|
+
clientSecret?: string;
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* OAuth client provider that implements the MCP SDK's OAuthClientProvider interface
|
|
1111
|
+
*
|
|
1112
|
+
* Uses file-based storage for integration with Playwright's auth state pattern.
|
|
1113
|
+
* Auth state is persisted to disk so it can be reused across test runs.
|
|
1114
|
+
*
|
|
1115
|
+
* @example
|
|
1116
|
+
* ```typescript
|
|
1117
|
+
* const provider = new PlaywrightOAuthClientProvider({
|
|
1118
|
+
* storagePath: 'playwright/.auth/oauth-state.json',
|
|
1119
|
+
* redirectUri: 'http://localhost:3000/callback',
|
|
1120
|
+
* });
|
|
1121
|
+
*
|
|
1122
|
+
* const transport = new StreamableHTTPClientTransport(serverUrl, {
|
|
1123
|
+
* authProvider: provider,
|
|
1124
|
+
* });
|
|
1125
|
+
* ```
|
|
1126
|
+
*/
|
|
1127
|
+
declare class PlaywrightOAuthClientProvider implements OAuthClientProvider {
|
|
1128
|
+
private readonly config;
|
|
1129
|
+
private cachedState;
|
|
1130
|
+
private stateParam;
|
|
1131
|
+
constructor(config: PlaywrightOAuthClientProviderConfig);
|
|
1132
|
+
/**
|
|
1133
|
+
* The URL to redirect the user agent to after authorization
|
|
1134
|
+
*/
|
|
1135
|
+
get redirectUrl(): string;
|
|
1136
|
+
/**
|
|
1137
|
+
* Metadata about this OAuth client
|
|
1138
|
+
*/
|
|
1139
|
+
get clientMetadata(): OAuthClientMetadata;
|
|
1140
|
+
/**
|
|
1141
|
+
* Returns an OAuth2 state parameter
|
|
1142
|
+
*/
|
|
1143
|
+
state(): string;
|
|
1144
|
+
/**
|
|
1145
|
+
* Loads information about this OAuth client
|
|
1146
|
+
*/
|
|
1147
|
+
clientInformation(): Promise<OAuthClientInformationFull | undefined>;
|
|
1148
|
+
/**
|
|
1149
|
+
* Saves client information from Dynamic Client Registration
|
|
1150
|
+
*/
|
|
1151
|
+
saveClientInformation(clientInformation: OAuthClientInformationFull): Promise<void>;
|
|
1152
|
+
/**
|
|
1153
|
+
* Loads any existing OAuth tokens for the current session
|
|
1154
|
+
*/
|
|
1155
|
+
tokens(): Promise<OAuthTokens | undefined>;
|
|
1156
|
+
/**
|
|
1157
|
+
* Stores new OAuth tokens for the current session
|
|
1158
|
+
*
|
|
1159
|
+
* The code verifier is cleared after a successful token exchange — it is
|
|
1160
|
+
* single-use per PKCE spec and must not persist beyond the exchange.
|
|
1161
|
+
*/
|
|
1162
|
+
saveTokens(tokens: OAuthTokens): Promise<void>;
|
|
1163
|
+
/**
|
|
1164
|
+
* Invoked to redirect the user agent to the given URL
|
|
1165
|
+
*
|
|
1166
|
+
* In a testing context, this is typically handled by Playwright automation.
|
|
1167
|
+
* This implementation throws an error to signal that the caller needs to
|
|
1168
|
+
* handle the redirect externally.
|
|
1169
|
+
*/
|
|
1170
|
+
redirectToAuthorization(authorizationUrl: URL): Promise<void>;
|
|
1171
|
+
/**
|
|
1172
|
+
* Saves a PKCE code verifier for the current session
|
|
1173
|
+
*/
|
|
1174
|
+
saveCodeVerifier(codeVerifier: string): Promise<void>;
|
|
1175
|
+
/**
|
|
1176
|
+
* Loads the PKCE code verifier for the current session
|
|
1177
|
+
*/
|
|
1178
|
+
codeVerifier(): Promise<string>;
|
|
1179
|
+
/**
|
|
1180
|
+
* Invalidates the specified credentials
|
|
1181
|
+
*/
|
|
1182
|
+
invalidateCredentials(scope: 'all' | 'client' | 'tokens' | 'verifier'): Promise<void>;
|
|
1183
|
+
private loadState;
|
|
1184
|
+
private saveState;
|
|
1185
|
+
private deleteState;
|
|
1186
|
+
private createEmptyState;
|
|
1187
|
+
private generateRandomString;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* OAuth flow utilities using oauth4webapi
|
|
1192
|
+
*
|
|
1193
|
+
* Implements OAuth 2.1 with PKCE as required by MCP specification
|
|
1194
|
+
*/
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Discovered OAuth authorization server metadata
|
|
1198
|
+
*/
|
|
1199
|
+
interface AuthServerMetadata {
|
|
1200
|
+
/**
|
|
1201
|
+
* The oauth4webapi AuthorizationServer object
|
|
1202
|
+
*/
|
|
1203
|
+
server: oauth.AuthorizationServer;
|
|
1204
|
+
/**
|
|
1205
|
+
* Issuer URL
|
|
1206
|
+
*/
|
|
1207
|
+
issuer: string;
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Configuration for token refresh
|
|
1211
|
+
*/
|
|
1212
|
+
interface TokenRefreshConfig {
|
|
1213
|
+
/**
|
|
1214
|
+
* Authorization server metadata
|
|
1215
|
+
*/
|
|
1216
|
+
authServer: AuthServerMetadata;
|
|
1217
|
+
/**
|
|
1218
|
+
* Client ID
|
|
1219
|
+
*/
|
|
1220
|
+
clientId: string;
|
|
1221
|
+
/**
|
|
1222
|
+
* Client secret (for confidential clients)
|
|
1223
|
+
*/
|
|
1224
|
+
clientSecret?: string;
|
|
1225
|
+
/**
|
|
1226
|
+
* Refresh token
|
|
1227
|
+
*/
|
|
1228
|
+
refreshToken: string;
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Refreshes an access token using a refresh token
|
|
1232
|
+
*
|
|
1233
|
+
* @param config - Token refresh configuration
|
|
1234
|
+
* @returns New token result
|
|
1235
|
+
*/
|
|
1236
|
+
declare function refreshAccessToken(config: TokenRefreshConfig): Promise<TokenResult>;
|
|
1237
|
+
/**
|
|
1238
|
+
* Configuration for client credentials grant
|
|
1239
|
+
*/
|
|
1240
|
+
interface ClientCredentialsConfig {
|
|
1241
|
+
/**
|
|
1242
|
+
* Token endpoint URL
|
|
1243
|
+
*/
|
|
1244
|
+
tokenEndpoint: string;
|
|
1245
|
+
/**
|
|
1246
|
+
* OAuth client ID
|
|
1247
|
+
*/
|
|
1248
|
+
clientId: string;
|
|
1249
|
+
/**
|
|
1250
|
+
* OAuth client secret
|
|
1251
|
+
*/
|
|
1252
|
+
clientSecret: string;
|
|
1253
|
+
/**
|
|
1254
|
+
* Scopes to request (optional)
|
|
1255
|
+
*/
|
|
1256
|
+
scopes?: string[];
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Performs the OAuth 2.1 client credentials grant to obtain an access token.
|
|
1260
|
+
* Suitable for CI/CD machine-to-machine authentication.
|
|
1261
|
+
*
|
|
1262
|
+
* Uses oauth4webapi for spec-compliant request construction and response validation,
|
|
1263
|
+
* consistent with how the rest of this module handles OAuth flows.
|
|
1264
|
+
*
|
|
1265
|
+
* @param config - Client credentials configuration
|
|
1266
|
+
* @returns Token result
|
|
1267
|
+
*/
|
|
1268
|
+
declare function performClientCredentialsFlow(config: ClientCredentialsConfig): Promise<TokenResult>;
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* OAuth Protected Resource and Authorization Server discovery
|
|
1272
|
+
*
|
|
1273
|
+
* Implements RFC 9728 (OAuth Protected Resource Metadata) and
|
|
1274
|
+
* RFC 8414 (Authorization Server Metadata) for MCP servers.
|
|
1275
|
+
*/
|
|
1276
|
+
|
|
1277
|
+
/**
|
|
1278
|
+
* MCP Protocol version header value
|
|
1279
|
+
*/
|
|
1280
|
+
declare const MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
1281
|
+
/**
|
|
1282
|
+
* Protected Resource Metadata (RFC 9728)
|
|
1283
|
+
*/
|
|
1284
|
+
interface ProtectedResourceMetadata {
|
|
1285
|
+
/**
|
|
1286
|
+
* The protected resource URL
|
|
1287
|
+
*/
|
|
1288
|
+
resource: string;
|
|
1289
|
+
/**
|
|
1290
|
+
* Array of authorization server URLs
|
|
1291
|
+
*/
|
|
1292
|
+
authorization_servers?: Array<string>;
|
|
1293
|
+
/**
|
|
1294
|
+
* Scopes supported by the protected resource
|
|
1295
|
+
*/
|
|
1296
|
+
scopes_supported?: Array<string>;
|
|
1297
|
+
/**
|
|
1298
|
+
* Bearer token formats supported
|
|
1299
|
+
*/
|
|
1300
|
+
bearer_methods_supported?: Array<string>;
|
|
1301
|
+
/**
|
|
1302
|
+
* Resource documentation URL
|
|
1303
|
+
*/
|
|
1304
|
+
resource_documentation?: string;
|
|
1305
|
+
/**
|
|
1306
|
+
* Resource signing algorithms
|
|
1307
|
+
*/
|
|
1308
|
+
resource_signing_alg_values_supported?: Array<string>;
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Result of protected resource discovery
|
|
1312
|
+
*/
|
|
1313
|
+
interface ProtectedResourceDiscoveryResult {
|
|
1314
|
+
/**
|
|
1315
|
+
* The discovered metadata
|
|
1316
|
+
*/
|
|
1317
|
+
metadata: ProtectedResourceMetadata;
|
|
1318
|
+
/**
|
|
1319
|
+
* The URL where metadata was found
|
|
1320
|
+
*/
|
|
1321
|
+
discoveryUrl: string;
|
|
1322
|
+
/**
|
|
1323
|
+
* Whether path-aware discovery was used (vs base discovery)
|
|
1324
|
+
*/
|
|
1325
|
+
usedPathAwareDiscovery: boolean;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Discovers protected resource metadata per RFC 9728
|
|
1329
|
+
*
|
|
1330
|
+
* Follows RFC 9728 Section 4.1 for path-aware discovery:
|
|
1331
|
+
* 1. First tries: {origin}/.well-known/oauth-protected-resource{pathname}
|
|
1332
|
+
* 2. Falls back to: {origin}/.well-known/oauth-protected-resource
|
|
1333
|
+
*
|
|
1334
|
+
* @param mcpServerUrl - The MCP server URL
|
|
1335
|
+
* @returns Protected resource discovery result
|
|
1336
|
+
* @throws Error if discovery fails completely
|
|
1337
|
+
*
|
|
1338
|
+
* @example
|
|
1339
|
+
* const result = await discoverProtectedResource('https://api.example.com/mcp/default');
|
|
1340
|
+
* console.log(result.metadata.authorization_servers);
|
|
1341
|
+
*/
|
|
1342
|
+
declare function discoverProtectedResource(mcpServerUrl: string): Promise<ProtectedResourceDiscoveryResult>;
|
|
1343
|
+
/**
|
|
1344
|
+
* Error thrown when discovery fails
|
|
1345
|
+
*/
|
|
1346
|
+
declare class DiscoveryError extends Error {
|
|
1347
|
+
readonly status?: number | undefined;
|
|
1348
|
+
readonly url?: string | undefined;
|
|
1349
|
+
constructor(message: string, status?: number | undefined, url?: string | undefined);
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Discovers OAuth Authorization Server metadata per RFC 8414
|
|
1353
|
+
*
|
|
1354
|
+
* Wraps oauth4webapi's discovery with MCP-specific headers.
|
|
1355
|
+
*
|
|
1356
|
+
* @param authServerUrl - The authorization server URL
|
|
1357
|
+
* @returns Authorization server metadata
|
|
1358
|
+
* @throws Error if discovery fails
|
|
1359
|
+
*
|
|
1360
|
+
* @example
|
|
1361
|
+
* const authServer = await discoverAuthorizationServer('https://auth.example.com');
|
|
1362
|
+
* console.log(authServer.server.token_endpoint);
|
|
1363
|
+
*/
|
|
1364
|
+
declare function discoverAuthorizationServer(authServerUrl: string): Promise<AuthServerMetadata>;
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* OAuth token storage with environment variable support for CI/CD
|
|
1368
|
+
*
|
|
1369
|
+
* Provides file-based storage for OAuth state per MCP server, with support
|
|
1370
|
+
* for token injection via environment variables for automated testing.
|
|
1371
|
+
*/
|
|
1372
|
+
|
|
1373
|
+
/**
|
|
1374
|
+
* Combined server metadata (auth server + protected resource)
|
|
1375
|
+
*/
|
|
1376
|
+
interface StoredServerMetadata {
|
|
1377
|
+
/**
|
|
1378
|
+
* Authorization server metadata
|
|
1379
|
+
*/
|
|
1380
|
+
authServer: AuthServerMetadata;
|
|
1381
|
+
/**
|
|
1382
|
+
* Protected resource metadata
|
|
1383
|
+
*/
|
|
1384
|
+
protectedResource: ProtectedResourceMetadata;
|
|
1385
|
+
/**
|
|
1386
|
+
* Timestamp when metadata was discovered
|
|
1387
|
+
*/
|
|
1388
|
+
discoveredAt: number;
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Environment variable names for CI/CD token injection
|
|
1392
|
+
*/
|
|
1393
|
+
declare const ENV_VAR_NAMES: {
|
|
1394
|
+
readonly accessToken: "MCP_ACCESS_TOKEN";
|
|
1395
|
+
readonly refreshToken: "MCP_REFRESH_TOKEN";
|
|
1396
|
+
readonly tokenType: "MCP_TOKEN_TYPE";
|
|
1397
|
+
readonly expiresAt: "MCP_TOKEN_EXPIRES_AT";
|
|
1398
|
+
};
|
|
1399
|
+
/**
|
|
1400
|
+
* Reads tokens from environment variables (for CI/CD)
|
|
1401
|
+
*
|
|
1402
|
+
* @returns StoredTokens if MCP_ACCESS_TOKEN is set, null otherwise
|
|
1403
|
+
*/
|
|
1404
|
+
declare function loadTokensFromEnv(): StoredTokens | null;
|
|
1405
|
+
/**
|
|
1406
|
+
* Programmatically inject tokens into storage (for CI/CD setup)
|
|
1407
|
+
*
|
|
1408
|
+
* @param serverUrl - The MCP server URL
|
|
1409
|
+
* @param tokens - The tokens to inject
|
|
1410
|
+
* @param stateDir - Optional custom state directory
|
|
1411
|
+
*/
|
|
1412
|
+
declare function injectTokens(serverUrl: string, tokens: StoredTokens, stateDir?: string): Promise<void>;
|
|
1413
|
+
/**
|
|
1414
|
+
* Load stored OAuth tokens for an MCP server
|
|
1415
|
+
*
|
|
1416
|
+
* Reads tokens from the standard storage location for the given server URL.
|
|
1417
|
+
* Tokens are stored by `mcp-server-tester login` or `injectTokens()`.
|
|
1418
|
+
*
|
|
1419
|
+
* @param serverUrl - The MCP server URL
|
|
1420
|
+
* @param stateDir - Optional custom state directory
|
|
1421
|
+
* @returns StoredTokens if found, null otherwise
|
|
1422
|
+
*
|
|
1423
|
+
* @example
|
|
1424
|
+
* ```typescript
|
|
1425
|
+
* // After running: npx mcp-server-tester login https://api.example.com/mcp
|
|
1426
|
+
* const tokens = await loadTokens('https://api.example.com/mcp');
|
|
1427
|
+
* if (tokens?.accessToken) {
|
|
1428
|
+
* // Use the token — never log raw token values
|
|
1429
|
+
* headers.Authorization = `Bearer ${tokens.accessToken}`;
|
|
1430
|
+
* }
|
|
1431
|
+
* ```
|
|
1432
|
+
*/
|
|
1433
|
+
declare function loadTokens(serverUrl: string, stateDir?: string): Promise<StoredTokens | null>;
|
|
1434
|
+
/**
|
|
1435
|
+
* Check if valid OAuth tokens exist for an MCP server
|
|
1436
|
+
*
|
|
1437
|
+
* Returns true if tokens exist and are not expired (with buffer).
|
|
1438
|
+
* Use this to check if authentication is needed before making requests.
|
|
1439
|
+
*
|
|
1440
|
+
* @param serverUrl - The MCP server URL
|
|
1441
|
+
* @param options - Optional configuration
|
|
1442
|
+
* @param options.stateDir - Custom state directory
|
|
1443
|
+
* @param options.bufferMs - Buffer time before expiration (default: 60000ms)
|
|
1444
|
+
* @returns true if valid (non-expired) tokens exist
|
|
1445
|
+
*
|
|
1446
|
+
* @example
|
|
1447
|
+
* ```typescript
|
|
1448
|
+
* if (await hasValidTokens('https://api.example.com/mcp')) {
|
|
1449
|
+
* // Use stored tokens
|
|
1450
|
+
* const tokens = await loadTokens('https://api.example.com/mcp');
|
|
1451
|
+
* } else {
|
|
1452
|
+
* console.log('Run: npx mcp-server-tester login https://api.example.com/mcp');
|
|
1453
|
+
* }
|
|
1454
|
+
* ```
|
|
1455
|
+
*/
|
|
1456
|
+
declare function hasValidTokens(serverUrl: string, options?: {
|
|
1457
|
+
stateDir?: string;
|
|
1458
|
+
bufferMs?: number;
|
|
1459
|
+
}): Promise<boolean>;
|
|
1460
|
+
|
|
1461
|
+
/**
|
|
1462
|
+
* CLI OAuth client for command-line authentication flows
|
|
1463
|
+
*
|
|
1464
|
+
* Provides browser-based OAuth authentication for CLI environments,
|
|
1465
|
+
* with support for environment variable token injection for CI/CD.
|
|
1466
|
+
*/
|
|
1467
|
+
/**
|
|
1468
|
+
* Configuration for CLI OAuth client
|
|
1469
|
+
*/
|
|
1470
|
+
interface CLIOAuthClientConfig {
|
|
1471
|
+
/**
|
|
1472
|
+
* MCP server URL (for protected resource discovery)
|
|
1473
|
+
*/
|
|
1474
|
+
mcpServerUrl: string;
|
|
1475
|
+
/**
|
|
1476
|
+
* Scopes to request (optional, uses discovered scopes if not provided)
|
|
1477
|
+
*/
|
|
1478
|
+
scopes?: Array<string>;
|
|
1479
|
+
/**
|
|
1480
|
+
* Custom storage directory
|
|
1481
|
+
*/
|
|
1482
|
+
stateDir?: string;
|
|
1483
|
+
/**
|
|
1484
|
+
* Pre-registered client ID (skips DCR if provided)
|
|
1485
|
+
*/
|
|
1486
|
+
clientId?: string;
|
|
1487
|
+
/**
|
|
1488
|
+
* Pre-registered client secret
|
|
1489
|
+
*/
|
|
1490
|
+
clientSecret?: string;
|
|
1491
|
+
/**
|
|
1492
|
+
* Preferred callback port (default: random available port)
|
|
1493
|
+
*/
|
|
1494
|
+
callbackPort?: number;
|
|
1495
|
+
/**
|
|
1496
|
+
* Timeout for OAuth flow in milliseconds (default: 300000 = 5 min)
|
|
1497
|
+
*/
|
|
1498
|
+
timeoutMs?: number;
|
|
1499
|
+
/**
|
|
1500
|
+
* Client name for DCR registration
|
|
1501
|
+
*/
|
|
1502
|
+
clientName?: string;
|
|
1503
|
+
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Result of CLI OAuth authentication
|
|
1506
|
+
*/
|
|
1507
|
+
interface CLIOAuthResult {
|
|
1508
|
+
/**
|
|
1509
|
+
* Access token
|
|
1510
|
+
*/
|
|
1511
|
+
accessToken: string;
|
|
1512
|
+
/**
|
|
1513
|
+
* Token type (typically "Bearer")
|
|
1514
|
+
*/
|
|
1515
|
+
tokenType: string;
|
|
1516
|
+
/**
|
|
1517
|
+
* Expiration timestamp (Unix ms)
|
|
1518
|
+
*/
|
|
1519
|
+
expiresAt?: number;
|
|
1520
|
+
/**
|
|
1521
|
+
* Whether token was refreshed vs newly acquired
|
|
1522
|
+
*/
|
|
1523
|
+
refreshed: boolean;
|
|
1524
|
+
/**
|
|
1525
|
+
* Scopes that were requested (only set for new authentications)
|
|
1526
|
+
*/
|
|
1527
|
+
requestedScopes?: string[];
|
|
1528
|
+
/**
|
|
1529
|
+
* Whether token came from environment variables
|
|
1530
|
+
*/
|
|
1531
|
+
fromEnv: boolean;
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* CLI OAuth client for command-line authentication flows
|
|
1535
|
+
*/
|
|
1536
|
+
declare class CLIOAuthClient {
|
|
1537
|
+
private readonly config;
|
|
1538
|
+
private readonly storage;
|
|
1539
|
+
constructor(config: CLIOAuthClientConfig);
|
|
1540
|
+
/**
|
|
1541
|
+
* Get a valid access token, authenticating if necessary
|
|
1542
|
+
*
|
|
1543
|
+
* Token resolution priority:
|
|
1544
|
+
* 1. Check environment variables (for CI/CD)
|
|
1545
|
+
* 2. Check file storage for cached tokens
|
|
1546
|
+
* 3. Try to refresh if expired but refresh token exists
|
|
1547
|
+
* 4. Run full OAuth flow if needed
|
|
1548
|
+
*/
|
|
1549
|
+
getAccessToken(): Promise<CLIOAuthResult>;
|
|
1550
|
+
/**
|
|
1551
|
+
* Try to get a valid access token without triggering browser auth
|
|
1552
|
+
*
|
|
1553
|
+
* Returns null if no valid token is available (no stored tokens,
|
|
1554
|
+
* expired without refresh token, or refresh failed). Unlike getAccessToken(),
|
|
1555
|
+
* this will NOT open a browser for authentication.
|
|
1556
|
+
*
|
|
1557
|
+
* Use this for CLI commands that should prompt the user to run `login`
|
|
1558
|
+
* instead of automatically starting the OAuth flow.
|
|
1559
|
+
*/
|
|
1560
|
+
tryGetAccessToken(): Promise<CLIOAuthResult | null>;
|
|
1561
|
+
/**
|
|
1562
|
+
* Force a new authentication flow
|
|
1563
|
+
*/
|
|
1564
|
+
authenticate(): Promise<CLIOAuthResult>;
|
|
1565
|
+
/**
|
|
1566
|
+
* Check if stored credentials exist (may be expired)
|
|
1567
|
+
*/
|
|
1568
|
+
hasStoredCredentials(): Promise<boolean>;
|
|
1569
|
+
/**
|
|
1570
|
+
* Clear stored credentials
|
|
1571
|
+
*/
|
|
1572
|
+
clearCredentials(): Promise<void>;
|
|
1573
|
+
/**
|
|
1574
|
+
* Discover protected resource and authorization server
|
|
1575
|
+
*/
|
|
1576
|
+
private discoverServers;
|
|
1577
|
+
/**
|
|
1578
|
+
* Get existing client or register new one via DCR
|
|
1579
|
+
*/
|
|
1580
|
+
private getOrRegisterClient;
|
|
1581
|
+
/**
|
|
1582
|
+
* Register a new client via Dynamic Client Registration
|
|
1583
|
+
*/
|
|
1584
|
+
private registerClient;
|
|
1585
|
+
/**
|
|
1586
|
+
* Perform the full OAuth authorization flow
|
|
1587
|
+
*/
|
|
1588
|
+
private performOAuthFlow;
|
|
1589
|
+
/**
|
|
1590
|
+
* Refresh an expired token
|
|
1591
|
+
*
|
|
1592
|
+
* Uses the clientId stored with the tokens (if available) to ensure
|
|
1593
|
+
* the refresh request uses the same client that obtained the original tokens.
|
|
1594
|
+
* This is important because refresh tokens are bound to the client_id.
|
|
1595
|
+
*/
|
|
1596
|
+
private refreshStoredToken;
|
|
1597
|
+
/**
|
|
1598
|
+
* Start local callback server
|
|
1599
|
+
*/
|
|
1600
|
+
private startCallbackServer;
|
|
1601
|
+
/**
|
|
1602
|
+
* Open browser or print URL for headless environments
|
|
1603
|
+
*/
|
|
1604
|
+
private openBrowserOrPrintUrl;
|
|
1605
|
+
/**
|
|
1606
|
+
* Convert TokenResult to StoredTokens
|
|
1607
|
+
*
|
|
1608
|
+
* @param result - Token result from exchange or refresh
|
|
1609
|
+
* @param clientId - Client ID that was used to obtain these tokens
|
|
1610
|
+
*/
|
|
1611
|
+
private tokenResultToStoredTokens;
|
|
1612
|
+
/**
|
|
1613
|
+
* HTML page for successful authentication
|
|
1614
|
+
*/
|
|
1615
|
+
private successHtml;
|
|
1616
|
+
/**
|
|
1617
|
+
* HTML page for authentication error
|
|
1618
|
+
*/
|
|
1619
|
+
private errorHtml;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
/**
|
|
1623
|
+
* Options for creating an MCP fixture
|
|
1624
|
+
*/
|
|
1625
|
+
interface MCPFixtureOptions {
|
|
1626
|
+
/**
|
|
1627
|
+
* Authentication type used for this test
|
|
1628
|
+
* - 'oauth': Interactive OAuth 2.1 with PKCE (browser-based authentication)
|
|
1629
|
+
* - 'api-token': Static API token (e.g., from a dashboard or environment variable)
|
|
1630
|
+
* - 'none': No authentication
|
|
1631
|
+
*/
|
|
1632
|
+
authType?: AuthType;
|
|
1633
|
+
/**
|
|
1634
|
+
* Playwright project name for this test
|
|
1635
|
+
* Used for filtering and grouping in the reporter
|
|
1636
|
+
*/
|
|
1637
|
+
project?: string;
|
|
1638
|
+
/**
|
|
1639
|
+
* Timeout in milliseconds for MCP tool/list operations. Default: 30000
|
|
1640
|
+
*/
|
|
1641
|
+
callTimeoutMs?: number;
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* High-level API for interacting with MCP servers in tests
|
|
1645
|
+
*
|
|
1646
|
+
* This interface wraps the raw MCP Client with test-friendly methods
|
|
1647
|
+
*/
|
|
1648
|
+
interface MCPFixtureApi {
|
|
1649
|
+
/**
|
|
1650
|
+
* The underlying MCP client (for advanced usage)
|
|
1651
|
+
*/
|
|
1652
|
+
client: Client;
|
|
1653
|
+
/**
|
|
1654
|
+
* Authentication type used for this test session
|
|
1655
|
+
*/
|
|
1656
|
+
authType: AuthType;
|
|
1657
|
+
/**
|
|
1658
|
+
* Playwright project name for this test session
|
|
1659
|
+
*/
|
|
1660
|
+
project?: string;
|
|
1661
|
+
/**
|
|
1662
|
+
* Lists all available tools from the MCP server
|
|
1663
|
+
*
|
|
1664
|
+
* @returns Array of tool definitions
|
|
1665
|
+
*/
|
|
1666
|
+
listTools(): Promise<Array<Tool>>;
|
|
1667
|
+
/**
|
|
1668
|
+
* Calls a tool on the MCP server
|
|
1669
|
+
*
|
|
1670
|
+
* @param name - Tool name
|
|
1671
|
+
* @param args - Tool arguments
|
|
1672
|
+
* @returns Tool call result
|
|
1673
|
+
*/
|
|
1674
|
+
callTool<TArgs extends Record<string, unknown> = Record<string, unknown>>(name: string, args: TArgs): Promise<CallToolResult>;
|
|
1675
|
+
/**
|
|
1676
|
+
* Gets information about the connected server
|
|
1677
|
+
*/
|
|
1678
|
+
getServerInfo(): {
|
|
1679
|
+
name?: string;
|
|
1680
|
+
version?: string;
|
|
1681
|
+
} | null;
|
|
1682
|
+
}
|
|
1683
|
+
/**
|
|
1684
|
+
* Creates an MCP fixture wrapper around a Client, providing a high-level
|
|
1685
|
+
* {@link MCPFixtureApi} without requiring Playwright's `test.extend` pattern.
|
|
1686
|
+
*
|
|
1687
|
+
* Use this when you need to set up an MCP fixture manually — for example in
|
|
1688
|
+
* custom fixture hierarchies, non-Playwright test runners (e.g. Vitest,
|
|
1689
|
+
* Jest), or when you want to compose the fixture with other lifecycle
|
|
1690
|
+
* management logic that doesn't fit the standard `test.extend` model.
|
|
1691
|
+
*
|
|
1692
|
+
* For the typical Playwright use case, prefer importing `test` and `mcp`
|
|
1693
|
+
* directly from `@gleanwork/mcp-server-tester/fixtures/mcp`, which wires
|
|
1694
|
+
* this function up automatically.
|
|
1695
|
+
*
|
|
1696
|
+
* When `testInfo` is provided, all MCP operations are automatically wrapped
|
|
1697
|
+
* in `test.step()` calls and attachments are created for the MCP Test
|
|
1698
|
+
* Reporter. Omit `testInfo` for lightweight usage outside Playwright.
|
|
1699
|
+
*
|
|
1700
|
+
* @param client - The MCP client to wrap (created via `createMCPClientForConfig`)
|
|
1701
|
+
* @param testInfo - Optional Playwright TestInfo for auto-tracking and reporter attachments
|
|
1702
|
+
* @param options - Optional fixture options (authType, project)
|
|
1703
|
+
* @returns MCPFixtureApi instance
|
|
1704
|
+
*
|
|
1705
|
+
* @example
|
|
1706
|
+
* ```typescript
|
|
1707
|
+
* // Advanced: custom fixture setup inside test.extend
|
|
1708
|
+
* const test = base.extend<{ mcp: MCPFixtureApi }>({
|
|
1709
|
+
* mcp: async ({}, use, testInfo) => {
|
|
1710
|
+
* const client = await createMCPClientForConfig(config);
|
|
1711
|
+
* const api = createMCPFixture(client, testInfo, { authType: 'api-token' });
|
|
1712
|
+
* await use(api);
|
|
1713
|
+
* await closeMCPClient(client);
|
|
1714
|
+
* }
|
|
1715
|
+
* });
|
|
1716
|
+
*
|
|
1717
|
+
* // Non-Playwright usage (no reporter attachments)
|
|
1718
|
+
* const client = await createMCPClientForConfig(config);
|
|
1719
|
+
* const api = createMCPFixture(client);
|
|
1720
|
+
* const tools = await api.listTools();
|
|
1721
|
+
* ```
|
|
1722
|
+
*/
|
|
1723
|
+
declare function createMCPFixture(client: Client, testInfo?: TestInfo, options?: MCPFixtureOptions): MCPFixtureApi;
|
|
1724
|
+
|
|
1725
|
+
/**
|
|
1726
|
+
* Test-scoped auth fixtures interface
|
|
1727
|
+
*/
|
|
1728
|
+
interface MCPAuthFixtures {
|
|
1729
|
+
/**
|
|
1730
|
+
* OAuth client provider for MCP authentication
|
|
1731
|
+
*/
|
|
1732
|
+
mcpAuthProvider: OAuthClientProvider | undefined;
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Extended Playwright test with MCP auth fixtures
|
|
1736
|
+
*
|
|
1737
|
+
* Use this when you need OAuth authentication for MCP server testing.
|
|
1738
|
+
*
|
|
1739
|
+
* @example
|
|
1740
|
+
* ```typescript
|
|
1741
|
+
* // test.ts
|
|
1742
|
+
* import { test } from '@gleanwork/mcp-server-tester/fixtures/mcpAuth';
|
|
1743
|
+
*
|
|
1744
|
+
* test('authenticated MCP call', async ({ mcpAuthProvider }) => {
|
|
1745
|
+
* // mcpAuthProvider can be passed to createMCPClientForConfig
|
|
1746
|
+
* });
|
|
1747
|
+
* ```
|
|
1748
|
+
*/
|
|
1749
|
+
declare const test: playwright_test.TestType<playwright_test.PlaywrightTestArgs & playwright_test.PlaywrightTestOptions & MCPAuthFixtures, playwright_test.PlaywrightWorkerArgs & playwright_test.PlaywrightWorkerOptions>;
|
|
1750
|
+
|
|
1751
|
+
/**
|
|
1752
|
+
* Options for creating an MCP client
|
|
1753
|
+
*/
|
|
1754
|
+
interface CreateMCPClientOptions {
|
|
1755
|
+
/**
|
|
1756
|
+
* Client information (name and version)
|
|
1757
|
+
*/
|
|
1758
|
+
clientInfo?: {
|
|
1759
|
+
name?: string;
|
|
1760
|
+
version?: string;
|
|
1761
|
+
};
|
|
1762
|
+
/**
|
|
1763
|
+
* OAuth client provider for authentication
|
|
1764
|
+
*
|
|
1765
|
+
* When provided, the MCP SDK handles OAuth flow automatically.
|
|
1766
|
+
* This takes precedence over static token auth in config.auth.accessToken.
|
|
1767
|
+
*/
|
|
1768
|
+
authProvider?: OAuthClientProvider;
|
|
1769
|
+
/**
|
|
1770
|
+
* Sampling handler callback for LLM sampling requests from the server.
|
|
1771
|
+
*
|
|
1772
|
+
* When provided, the client will advertise sampling capability to the server.
|
|
1773
|
+
* When absent, sampling is removed from declared capabilities so the client
|
|
1774
|
+
* does not falsely advertise support it cannot fulfill.
|
|
1775
|
+
*/
|
|
1776
|
+
samplingHandler?: (...args: unknown[]) => unknown;
|
|
1777
|
+
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Creates and connects an MCP client based on the provided configuration
|
|
1780
|
+
*
|
|
1781
|
+
* @param config - MCP configuration (will be validated)
|
|
1782
|
+
* @param options - Optional client options including auth provider
|
|
1783
|
+
* @returns Connected MCP Client instance
|
|
1784
|
+
* @throws {Error} If config is invalid or connection fails
|
|
1785
|
+
*
|
|
1786
|
+
* @example
|
|
1787
|
+
* // Stdio transport
|
|
1788
|
+
* const client = await createMCPClientForConfig({
|
|
1789
|
+
* transport: 'stdio',
|
|
1790
|
+
* command: 'node',
|
|
1791
|
+
* args: ['server.js']
|
|
1792
|
+
* });
|
|
1793
|
+
*
|
|
1794
|
+
* @example
|
|
1795
|
+
* // HTTP transport with static token auth
|
|
1796
|
+
* const client = await createMCPClientForConfig({
|
|
1797
|
+
* transport: 'http',
|
|
1798
|
+
* serverUrl: 'http://localhost:3000/mcp',
|
|
1799
|
+
* auth: { accessToken: 'your-token' }
|
|
1800
|
+
* });
|
|
1801
|
+
*
|
|
1802
|
+
* @example
|
|
1803
|
+
* // HTTP transport with OAuth provider
|
|
1804
|
+
* const client = await createMCPClientForConfig(
|
|
1805
|
+
* { transport: 'http', serverUrl: 'http://localhost:3000/mcp' },
|
|
1806
|
+
* { authProvider: myOAuthProvider }
|
|
1807
|
+
* );
|
|
1808
|
+
*/
|
|
1809
|
+
declare function createMCPClientForConfig(config: MCPConfig, options?: CreateMCPClientOptions): Promise<Client>;
|
|
1810
|
+
/**
|
|
1811
|
+
* Safely closes an MCP client connection
|
|
1812
|
+
*
|
|
1813
|
+
* @param client - The client to close
|
|
1814
|
+
*/
|
|
1815
|
+
declare function closeMCPClient(client: Client): Promise<void>;
|
|
1816
|
+
|
|
1817
|
+
/**
|
|
1818
|
+
* A single content block from an MCP response
|
|
1819
|
+
*/
|
|
1820
|
+
interface ContentBlock {
|
|
1821
|
+
type: string;
|
|
1822
|
+
text?: string;
|
|
1823
|
+
data?: unknown;
|
|
1824
|
+
mimeType?: string;
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Normalized representation of an MCP tool response
|
|
1828
|
+
*
|
|
1829
|
+
* This provides a consistent interface regardless of the response format
|
|
1830
|
+
* returned by the MCP server.
|
|
1831
|
+
*/
|
|
1832
|
+
interface NormalizedToolResponse {
|
|
1833
|
+
/**
|
|
1834
|
+
* Extracted text content (concatenated from all text blocks)
|
|
1835
|
+
*/
|
|
1836
|
+
text: string;
|
|
1837
|
+
/**
|
|
1838
|
+
* Original raw response from the MCP SDK
|
|
1839
|
+
*/
|
|
1840
|
+
raw: CallToolResult;
|
|
1841
|
+
/**
|
|
1842
|
+
* Whether the tool call resulted in an error
|
|
1843
|
+
*/
|
|
1844
|
+
isError: boolean;
|
|
1845
|
+
/**
|
|
1846
|
+
* Parsed content blocks from the response
|
|
1847
|
+
*/
|
|
1848
|
+
contentBlocks: ContentBlock[];
|
|
1849
|
+
/**
|
|
1850
|
+
* Structured content if present (parsed JSON or raw data)
|
|
1851
|
+
*/
|
|
1852
|
+
structuredContent: unknown;
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Normalizes an MCP CallToolResult into a consistent format
|
|
1856
|
+
*
|
|
1857
|
+
* @param result - Raw CallToolResult from the MCP SDK
|
|
1858
|
+
* @returns Normalized response with extracted text, content blocks, etc.
|
|
1859
|
+
*
|
|
1860
|
+
* @example
|
|
1861
|
+
* ```typescript
|
|
1862
|
+
* const result = await client.callTool({ name: 'read_file', arguments: { path: 'readme.txt' } });
|
|
1863
|
+
* const normalized = normalizeToolResponse(result);
|
|
1864
|
+
*
|
|
1865
|
+
* console.log(normalized.text); // "Hello World"
|
|
1866
|
+
* console.log(normalized.isError); // false
|
|
1867
|
+
* console.log(normalized.contentBlocks); // [{ type: 'text', text: 'Hello World' }]
|
|
1868
|
+
* ```
|
|
1869
|
+
*/
|
|
1870
|
+
declare function normalizeToolResponse(result: CallToolResult): NormalizedToolResponse;
|
|
1871
|
+
/**
|
|
1872
|
+
* Extracts just the text content from a normalized or raw response
|
|
1873
|
+
*
|
|
1874
|
+
* This is a convenience function that works with both:
|
|
1875
|
+
* - Raw CallToolResult from the MCP SDK
|
|
1876
|
+
* - NormalizedToolResponse from normalizeToolResponse()
|
|
1877
|
+
* - Plain strings or other legacy formats
|
|
1878
|
+
*
|
|
1879
|
+
* @param response - Response in any supported format
|
|
1880
|
+
* @returns Extracted text content
|
|
1881
|
+
*/
|
|
1882
|
+
declare function extractText(response: unknown): string;
|
|
1883
|
+
|
|
1884
|
+
/**
|
|
1885
|
+
* Types and interfaces for MCP host simulation mode
|
|
1886
|
+
*
|
|
1887
|
+
* This module provides types for testing MCP servers through MCP hosts,
|
|
1888
|
+
* validating tool descriptions, parameter clarity, and discoverability.
|
|
1889
|
+
*/
|
|
1890
|
+
|
|
1891
|
+
/**
|
|
1892
|
+
* Host type for MCP host simulation.
|
|
1893
|
+
*
|
|
1894
|
+
* - 'sdk': Programmatic via Vercel AI SDK (default). The framework's MCP connection is reused.
|
|
1895
|
+
* - 'cli': CLI-based hosts (e.g., Claude Code, Codex). Spawns a process with its own MCP connection.
|
|
1896
|
+
* - 'browser': Web-based hosts (e.g., claude.ai). Uses Playwright/CDP. (Not yet implemented.)
|
|
1897
|
+
* - 'desktop': Desktop app hosts (e.g., Claude Desktop). Uses computer use. (Not yet implemented.)
|
|
1898
|
+
*/
|
|
1899
|
+
type HostType = 'sdk' | 'cli' | 'browser' | 'desktop';
|
|
1900
|
+
/**
|
|
1901
|
+
* LLM provider for SDK-based host simulation.
|
|
1902
|
+
*
|
|
1903
|
+
* Each provider runs through the Vercel AI SDK (`ai` package)
|
|
1904
|
+
* and requires its corresponding @ai-sdk/* package:
|
|
1905
|
+
*
|
|
1906
|
+
* openai → npm install ai @ai-sdk/openai
|
|
1907
|
+
* anthropic → npm install ai @ai-sdk/anthropic
|
|
1908
|
+
* google → npm install ai @ai-sdk/google
|
|
1909
|
+
* azure → npm install ai @ai-sdk/azure
|
|
1910
|
+
* mistral → npm install ai @ai-sdk/mistral
|
|
1911
|
+
* deepseek → npm install ai @ai-sdk/deepseek
|
|
1912
|
+
* openrouter → npm install ai @openrouter/ai-sdk-provider
|
|
1913
|
+
* xai → npm install ai @ai-sdk/xai
|
|
1914
|
+
*/
|
|
1915
|
+
type LLMProvider = 'openai' | 'anthropic' | 'azure' | 'google' | 'mistral' | 'deepseek' | 'openrouter' | 'xai'
|
|
1916
|
+
/**
|
|
1917
|
+
* Anthropic Claude via Google Vertex AI.
|
|
1918
|
+
* Requires @ai-sdk/google-vertex and Application Default Credentials (gcloud auth).
|
|
1919
|
+
* Set GOOGLE_VERTEX_PROJECT and GOOGLE_VERTEX_LOCATION env vars.
|
|
1920
|
+
* Use this instead of 'anthropic' in environments where api.anthropic.com is blocked.
|
|
1921
|
+
* @example model: 'claude-3-5-haiku@20241022'
|
|
1922
|
+
*/
|
|
1923
|
+
| 'vertex-anthropic';
|
|
1924
|
+
/**
|
|
1925
|
+
* Output format for CLI host processes.
|
|
1926
|
+
*
|
|
1927
|
+
* - 'stream-json': NDJSON (one JSON object per line). Used by Claude Code (`--output-format stream-json`).
|
|
1928
|
+
* - 'json': Single JSON object on stdout.
|
|
1929
|
+
*/
|
|
1930
|
+
type CLIOutputFormat = 'stream-json' | 'json';
|
|
1931
|
+
/**
|
|
1932
|
+
* Configuration for a CLI host process.
|
|
1933
|
+
*
|
|
1934
|
+
* The process is spawned directly (no shell) with `command` and `args`.
|
|
1935
|
+
* Use `{{scenario}}` in any args entry as a placeholder for the natural
|
|
1936
|
+
* language prompt — the framework replaces it before spawning.
|
|
1937
|
+
*
|
|
1938
|
+
* Because args are passed directly to the process (not through a shell),
|
|
1939
|
+
* special characters in the scenario (quotes, newlines, `$`, etc.) are
|
|
1940
|
+
* handled safely without escaping.
|
|
1941
|
+
*
|
|
1942
|
+
* @example Claude Code
|
|
1943
|
+
* ```json
|
|
1944
|
+
* {
|
|
1945
|
+
* "command": "claude",
|
|
1946
|
+
* "args": ["-p", "{{scenario}}", "--output-format", "stream-json",
|
|
1947
|
+
* "--verbose", "--mcp-config", "{...}"]
|
|
1948
|
+
* }
|
|
1949
|
+
* ```
|
|
1950
|
+
*
|
|
1951
|
+
* @example Custom CLI
|
|
1952
|
+
* ```json
|
|
1953
|
+
* {
|
|
1954
|
+
* "command": "my-agent",
|
|
1955
|
+
* "args": ["--prompt", "{{scenario}}", "--config", "./mcp.json"],
|
|
1956
|
+
* "outputFormat": "json"
|
|
1957
|
+
* }
|
|
1958
|
+
* ```
|
|
1959
|
+
*/
|
|
1960
|
+
interface CLIConfig {
|
|
1961
|
+
/**
|
|
1962
|
+
* CLI binary to invoke.
|
|
1963
|
+
*/
|
|
1964
|
+
command: string;
|
|
1965
|
+
/**
|
|
1966
|
+
* Arguments to pass. Use `{{scenario}}` as a placeholder for the prompt.
|
|
1967
|
+
*/
|
|
1968
|
+
args: string[];
|
|
1969
|
+
/**
|
|
1970
|
+
* How to parse stdout.
|
|
1971
|
+
* @default 'stream-json'
|
|
1972
|
+
*/
|
|
1973
|
+
outputFormat?: CLIOutputFormat;
|
|
1974
|
+
/**
|
|
1975
|
+
* Timeout in milliseconds.
|
|
1976
|
+
* @default 120000 (2 minutes)
|
|
1977
|
+
*/
|
|
1978
|
+
timeout?: number;
|
|
1979
|
+
}
|
|
1980
|
+
/**
|
|
1981
|
+
* A cookie to inject into the browser context before running the script.
|
|
1982
|
+
* Matches the shape expected by Playwright's `BrowserContext.addCookies()`.
|
|
1983
|
+
*/
|
|
1984
|
+
interface BrowserCookie {
|
|
1985
|
+
name: string;
|
|
1986
|
+
value: string;
|
|
1987
|
+
url?: string;
|
|
1988
|
+
domain?: string;
|
|
1989
|
+
path?: string;
|
|
1990
|
+
expires?: number;
|
|
1991
|
+
httpOnly?: boolean;
|
|
1992
|
+
secure?: boolean;
|
|
1993
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
1994
|
+
partitionKey?: string;
|
|
1995
|
+
}
|
|
1996
|
+
/**
|
|
1997
|
+
* Configuration for a browser-based host.
|
|
1998
|
+
*
|
|
1999
|
+
* Uses Playwright to launch a Chromium instance, inject auth state,
|
|
2000
|
+
* and execute a user-provided script that drives a web-based MCP host
|
|
2001
|
+
* (e.g., claude.ai).
|
|
2002
|
+
*/
|
|
2003
|
+
interface BrowserConfig {
|
|
2004
|
+
/**
|
|
2005
|
+
* Path to the browser script (resolved relative to cwd).
|
|
2006
|
+
* The script must default-export an async function
|
|
2007
|
+
* `(page: Page, scenario: string) => Promise<MCPHostSimulationResult>`.
|
|
2008
|
+
*/
|
|
2009
|
+
script: string;
|
|
2010
|
+
/**
|
|
2011
|
+
* Timeout in milliseconds for the browser script.
|
|
2012
|
+
* @default 120000 (2 minutes)
|
|
2013
|
+
*/
|
|
2014
|
+
timeout?: number;
|
|
2015
|
+
/**
|
|
2016
|
+
* Whether to launch in headless mode.
|
|
2017
|
+
* @default true
|
|
2018
|
+
*/
|
|
2019
|
+
headless?: boolean;
|
|
2020
|
+
/**
|
|
2021
|
+
* Path to a Playwright storage state JSON file (cookies + localStorage).
|
|
2022
|
+
* Resolved relative to cwd.
|
|
2023
|
+
*/
|
|
2024
|
+
storageState?: string;
|
|
2025
|
+
/**
|
|
2026
|
+
* Extra cookies to inject into the browser context.
|
|
2027
|
+
*/
|
|
2028
|
+
cookies?: BrowserCookie[];
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Configuration for MCP host simulation
|
|
2032
|
+
*/
|
|
2033
|
+
interface MCPHostConfig {
|
|
2034
|
+
/**
|
|
2035
|
+
* Host type for the simulation.
|
|
2036
|
+
*
|
|
2037
|
+
* - 'sdk': Programmatic via Vercel AI SDK (default). The framework's MCP connection is reused.
|
|
2038
|
+
* - 'cli': CLI-based hosts (e.g., Claude Code, Codex). Spawns a process with its own MCP connection.
|
|
2039
|
+
* - 'browser': Web-based hosts (not yet implemented).
|
|
2040
|
+
* - 'desktop': Desktop app hosts (not yet implemented).
|
|
2041
|
+
*
|
|
2042
|
+
* @default 'sdk'
|
|
2043
|
+
*/
|
|
2044
|
+
hostType?: HostType;
|
|
2045
|
+
/**
|
|
2046
|
+
* LLM provider (required for 'sdk' host type, ignored for 'cli')
|
|
2047
|
+
*/
|
|
2048
|
+
provider?: LLMProvider;
|
|
2049
|
+
/**
|
|
2050
|
+
* Environment variable name containing the API key
|
|
2051
|
+
*/
|
|
2052
|
+
apiKeyEnvVar?: string;
|
|
2053
|
+
/**
|
|
2054
|
+
* Model to use (provider-specific default if omitted)
|
|
2055
|
+
*/
|
|
2056
|
+
model?: string;
|
|
2057
|
+
/**
|
|
2058
|
+
* Maximum tokens for response
|
|
2059
|
+
*/
|
|
2060
|
+
maxTokens?: number;
|
|
2061
|
+
/**
|
|
2062
|
+
* Temperature (0-1, lower is more deterministic)
|
|
2063
|
+
* @default 0
|
|
2064
|
+
*/
|
|
2065
|
+
temperature?: number;
|
|
2066
|
+
/**
|
|
2067
|
+
* Maximum number of tool call steps to allow in a single conversation
|
|
2068
|
+
* @default 10
|
|
2069
|
+
*/
|
|
2070
|
+
maxToolCalls?: number;
|
|
2071
|
+
/**
|
|
2072
|
+
* CLI host configuration (required for 'cli' host type).
|
|
2073
|
+
*/
|
|
2074
|
+
cli?: CLIConfig;
|
|
2075
|
+
/**
|
|
2076
|
+
* Browser host configuration (required for 'browser' host type).
|
|
2077
|
+
*/
|
|
2078
|
+
browser?: BrowserConfig;
|
|
2079
|
+
}
|
|
2080
|
+
/**
|
|
2081
|
+
* A tool call made by the LLM
|
|
2082
|
+
*/
|
|
2083
|
+
interface LLMToolCall {
|
|
2084
|
+
/** Tool name */
|
|
2085
|
+
name: string;
|
|
2086
|
+
/** Tool arguments (as provided by LLM) */
|
|
2087
|
+
arguments: Record<string, unknown>;
|
|
2088
|
+
/** Optional tool call ID (for tracking) */
|
|
2089
|
+
id?: string;
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Result from an MCP host simulation
|
|
2093
|
+
*/
|
|
2094
|
+
interface MCPHostSimulationResult {
|
|
2095
|
+
/** Whether the simulation succeeded */
|
|
2096
|
+
success: boolean;
|
|
2097
|
+
/** Tool calls made by the LLM */
|
|
2098
|
+
toolCalls: Array<LLMToolCall>;
|
|
2099
|
+
/** Final response from the LLM */
|
|
2100
|
+
response?: string;
|
|
2101
|
+
/** Error message if simulation failed */
|
|
2102
|
+
error?: string;
|
|
2103
|
+
/** The scenario prompt that was given to the LLM */
|
|
2104
|
+
scenario?: string;
|
|
2105
|
+
/** The conversation turns for attribution analysis */
|
|
2106
|
+
conversationHistory?: Array<{
|
|
2107
|
+
role: 'user' | 'assistant' | 'tool';
|
|
2108
|
+
content: string;
|
|
2109
|
+
}>;
|
|
2110
|
+
/**
|
|
2111
|
+
* Milliseconds spent waiting for LLM responses
|
|
2112
|
+
* (excludes MCP tool execution time)
|
|
2113
|
+
*/
|
|
2114
|
+
llmDurationMs?: number;
|
|
2115
|
+
/**
|
|
2116
|
+
* Milliseconds spent executing MCP tool calls
|
|
2117
|
+
* (excludes LLM response time)
|
|
2118
|
+
*/
|
|
2119
|
+
mcpDurationMs?: number;
|
|
2120
|
+
/**
|
|
2121
|
+
* Token usage from the LLM during simulation.
|
|
2122
|
+
* Populated by SDK-based hosts from the AI SDK response.
|
|
2123
|
+
*/
|
|
2124
|
+
usage?: UsageMetrics;
|
|
2125
|
+
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Interface for MCP host simulators.
|
|
2128
|
+
*
|
|
2129
|
+
* The only built-in implementation is the Vercel AI SDK orchestrator
|
|
2130
|
+
* (src/evals/mcpHost/adapters/vercel.ts). Custom implementations can be
|
|
2131
|
+
* created for specialised testing needs.
|
|
2132
|
+
*/
|
|
2133
|
+
interface MCPHostSimulator {
|
|
2134
|
+
/**
|
|
2135
|
+
* Simulates an MCP host interacting with an MCP server
|
|
2136
|
+
*
|
|
2137
|
+
* @param mcp - MCP fixture API
|
|
2138
|
+
* @param scenario - Natural language prompt describing what the LLM should do
|
|
2139
|
+
* @param config - MCP host configuration
|
|
2140
|
+
* @returns Simulation result with tool calls and response
|
|
2141
|
+
*/
|
|
2142
|
+
simulate(mcp: MCPFixtureApi, scenario: string, config: MCPHostConfig): Promise<MCPHostSimulationResult>;
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
/**
|
|
2146
|
+
* Evaluation mode
|
|
2147
|
+
*/
|
|
2148
|
+
type EvalMode = 'direct' | 'mcp_host';
|
|
2149
|
+
/**
|
|
2150
|
+
* A single eval test case
|
|
2151
|
+
*
|
|
2152
|
+
* For 'direct' mode: toolName and args are required
|
|
2153
|
+
* For 'mcp_host' mode: scenario and mcpHostConfig are required
|
|
2154
|
+
*/
|
|
2155
|
+
interface EvalCase {
|
|
2156
|
+
/**
|
|
2157
|
+
* Unique identifier for this test case
|
|
2158
|
+
*/
|
|
2159
|
+
id: string;
|
|
2160
|
+
/**
|
|
2161
|
+
* Human-readable description of what this test case validates
|
|
2162
|
+
*/
|
|
2163
|
+
description?: string;
|
|
2164
|
+
/**
|
|
2165
|
+
* Evaluation mode
|
|
2166
|
+
* - 'direct': Direct API calls to MCP tools (default)
|
|
2167
|
+
* - 'mcp_host': LLM-driven tool selection via natural language
|
|
2168
|
+
*
|
|
2169
|
+
* @default 'direct'
|
|
2170
|
+
*/
|
|
2171
|
+
mode?: EvalMode;
|
|
2172
|
+
/**
|
|
2173
|
+
* Name of the MCP tool to call (required for 'direct' mode, optional for 'mcp_host' mode)
|
|
2174
|
+
*/
|
|
2175
|
+
toolName?: string;
|
|
2176
|
+
/**
|
|
2177
|
+
* Arguments to pass to the tool (required for 'direct' mode, optional for 'mcp_host' mode)
|
|
2178
|
+
*/
|
|
2179
|
+
args?: Record<string, unknown>;
|
|
2180
|
+
/**
|
|
2181
|
+
* Natural language scenario for LLM to execute (optional, required for 'mcp_host' mode)
|
|
2182
|
+
*
|
|
2183
|
+
* @example "Get the weather for London and tell me if I need an umbrella"
|
|
2184
|
+
*/
|
|
2185
|
+
scenario?: string;
|
|
2186
|
+
/**
|
|
2187
|
+
* MCP host configuration (optional for 'mcp_host' mode)
|
|
2188
|
+
*
|
|
2189
|
+
* If not specified, uses default configuration from test environment
|
|
2190
|
+
*/
|
|
2191
|
+
mcpHostConfig?: MCPHostConfig;
|
|
2192
|
+
/**
|
|
2193
|
+
* Additional metadata for this test case
|
|
2194
|
+
*
|
|
2195
|
+
* For 'mcp_host' mode, can include 'expectedToolCalls' for validation
|
|
2196
|
+
*/
|
|
2197
|
+
metadata?: Record<string, unknown>;
|
|
2198
|
+
/**
|
|
2199
|
+
* Number of times to run this case and compute an assertion pass rate.
|
|
2200
|
+
* When > 1, `EvalCaseResult.assertionPassRate` is populated and `pass` is determined
|
|
2201
|
+
* by `accuracyThreshold` rather than a single run.
|
|
2202
|
+
* @default 1
|
|
2203
|
+
*/
|
|
2204
|
+
iterations?: number;
|
|
2205
|
+
/**
|
|
2206
|
+
* Minimum accuracy (0–1) required to pass when `iterations > 1`.
|
|
2207
|
+
* @default 1.0 (all iterations must pass)
|
|
2208
|
+
*/
|
|
2209
|
+
accuracyThreshold?: number;
|
|
2210
|
+
/**
|
|
2211
|
+
* Number of times to invoke the LLM judge per `passesJudge` assertion.
|
|
2212
|
+
* Scores are averaged; the mean must meet the threshold to pass.
|
|
2213
|
+
* Reduces judge variance caused by non-determinism.
|
|
2214
|
+
* Per-assertion `passesJudge.reps` overrides this value.
|
|
2215
|
+
* @default 1
|
|
2216
|
+
*/
|
|
2217
|
+
judgeReps?: number;
|
|
2218
|
+
/**
|
|
2219
|
+
* Golden/expected answer for this case.
|
|
2220
|
+
* When set, automatically passed as `reference` to the LLM judge
|
|
2221
|
+
* (unless passesJudge.reference is explicitly provided).
|
|
2222
|
+
* Mirrors EvalV2's `canonical_answer` field.
|
|
2223
|
+
*/
|
|
2224
|
+
canonicalAnswer?: string;
|
|
2225
|
+
/**
|
|
2226
|
+
* Arbitrary string labels for this case.
|
|
2227
|
+
* Use for filtering eval runs with `EvalRunnerOptions.filterTags`
|
|
2228
|
+
* and for slicing results by category.
|
|
2229
|
+
*
|
|
2230
|
+
* @example ['tool-finding', 'multi-hop', 'search']
|
|
2231
|
+
*/
|
|
2232
|
+
tags?: string[];
|
|
2233
|
+
/**
|
|
2234
|
+
* Expectations to validate against the tool response
|
|
2235
|
+
*
|
|
2236
|
+
* Multiple expectations can be combined and will all be validated.
|
|
2237
|
+
*
|
|
2238
|
+
* @example
|
|
2239
|
+
* ```json
|
|
2240
|
+
* {
|
|
2241
|
+
* "id": "weather-london",
|
|
2242
|
+
* "toolName": "get_weather",
|
|
2243
|
+
* "args": { "city": "London" },
|
|
2244
|
+
* "expect": {
|
|
2245
|
+
* "containsText": ["temperature", "conditions"],
|
|
2246
|
+
* "schema": "WeatherResponse",
|
|
2247
|
+
* "responseSize": { "maxBytes": 10000 },
|
|
2248
|
+
* "isError": false
|
|
2249
|
+
* }
|
|
2250
|
+
* }
|
|
2251
|
+
* ```
|
|
2252
|
+
*/
|
|
2253
|
+
expect?: EvalExpectBlock;
|
|
2254
|
+
}
|
|
2255
|
+
/**
|
|
2256
|
+
* Configuration for a single LLM-as-judge evaluation
|
|
2257
|
+
*/
|
|
2258
|
+
interface JudgeExpectConfig {
|
|
2259
|
+
/**
|
|
2260
|
+
* Name of a registered custom judge executor.
|
|
2261
|
+
* When set, the named judge handles evaluation and returns a normalized score.
|
|
2262
|
+
* The `threshold` determines pass/fail. `reps` and LLM config fields
|
|
2263
|
+
* (provider, model, etc.) are ignored.
|
|
2264
|
+
*/
|
|
2265
|
+
judge?: string;
|
|
2266
|
+
/** Built-in rubric name or custom rubric object. Required when no `judge` is specified. */
|
|
2267
|
+
rubric?: BuiltInRubric | {
|
|
2268
|
+
text: string;
|
|
2269
|
+
};
|
|
2270
|
+
/** Reference response to compare against */
|
|
2271
|
+
reference?: unknown;
|
|
2272
|
+
/** Score threshold for passing (0-1, default: 0.7) */
|
|
2273
|
+
threshold?: number;
|
|
2274
|
+
/** Number of judge evaluations for this assertion. Overrides EvalCase.judgeReps. */
|
|
2275
|
+
reps?: number;
|
|
2276
|
+
/** Judge provider. @default 'anthropic' */
|
|
2277
|
+
provider?: 'anthropic' | 'vertex-anthropic' | 'anthropic-agent-sdk' | 'openai' | 'google';
|
|
2278
|
+
/** Model override (e.g., 'claude-opus-4-20250514') */
|
|
2279
|
+
model?: string;
|
|
2280
|
+
/** Environment variable name for API key */
|
|
2281
|
+
apiKeyEnvVar?: string;
|
|
2282
|
+
/** Max tokens for judge response */
|
|
2283
|
+
maxTokens?: number;
|
|
2284
|
+
/** Temperature for judge LLM (0–1) */
|
|
2285
|
+
temperature?: number;
|
|
2286
|
+
/** Max budget in USD per evaluation */
|
|
2287
|
+
maxBudgetUsd?: number;
|
|
2288
|
+
/** Fail if response exceeds this size in bytes before judging */
|
|
2289
|
+
maxToolOutputSize?: number;
|
|
2290
|
+
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Unified expectation block for eval cases
|
|
2293
|
+
*
|
|
2294
|
+
* Mirrors the Playwright matcher API for consistency.
|
|
2295
|
+
*/
|
|
2296
|
+
interface EvalExpectBlock {
|
|
2297
|
+
/**
|
|
2298
|
+
* Exact response match (toMatchToolResponse)
|
|
2299
|
+
*/
|
|
2300
|
+
response?: unknown;
|
|
2301
|
+
/**
|
|
2302
|
+
* Name of schema to validate against (toMatchToolSchema)
|
|
2303
|
+
*/
|
|
2304
|
+
schema?: string;
|
|
2305
|
+
/**
|
|
2306
|
+
* Text substring(s) that must be present (toContainToolText)
|
|
2307
|
+
*/
|
|
2308
|
+
containsText?: string | string[];
|
|
2309
|
+
/**
|
|
2310
|
+
* Regex pattern(s) that must match (toMatchToolPattern)
|
|
2311
|
+
*/
|
|
2312
|
+
matchesPattern?: string | string[];
|
|
2313
|
+
/**
|
|
2314
|
+
* Snapshot name for comparison (toMatchToolSnapshot)
|
|
2315
|
+
*/
|
|
2316
|
+
snapshot?: string;
|
|
2317
|
+
/**
|
|
2318
|
+
* Snapshot sanitizers to apply
|
|
2319
|
+
*/
|
|
2320
|
+
snapshotSanitizers?: SnapshotSanitizer[];
|
|
2321
|
+
/**
|
|
2322
|
+
* Error expectation (toBeToolError)
|
|
2323
|
+
* - true: expects any error
|
|
2324
|
+
* - false: expects no error
|
|
2325
|
+
* - string: expects error containing this message
|
|
2326
|
+
*/
|
|
2327
|
+
isError?: boolean | string | string[];
|
|
2328
|
+
/**
|
|
2329
|
+
* LLM-as-judge evaluation (toPassToolJudge)
|
|
2330
|
+
*
|
|
2331
|
+
* Accepts a single judge config or an array for multi-judge evaluation.
|
|
2332
|
+
* When an array is provided, all judges must pass (AND semantics).
|
|
2333
|
+
*/
|
|
2334
|
+
passesJudge?: JudgeExpectConfig | JudgeExpectConfig[];
|
|
2335
|
+
/**
|
|
2336
|
+
* Response size validation (toHaveToolResponseSize)
|
|
2337
|
+
*/
|
|
2338
|
+
responseSize?: {
|
|
2339
|
+
/** Maximum allowed size in bytes */
|
|
2340
|
+
maxBytes?: number;
|
|
2341
|
+
/** Minimum required size in bytes */
|
|
2342
|
+
minBytes?: number;
|
|
2343
|
+
};
|
|
2344
|
+
/**
|
|
2345
|
+
* Asserts which tools the LLM called during a mcp_host simulation.
|
|
2346
|
+
* Only meaningful for mcp_host mode — direct mode has no tool call trace.
|
|
2347
|
+
*/
|
|
2348
|
+
toolsTriggered?: {
|
|
2349
|
+
/** Expected tool calls */
|
|
2350
|
+
calls: Array<{
|
|
2351
|
+
/** Tool name */
|
|
2352
|
+
name: string;
|
|
2353
|
+
/** Expected arguments (partial match — extra keys are allowed) */
|
|
2354
|
+
arguments?: Record<string, unknown>;
|
|
2355
|
+
/** Whether this call MUST have been made (default: true) */
|
|
2356
|
+
required?: boolean;
|
|
2357
|
+
}>;
|
|
2358
|
+
/**
|
|
2359
|
+
* 'strict': calls must appear in the exact order listed
|
|
2360
|
+
* 'any': calls can appear in any order (default)
|
|
2361
|
+
*/
|
|
2362
|
+
order?: 'strict' | 'any';
|
|
2363
|
+
/** If true, no tool calls outside the `calls` list are allowed */
|
|
2364
|
+
exclusive?: boolean;
|
|
2365
|
+
};
|
|
2366
|
+
/**
|
|
2367
|
+
* Asserts the number of tool calls made during a mcp_host simulation.
|
|
2368
|
+
*/
|
|
2369
|
+
toolCallCount?: {
|
|
2370
|
+
/** Minimum number of tool calls */
|
|
2371
|
+
min?: number;
|
|
2372
|
+
/** Maximum number of tool calls */
|
|
2373
|
+
max?: number;
|
|
2374
|
+
/** Exact number of tool calls */
|
|
2375
|
+
exact?: number;
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
/**
|
|
2379
|
+
* A complete eval dataset containing multiple test cases
|
|
2380
|
+
*/
|
|
2381
|
+
interface EvalDataset {
|
|
2382
|
+
/**
|
|
2383
|
+
* Dataset name
|
|
2384
|
+
*/
|
|
2385
|
+
name: string;
|
|
2386
|
+
/**
|
|
2387
|
+
* Dataset description
|
|
2388
|
+
*/
|
|
2389
|
+
description?: string;
|
|
2390
|
+
/**
|
|
2391
|
+
* Test cases in this dataset
|
|
2392
|
+
*/
|
|
2393
|
+
cases: Array<EvalCase>;
|
|
2394
|
+
/**
|
|
2395
|
+
* Optional schema definitions referenced by test cases
|
|
2396
|
+
*/
|
|
2397
|
+
schemas?: Record<string, z.ZodSchema>;
|
|
2398
|
+
/**
|
|
2399
|
+
* Additional dataset metadata
|
|
2400
|
+
*/
|
|
2401
|
+
metadata?: Record<string, unknown>;
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2404
|
+
* Zod schema for EvalCase
|
|
2405
|
+
*
|
|
2406
|
+
* toolName and args are optional for mcp_host mode (which uses scenario instead)
|
|
2407
|
+
*/
|
|
2408
|
+
declare const EvalCaseSchema: z.ZodObject<{
|
|
2409
|
+
id: z.ZodString;
|
|
2410
|
+
description: z.ZodOptional<z.ZodString>;
|
|
2411
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
2412
|
+
direct: "direct";
|
|
2413
|
+
mcp_host: "mcp_host";
|
|
2414
|
+
}>>;
|
|
2415
|
+
toolName: z.ZodOptional<z.ZodString>;
|
|
2416
|
+
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
2417
|
+
scenario: z.ZodOptional<z.ZodString>;
|
|
2418
|
+
mcpHostConfig: z.ZodOptional<z.ZodObject<{
|
|
2419
|
+
hostType: z.ZodOptional<z.ZodEnum<{
|
|
2420
|
+
sdk: "sdk";
|
|
2421
|
+
cli: "cli";
|
|
2422
|
+
browser: "browser";
|
|
2423
|
+
desktop: "desktop";
|
|
2424
|
+
}>>;
|
|
2425
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
2426
|
+
openai: "openai";
|
|
2427
|
+
anthropic: "anthropic";
|
|
2428
|
+
azure: "azure";
|
|
2429
|
+
google: "google";
|
|
2430
|
+
mistral: "mistral";
|
|
2431
|
+
deepseek: "deepseek";
|
|
2432
|
+
openrouter: "openrouter";
|
|
2433
|
+
xai: "xai";
|
|
2434
|
+
"vertex-anthropic": "vertex-anthropic";
|
|
2435
|
+
}>>;
|
|
2436
|
+
apiKeyEnvVar: z.ZodOptional<z.ZodString>;
|
|
2437
|
+
model: z.ZodOptional<z.ZodString>;
|
|
2438
|
+
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
2439
|
+
temperature: z.ZodOptional<z.ZodNumber>;
|
|
2440
|
+
maxToolCalls: z.ZodOptional<z.ZodNumber>;
|
|
2441
|
+
cli: z.ZodOptional<z.ZodObject<{
|
|
2442
|
+
command: z.ZodString;
|
|
2443
|
+
args: z.ZodArray<z.ZodString>;
|
|
2444
|
+
outputFormat: z.ZodOptional<z.ZodEnum<{
|
|
2445
|
+
"stream-json": "stream-json";
|
|
2446
|
+
json: "json";
|
|
2447
|
+
}>>;
|
|
2448
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2449
|
+
}, z.core.$strip>>;
|
|
2450
|
+
}, z.core.$strip>>;
|
|
2451
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
2452
|
+
iterations: z.ZodOptional<z.ZodNumber>;
|
|
2453
|
+
accuracyThreshold: z.ZodOptional<z.ZodNumber>;
|
|
2454
|
+
judgeReps: z.ZodOptional<z.ZodNumber>;
|
|
2455
|
+
canonicalAnswer: z.ZodOptional<z.ZodString>;
|
|
2456
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
2457
|
+
expect: z.ZodOptional<z.ZodObject<{
|
|
2458
|
+
response: z.ZodOptional<z.ZodUnknown>;
|
|
2459
|
+
schema: z.ZodOptional<z.ZodString>;
|
|
2460
|
+
containsText: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
2461
|
+
matchesPattern: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
2462
|
+
snapshot: z.ZodOptional<z.ZodString>;
|
|
2463
|
+
snapshotSanitizers: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodEnum<{
|
|
2464
|
+
timestamp: "timestamp";
|
|
2465
|
+
uuid: "uuid";
|
|
2466
|
+
"iso-date": "iso-date";
|
|
2467
|
+
objectId: "objectId";
|
|
2468
|
+
jwt: "jwt";
|
|
2469
|
+
}>, z.ZodObject<{
|
|
2470
|
+
pattern: z.ZodString;
|
|
2471
|
+
replacement: z.ZodOptional<z.ZodString>;
|
|
2472
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2473
|
+
remove: z.ZodArray<z.ZodString>;
|
|
2474
|
+
}, z.core.$strip>]>>>;
|
|
2475
|
+
isError: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
2476
|
+
passesJudge: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
2477
|
+
judge: z.ZodOptional<z.ZodString>;
|
|
2478
|
+
rubric: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
2479
|
+
correctness: "correctness";
|
|
2480
|
+
completeness: "completeness";
|
|
2481
|
+
groundedness: "groundedness";
|
|
2482
|
+
"instruction-following": "instruction-following";
|
|
2483
|
+
conciseness: "conciseness";
|
|
2484
|
+
}>, z.ZodObject<{
|
|
2485
|
+
text: z.ZodString;
|
|
2486
|
+
}, z.core.$strip>]>>;
|
|
2487
|
+
reference: z.ZodOptional<z.ZodUnknown>;
|
|
2488
|
+
threshold: z.ZodOptional<z.ZodNumber>;
|
|
2489
|
+
reps: z.ZodOptional<z.ZodNumber>;
|
|
2490
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
2491
|
+
openai: "openai";
|
|
2492
|
+
anthropic: "anthropic";
|
|
2493
|
+
google: "google";
|
|
2494
|
+
"vertex-anthropic": "vertex-anthropic";
|
|
2495
|
+
"anthropic-agent-sdk": "anthropic-agent-sdk";
|
|
2496
|
+
}>>;
|
|
2497
|
+
model: z.ZodOptional<z.ZodString>;
|
|
2498
|
+
apiKeyEnvVar: z.ZodOptional<z.ZodString>;
|
|
2499
|
+
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
2500
|
+
temperature: z.ZodOptional<z.ZodNumber>;
|
|
2501
|
+
maxBudgetUsd: z.ZodOptional<z.ZodNumber>;
|
|
2502
|
+
maxToolOutputSize: z.ZodOptional<z.ZodNumber>;
|
|
2503
|
+
}, z.core.$strip>, z.ZodArray<z.ZodObject<{
|
|
2504
|
+
judge: z.ZodOptional<z.ZodString>;
|
|
2505
|
+
rubric: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
2506
|
+
correctness: "correctness";
|
|
2507
|
+
completeness: "completeness";
|
|
2508
|
+
groundedness: "groundedness";
|
|
2509
|
+
"instruction-following": "instruction-following";
|
|
2510
|
+
conciseness: "conciseness";
|
|
2511
|
+
}>, z.ZodObject<{
|
|
2512
|
+
text: z.ZodString;
|
|
2513
|
+
}, z.core.$strip>]>>;
|
|
2514
|
+
reference: z.ZodOptional<z.ZodUnknown>;
|
|
2515
|
+
threshold: z.ZodOptional<z.ZodNumber>;
|
|
2516
|
+
reps: z.ZodOptional<z.ZodNumber>;
|
|
2517
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
2518
|
+
openai: "openai";
|
|
2519
|
+
anthropic: "anthropic";
|
|
2520
|
+
google: "google";
|
|
2521
|
+
"vertex-anthropic": "vertex-anthropic";
|
|
2522
|
+
"anthropic-agent-sdk": "anthropic-agent-sdk";
|
|
2523
|
+
}>>;
|
|
2524
|
+
model: z.ZodOptional<z.ZodString>;
|
|
2525
|
+
apiKeyEnvVar: z.ZodOptional<z.ZodString>;
|
|
2526
|
+
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
2527
|
+
temperature: z.ZodOptional<z.ZodNumber>;
|
|
2528
|
+
maxBudgetUsd: z.ZodOptional<z.ZodNumber>;
|
|
2529
|
+
maxToolOutputSize: z.ZodOptional<z.ZodNumber>;
|
|
2530
|
+
}, z.core.$strip>>]>>;
|
|
2531
|
+
responseSize: z.ZodOptional<z.ZodObject<{
|
|
2532
|
+
maxBytes: z.ZodOptional<z.ZodNumber>;
|
|
2533
|
+
minBytes: z.ZodOptional<z.ZodNumber>;
|
|
2534
|
+
}, z.core.$strip>>;
|
|
2535
|
+
toolsTriggered: z.ZodOptional<z.ZodObject<{
|
|
2536
|
+
calls: z.ZodArray<z.ZodObject<{
|
|
2537
|
+
name: z.ZodString;
|
|
2538
|
+
arguments: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
2539
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
2540
|
+
}, z.core.$strip>>;
|
|
2541
|
+
order: z.ZodOptional<z.ZodEnum<{
|
|
2542
|
+
strict: "strict";
|
|
2543
|
+
any: "any";
|
|
2544
|
+
}>>;
|
|
2545
|
+
exclusive: z.ZodOptional<z.ZodBoolean>;
|
|
2546
|
+
}, z.core.$strip>>;
|
|
2547
|
+
toolCallCount: z.ZodOptional<z.ZodObject<{
|
|
2548
|
+
min: z.ZodOptional<z.ZodNumber>;
|
|
2549
|
+
max: z.ZodOptional<z.ZodNumber>;
|
|
2550
|
+
exact: z.ZodOptional<z.ZodNumber>;
|
|
2551
|
+
}, z.core.$strip>>;
|
|
2552
|
+
}, z.core.$strip>>;
|
|
2553
|
+
}, z.core.$strip>;
|
|
2554
|
+
/**
|
|
2555
|
+
* Zod schema for EvalDataset (without schemas field, as schemas aren't serializable)
|
|
2556
|
+
*/
|
|
2557
|
+
declare const EvalDatasetSchema: z.ZodObject<{
|
|
2558
|
+
name: z.ZodString;
|
|
2559
|
+
description: z.ZodOptional<z.ZodString>;
|
|
2560
|
+
cases: z.ZodArray<z.ZodObject<{
|
|
2561
|
+
id: z.ZodString;
|
|
2562
|
+
description: z.ZodOptional<z.ZodString>;
|
|
2563
|
+
mode: z.ZodOptional<z.ZodEnum<{
|
|
2564
|
+
direct: "direct";
|
|
2565
|
+
mcp_host: "mcp_host";
|
|
2566
|
+
}>>;
|
|
2567
|
+
toolName: z.ZodOptional<z.ZodString>;
|
|
2568
|
+
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
2569
|
+
scenario: z.ZodOptional<z.ZodString>;
|
|
2570
|
+
mcpHostConfig: z.ZodOptional<z.ZodObject<{
|
|
2571
|
+
hostType: z.ZodOptional<z.ZodEnum<{
|
|
2572
|
+
sdk: "sdk";
|
|
2573
|
+
cli: "cli";
|
|
2574
|
+
browser: "browser";
|
|
2575
|
+
desktop: "desktop";
|
|
2576
|
+
}>>;
|
|
2577
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
2578
|
+
openai: "openai";
|
|
2579
|
+
anthropic: "anthropic";
|
|
2580
|
+
azure: "azure";
|
|
2581
|
+
google: "google";
|
|
2582
|
+
mistral: "mistral";
|
|
2583
|
+
deepseek: "deepseek";
|
|
2584
|
+
openrouter: "openrouter";
|
|
2585
|
+
xai: "xai";
|
|
2586
|
+
"vertex-anthropic": "vertex-anthropic";
|
|
2587
|
+
}>>;
|
|
2588
|
+
apiKeyEnvVar: z.ZodOptional<z.ZodString>;
|
|
2589
|
+
model: z.ZodOptional<z.ZodString>;
|
|
2590
|
+
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
2591
|
+
temperature: z.ZodOptional<z.ZodNumber>;
|
|
2592
|
+
maxToolCalls: z.ZodOptional<z.ZodNumber>;
|
|
2593
|
+
cli: z.ZodOptional<z.ZodObject<{
|
|
2594
|
+
command: z.ZodString;
|
|
2595
|
+
args: z.ZodArray<z.ZodString>;
|
|
2596
|
+
outputFormat: z.ZodOptional<z.ZodEnum<{
|
|
2597
|
+
"stream-json": "stream-json";
|
|
2598
|
+
json: "json";
|
|
2599
|
+
}>>;
|
|
2600
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
2601
|
+
}, z.core.$strip>>;
|
|
2602
|
+
}, z.core.$strip>>;
|
|
2603
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
2604
|
+
iterations: z.ZodOptional<z.ZodNumber>;
|
|
2605
|
+
accuracyThreshold: z.ZodOptional<z.ZodNumber>;
|
|
2606
|
+
judgeReps: z.ZodOptional<z.ZodNumber>;
|
|
2607
|
+
canonicalAnswer: z.ZodOptional<z.ZodString>;
|
|
2608
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
2609
|
+
expect: z.ZodOptional<z.ZodObject<{
|
|
2610
|
+
response: z.ZodOptional<z.ZodUnknown>;
|
|
2611
|
+
schema: z.ZodOptional<z.ZodString>;
|
|
2612
|
+
containsText: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
2613
|
+
matchesPattern: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
2614
|
+
snapshot: z.ZodOptional<z.ZodString>;
|
|
2615
|
+
snapshotSanitizers: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodEnum<{
|
|
2616
|
+
timestamp: "timestamp";
|
|
2617
|
+
uuid: "uuid";
|
|
2618
|
+
"iso-date": "iso-date";
|
|
2619
|
+
objectId: "objectId";
|
|
2620
|
+
jwt: "jwt";
|
|
2621
|
+
}>, z.ZodObject<{
|
|
2622
|
+
pattern: z.ZodString;
|
|
2623
|
+
replacement: z.ZodOptional<z.ZodString>;
|
|
2624
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
2625
|
+
remove: z.ZodArray<z.ZodString>;
|
|
2626
|
+
}, z.core.$strip>]>>>;
|
|
2627
|
+
isError: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
2628
|
+
passesJudge: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
|
|
2629
|
+
judge: z.ZodOptional<z.ZodString>;
|
|
2630
|
+
rubric: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
2631
|
+
correctness: "correctness";
|
|
2632
|
+
completeness: "completeness";
|
|
2633
|
+
groundedness: "groundedness";
|
|
2634
|
+
"instruction-following": "instruction-following";
|
|
2635
|
+
conciseness: "conciseness";
|
|
2636
|
+
}>, z.ZodObject<{
|
|
2637
|
+
text: z.ZodString;
|
|
2638
|
+
}, z.core.$strip>]>>;
|
|
2639
|
+
reference: z.ZodOptional<z.ZodUnknown>;
|
|
2640
|
+
threshold: z.ZodOptional<z.ZodNumber>;
|
|
2641
|
+
reps: z.ZodOptional<z.ZodNumber>;
|
|
2642
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
2643
|
+
openai: "openai";
|
|
2644
|
+
anthropic: "anthropic";
|
|
2645
|
+
google: "google";
|
|
2646
|
+
"vertex-anthropic": "vertex-anthropic";
|
|
2647
|
+
"anthropic-agent-sdk": "anthropic-agent-sdk";
|
|
2648
|
+
}>>;
|
|
2649
|
+
model: z.ZodOptional<z.ZodString>;
|
|
2650
|
+
apiKeyEnvVar: z.ZodOptional<z.ZodString>;
|
|
2651
|
+
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
2652
|
+
temperature: z.ZodOptional<z.ZodNumber>;
|
|
2653
|
+
maxBudgetUsd: z.ZodOptional<z.ZodNumber>;
|
|
2654
|
+
maxToolOutputSize: z.ZodOptional<z.ZodNumber>;
|
|
2655
|
+
}, z.core.$strip>, z.ZodArray<z.ZodObject<{
|
|
2656
|
+
judge: z.ZodOptional<z.ZodString>;
|
|
2657
|
+
rubric: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
2658
|
+
correctness: "correctness";
|
|
2659
|
+
completeness: "completeness";
|
|
2660
|
+
groundedness: "groundedness";
|
|
2661
|
+
"instruction-following": "instruction-following";
|
|
2662
|
+
conciseness: "conciseness";
|
|
2663
|
+
}>, z.ZodObject<{
|
|
2664
|
+
text: z.ZodString;
|
|
2665
|
+
}, z.core.$strip>]>>;
|
|
2666
|
+
reference: z.ZodOptional<z.ZodUnknown>;
|
|
2667
|
+
threshold: z.ZodOptional<z.ZodNumber>;
|
|
2668
|
+
reps: z.ZodOptional<z.ZodNumber>;
|
|
2669
|
+
provider: z.ZodOptional<z.ZodEnum<{
|
|
2670
|
+
openai: "openai";
|
|
2671
|
+
anthropic: "anthropic";
|
|
2672
|
+
google: "google";
|
|
2673
|
+
"vertex-anthropic": "vertex-anthropic";
|
|
2674
|
+
"anthropic-agent-sdk": "anthropic-agent-sdk";
|
|
2675
|
+
}>>;
|
|
2676
|
+
model: z.ZodOptional<z.ZodString>;
|
|
2677
|
+
apiKeyEnvVar: z.ZodOptional<z.ZodString>;
|
|
2678
|
+
maxTokens: z.ZodOptional<z.ZodNumber>;
|
|
2679
|
+
temperature: z.ZodOptional<z.ZodNumber>;
|
|
2680
|
+
maxBudgetUsd: z.ZodOptional<z.ZodNumber>;
|
|
2681
|
+
maxToolOutputSize: z.ZodOptional<z.ZodNumber>;
|
|
2682
|
+
}, z.core.$strip>>]>>;
|
|
2683
|
+
responseSize: z.ZodOptional<z.ZodObject<{
|
|
2684
|
+
maxBytes: z.ZodOptional<z.ZodNumber>;
|
|
2685
|
+
minBytes: z.ZodOptional<z.ZodNumber>;
|
|
2686
|
+
}, z.core.$strip>>;
|
|
2687
|
+
toolsTriggered: z.ZodOptional<z.ZodObject<{
|
|
2688
|
+
calls: z.ZodArray<z.ZodObject<{
|
|
2689
|
+
name: z.ZodString;
|
|
2690
|
+
arguments: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
2691
|
+
required: z.ZodOptional<z.ZodBoolean>;
|
|
2692
|
+
}, z.core.$strip>>;
|
|
2693
|
+
order: z.ZodOptional<z.ZodEnum<{
|
|
2694
|
+
strict: "strict";
|
|
2695
|
+
any: "any";
|
|
2696
|
+
}>>;
|
|
2697
|
+
exclusive: z.ZodOptional<z.ZodBoolean>;
|
|
2698
|
+
}, z.core.$strip>>;
|
|
2699
|
+
toolCallCount: z.ZodOptional<z.ZodObject<{
|
|
2700
|
+
min: z.ZodOptional<z.ZodNumber>;
|
|
2701
|
+
max: z.ZodOptional<z.ZodNumber>;
|
|
2702
|
+
exact: z.ZodOptional<z.ZodNumber>;
|
|
2703
|
+
}, z.core.$strip>>;
|
|
2704
|
+
}, z.core.$strip>>;
|
|
2705
|
+
}, z.core.$strip>>;
|
|
2706
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
2707
|
+
}, z.core.$strip>;
|
|
2708
|
+
/**
|
|
2709
|
+
* Type for serialized eval dataset (without Zod schemas)
|
|
2710
|
+
*/
|
|
2711
|
+
type SerializedEvalDataset = z.infer<typeof EvalDatasetSchema>;
|
|
2712
|
+
/**
|
|
2713
|
+
* Validates an eval case
|
|
2714
|
+
*
|
|
2715
|
+
* @param evalCase - The eval case to validate
|
|
2716
|
+
* @returns The validated eval case
|
|
2717
|
+
* @throws {z.ZodError} If validation fails
|
|
2718
|
+
*/
|
|
2719
|
+
declare function validateEvalCase(evalCase: unknown): EvalCase;
|
|
2720
|
+
/**
|
|
2721
|
+
* Validates a serialized eval dataset
|
|
2722
|
+
*
|
|
2723
|
+
* @param dataset - The dataset to validate
|
|
2724
|
+
* @returns The validated dataset
|
|
2725
|
+
* @throws {z.ZodError} If validation fails
|
|
2726
|
+
*/
|
|
2727
|
+
declare function validateEvalDataset(dataset: unknown): SerializedEvalDataset;
|
|
2728
|
+
|
|
2729
|
+
/**
|
|
2730
|
+
* Options for loading an eval dataset
|
|
2731
|
+
*/
|
|
2732
|
+
interface LoadDatasetOptions {
|
|
2733
|
+
/**
|
|
2734
|
+
* Optional schema definitions to attach to the dataset
|
|
2735
|
+
*
|
|
2736
|
+
* Keys should match the expectedSchemaName in eval cases
|
|
2737
|
+
*/
|
|
2738
|
+
schemas?: Record<string, z.ZodSchema>;
|
|
2739
|
+
/**
|
|
2740
|
+
* Whether to validate the loaded dataset
|
|
2741
|
+
* @default true
|
|
2742
|
+
*/
|
|
2743
|
+
validate?: boolean;
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* Loads an eval dataset from a JSON file
|
|
2747
|
+
*
|
|
2748
|
+
* @param filePath - Absolute path to the JSON file
|
|
2749
|
+
* @param options - Load options
|
|
2750
|
+
* @returns The loaded and validated dataset
|
|
2751
|
+
* @throws {Error} If file cannot be read or JSON is invalid
|
|
2752
|
+
* @throws {z.ZodError} If validation fails
|
|
2753
|
+
*
|
|
2754
|
+
* @example
|
|
2755
|
+
* const dataset = await loadEvalDataset('./data/my-evals.json', {
|
|
2756
|
+
* schemas: {
|
|
2757
|
+
* 'weather-response': WeatherResponseSchema,
|
|
2758
|
+
* },
|
|
2759
|
+
* });
|
|
2760
|
+
*/
|
|
2761
|
+
declare function loadEvalDataset(filePath: string, options?: LoadDatasetOptions): Promise<EvalDataset>;
|
|
2762
|
+
/**
|
|
2763
|
+
* Loads an eval dataset from a plain object
|
|
2764
|
+
*
|
|
2765
|
+
* Useful for programmatically creating datasets in tests
|
|
2766
|
+
*
|
|
2767
|
+
* @param data - The dataset data
|
|
2768
|
+
* @param options - Load options
|
|
2769
|
+
* @returns The loaded and validated dataset
|
|
2770
|
+
* @throws {z.ZodError} If validation fails
|
|
2771
|
+
*
|
|
2772
|
+
* @example
|
|
2773
|
+
* const dataset = loadEvalDatasetFromObject({
|
|
2774
|
+
* name: 'my-test-dataset',
|
|
2775
|
+
* cases: [
|
|
2776
|
+
* {
|
|
2777
|
+
* id: 'case-1',
|
|
2778
|
+
* toolName: 'get_weather',
|
|
2779
|
+
* args: { city: 'London' },
|
|
2780
|
+
* },
|
|
2781
|
+
* ],
|
|
2782
|
+
* });
|
|
2783
|
+
*/
|
|
2784
|
+
declare function loadEvalDatasetFromObject(data: unknown, options?: LoadDatasetOptions): EvalDataset;
|
|
2785
|
+
|
|
2786
|
+
type StoredArtifactKind = 'eval-runner-result' | 'reporter-run' | 'eval-run-comparison' | 'server-comparison';
|
|
2787
|
+
interface StoredEvalArtifactMetadata {
|
|
2788
|
+
datasetName?: string;
|
|
2789
|
+
gitHash?: string;
|
|
2790
|
+
branch?: string;
|
|
2791
|
+
runNumber?: string;
|
|
2792
|
+
trigger?: string;
|
|
2793
|
+
packageVersion?: string;
|
|
2794
|
+
toolOverrideVariantId?: string;
|
|
2795
|
+
mcpHostModel?: string;
|
|
2796
|
+
judgeModel?: string;
|
|
2797
|
+
labels?: Record<string, string>;
|
|
2798
|
+
[key: string]: unknown;
|
|
2799
|
+
}
|
|
2800
|
+
interface StoredEvalArtifact<T> {
|
|
2801
|
+
schemaVersion: 1;
|
|
2802
|
+
kind: StoredArtifactKind;
|
|
2803
|
+
id: string;
|
|
2804
|
+
createdAt: string;
|
|
2805
|
+
metadata: StoredEvalArtifactMetadata;
|
|
2806
|
+
data: T;
|
|
2807
|
+
}
|
|
2808
|
+
interface StoredArtifactSummary {
|
|
2809
|
+
kind: StoredArtifactKind;
|
|
2810
|
+
id: string;
|
|
2811
|
+
createdAt: string;
|
|
2812
|
+
metadata: StoredEvalArtifactMetadata;
|
|
2813
|
+
}
|
|
2814
|
+
interface ListStoredArtifactsOptions {
|
|
2815
|
+
limit?: number;
|
|
2816
|
+
}
|
|
2817
|
+
interface EvalResultStore {
|
|
2818
|
+
saveArtifact<T>(artifact: StoredEvalArtifact<T>): Promise<void>;
|
|
2819
|
+
loadArtifact<T>(kind: StoredArtifactKind, id: string): Promise<StoredEvalArtifact<T>>;
|
|
2820
|
+
loadLatestArtifact<T>(kind: StoredArtifactKind): Promise<StoredEvalArtifact<T> | null>;
|
|
2821
|
+
listArtifacts(kind: StoredArtifactKind, options?: ListStoredArtifactsOptions): Promise<StoredArtifactSummary[]>;
|
|
2822
|
+
}
|
|
2823
|
+
interface FileEvalResultStoreConfig {
|
|
2824
|
+
provider: 'file';
|
|
2825
|
+
dir: string;
|
|
2826
|
+
}
|
|
2827
|
+
interface GCSEvalResultStoreConfig {
|
|
2828
|
+
provider: 'gcs';
|
|
2829
|
+
bucket: string;
|
|
2830
|
+
prefix?: string;
|
|
2831
|
+
}
|
|
2832
|
+
type EvalResultStoreConfig = FileEvalResultStoreConfig | GCSEvalResultStoreConfig;
|
|
2833
|
+
type EvalResultStoreLike = EvalResultStore | EvalResultStoreConfig;
|
|
2834
|
+
declare function createEvalResultStore(config: EvalResultStoreConfig): EvalResultStore;
|
|
2835
|
+
declare function resolveEvalResultStore(store: EvalResultStoreLike): EvalResultStore;
|
|
2836
|
+
declare function isEvalResultStore(value: unknown): value is EvalResultStore;
|
|
2837
|
+
declare function createStoredEvalArtifact<T>(options: {
|
|
2838
|
+
kind: StoredArtifactKind;
|
|
2839
|
+
data: T;
|
|
2840
|
+
id?: string;
|
|
2841
|
+
metadata?: StoredEvalArtifactMetadata;
|
|
2842
|
+
createdAt?: string;
|
|
2843
|
+
}): StoredEvalArtifact<T>;
|
|
2844
|
+
declare function createDefaultArtifactId(timestamp?: string): string;
|
|
2845
|
+
declare function defaultEnvironmentMetadata(): StoredEvalArtifactMetadata;
|
|
2846
|
+
declare class FileEvalResultStore implements EvalResultStore {
|
|
2847
|
+
private readonly dir;
|
|
2848
|
+
constructor(config: FileEvalResultStoreConfig);
|
|
2849
|
+
saveArtifact<T>(artifact: StoredEvalArtifact<T>): Promise<void>;
|
|
2850
|
+
loadArtifact<T>(kind: StoredArtifactKind, id: string): Promise<StoredEvalArtifact<T>>;
|
|
2851
|
+
loadLatestArtifact<T>(kind: StoredArtifactKind): Promise<StoredEvalArtifact<T> | null>;
|
|
2852
|
+
listArtifacts(kind: StoredArtifactKind, options?: ListStoredArtifactsOptions): Promise<StoredArtifactSummary[]>;
|
|
2853
|
+
}
|
|
2854
|
+
declare class GCSEvalResultStore implements EvalResultStore {
|
|
2855
|
+
private readonly bucketName;
|
|
2856
|
+
private readonly prefix;
|
|
2857
|
+
private storage;
|
|
2858
|
+
constructor(config: GCSEvalResultStoreConfig);
|
|
2859
|
+
saveArtifact<T>(artifact: StoredEvalArtifact<T>): Promise<void>;
|
|
2860
|
+
loadArtifact<T>(kind: StoredArtifactKind, id: string): Promise<StoredEvalArtifact<T>>;
|
|
2861
|
+
loadLatestArtifact<T>(kind: StoredArtifactKind): Promise<StoredEvalArtifact<T> | null>;
|
|
2862
|
+
listArtifacts(kind: StoredArtifactKind, options?: ListStoredArtifactsOptions): Promise<StoredArtifactSummary[]>;
|
|
2863
|
+
private getBucket;
|
|
2864
|
+
private objectPath;
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
/**
|
|
2868
|
+
* Reporter-specific type definitions
|
|
2869
|
+
*
|
|
2870
|
+
* These types are used by the MCP reporter and UI.
|
|
2871
|
+
*
|
|
2872
|
+
* @packageDocumentation
|
|
2873
|
+
*/
|
|
2874
|
+
|
|
2875
|
+
/**
|
|
2876
|
+
* Configuration options for MCP Eval Reporter
|
|
2877
|
+
*/
|
|
2878
|
+
interface MCPEvalReporterConfig {
|
|
2879
|
+
/**
|
|
2880
|
+
* Output directory for reports and historical data
|
|
2881
|
+
* @default '.mcp-test-results'
|
|
2882
|
+
*/
|
|
2883
|
+
outputDir?: string;
|
|
2884
|
+
/**
|
|
2885
|
+
* Auto-open report in browser after test run
|
|
2886
|
+
* @default false
|
|
2887
|
+
*/
|
|
2888
|
+
autoOpen?: boolean;
|
|
2889
|
+
/**
|
|
2890
|
+
* Number of historical runs to keep
|
|
2891
|
+
* @default 10
|
|
2892
|
+
*/
|
|
2893
|
+
historyLimit?: number;
|
|
2894
|
+
/**
|
|
2895
|
+
* Suppress console output (report still generated)
|
|
2896
|
+
* @default false
|
|
2897
|
+
*/
|
|
2898
|
+
quiet?: boolean;
|
|
2899
|
+
/**
|
|
2900
|
+
* Include auto-tracked MCP tool calls from tests without explicit eval results.
|
|
2901
|
+
* When true, any test using the MCP fixture will have its tool calls
|
|
2902
|
+
* included in the report, even without using runEvalCase/runEvalDataset.
|
|
2903
|
+
* When false, only tests with explicit eval results are included.
|
|
2904
|
+
* @default true
|
|
2905
|
+
*/
|
|
2906
|
+
includeAutoTracking?: boolean;
|
|
2907
|
+
/**
|
|
2908
|
+
* Optional external result store for durable reporter run history.
|
|
2909
|
+
*/
|
|
2910
|
+
resultStore?: EvalResultStoreLike;
|
|
2911
|
+
/**
|
|
2912
|
+
* Optional run ID for externally stored reporter results.
|
|
2913
|
+
* Defaults to a generated timestamp-based ID.
|
|
2914
|
+
*/
|
|
2915
|
+
runId?: string;
|
|
2916
|
+
/**
|
|
2917
|
+
* Extra metadata to attach to externally stored reporter results.
|
|
2918
|
+
*/
|
|
2919
|
+
runMetadata?: Record<string, unknown>;
|
|
2920
|
+
/**
|
|
2921
|
+
* When true, strips response payloads before storing reporter results externally.
|
|
2922
|
+
* Local report output is unchanged.
|
|
2923
|
+
* @default true
|
|
2924
|
+
*/
|
|
2925
|
+
redactStoredResponses?: boolean;
|
|
2926
|
+
}
|
|
2927
|
+
/**
|
|
2928
|
+
* Experiment tracking metadata for an eval run
|
|
2929
|
+
*/
|
|
2930
|
+
interface EvalRunMetadata {
|
|
2931
|
+
/** Git commit hash at time of run */
|
|
2932
|
+
gitHash?: string;
|
|
2933
|
+
/** ISO timestamp of the run */
|
|
2934
|
+
timestamp: string;
|
|
2935
|
+
/** Package version from package.json */
|
|
2936
|
+
packageVersion: string;
|
|
2937
|
+
/** Runtime tool override variant identifier, when one was used */
|
|
2938
|
+
toolOverrideVariantId?: string;
|
|
2939
|
+
/** MCP host model identifier (if mcp_host mode) */
|
|
2940
|
+
mcpHostModel?: string;
|
|
2941
|
+
/** Judge model identifier (if judge was used) */
|
|
2942
|
+
judgeModel?: string;
|
|
2943
|
+
}
|
|
2944
|
+
/**
|
|
2945
|
+
* Individual conformance check result
|
|
2946
|
+
*/
|
|
2947
|
+
interface MCPConformanceCheck {
|
|
2948
|
+
/**
|
|
2949
|
+
* Check name (e.g., 'server_info_present', 'list_tools_succeeds')
|
|
2950
|
+
*/
|
|
2951
|
+
name: string;
|
|
2952
|
+
/**
|
|
2953
|
+
* Whether the check passed
|
|
2954
|
+
*/
|
|
2955
|
+
pass: boolean;
|
|
2956
|
+
/**
|
|
2957
|
+
* Human-readable message describing the result
|
|
2958
|
+
*/
|
|
2959
|
+
message: string;
|
|
2960
|
+
}
|
|
2961
|
+
/**
|
|
2962
|
+
* Conformance check result as stored in reporter data
|
|
2963
|
+
*/
|
|
2964
|
+
interface MCPConformanceResultData {
|
|
2965
|
+
/**
|
|
2966
|
+
* Test title where conformance check was run
|
|
2967
|
+
*/
|
|
2968
|
+
testTitle: string;
|
|
2969
|
+
/**
|
|
2970
|
+
* Whether all checks passed
|
|
2971
|
+
*/
|
|
2972
|
+
pass: boolean;
|
|
2973
|
+
/**
|
|
2974
|
+
* Individual check results
|
|
2975
|
+
*/
|
|
2976
|
+
checks: MCPConformanceCheck[];
|
|
2977
|
+
/**
|
|
2978
|
+
* Server info if available
|
|
2979
|
+
*/
|
|
2980
|
+
serverInfo?: {
|
|
2981
|
+
name?: string;
|
|
2982
|
+
version?: string;
|
|
2983
|
+
};
|
|
2984
|
+
/**
|
|
2985
|
+
* Number of tools discovered
|
|
2986
|
+
*/
|
|
2987
|
+
toolCount: number;
|
|
2988
|
+
/**
|
|
2989
|
+
* Auth type used for this check
|
|
2990
|
+
*/
|
|
2991
|
+
authType?: AuthType;
|
|
2992
|
+
/**
|
|
2993
|
+
* Project name
|
|
2994
|
+
*/
|
|
2995
|
+
project?: string;
|
|
2996
|
+
}
|
|
2997
|
+
/**
|
|
2998
|
+
* Server capabilities data from mcp-list-tools attachment
|
|
2999
|
+
*/
|
|
3000
|
+
interface MCPServerCapabilitiesData {
|
|
3001
|
+
/**
|
|
3002
|
+
* Test title where listTools was called
|
|
3003
|
+
*/
|
|
3004
|
+
testTitle: string;
|
|
3005
|
+
/**
|
|
3006
|
+
* List of tools available on the server
|
|
3007
|
+
*/
|
|
3008
|
+
tools: Array<{
|
|
3009
|
+
name: string;
|
|
3010
|
+
description?: string;
|
|
3011
|
+
}>;
|
|
3012
|
+
/**
|
|
3013
|
+
* Total number of tools
|
|
3014
|
+
*/
|
|
3015
|
+
toolCount: number;
|
|
3016
|
+
/**
|
|
3017
|
+
* Auth type used for this test
|
|
3018
|
+
*/
|
|
3019
|
+
authType?: AuthType;
|
|
3020
|
+
/**
|
|
3021
|
+
* Project name
|
|
3022
|
+
*/
|
|
3023
|
+
project?: string;
|
|
3024
|
+
}
|
|
3025
|
+
/**
|
|
3026
|
+
* Result of a single iteration within a multi-iteration eval case
|
|
3027
|
+
*/
|
|
3028
|
+
interface IterationResult {
|
|
3029
|
+
/** Whether this iteration passed */
|
|
3030
|
+
pass: boolean;
|
|
3031
|
+
/** Execution time for this iteration */
|
|
3032
|
+
durationMs: number;
|
|
3033
|
+
/** Error message if the iteration failed with an exception */
|
|
3034
|
+
error?: string;
|
|
3035
|
+
/** When true, this iteration failed due to network/infrastructure issues rather than an assertion failure */
|
|
3036
|
+
isInfrastructureError?: boolean;
|
|
3037
|
+
/**
|
|
3038
|
+
* Ordered trace of tool calls made by the LLM during this iteration (mcp_host mode only).
|
|
3039
|
+
* Captures what was actually called so you can distinguish "LLM didn't call the tool"
|
|
3040
|
+
* from "LLM called the wrong tool" from "tool was called but assertion failed".
|
|
3041
|
+
*/
|
|
3042
|
+
mcpHostTrace?: {
|
|
3043
|
+
calls: Array<{
|
|
3044
|
+
name: string;
|
|
3045
|
+
arguments: Record<string, unknown>;
|
|
3046
|
+
status: 'expected' | 'unexpected';
|
|
3047
|
+
}>;
|
|
3048
|
+
missed: Array<{
|
|
3049
|
+
name: string;
|
|
3050
|
+
}>;
|
|
3051
|
+
};
|
|
3052
|
+
/** Token usage from mcp_host LLM simulation in this iteration */
|
|
3053
|
+
hostUsage?: UsageMetrics;
|
|
3054
|
+
}
|
|
3055
|
+
/**
|
|
3056
|
+
* Request data captured from the eval case input.
|
|
3057
|
+
* Preserves what was sent so results are self-contained for debugging.
|
|
3058
|
+
*/
|
|
3059
|
+
interface EvalCaseRequest {
|
|
3060
|
+
/** Human-readable description of the case */
|
|
3061
|
+
description?: string;
|
|
3062
|
+
/** Runtime tool override variant identifier, when one was used */
|
|
3063
|
+
toolOverrideVariantId?: string;
|
|
3064
|
+
/** Tool arguments (direct mode) */
|
|
3065
|
+
args?: Record<string, unknown>;
|
|
3066
|
+
/** Natural language scenario sent to the LLM (mcp_host mode) */
|
|
3067
|
+
scenario?: string;
|
|
3068
|
+
/** LLM provider/model configuration (mcp_host mode) */
|
|
3069
|
+
mcpHostConfig?: {
|
|
3070
|
+
provider?: string;
|
|
3071
|
+
model?: string;
|
|
3072
|
+
};
|
|
3073
|
+
}
|
|
3074
|
+
/**
|
|
3075
|
+
* Result of a single eval case
|
|
3076
|
+
*/
|
|
3077
|
+
interface EvalCaseResult {
|
|
3078
|
+
/**
|
|
3079
|
+
* Case ID
|
|
3080
|
+
*/
|
|
3081
|
+
id: string;
|
|
3082
|
+
/**
|
|
3083
|
+
* Dataset name this case belongs to
|
|
3084
|
+
*/
|
|
3085
|
+
datasetName: string;
|
|
3086
|
+
/**
|
|
3087
|
+
* MCP tool name that was called
|
|
3088
|
+
*/
|
|
3089
|
+
toolName: string;
|
|
3090
|
+
/**
|
|
3091
|
+
* Source of this result
|
|
3092
|
+
*/
|
|
3093
|
+
source: ResultSource;
|
|
3094
|
+
/**
|
|
3095
|
+
* Overall pass/fail status
|
|
3096
|
+
*/
|
|
3097
|
+
pass: boolean;
|
|
3098
|
+
/**
|
|
3099
|
+
* Request data from the eval case input (tool args, scenario, LLM config).
|
|
3100
|
+
* Populated so results are self-contained for debugging without the original dataset.
|
|
3101
|
+
*/
|
|
3102
|
+
request?: EvalCaseRequest;
|
|
3103
|
+
/**
|
|
3104
|
+
* Tool response
|
|
3105
|
+
*/
|
|
3106
|
+
response?: unknown;
|
|
3107
|
+
/**
|
|
3108
|
+
* Error if tool call failed
|
|
3109
|
+
*/
|
|
3110
|
+
error?: string;
|
|
3111
|
+
/**
|
|
3112
|
+
* Expectation results
|
|
3113
|
+
*/
|
|
3114
|
+
expectations: Partial<Record<ExpectationType, EvalExpectationResult>>;
|
|
3115
|
+
/**
|
|
3116
|
+
* Authentication type used for this test
|
|
3117
|
+
*/
|
|
3118
|
+
authType?: AuthType;
|
|
3119
|
+
/**
|
|
3120
|
+
* Playwright project name this test belongs to
|
|
3121
|
+
*/
|
|
3122
|
+
project?: string;
|
|
3123
|
+
/**
|
|
3124
|
+
* Execution time in milliseconds
|
|
3125
|
+
*/
|
|
3126
|
+
durationMs: number;
|
|
3127
|
+
/**
|
|
3128
|
+
* Assertion pass rate (0–1): passes divided by non-infrastructure iterations.
|
|
3129
|
+
* Only present when the case was run with `iterations > 1`.
|
|
3130
|
+
*
|
|
3131
|
+
* Infrastructure errors (network timeouts, rate limits, etc.) are excluded from
|
|
3132
|
+
* the denominator so that environment reliability does not inflate this metric.
|
|
3133
|
+
*/
|
|
3134
|
+
assertionPassRate?: number;
|
|
3135
|
+
/**
|
|
3136
|
+
* 95% Wilson score confidence interval for `assertionPassRate`.
|
|
3137
|
+
* Only present when the case was run with `iterations > 1`.
|
|
3138
|
+
*
|
|
3139
|
+
* Interpet as: the true pass rate is likely between `lower` and `upper`.
|
|
3140
|
+
* Wider intervals mean fewer iterations were run; run more iterations to narrow them.
|
|
3141
|
+
*
|
|
3142
|
+
* @example { lower: 0.35, upper: 0.93 } // 7/10 passes → 70% ± wide CI
|
|
3143
|
+
* @example { lower: 0.57, upper: 0.80 } // 35/50 passes → 70% ± narrow CI
|
|
3144
|
+
*/
|
|
3145
|
+
assertionPassRateCI?: {
|
|
3146
|
+
/** Lower bound of the 95% confidence interval (0–1) */
|
|
3147
|
+
lower: number;
|
|
3148
|
+
/** Upper bound of the 95% confidence interval (0–1) */
|
|
3149
|
+
upper: number;
|
|
3150
|
+
};
|
|
3151
|
+
/**
|
|
3152
|
+
* Infrastructure error rate (0–1): infra errors divided by total iterations.
|
|
3153
|
+
* Only present when the case was run with `iterations > 1`.
|
|
3154
|
+
*/
|
|
3155
|
+
infrastructureErrorRate?: number;
|
|
3156
|
+
/**
|
|
3157
|
+
* Per-iteration pass/fail breakdown.
|
|
3158
|
+
* Only present when the case was run with `iterations > 1`.
|
|
3159
|
+
*/
|
|
3160
|
+
iterationResults?: Array<IterationResult>;
|
|
3161
|
+
/**
|
|
3162
|
+
* Tags from the source eval case, for filtering and slicing reports.
|
|
3163
|
+
*/
|
|
3164
|
+
tags?: string[];
|
|
3165
|
+
/**
|
|
3166
|
+
* Precision of tool calls made (0–1).
|
|
3167
|
+
* 1.0 means every tool called was expected; <1.0 means unexpected tools were called.
|
|
3168
|
+
* Populated whenever a `toolsTriggered` expectation is evaluated.
|
|
3169
|
+
*/
|
|
3170
|
+
toolPrecision?: number;
|
|
3171
|
+
/**
|
|
3172
|
+
* Recall of required tool calls (0–1).
|
|
3173
|
+
* 1.0 means all required tools were called; <1.0 means some were missed.
|
|
3174
|
+
* Only populated when toolsTriggered expectation was evaluated.
|
|
3175
|
+
*/
|
|
3176
|
+
toolRecall?: number;
|
|
3177
|
+
/**
|
|
3178
|
+
* Pass/fail status of this case in the baseline run.
|
|
3179
|
+
* Only present when a baseline was provided to runEvalDataset.
|
|
3180
|
+
*/
|
|
3181
|
+
baselinePass?: boolean;
|
|
3182
|
+
/**
|
|
3183
|
+
* Number of iterations that failed due to infrastructure errors (network, rate limits, etc.)
|
|
3184
|
+
* Only present when the case was run with `iterations > 1`.
|
|
3185
|
+
*/
|
|
3186
|
+
infrastructureErrorCount?: number;
|
|
3187
|
+
/**
|
|
3188
|
+
* Ordered trace of tool calls made by the LLM in mcp_host mode.
|
|
3189
|
+
* Only populated when the eval case uses toolsTriggered expectations.
|
|
3190
|
+
*/
|
|
3191
|
+
mcpHostTrace?: {
|
|
3192
|
+
/** The ordered sequence of tool calls made by the LLM */
|
|
3193
|
+
calls: Array<{
|
|
3194
|
+
name: string;
|
|
3195
|
+
arguments: Record<string, unknown>;
|
|
3196
|
+
/** 'expected' = was in the expected set, 'unexpected' = was not expected */
|
|
3197
|
+
status: 'expected' | 'unexpected';
|
|
3198
|
+
}>;
|
|
3199
|
+
/** Tools that were required but never called */
|
|
3200
|
+
missed: Array<{
|
|
3201
|
+
name: string;
|
|
3202
|
+
}>;
|
|
3203
|
+
};
|
|
3204
|
+
/**
|
|
3205
|
+
* Aggregate token usage from mcp_host LLM simulation for this case.
|
|
3206
|
+
* Summed across all iterations. Only populated for mcp_host mode cases.
|
|
3207
|
+
*/
|
|
3208
|
+
hostUsage?: UsageMetrics;
|
|
3209
|
+
}
|
|
3210
|
+
/**
|
|
3211
|
+
* Aggregated MCP eval run data
|
|
3212
|
+
*/
|
|
3213
|
+
interface MCPEvalRunData {
|
|
3214
|
+
/**
|
|
3215
|
+
* Run timestamp (ISO 8601)
|
|
3216
|
+
*/
|
|
3217
|
+
timestamp: string;
|
|
3218
|
+
/**
|
|
3219
|
+
* Total duration in milliseconds
|
|
3220
|
+
*/
|
|
3221
|
+
durationMs: number;
|
|
3222
|
+
/**
|
|
3223
|
+
* Environment info
|
|
3224
|
+
*/
|
|
3225
|
+
environment: {
|
|
3226
|
+
ci: boolean;
|
|
3227
|
+
node: string;
|
|
3228
|
+
platform: string;
|
|
3229
|
+
};
|
|
3230
|
+
/**
|
|
3231
|
+
* Aggregate metrics
|
|
3232
|
+
*/
|
|
3233
|
+
metrics: {
|
|
3234
|
+
/**
|
|
3235
|
+
* Total number of eval cases
|
|
3236
|
+
*/
|
|
3237
|
+
total: number;
|
|
3238
|
+
/**
|
|
3239
|
+
* Number of passed cases
|
|
3240
|
+
*/
|
|
3241
|
+
passed: number;
|
|
3242
|
+
/**
|
|
3243
|
+
* Number of failed cases
|
|
3244
|
+
*/
|
|
3245
|
+
failed: number;
|
|
3246
|
+
/**
|
|
3247
|
+
* Pass rate (0-1)
|
|
3248
|
+
*/
|
|
3249
|
+
passRate: number;
|
|
3250
|
+
/**
|
|
3251
|
+
* Dataset breakdown: dataset name -> count
|
|
3252
|
+
*/
|
|
3253
|
+
datasetBreakdown: Record<string, number>;
|
|
3254
|
+
/**
|
|
3255
|
+
* Expectation type breakdown
|
|
3256
|
+
*/
|
|
3257
|
+
expectationBreakdown: ExpectationBreakdown;
|
|
3258
|
+
/**
|
|
3259
|
+
* Aggregate token usage from all mcp_host LLM simulations in this run.
|
|
3260
|
+
*/
|
|
3261
|
+
totalHostUsage?: UsageMetrics;
|
|
3262
|
+
};
|
|
3263
|
+
/**
|
|
3264
|
+
* All eval results from this run
|
|
3265
|
+
*/
|
|
3266
|
+
results: EvalCaseResult[];
|
|
3267
|
+
/**
|
|
3268
|
+
* Conformance check results (optional)
|
|
3269
|
+
*/
|
|
3270
|
+
conformanceChecks?: MCPConformanceResultData[];
|
|
3271
|
+
/**
|
|
3272
|
+
* Server capabilities discovered via listTools (optional)
|
|
3273
|
+
*/
|
|
3274
|
+
serverCapabilities?: MCPServerCapabilitiesData[];
|
|
3275
|
+
}
|
|
3276
|
+
/**
|
|
3277
|
+
* Historical summary for trend charts
|
|
3278
|
+
*/
|
|
3279
|
+
interface MCPEvalHistoricalSummary {
|
|
3280
|
+
timestamp: string;
|
|
3281
|
+
total: number;
|
|
3282
|
+
passed: number;
|
|
3283
|
+
failed: number;
|
|
3284
|
+
passRate: number;
|
|
3285
|
+
durationMs: number;
|
|
3286
|
+
}
|
|
3287
|
+
/**
|
|
3288
|
+
* Complete data structure passed to UI
|
|
3289
|
+
*/
|
|
3290
|
+
interface MCPEvalData {
|
|
3291
|
+
runData: MCPEvalRunData;
|
|
3292
|
+
historical: MCPEvalHistoricalSummary[];
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
/**
|
|
3296
|
+
* Context passed to the eval runner
|
|
3297
|
+
*/
|
|
3298
|
+
interface EvalContext {
|
|
3299
|
+
/**
|
|
3300
|
+
* MCP fixture API for interacting with the server
|
|
3301
|
+
*/
|
|
3302
|
+
mcp: MCPFixtureApi;
|
|
3303
|
+
/**
|
|
3304
|
+
* Optional Playwright TestInfo for reporter integration
|
|
3305
|
+
* When provided, eval results will be attached to the test for the MCP reporter
|
|
3306
|
+
*/
|
|
3307
|
+
testInfo?: TestInfo;
|
|
3308
|
+
/**
|
|
3309
|
+
* Optional Playwright expect function for snapshot testing
|
|
3310
|
+
* Required for snapshot expectations to work properly
|
|
3311
|
+
*/
|
|
3312
|
+
expect?: Expect;
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
/**
|
|
3316
|
+
* Metadata overrides for a single existing MCP tool.
|
|
3317
|
+
*/
|
|
3318
|
+
interface ToolMetadataOverride {
|
|
3319
|
+
/**
|
|
3320
|
+
* Replacement tool description shown to MCP hosts.
|
|
3321
|
+
*/
|
|
3322
|
+
description?: string;
|
|
3323
|
+
/**
|
|
3324
|
+
* Replacement input schema shown to MCP hosts.
|
|
3325
|
+
*/
|
|
3326
|
+
inputSchema?: Record<string, unknown>;
|
|
3327
|
+
}
|
|
3328
|
+
/**
|
|
3329
|
+
* Runtime metadata variant for experimenting with MCP tool discoverability.
|
|
3330
|
+
*
|
|
3331
|
+
* Tool keys are canonical MCP server tool names. Overrides affect only the
|
|
3332
|
+
* metadata returned from listTools(); callTool() still forwards canonical tool
|
|
3333
|
+
* names and arguments to the original MCP server.
|
|
3334
|
+
*/
|
|
3335
|
+
interface ToolOverrideVariant {
|
|
3336
|
+
/**
|
|
3337
|
+
* Stable identifier for this runtime variant.
|
|
3338
|
+
*/
|
|
3339
|
+
id: string;
|
|
3340
|
+
/**
|
|
3341
|
+
* Optional human-readable explanation of what this variant is testing.
|
|
3342
|
+
*/
|
|
3343
|
+
description?: string;
|
|
3344
|
+
/**
|
|
3345
|
+
* Per-tool metadata overrides keyed by canonical tool name.
|
|
3346
|
+
*/
|
|
3347
|
+
tools: Record<string, ToolMetadataOverride>;
|
|
3348
|
+
}
|
|
3349
|
+
/**
|
|
3350
|
+
* Overall result of running an eval dataset
|
|
3351
|
+
*/
|
|
3352
|
+
interface EvalRunnerResult {
|
|
3353
|
+
/**
|
|
3354
|
+
* Total number of cases
|
|
3355
|
+
*/
|
|
3356
|
+
total: number;
|
|
3357
|
+
/**
|
|
3358
|
+
* Number of passing cases
|
|
3359
|
+
*/
|
|
3360
|
+
passed: number;
|
|
3361
|
+
/**
|
|
3362
|
+
* Number of failing cases
|
|
3363
|
+
*/
|
|
3364
|
+
failed: number;
|
|
3365
|
+
/**
|
|
3366
|
+
* Individual case results
|
|
3367
|
+
*/
|
|
3368
|
+
caseResults: Array<EvalCaseResult>;
|
|
3369
|
+
/**
|
|
3370
|
+
* Overall execution time in milliseconds
|
|
3371
|
+
*/
|
|
3372
|
+
durationMs: number;
|
|
3373
|
+
/**
|
|
3374
|
+
* Difference between current pass rate and baseline pass rate.
|
|
3375
|
+
* Positive = improvement, negative = regression.
|
|
3376
|
+
* Only present when `baselineResultsFrom` was provided.
|
|
3377
|
+
*/
|
|
3378
|
+
deltaPassRate?: number;
|
|
3379
|
+
/**
|
|
3380
|
+
* Number of cases that regressed: passed in baseline, failed now.
|
|
3381
|
+
* Only present when `baselineResultsFrom` was provided.
|
|
3382
|
+
*/
|
|
3383
|
+
regressions?: number;
|
|
3384
|
+
/**
|
|
3385
|
+
* Number of cases that improved: failed in baseline, passed now.
|
|
3386
|
+
* Only present when `baselineResultsFrom` was provided.
|
|
3387
|
+
*/
|
|
3388
|
+
improvements?: number;
|
|
3389
|
+
/**
|
|
3390
|
+
* Average tool precision across all mcp_host cases that have a
|
|
3391
|
+
* `toolsTriggered` expectation (precision = fraction of called tools
|
|
3392
|
+
* that were expected). Only present when at least one such case ran.
|
|
3393
|
+
*/
|
|
3394
|
+
datasetToolPrecision?: number;
|
|
3395
|
+
/**
|
|
3396
|
+
* Average tool recall across all mcp_host cases that have a
|
|
3397
|
+
* `toolsTriggered` expectation (recall = fraction of required tools
|
|
3398
|
+
* that were actually called). Only present when at least one such case ran.
|
|
3399
|
+
*/
|
|
3400
|
+
datasetToolRecall?: number;
|
|
3401
|
+
/**
|
|
3402
|
+
* Harmonic mean of `datasetToolPrecision` and `datasetToolRecall`.
|
|
3403
|
+
* Only present when at least one case contributes precision/recall data.
|
|
3404
|
+
*/
|
|
3405
|
+
datasetToolF1?: number;
|
|
3406
|
+
/**
|
|
3407
|
+
* Experiment tracking metadata captured at run time.
|
|
3408
|
+
*/
|
|
3409
|
+
metadata?: EvalRunMetadata;
|
|
3410
|
+
/**
|
|
3411
|
+
* Aggregate token usage from all mcp_host LLM simulations across all cases.
|
|
3412
|
+
*/
|
|
3413
|
+
totalHostUsage?: UsageMetrics;
|
|
3414
|
+
}
|
|
3415
|
+
type StoredEvalResultRef = 'latest' | {
|
|
3416
|
+
id: string;
|
|
3417
|
+
};
|
|
3418
|
+
interface StoredEvalResultLoadOptions {
|
|
3419
|
+
store: true;
|
|
3420
|
+
ref: StoredEvalResultRef;
|
|
3421
|
+
}
|
|
3422
|
+
interface StoredEvalResultSaveOptions {
|
|
3423
|
+
store: true;
|
|
3424
|
+
ref?: 'latest' | {
|
|
3425
|
+
id?: string;
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
/**
|
|
3429
|
+
* Options for running eval dataset
|
|
3430
|
+
*/
|
|
3431
|
+
interface EvalRunnerOptions {
|
|
3432
|
+
/**
|
|
3433
|
+
* The dataset to run
|
|
3434
|
+
*/
|
|
3435
|
+
dataset: EvalDataset;
|
|
3436
|
+
/**
|
|
3437
|
+
* Schema registry for schema validation by name
|
|
3438
|
+
*
|
|
3439
|
+
* Maps schema names to Zod schemas for use with expect.schema
|
|
3440
|
+
*
|
|
3441
|
+
* @example
|
|
3442
|
+
* ```typescript
|
|
3443
|
+
* {
|
|
3444
|
+
* schemas: {
|
|
3445
|
+
* WeatherResponse: z.object({ temperature: z.number() }),
|
|
3446
|
+
* ErrorResponse: z.object({ error: z.string() }),
|
|
3447
|
+
* }
|
|
3448
|
+
* }
|
|
3449
|
+
* ```
|
|
3450
|
+
*/
|
|
3451
|
+
schemas?: Record<string, ZodType>;
|
|
3452
|
+
/**
|
|
3453
|
+
* Whether to stop on first failure
|
|
3454
|
+
* @default false
|
|
3455
|
+
*/
|
|
3456
|
+
stopOnFailure?: boolean;
|
|
3457
|
+
/**
|
|
3458
|
+
* Optional callback called after each case
|
|
3459
|
+
*/
|
|
3460
|
+
onCaseComplete?: (result: EvalCaseResult) => void | Promise<void>;
|
|
3461
|
+
/**
|
|
3462
|
+
* Maximum number of eval cases to run concurrently.
|
|
3463
|
+
* When > 1, cases run in parallel (ignores stopOnFailure ordering).
|
|
3464
|
+
* @default 1 (sequential)
|
|
3465
|
+
*/
|
|
3466
|
+
concurrency?: number;
|
|
3467
|
+
/**
|
|
3468
|
+
* Default iteration count for `mcp_host` mode cases that do not specify
|
|
3469
|
+
* `iterations` explicitly. Has no effect on `direct` mode cases (which are
|
|
3470
|
+
* deterministic and always default to 1 iteration).
|
|
3471
|
+
*
|
|
3472
|
+
* Set to 10 for standard runs or 20 for release gates. Individual cases can
|
|
3473
|
+
* still override this with their own `iterations` field.
|
|
3474
|
+
*
|
|
3475
|
+
* @default 1 (preserves historical behaviour when not set)
|
|
3476
|
+
*
|
|
3477
|
+
* @example
|
|
3478
|
+
* ```typescript
|
|
3479
|
+
* // Run all mcp_host cases 10 times each by default
|
|
3480
|
+
* await runEvalDataset({ dataset, defaultLlmIterations: 10 }, { mcp });
|
|
3481
|
+
* ```
|
|
3482
|
+
*/
|
|
3483
|
+
defaultLlmIterations?: number;
|
|
3484
|
+
/**
|
|
3485
|
+
* Default number of judge evaluations for cases that do not specify
|
|
3486
|
+
* `judgeReps` explicitly. Applies to any case with a `passesJudge`
|
|
3487
|
+
* expectation. Per-case `judgeReps` overrides this.
|
|
3488
|
+
*
|
|
3489
|
+
* @default 1 (single judge run)
|
|
3490
|
+
*/
|
|
3491
|
+
defaultJudgeReps?: number;
|
|
3492
|
+
/**
|
|
3493
|
+
* When set, only eval cases whose `tags` array contains at least one of
|
|
3494
|
+
* the specified tags are run. Cases without a `tags` field are excluded.
|
|
3495
|
+
* When undefined or empty, all cases run (default behavior).
|
|
3496
|
+
*/
|
|
3497
|
+
filterTags?: string[];
|
|
3498
|
+
/**
|
|
3499
|
+
* If set, saves the run results to this file path after completion.
|
|
3500
|
+
* Use with `baselineResultsFrom` on the next run for regression detection.
|
|
3501
|
+
*
|
|
3502
|
+
* @example '.mcp-test-results/baseline.json'
|
|
3503
|
+
*/
|
|
3504
|
+
saveResultsTo?: string | StoredEvalResultSaveOptions;
|
|
3505
|
+
/**
|
|
3506
|
+
* When true (default), strips the `response` field from each case result
|
|
3507
|
+
* before saving the baseline file. Keeps baseline files small and git-friendly —
|
|
3508
|
+
* the full tool response is not needed for pass/fail regression detection.
|
|
3509
|
+
*
|
|
3510
|
+
* Set to false to preserve complete responses in the saved file.
|
|
3511
|
+
*
|
|
3512
|
+
* @default true
|
|
3513
|
+
*/
|
|
3514
|
+
omitResponsesFromBaseline?: boolean;
|
|
3515
|
+
/**
|
|
3516
|
+
* When true (default), strips response bodies from each case result before
|
|
3517
|
+
* saving to an external result store. Stored artifacts only need the pass/fail
|
|
3518
|
+
* shape and tool-call metadata — full response payloads are not necessary
|
|
3519
|
+
* for regression detection or history comparison. Set to false when you
|
|
3520
|
+
* specifically need stored artifacts to retain complete responses.
|
|
3521
|
+
*
|
|
3522
|
+
* Defaults to `true` to match the reporter's `redactStoredResponses` default
|
|
3523
|
+
* (see `MCPReporter`). Both write paths produce the same redaction shape, so
|
|
3524
|
+
* users with both configured don't end up with a mix of redacted and
|
|
3525
|
+
* non-redacted artifacts depending on which code path wrote them.
|
|
3526
|
+
*
|
|
3527
|
+
* @default true
|
|
3528
|
+
*/
|
|
3529
|
+
redactStoredResponses?: boolean;
|
|
3530
|
+
/**
|
|
3531
|
+
* Optional external result store for loading/saving eval run artifacts.
|
|
3532
|
+
*/
|
|
3533
|
+
resultStore?: EvalResultStoreLike;
|
|
3534
|
+
/**
|
|
3535
|
+
* If set, loads this file or stored result as the baseline and computes delta metrics vs the current run.
|
|
3536
|
+
* Populates `EvalRunnerResult.deltaPassRate`, `.regressions`, `.improvements`,
|
|
3537
|
+
* and tags each `EvalCaseResult.baselinePass`.
|
|
3538
|
+
*/
|
|
3539
|
+
baselineResultsFrom?: string | StoredEvalResultLoadOptions;
|
|
3540
|
+
/**
|
|
3541
|
+
* Runtime MCP tool metadata overrides used for variant experiments.
|
|
3542
|
+
*
|
|
3543
|
+
* Overrides are applied to the tool list shown to MCP hosts without changing
|
|
3544
|
+
* the eval dataset or mutating the underlying MCP server. Tool keys must be
|
|
3545
|
+
* canonical tool names exposed by the server.
|
|
3546
|
+
*/
|
|
3547
|
+
toolOverrides?: ToolOverrideVariant;
|
|
3548
|
+
/**
|
|
3549
|
+
* MCP host model identifier to record in run metadata.
|
|
3550
|
+
* Use this to identify which model was used when running mcp_host cases.
|
|
3551
|
+
*
|
|
3552
|
+
* @example 'claude-opus-4-20250514'
|
|
3553
|
+
*/
|
|
3554
|
+
mcpHostModel?: string;
|
|
3555
|
+
/**
|
|
3556
|
+
* Judge model identifier to record in run metadata.
|
|
3557
|
+
* Use this to identify which model was used for judge evaluations.
|
|
3558
|
+
*
|
|
3559
|
+
* @example 'claude-sonnet-4-20250514'
|
|
3560
|
+
*/
|
|
3561
|
+
judgeModel?: string;
|
|
3562
|
+
}
|
|
3563
|
+
/**
|
|
3564
|
+
* Options for running a single eval case
|
|
3565
|
+
*/
|
|
3566
|
+
interface EvalCaseOptions {
|
|
3567
|
+
/**
|
|
3568
|
+
* Dataset name for the result (defaults to 'single-case')
|
|
3569
|
+
*/
|
|
3570
|
+
datasetName?: string;
|
|
3571
|
+
/**
|
|
3572
|
+
* Schema registry for schema validation by name
|
|
3573
|
+
*/
|
|
3574
|
+
schemas?: Record<string, ZodType>;
|
|
3575
|
+
/**
|
|
3576
|
+
* Runtime tool override variant id for reporter/debug metadata.
|
|
3577
|
+
*/
|
|
3578
|
+
toolOverrideVariantId?: string;
|
|
3579
|
+
}
|
|
3580
|
+
/**
|
|
3581
|
+
* Runs a single eval case and returns the result.
|
|
3582
|
+
* When `evalCase.iterations > 1`, runs the case N times and returns accuracy.
|
|
3583
|
+
*
|
|
3584
|
+
* @param evalCase - The eval case to run
|
|
3585
|
+
* @param context - Context containing mcp, testInfo, expect
|
|
3586
|
+
* @param options - Optional configuration (datasetName, schemas)
|
|
3587
|
+
* @returns The result of running the eval case
|
|
3588
|
+
*
|
|
3589
|
+
* @example
|
|
3590
|
+
* ```typescript
|
|
3591
|
+
* const result = await runEvalCase(
|
|
3592
|
+
* evalCase,
|
|
3593
|
+
* { mcp, testInfo, expect },
|
|
3594
|
+
* { schemas: { WeatherResponse: WeatherSchema } }
|
|
3595
|
+
* );
|
|
3596
|
+
*
|
|
3597
|
+
* expect(result.pass).toBe(true);
|
|
3598
|
+
* ```
|
|
3599
|
+
*/
|
|
3600
|
+
declare function runEvalCase(evalCase: EvalCase, context: EvalContext, options?: EvalCaseOptions): Promise<EvalCaseResult>;
|
|
3601
|
+
declare function runEvalDataset(options: EvalRunnerOptions, context: EvalContext): Promise<EvalRunnerResult>;
|
|
3602
|
+
|
|
3603
|
+
/** Outcome of comparing two servers on a single eval case. */
|
|
3604
|
+
type ComparisonOutcome = 'A_WINS' | 'B_WINS' | 'TIE' | 'BOTH_FAIL';
|
|
3605
|
+
/** Result of comparing a single eval case across two servers. */
|
|
3606
|
+
interface CaseComparisonResult {
|
|
3607
|
+
/** Case ID */
|
|
3608
|
+
id: string;
|
|
3609
|
+
/** Comparison outcome */
|
|
3610
|
+
outcome: ComparisonOutcome;
|
|
3611
|
+
/** Result from server A */
|
|
3612
|
+
serverA: EvalCaseResult;
|
|
3613
|
+
/** Result from server B */
|
|
3614
|
+
serverB: EvalCaseResult;
|
|
3615
|
+
}
|
|
3616
|
+
/** Aggregated result of running a dataset against two servers. */
|
|
3617
|
+
interface ServerComparisonResult {
|
|
3618
|
+
/** Dataset name */
|
|
3619
|
+
dataset: string;
|
|
3620
|
+
/** Total cases compared (cases present in both runs) */
|
|
3621
|
+
total: number;
|
|
3622
|
+
/** Cases where server A passed and server B failed */
|
|
3623
|
+
aWins: number;
|
|
3624
|
+
/** Cases where server B passed and server A failed */
|
|
3625
|
+
bWins: number;
|
|
3626
|
+
/** Cases where both passed */
|
|
3627
|
+
ties: number;
|
|
3628
|
+
/** Cases where both failed */
|
|
3629
|
+
bothFail: number;
|
|
3630
|
+
/** Cases with a decisive outcome (aWins + bWins + ties, excludes BOTH_FAIL) */
|
|
3631
|
+
decidedCases: number;
|
|
3632
|
+
/** Fraction of total cases where both servers failed (bothFail / total) */
|
|
3633
|
+
failureAlignment: number;
|
|
3634
|
+
/** A win rate (aWins / decidedCases, excludes BOTH_FAIL) */
|
|
3635
|
+
aWinRate: number;
|
|
3636
|
+
/** B win rate (bWins / decidedCases, excludes BOTH_FAIL) */
|
|
3637
|
+
bWinRate: number;
|
|
3638
|
+
/** Tie rate (ties / decidedCases, excludes BOTH_FAIL) */
|
|
3639
|
+
tieRate: number;
|
|
3640
|
+
/** Per-case comparison results */
|
|
3641
|
+
cases: CaseComparisonResult[];
|
|
3642
|
+
/** Full result from server A */
|
|
3643
|
+
serverAResult: EvalRunnerResult;
|
|
3644
|
+
/** Full result from server B */
|
|
3645
|
+
serverBResult: EvalRunnerResult;
|
|
3646
|
+
/** Total duration in milliseconds */
|
|
3647
|
+
durationMs: number;
|
|
3648
|
+
}
|
|
3649
|
+
/**
|
|
3650
|
+
* Options for `runServerComparison`.
|
|
3651
|
+
* Same as `EvalRunnerOptions` without baseline-specific fields.
|
|
3652
|
+
*/
|
|
3653
|
+
type ServerComparisonOptions = Omit<EvalRunnerOptions, 'saveResultsTo' | 'baselineResultsFrom'> & {
|
|
3654
|
+
comparisonStore?: EvalResultStoreLike;
|
|
3655
|
+
comparisonId?: string;
|
|
3656
|
+
comparisonMetadata?: StoredEvalArtifactMetadata;
|
|
3657
|
+
redactStoredResponses?: boolean;
|
|
3658
|
+
};
|
|
3659
|
+
/**
|
|
3660
|
+
* Runs the same eval dataset against two MCP servers in parallel and
|
|
3661
|
+
* returns a detailed per-case comparison of results.
|
|
3662
|
+
*
|
|
3663
|
+
* Both servers receive identical cases and options. The comparison uses
|
|
3664
|
+
* simple pass/fail per case: A_WINS means A passed and B failed, etc.
|
|
3665
|
+
*
|
|
3666
|
+
* @param options - Eval dataset and runner options (shared between both servers)
|
|
3667
|
+
* @param contextA - MCP context for server A (e.g., Glean MCP)
|
|
3668
|
+
* @param contextB - MCP context for server B (e.g., native MCP)
|
|
3669
|
+
* @returns Comparison result with per-case outcomes and aggregate win rates
|
|
3670
|
+
*
|
|
3671
|
+
* @example
|
|
3672
|
+
* ```typescript
|
|
3673
|
+
* const comparison = await runServerComparison(
|
|
3674
|
+
* { dataset },
|
|
3675
|
+
* { mcp: gleanMcpFixture },
|
|
3676
|
+
* { mcp: nativeMcpFixture }
|
|
3677
|
+
* );
|
|
3678
|
+
* console.log(`Glean MCP wins: ${(comparison.aWinRate * 100).toFixed(1)}%`);
|
|
3679
|
+
* console.log(`Native MCP wins: ${(comparison.bWinRate * 100).toFixed(1)}%`);
|
|
3680
|
+
* ```
|
|
3681
|
+
*/
|
|
3682
|
+
declare function runServerComparison(options: ServerComparisonOptions, contextA: EvalContext, contextB: EvalContext): Promise<ServerComparisonResult>;
|
|
3683
|
+
interface SaveServerComparisonOptions {
|
|
3684
|
+
store: EvalResultStoreLike;
|
|
3685
|
+
comparison: ServerComparisonResult;
|
|
3686
|
+
id?: string;
|
|
3687
|
+
metadata?: StoredEvalArtifactMetadata;
|
|
3688
|
+
redactStoredResponses?: boolean;
|
|
3689
|
+
}
|
|
3690
|
+
declare function saveServerComparison(options: SaveServerComparisonOptions): Promise<StoredEvalArtifact<ServerComparisonResult>>;
|
|
3691
|
+
|
|
3692
|
+
/** Labels used when presenting an eval run comparison. */
|
|
3693
|
+
interface EvalRunComparisonLabels {
|
|
3694
|
+
/** Label for the baseline run. Defaults to "baseline". */
|
|
3695
|
+
baseline?: string;
|
|
3696
|
+
/** Label for the candidate run. Defaults to the candidate variant id or "candidate". */
|
|
3697
|
+
candidate?: string;
|
|
3698
|
+
}
|
|
3699
|
+
/** Outcome of comparing one eval case across two completed eval runs. */
|
|
3700
|
+
type EvalCaseComparisonOutcome = 'IMPROVED' | 'REGRESSED' | 'UNCHANGED_PASS' | 'UNCHANGED_FAIL' | 'MISSING_FROM_BASELINE' | 'MISSING_FROM_CANDIDATE';
|
|
3701
|
+
/** Per-case comparison between a baseline run and candidate run. */
|
|
3702
|
+
interface EvalCaseComparison {
|
|
3703
|
+
/** Case ID */
|
|
3704
|
+
id: string;
|
|
3705
|
+
/** Outcome for this case */
|
|
3706
|
+
outcome: EvalCaseComparisonOutcome;
|
|
3707
|
+
/** Baseline case result, absent when the case only exists in the candidate run */
|
|
3708
|
+
baseline?: EvalCaseResult;
|
|
3709
|
+
/** Candidate case result, absent when the case only exists in the baseline run */
|
|
3710
|
+
candidate?: EvalCaseResult;
|
|
3711
|
+
}
|
|
3712
|
+
/** Options for comparing two completed eval runs. */
|
|
3713
|
+
interface CompareEvalRunsOptions {
|
|
3714
|
+
/** Baseline run result */
|
|
3715
|
+
baseline: EvalRunnerResult;
|
|
3716
|
+
/** Candidate run result */
|
|
3717
|
+
candidate: EvalRunnerResult;
|
|
3718
|
+
/** Optional human-readable run labels */
|
|
3719
|
+
labels?: EvalRunComparisonLabels;
|
|
3720
|
+
}
|
|
3721
|
+
/** Aggregated comparison between two completed eval runs. */
|
|
3722
|
+
interface EvalRunComparisonResult {
|
|
3723
|
+
/** Baseline label */
|
|
3724
|
+
baselineLabel: string;
|
|
3725
|
+
/** Candidate label */
|
|
3726
|
+
candidateLabel: string;
|
|
3727
|
+
/** Baseline pass rate (passed / total, or 0 when total is 0) */
|
|
3728
|
+
baselinePassRate: number;
|
|
3729
|
+
/** Candidate pass rate (passed / total, or 0 when total is 0) */
|
|
3730
|
+
candidatePassRate: number;
|
|
3731
|
+
/** Candidate pass rate minus baseline pass rate */
|
|
3732
|
+
deltaPassRate: number;
|
|
3733
|
+
/** Baseline dataset tool precision, when present */
|
|
3734
|
+
baselineToolPrecision?: number;
|
|
3735
|
+
/** Candidate dataset tool precision, when present */
|
|
3736
|
+
candidateToolPrecision?: number;
|
|
3737
|
+
/** Candidate precision minus baseline precision, when both are present */
|
|
3738
|
+
deltaToolPrecision?: number;
|
|
3739
|
+
/** Baseline dataset tool recall, when present */
|
|
3740
|
+
baselineToolRecall?: number;
|
|
3741
|
+
/** Candidate dataset tool recall, when present */
|
|
3742
|
+
candidateToolRecall?: number;
|
|
3743
|
+
/** Candidate recall minus baseline recall, when both are present */
|
|
3744
|
+
deltaToolRecall?: number;
|
|
3745
|
+
/** Baseline dataset tool F1, when present */
|
|
3746
|
+
baselineToolF1?: number;
|
|
3747
|
+
/** Candidate dataset tool F1, when present */
|
|
3748
|
+
candidateToolF1?: number;
|
|
3749
|
+
/** Candidate F1 minus baseline F1, when both are present */
|
|
3750
|
+
deltaToolF1?: number;
|
|
3751
|
+
/** All per-case comparison records in deterministic order */
|
|
3752
|
+
cases: EvalCaseComparison[];
|
|
3753
|
+
/** Cases that failed in baseline and passed in candidate */
|
|
3754
|
+
improvedCases: EvalCaseComparison[];
|
|
3755
|
+
/** Cases that passed in baseline and failed in candidate */
|
|
3756
|
+
regressedCases: EvalCaseComparison[];
|
|
3757
|
+
/** Cases that passed in both runs */
|
|
3758
|
+
unchangedPasses: EvalCaseComparison[];
|
|
3759
|
+
/** Cases that failed in both runs */
|
|
3760
|
+
unchangedFailures: EvalCaseComparison[];
|
|
3761
|
+
/** Cases present only in candidate */
|
|
3762
|
+
missingFromBaseline: EvalCaseComparison[];
|
|
3763
|
+
/** Cases present only in baseline */
|
|
3764
|
+
missingFromCandidate: EvalCaseComparison[];
|
|
3765
|
+
}
|
|
3766
|
+
type StoredEvalRunRef = 'latest' | {
|
|
3767
|
+
id: string;
|
|
3768
|
+
};
|
|
3769
|
+
interface SaveEvalRunComparisonOptions {
|
|
3770
|
+
store: EvalResultStoreLike;
|
|
3771
|
+
comparison: EvalRunComparisonResult;
|
|
3772
|
+
id?: string;
|
|
3773
|
+
metadata?: StoredEvalArtifactMetadata;
|
|
3774
|
+
redactStoredResponses?: boolean;
|
|
3775
|
+
}
|
|
3776
|
+
/**
|
|
3777
|
+
* Compares two completed eval runs without running any evals or reading files.
|
|
3778
|
+
*
|
|
3779
|
+
* Use this after running a baseline and candidate (for example a toolOverrides
|
|
3780
|
+
* variant) to compute pass-rate deltas, tool metric deltas, and per-case
|
|
3781
|
+
* improvement/regression buckets.
|
|
3782
|
+
*/
|
|
3783
|
+
declare function compareEvalRuns(options: CompareEvalRunsOptions): EvalRunComparisonResult;
|
|
3784
|
+
declare function loadStoredEvalRunnerResult(storeLike: EvalResultStoreLike, ref: StoredEvalRunRef): Promise<StoredEvalArtifact<EvalRunnerResult>>;
|
|
3785
|
+
declare function saveEvalRunComparison(options: SaveEvalRunComparisonOptions): Promise<StoredEvalArtifact<EvalRunComparisonResult>>;
|
|
3786
|
+
|
|
3787
|
+
/**
|
|
3788
|
+
* Metric used to rank variant candidates and decide improvement.
|
|
3789
|
+
*
|
|
3790
|
+
* - `passRate`: passed / total across the dataset (always available).
|
|
3791
|
+
* - `toolF1` / `toolPrecision` / `toolRecall`: dataset-level tool-call metrics,
|
|
3792
|
+
* only available when the dataset has `mcp_host` cases with `toolsTriggered`
|
|
3793
|
+
* expectations. Choosing one of these when no such cases exist throws a clear
|
|
3794
|
+
* error rather than silently ranking on nothing.
|
|
3795
|
+
*/
|
|
3796
|
+
type ExperimentMetric = 'passRate' | 'toolF1' | 'toolPrecision' | 'toolRecall';
|
|
3797
|
+
/**
|
|
3798
|
+
* Why a variant experiment stopped.
|
|
3799
|
+
*
|
|
3800
|
+
* - `no-variants`: no candidates were ever produced (round 0 yielded none).
|
|
3801
|
+
* - `no-improvement`: a round's best candidate did not beat the best-so-far by
|
|
3802
|
+
* at least `minImprovement`, or `proposeVariants` returned no further
|
|
3803
|
+
* candidates.
|
|
3804
|
+
* - `max-rounds`: the configured `maxRounds` budget was exhausted.
|
|
3805
|
+
* - `threshold-met`: reserved for future absolute-target convergence; not
|
|
3806
|
+
* emitted by the current delta-based logic.
|
|
3807
|
+
*/
|
|
3808
|
+
type VariantExperimentReason = 'threshold-met' | 'no-improvement' | 'max-rounds' | 'no-variants';
|
|
3809
|
+
/** Whether a winning variant should be applied, rejected, or is inconclusive. */
|
|
3810
|
+
type VariantRecommendation = 'apply' | 'reject' | 'inconclusive';
|
|
3811
|
+
/** Result of running and scoring a single candidate variant. */
|
|
3812
|
+
interface VariantCandidateResult {
|
|
3813
|
+
/** The variant that was injected via `toolOverrides`. */
|
|
3814
|
+
variant: ToolOverrideVariant;
|
|
3815
|
+
/** The eval run produced for this variant. */
|
|
3816
|
+
result: EvalRunnerResult;
|
|
3817
|
+
/** Comparison of this candidate against the original baseline run. */
|
|
3818
|
+
comparison: EvalRunComparisonResult;
|
|
3819
|
+
/** The selected metric's value for this candidate. */
|
|
3820
|
+
metricValue: number;
|
|
3821
|
+
/** `metricValue` minus the baseline's metric value. */
|
|
3822
|
+
metricDelta: number;
|
|
3823
|
+
/**
|
|
3824
|
+
* True when this candidate regressed at least one case and `allowRegressions`
|
|
3825
|
+
* is not set. Disqualified candidates can never become the winner.
|
|
3826
|
+
*/
|
|
3827
|
+
disqualified: boolean;
|
|
3828
|
+
}
|
|
3829
|
+
/** All candidates tried in a single round, plus the round's best non-disqualified pick. */
|
|
3830
|
+
interface VariantExperimentRound {
|
|
3831
|
+
/** 0-based round index. */
|
|
3832
|
+
round: number;
|
|
3833
|
+
/** Every candidate scored this round, in input order. */
|
|
3834
|
+
candidates: VariantCandidateResult[];
|
|
3835
|
+
/** Highest-scoring non-disqualified candidate this round, if any. */
|
|
3836
|
+
best?: VariantCandidateResult;
|
|
3837
|
+
}
|
|
3838
|
+
/** Context passed to a `proposeVariants` callback before each round. */
|
|
3839
|
+
interface ProposeVariantsContext {
|
|
3840
|
+
/** 0-based index of the round about to run. */
|
|
3841
|
+
round: number;
|
|
3842
|
+
/** The original baseline run (no overrides). */
|
|
3843
|
+
baseline: EvalRunnerResult;
|
|
3844
|
+
/** The metric the experiment is optimizing. */
|
|
3845
|
+
metric: ExperimentMetric;
|
|
3846
|
+
/** All completed rounds so far, in order. */
|
|
3847
|
+
history: VariantExperimentRound[];
|
|
3848
|
+
/** Best non-disqualified candidate across all prior rounds, if any. */
|
|
3849
|
+
bestSoFar?: VariantCandidateResult;
|
|
3850
|
+
}
|
|
3851
|
+
/** A structured, ready-to-act proposal derived from the best attempted candidate. */
|
|
3852
|
+
interface VariantImprovementProposal {
|
|
3853
|
+
/** `id` of the variant this proposal describes. */
|
|
3854
|
+
variantId: string;
|
|
3855
|
+
/** Metric the experiment optimized. */
|
|
3856
|
+
metric: ExperimentMetric;
|
|
3857
|
+
/** Baseline metric value. */
|
|
3858
|
+
baselineValue: number;
|
|
3859
|
+
/** Candidate metric value. */
|
|
3860
|
+
candidateValue: number;
|
|
3861
|
+
/** `candidateValue` minus `baselineValue`. */
|
|
3862
|
+
delta: number;
|
|
3863
|
+
/** Per-tool overrides this variant applied, keyed by canonical tool name. */
|
|
3864
|
+
toolChanges: Record<string, ToolMetadataOverride>;
|
|
3865
|
+
/** IDs of cases that failed in baseline and passed with this variant. */
|
|
3866
|
+
improvedCaseIds: string[];
|
|
3867
|
+
/** IDs of cases that passed in baseline and failed with this variant. */
|
|
3868
|
+
regressedCaseIds: string[];
|
|
3869
|
+
/**
|
|
3870
|
+
* `apply` when the variant improved the metric without disqualifying
|
|
3871
|
+
* regressions; `reject` when the best attempt regressed cases (and
|
|
3872
|
+
* regressions are not allowed); `inconclusive` when nothing beat baseline.
|
|
3873
|
+
*/
|
|
3874
|
+
recommendation: VariantRecommendation;
|
|
3875
|
+
}
|
|
3876
|
+
/** Options for {@link runVariantExperiment}. */
|
|
3877
|
+
interface VariantExperimentOptions {
|
|
3878
|
+
/** The eval dataset. Treated as the stable behavioral contract; never mutated. */
|
|
3879
|
+
dataset: EvalDataset;
|
|
3880
|
+
/** Static candidates tried in round 0. */
|
|
3881
|
+
variants?: ToolOverrideVariant[];
|
|
3882
|
+
/**
|
|
3883
|
+
* AI hook returning the next candidates given prior-round results. Invoked for
|
|
3884
|
+
* rounds >= 1, and for round 0 when `variants` is omitted. Return `[]` to stop.
|
|
3885
|
+
*/
|
|
3886
|
+
proposeVariants?: (context: ProposeVariantsContext) => Promise<ToolOverrideVariant[]>;
|
|
3887
|
+
/** Metric to optimize. @default 'passRate' */
|
|
3888
|
+
metric?: ExperimentMetric;
|
|
3889
|
+
/** Maximum number of rounds to run. @default 1 */
|
|
3890
|
+
maxRounds?: number;
|
|
3891
|
+
/**
|
|
3892
|
+
* Convergence threshold. Stop when a round's best metric improvement over the
|
|
3893
|
+
* prior best-so-far is below this value. @default 0
|
|
3894
|
+
*/
|
|
3895
|
+
minImprovement?: number;
|
|
3896
|
+
/**
|
|
3897
|
+
* When false (default), any candidate that regresses a case is disqualified
|
|
3898
|
+
* from winning and surfaced with `recommendation: 'reject'`. When true,
|
|
3899
|
+
* regressions do not disqualify.
|
|
3900
|
+
* @default false
|
|
3901
|
+
*/
|
|
3902
|
+
allowRegressions?: boolean;
|
|
3903
|
+
/** Default `mcp_host` iterations per case. Forwarded to `runEvalDataset`. */
|
|
3904
|
+
defaultLlmIterations?: number;
|
|
3905
|
+
/** Default judge repetitions per case. Forwarded to `runEvalDataset`. */
|
|
3906
|
+
defaultJudgeReps?: number;
|
|
3907
|
+
/** Max eval cases to run concurrently within each run. Forwarded to `runEvalDataset`. */
|
|
3908
|
+
concurrency?: number;
|
|
3909
|
+
/** Run only cases with at least one of these tags. Forwarded to `runEvalDataset`. */
|
|
3910
|
+
filterTags?: string[];
|
|
3911
|
+
/** Schema registry for `expect.schema` cases. Forwarded to `runEvalDataset`. */
|
|
3912
|
+
schemas?: Record<string, ZodType>;
|
|
3913
|
+
/** MCP host model identifier recorded in run metadata. */
|
|
3914
|
+
mcpHostModel?: string;
|
|
3915
|
+
/** Judge model identifier recorded in run metadata. */
|
|
3916
|
+
judgeModel?: string;
|
|
3917
|
+
}
|
|
3918
|
+
/** Aggregated result of a variant experiment. */
|
|
3919
|
+
interface VariantExperimentResult {
|
|
3920
|
+
/** Metric that was optimized. */
|
|
3921
|
+
metric: ExperimentMetric;
|
|
3922
|
+
/** The original baseline run (no overrides). */
|
|
3923
|
+
baseline: EvalRunnerResult;
|
|
3924
|
+
/** Every round that ran, in order. */
|
|
3925
|
+
rounds: VariantExperimentRound[];
|
|
3926
|
+
/** Best non-disqualified candidate across all rounds, if any. */
|
|
3927
|
+
winner?: VariantCandidateResult;
|
|
3928
|
+
/** Structured proposal derived from the best attempted candidate, if any ran. */
|
|
3929
|
+
proposal?: VariantImprovementProposal;
|
|
3930
|
+
/** True when the experiment stopped on its own terms (always true today). */
|
|
3931
|
+
converged: boolean;
|
|
3932
|
+
/** Why the experiment stopped. */
|
|
3933
|
+
reason: VariantExperimentReason;
|
|
3934
|
+
}
|
|
3935
|
+
/**
|
|
3936
|
+
* Runs a tool-metadata variant experiment: establishes a baseline, then injects
|
|
3937
|
+
* each candidate variant via `toolOverrides`, compares it to the baseline,
|
|
3938
|
+
* ranks by the chosen metric, guards against regressions, and emits a structured
|
|
3939
|
+
* improvement proposal.
|
|
3940
|
+
*
|
|
3941
|
+
* The library owns the experiment mechanism; the *policy* — which variant to try
|
|
3942
|
+
* next — is the caller's, supplied either as a static `variants` list or an
|
|
3943
|
+
* iterative `proposeVariants` callback. This is the programmatic spine an AI or
|
|
3944
|
+
* skill drives to optimize tool descriptions/schemas for better host triggering.
|
|
3945
|
+
*
|
|
3946
|
+
* Candidates are always compared against the original baseline (not the prior
|
|
3947
|
+
* round), so the resulting proposal is directly applicable. Multi-round
|
|
3948
|
+
* convergence is tracked separately via best-so-far.
|
|
3949
|
+
*
|
|
3950
|
+
* @example
|
|
3951
|
+
* ```typescript
|
|
3952
|
+
* const result = await runVariantExperiment(
|
|
3953
|
+
* { dataset, variants: [variantA, variantB], metric: 'passRate' },
|
|
3954
|
+
* { mcp, testInfo }
|
|
3955
|
+
* );
|
|
3956
|
+
* if (result.proposal?.recommendation === 'apply') {
|
|
3957
|
+
* console.log('Apply:', result.winner?.variant.id, '+', result.proposal.delta);
|
|
3958
|
+
* }
|
|
3959
|
+
* ```
|
|
3960
|
+
*/
|
|
3961
|
+
declare function runVariantExperiment(options: VariantExperimentOptions, context: EvalContext): Promise<VariantExperimentResult>;
|
|
3962
|
+
|
|
3963
|
+
/**
|
|
3964
|
+
* Options for saveBaseline
|
|
3965
|
+
*/
|
|
3966
|
+
interface SaveBaselineOptions {
|
|
3967
|
+
/**
|
|
3968
|
+
* When true (default), strips the `response` field from each case result
|
|
3969
|
+
* before saving. Keeps baseline files small and git-friendly — the baseline
|
|
3970
|
+
* is a pass/fail record and the full response is not needed for comparison.
|
|
3971
|
+
*
|
|
3972
|
+
* Set to false to preserve the complete response in the saved file.
|
|
3973
|
+
*
|
|
3974
|
+
* @default true
|
|
3975
|
+
*/
|
|
3976
|
+
omitResponses?: boolean;
|
|
3977
|
+
}
|
|
3978
|
+
/**
|
|
3979
|
+
* Saves eval results to a JSON file for use as a baseline in future runs.
|
|
3980
|
+
*
|
|
3981
|
+
* @param result - The eval run result to save
|
|
3982
|
+
* @param filePath - Path to write the JSON file (parent dirs created automatically)
|
|
3983
|
+
* @param options - Save options
|
|
3984
|
+
*/
|
|
3985
|
+
declare function saveBaseline(result: EvalRunnerResult, filePath: string, options?: SaveBaselineOptions): Promise<void>;
|
|
3986
|
+
/**
|
|
3987
|
+
* Loads a previously saved baseline from a JSON file.
|
|
3988
|
+
*
|
|
3989
|
+
* @param filePath - Path to the JSON file written by saveBaseline
|
|
3990
|
+
* @returns The saved EvalRunnerResult
|
|
3991
|
+
* @throws If the file cannot be read or parsed
|
|
3992
|
+
*/
|
|
3993
|
+
declare function loadBaseline(filePath: string): Promise<EvalRunnerResult>;
|
|
3994
|
+
|
|
3995
|
+
/**
|
|
3996
|
+
* Custom Judge Registry
|
|
3997
|
+
*
|
|
3998
|
+
* Allows consumers to register named judge executors that can be referenced
|
|
3999
|
+
* by string ID in eval fixtures and programmatic tests. This enables
|
|
4000
|
+
* multi-step judge pipelines (LLM call + post-processing), custom scoring
|
|
4001
|
+
* logic, and reusable judge configurations without duplicating rubrics.
|
|
4002
|
+
*/
|
|
4003
|
+
/**
|
|
4004
|
+
* Result returned by a custom judge executor.
|
|
4005
|
+
*
|
|
4006
|
+
* Custom judges must return a normalized score (0–1). The framework applies
|
|
4007
|
+
* the caller's `threshold` (default 0.7) to determine pass/fail. This keeps
|
|
4008
|
+
* judges reusable — the same judge can be used with different thresholds in
|
|
4009
|
+
* different tests.
|
|
4010
|
+
*/
|
|
4011
|
+
interface CustomJudgeResult {
|
|
4012
|
+
/** Normalized score (0–1, where 1 is best) */
|
|
4013
|
+
score: number;
|
|
4014
|
+
/** Optional reasoning/explanation */
|
|
4015
|
+
reasoning?: string;
|
|
4016
|
+
}
|
|
4017
|
+
/**
|
|
4018
|
+
* A user-defined judge executor function.
|
|
4019
|
+
*
|
|
4020
|
+
* Custom executors own their entire evaluation pipeline — prompt construction,
|
|
4021
|
+
* LLM calls, and post-processing — but return a normalized score. The framework
|
|
4022
|
+
* determines pass/fail by comparing the score against the caller's threshold.
|
|
4023
|
+
*
|
|
4024
|
+
* @param candidate - The actual response to evaluate
|
|
4025
|
+
* @param reference - Optional reference/expected response
|
|
4026
|
+
* @returns Evaluation result with a normalized score and optional reasoning
|
|
4027
|
+
*
|
|
4028
|
+
* @example
|
|
4029
|
+
* ```typescript
|
|
4030
|
+
* const completenessJudge: CustomJudgeExecutor = async (candidate, reference) => {
|
|
4031
|
+
* // Step 1: LLM call with your own prompt and schema
|
|
4032
|
+
* const llmResult = await callLLM(COMPLETENESS_PROMPT, candidate);
|
|
4033
|
+
* const { verdict, reasoning } = JSON.parse(llmResult);
|
|
4034
|
+
*
|
|
4035
|
+
* // Step 2: Deterministic post-processing into a normalized score
|
|
4036
|
+
* const score = { Complete: 1.0, Incomplete: 0.5 }[verdict] ?? 0.0;
|
|
4037
|
+
*
|
|
4038
|
+
* return { score, reasoning };
|
|
4039
|
+
* };
|
|
4040
|
+
* ```
|
|
4041
|
+
*/
|
|
4042
|
+
type CustomJudgeExecutor = (candidate: unknown, reference?: unknown) => Promise<CustomJudgeResult>;
|
|
4043
|
+
/**
|
|
4044
|
+
* Registers a named custom judge executor.
|
|
4045
|
+
*
|
|
4046
|
+
* Call this in your test setup (e.g., `playwright.config.ts` or a global setup file)
|
|
4047
|
+
* before tests run. The name can then be referenced in JSON eval fixtures via the
|
|
4048
|
+
* `judge` field on `passesJudge`.
|
|
4049
|
+
*
|
|
4050
|
+
* @param name - Unique identifier for the judge
|
|
4051
|
+
* @param executor - The judge executor function
|
|
4052
|
+
* @throws {Error} If a judge with the same name is already registered
|
|
4053
|
+
*
|
|
4054
|
+
* @example
|
|
4055
|
+
* ```typescript
|
|
4056
|
+
* import { registerJudge } from '@gleanwork/mcp-server-tester';
|
|
4057
|
+
*
|
|
4058
|
+
* registerJudge('glean-completeness', async (candidate, reference) => {
|
|
4059
|
+
* // Step 1: LLM call with your own prompt and schema
|
|
4060
|
+
* const llmResult = await callLLM(COMPLETENESS_PROMPT, candidate);
|
|
4061
|
+
* const { verdict, reasoning } = JSON.parse(llmResult);
|
|
4062
|
+
*
|
|
4063
|
+
* // Step 2: Deterministic post-processing into a normalized score
|
|
4064
|
+
* const score = { Complete: 1.0, Incomplete: 0.5 }[verdict] ?? 0.0;
|
|
4065
|
+
*
|
|
4066
|
+
* return { score, reasoning };
|
|
4067
|
+
* });
|
|
4068
|
+
*
|
|
4069
|
+
* // Then in tests — same judge, different thresholds:
|
|
4070
|
+
* // expect(result).toPassToolJudge({ judge: 'glean-completeness', passingThreshold: 0.8 });
|
|
4071
|
+
* // expect(result).toPassToolJudge({ judge: 'glean-completeness', passingThreshold: 0.5 });
|
|
4072
|
+
* ```
|
|
4073
|
+
*/
|
|
4074
|
+
declare function registerJudge(name: string, executor: CustomJudgeExecutor): void;
|
|
4075
|
+
/**
|
|
4076
|
+
* Retrieves a registered custom judge executor by name.
|
|
4077
|
+
*
|
|
4078
|
+
* @param name - The judge name to look up
|
|
4079
|
+
* @returns The registered executor
|
|
4080
|
+
* @throws {Error} If no judge with the given name is registered
|
|
4081
|
+
*/
|
|
4082
|
+
declare function getRegisteredJudge(name: string): CustomJudgeExecutor;
|
|
4083
|
+
/**
|
|
4084
|
+
* Clears all registered judges. Intended for test teardown.
|
|
4085
|
+
*/
|
|
4086
|
+
declare function clearJudgeRegistry(): void;
|
|
4087
|
+
|
|
4088
|
+
/**
|
|
4089
|
+
* Options for conformance checks
|
|
4090
|
+
*/
|
|
4091
|
+
interface MCPConformanceOptions {
|
|
4092
|
+
/**
|
|
4093
|
+
* List of tools that must be present
|
|
4094
|
+
*/
|
|
4095
|
+
requiredTools?: Array<string>;
|
|
4096
|
+
/**
|
|
4097
|
+
* Whether to validate tool schemas
|
|
4098
|
+
* @default true
|
|
4099
|
+
*/
|
|
4100
|
+
validateSchemas?: boolean;
|
|
4101
|
+
/**
|
|
4102
|
+
* Whether to check server info is present
|
|
4103
|
+
* @default true
|
|
4104
|
+
*/
|
|
4105
|
+
checkServerInfo?: boolean;
|
|
4106
|
+
/**
|
|
4107
|
+
* Whether to check resources capability (if declared by server)
|
|
4108
|
+
* @default true
|
|
4109
|
+
*/
|
|
4110
|
+
checkResources?: boolean;
|
|
4111
|
+
/**
|
|
4112
|
+
* Whether to check prompts capability (if declared by server)
|
|
4113
|
+
* @default true
|
|
4114
|
+
*/
|
|
4115
|
+
checkPrompts?: boolean;
|
|
4116
|
+
}
|
|
4117
|
+
/**
|
|
4118
|
+
* Raw MCP responses for snapshotting
|
|
4119
|
+
*/
|
|
4120
|
+
interface MCPConformanceRaw {
|
|
4121
|
+
/**
|
|
4122
|
+
* Server info (name, version)
|
|
4123
|
+
* null if not available
|
|
4124
|
+
*/
|
|
4125
|
+
serverInfo: Implementation | null;
|
|
4126
|
+
/**
|
|
4127
|
+
* Server capabilities
|
|
4128
|
+
* null if not available
|
|
4129
|
+
*/
|
|
4130
|
+
capabilities: ServerCapabilities | null;
|
|
4131
|
+
/**
|
|
4132
|
+
* List of tools from the server
|
|
4133
|
+
*/
|
|
4134
|
+
tools: Tool[];
|
|
4135
|
+
/**
|
|
4136
|
+
* List of resources from the server
|
|
4137
|
+
* null if server doesn't declare resources capability
|
|
4138
|
+
*/
|
|
4139
|
+
resources: Resource[] | null;
|
|
4140
|
+
/**
|
|
4141
|
+
* List of prompts from the server
|
|
4142
|
+
* null if server doesn't declare prompts capability
|
|
4143
|
+
*/
|
|
4144
|
+
prompts: Prompt[] | null;
|
|
4145
|
+
}
|
|
4146
|
+
/**
|
|
4147
|
+
* Result of conformance checks
|
|
4148
|
+
*/
|
|
4149
|
+
interface MCPConformanceResult {
|
|
4150
|
+
/**
|
|
4151
|
+
* Whether all checks passed
|
|
4152
|
+
*/
|
|
4153
|
+
pass: boolean;
|
|
4154
|
+
/**
|
|
4155
|
+
* List of check results
|
|
4156
|
+
*/
|
|
4157
|
+
checks: MCPConformanceCheck[];
|
|
4158
|
+
/**
|
|
4159
|
+
* Raw MCP responses for snapshotting
|
|
4160
|
+
*
|
|
4161
|
+
* @example
|
|
4162
|
+
* ```typescript
|
|
4163
|
+
* const result = await runConformanceChecks(mcp);
|
|
4164
|
+
* expect(result.raw.tools).toMatchSnapshot();
|
|
4165
|
+
* expect(result.raw.capabilities).toMatchSnapshot();
|
|
4166
|
+
* ```
|
|
4167
|
+
*/
|
|
4168
|
+
raw: MCPConformanceRaw;
|
|
4169
|
+
}
|
|
4170
|
+
/**
|
|
4171
|
+
* Runs MCP protocol conformance checks
|
|
4172
|
+
*
|
|
4173
|
+
* Validates that the MCP server conforms to expected protocol behavior.
|
|
4174
|
+
* Returns both assertion results and raw MCP responses for snapshotting.
|
|
4175
|
+
*
|
|
4176
|
+
* When testInfo is provided, results are automatically attached for the MCP reporter.
|
|
4177
|
+
*
|
|
4178
|
+
* @param mcp - MCP fixture API
|
|
4179
|
+
* @param options - Conformance check options
|
|
4180
|
+
* @param testInfo - Optional Playwright TestInfo for reporter integration
|
|
4181
|
+
* @returns Conformance check results with raw responses
|
|
4182
|
+
*
|
|
4183
|
+
* @example
|
|
4184
|
+
* ```typescript
|
|
4185
|
+
* // Basic usage
|
|
4186
|
+
* const result = await runConformanceChecks(mcp, {
|
|
4187
|
+
* requiredTools: ['get_weather', 'search_docs'],
|
|
4188
|
+
* validateSchemas: true,
|
|
4189
|
+
* });
|
|
4190
|
+
*
|
|
4191
|
+
* // Check assertions
|
|
4192
|
+
* expect(result.pass).toBe(true);
|
|
4193
|
+
*
|
|
4194
|
+
* // With reporter integration (recommended in Playwright tests)
|
|
4195
|
+
* const result = await runConformanceChecks(mcp, {
|
|
4196
|
+
* requiredTools: ['search'],
|
|
4197
|
+
* }, testInfo);
|
|
4198
|
+
*
|
|
4199
|
+
* // Snapshot raw responses
|
|
4200
|
+
* expect(result.raw.tools).toMatchSnapshot();
|
|
4201
|
+
* expect(result.raw.capabilities).toMatchSnapshot();
|
|
4202
|
+
* ```
|
|
4203
|
+
*/
|
|
4204
|
+
declare function runConformanceChecks(mcp: MCPFixtureApi, options?: MCPConformanceOptions, testInfo?: TestInfo): Promise<MCPConformanceResult>;
|
|
4205
|
+
|
|
4206
|
+
/**
|
|
4207
|
+
* Canonical type definitions for @gleanwork/mcp-server-tester
|
|
4208
|
+
*
|
|
4209
|
+
* This module is the single source of truth for shared types.
|
|
4210
|
+
* All other modules should import from here rather than defining their own.
|
|
4211
|
+
*
|
|
4212
|
+
* @packageDocumentation
|
|
4213
|
+
*/
|
|
4214
|
+
/**
|
|
4215
|
+
* Authentication type for MCP connections
|
|
4216
|
+
*
|
|
4217
|
+
* - 'oauth': Interactive OAuth 2.1 with PKCE (browser-based authentication)
|
|
4218
|
+
* - 'api-token': Static API token (e.g., from a dashboard or environment variable)
|
|
4219
|
+
* - 'none': No authentication
|
|
4220
|
+
*/
|
|
4221
|
+
type AuthType = 'oauth' | 'api-token' | 'none';
|
|
4222
|
+
/**
|
|
4223
|
+
* Source of test results
|
|
4224
|
+
*
|
|
4225
|
+
* - 'eval': From runEvalDataset() using JSON eval datasets
|
|
4226
|
+
* - 'test': From direct API test tracking (MCP fixture calls)
|
|
4227
|
+
*/
|
|
4228
|
+
type ResultSource = 'eval' | 'test';
|
|
4229
|
+
/**
|
|
4230
|
+
* Known expectation types supported by the framework
|
|
4231
|
+
*/
|
|
4232
|
+
type ExpectationType = 'exact' | 'schema' | 'textContains' | 'regex' | 'snapshot' | 'judge' | 'error' | 'size' | 'toolsTriggered' | 'toolCallCount';
|
|
4233
|
+
/**
|
|
4234
|
+
* Result of an expectation check
|
|
4235
|
+
*/
|
|
4236
|
+
interface EvalExpectationResult {
|
|
4237
|
+
/**
|
|
4238
|
+
* Whether the expectation passed
|
|
4239
|
+
*/
|
|
4240
|
+
pass: boolean;
|
|
4241
|
+
/**
|
|
4242
|
+
* Optional details about the result
|
|
4243
|
+
*/
|
|
4244
|
+
details?: string;
|
|
4245
|
+
/**
|
|
4246
|
+
* Judge score (0-1). Populated for passesJudge expectations.
|
|
4247
|
+
*/
|
|
4248
|
+
score?: number;
|
|
4249
|
+
/**
|
|
4250
|
+
* Judge reasoning. Populated for passesJudge expectations.
|
|
4251
|
+
*/
|
|
4252
|
+
reasoning?: string;
|
|
4253
|
+
/**
|
|
4254
|
+
* Judge name — rubric name (e.g. 'correctness') or custom judge name.
|
|
4255
|
+
* Populated for passesJudge expectations.
|
|
4256
|
+
*/
|
|
4257
|
+
judgeName?: string;
|
|
4258
|
+
/**
|
|
4259
|
+
* Judge provider used. Populated for passesJudge expectations.
|
|
4260
|
+
*/
|
|
4261
|
+
judgeProvider?: string;
|
|
4262
|
+
/**
|
|
4263
|
+
* Judge model used. Populated for passesJudge expectations.
|
|
4264
|
+
*/
|
|
4265
|
+
judgeModel?: string;
|
|
4266
|
+
/**
|
|
4267
|
+
* Per-judge breakdown when multiple judges are used.
|
|
4268
|
+
* Each entry contains the individual judge's result.
|
|
4269
|
+
* Only populated when passesJudge is an array with 2+ entries.
|
|
4270
|
+
*/
|
|
4271
|
+
judgeResults?: EvalExpectationResult[];
|
|
4272
|
+
}
|
|
4273
|
+
/**
|
|
4274
|
+
* Map of expectation type to result
|
|
4275
|
+
*/
|
|
4276
|
+
type ExpectationResultMap = Partial<Record<ExpectationType, EvalExpectationResult>>;
|
|
4277
|
+
/**
|
|
4278
|
+
* Breakdown of expectation types used in a run
|
|
4279
|
+
*/
|
|
4280
|
+
type ExpectationBreakdown = Partial<Record<ExpectationType, number>>;
|
|
4281
|
+
|
|
4282
|
+
export { type EvalRunComparisonResult as $, type AuthType as A, BUILT_IN_RUBRICS as B, type CLIConfig as C, DiscoveryError as D, ENV_VAR_NAMES as E, type EvalCaseRequest as F, type EvalCaseResult as G, EvalCaseSchema as H, type EvalContext as I, type JudgeMatcherOptions as J, type EvalDataset as K, type LLMProvider as L, type MCPFixtureApi as M, EvalDatasetSchema as N, type OAuthSetupConfig as O, type PatternValidatorOptions as P, type EvalExpectBlock as Q, type RubricSpec as R, type SchemaValidatorOptions as S, type TextValidatorOptions as T, type EvalExpectationResult as U, type ValidationResult as V, type EvalMode as W, type EvalResultStore as X, type EvalResultStoreConfig as Y, type EvalResultStoreLike as Z, type EvalRunComparisonLabels as _, type MCPHostConfig as a, type StoredEvalArtifact as a$, type EvalRunMetadata as a0, type EvalRunnerOptions as a1, type EvalRunnerResult as a2, type ExpectationBreakdown as a3, type ExpectationResultMap as a4, type ExpectationType as a5, type ExperimentMetric as a6, type FieldRemovalSanitizer as a7, FileEvalResultStore as a8, type FileEvalResultStoreConfig as a9, type MCPHostCapabilities as aA, type MCPHostSimulator as aB, type MCPOAuthConfig as aC, type MCPServerCapabilitiesData as aD, MCP_PROTOCOL_VERSION as aE, type NormalizedToolResponse as aF, PlaywrightOAuthClientProvider as aG, type PlaywrightOAuthClientProviderConfig as aH, type PredicateResult as aI, type ProposeVariantsContext as aJ, type ProtectedResourceDiscoveryResult as aK, type ProtectedResourceMetadata as aL, type ProviderKind as aM, type RegexSanitizer as aN, type ResultSource as aO, type SaveBaselineOptions as aP, type SaveEvalRunComparisonOptions as aQ, type SaveServerComparisonOptions as aR, type SchemaRegistry as aS, type SerializedEvalDataset as aT, type ServerComparisonOptions as aU, type ServerComparisonResult as aV, SnapshotSanitizers as aW, type StdioMCPConfig as aX, type StoredArtifactKind as aY, type StoredArtifactSummary as aZ, type StoredClientInfo as a_, GCSEvalResultStore as aa, type GCSEvalResultStoreConfig as ab, type HostType as ac, type HttpMCPConfig as ad, type IterationResult as ae, type JudgeExpectConfig as af, type JudgeResult as ag, type JudgeValidatorConfig as ah, type LLMToolCall as ai, type ListStoredArtifactsOptions as aj, type LoadDatasetOptions as ak, type MCPAuthConfig as al, type MCPAuthFixtures as am, type MCPClientCredentialsConfig as an, type MCPConfig as ao, MCPConfigSchema as ap, type MCPConformanceCheck as aq, type MCPConformanceOptions as ar, type MCPConformanceRaw as as, type MCPConformanceResult as at, type MCPConformanceResultData as au, type MCPEvalData as av, type MCPEvalHistoricalSummary as aw, type MCPEvalReporterConfig as ax, type MCPEvalRunData as ay, type MCPFixtureOptions as az, type MCPHostSimulationResult as b, validateToolCallCount as b$, type StoredEvalArtifactMetadata as b0, type StoredEvalResultLoadOptions as b1, type StoredEvalResultRef as b2, type StoredEvalResultSaveOptions as b3, type StoredEvalRunRef as b4, type StoredOAuthState as b5, type StoredServerMetadata as b6, type StoredTokens as b7, type TokenResult as b8, type ToolMetadataOverride as b9, isHttpConfig as bA, isStdioConfig as bB, loadBaseline as bC, loadEvalDataset as bD, loadEvalDatasetFromObject as bE, loadStoredEvalRunnerResult as bF, loadTokens as bG, loadTokensFromEnv as bH, test as bI, normalizeToolResponse as bJ, performClientCredentialsFlow as bK, refreshAccessToken as bL, registerJudge as bM, resolveEvalResultStore as bN, resolveRubric as bO, runConformanceChecks as bP, runEvalCase as bQ, runEvalDataset as bR, runServerComparison as bS, runVariantExperiment as bT, saveBaseline as bU, saveEvalRunComparison as bV, saveServerComparison as bW, validateEvalCase as bX, validateEvalDataset as bY, validateJudge as bZ, validateMCPConfig as b_, type ToolOverrideVariant as ba, type UsageMetrics as bb, type VariantCandidateResult as bc, type VariantExperimentOptions as bd, type VariantExperimentReason as be, type VariantExperimentResult as bf, type VariantExperimentRound as bg, type VariantImprovementProposal as bh, type VariantRecommendation as bi, clearJudgeRegistry as bj, closeMCPClient as bk, compareEvalRuns as bl, createDefaultArtifactId as bm, createEvalResultStore as bn, createMCPClientForConfig as bo, createMCPFixture as bp, createStoredEvalArtifact as bq, defaultEnvironmentMetadata as br, discoverAuthorizationServer as bs, discoverProtectedResource as bt, extractText as bu, getRegisteredJudge as bv, hasValidTokens as bw, injectTokens as bx, isBuiltInRubric as by, isEvalResultStore as bz, type SizeValidatorOptions as c, validateToolCalls as c0, type SnapshotSanitizer as d, type ToolPredicate as e, type ToolCallExpectation as f, type ToolCallCountOptions as g, type JudgeConfig as h, type Judge as i, type BuiltInRubric as j, type BuiltInSanitizer as k, CLIOAuthClient as l, type CLIOAuthClientConfig as m, type CLIOAuthResult as n, type CLIOutputFormat as o, type CaseComparisonResult as p, type ClientCredentialsConfig as q, type CompareEvalRunsOptions as r, type ComparisonOutcome as s, type ContentBlock as t, type CreateMCPClientOptions as u, type CustomJudgeExecutor as v, type CustomJudgeResult as w, type EvalCase as x, type EvalCaseComparison as y, type EvalCaseComparisonOutcome as z };
|