@jmoak/vibrissa 0.1.0 → 2.0.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.
@@ -0,0 +1,1194 @@
1
+ import { Client } from '@modelcontextprotocol/client';
2
+
3
+ /**
4
+ * Vibrissa v2 shared types: case files, config, and the session's summary shapes.
5
+ * See docs/V2_ARCHITECTURE.md ("Shared interfaces") for the contract these implement.
6
+ */
7
+ type JsonValue = null | boolean | number | string | JsonValue[] | {
8
+ [key: string]: JsonValue;
9
+ };
10
+ type JsonObject = {
11
+ [key: string]: JsonValue;
12
+ };
13
+ /** A single MCP operation. Exactly one of tool | resource | resourceTemplate | prompt. */
14
+ interface Operation {
15
+ /** `tools/call` by tool name. */
16
+ tool?: string;
17
+ /** `resources/read` by URI. */
18
+ resource?: string;
19
+ /** RFC 6570 URI template; expanded with `args`, then `resources/read`. */
20
+ resourceTemplate?: string;
21
+ /** `prompts/get` by prompt name. */
22
+ prompt?: string;
23
+ /** Tool arguments, template variables, or prompt arguments. */
24
+ args?: Record<string, JsonValue>;
25
+ }
26
+ interface ExpectError {
27
+ /** Error code name (`InvalidParams`, `RequestTimeout`, ...) or numeric JSON-RPC code. */
28
+ code?: string | number;
29
+ message?: string | {
30
+ $regex: string;
31
+ flags?: string;
32
+ };
33
+ }
34
+ /** Tasks extension polling (SEP-2663): poll `tasks/get` until the task is terminal. */
35
+ interface AwaitOptions {
36
+ /** Poll interval override in ms. Default: the task's `pollIntervalMs`, else 500. */
37
+ pollMs?: number;
38
+ /** Give up after this many ms. Default 30000. */
39
+ timeoutMs?: number;
40
+ }
41
+ interface Step extends Operation {
42
+ name?: string;
43
+ expect?: JsonValue;
44
+ expectError?: ExpectError;
45
+ /** Scripted answers for MRTR input_required rounds, keyed by request method. */
46
+ respond?: Record<string, JsonValue>;
47
+ /** Matcher over the InputRequests map(s) the server sent (see Outcome.inputRequests). */
48
+ expectInputRequired?: JsonValue;
49
+ /** Matcher over the CreateTaskResult the server returned (tasks extension). */
50
+ expectTask?: JsonValue;
51
+ /** Tasks extension: poll until terminal (`true` = defaults). */
52
+ await?: AwaitOptions | boolean;
53
+ /** name → dotted path into the normalized result. */
54
+ capture?: Record<string, string>;
55
+ budgetMs?: number;
56
+ timeoutMs?: number;
57
+ /** Teardown steps: never fail the case. */
58
+ ignoreErrors?: boolean;
59
+ /** Validate structuredContent against the tool's outputSchema. Default true when declared. */
60
+ validateOutput?: boolean;
61
+ normalize?: NormalizeConfig | false;
62
+ }
63
+ interface TestCase extends Step {
64
+ $schema?: string;
65
+ tags?: string[];
66
+ /** Key into config.servers. */
67
+ server?: string;
68
+ /** Flow; when present, top-level op fields are ignored. */
69
+ steps?: Step[];
70
+ setup?: Step[];
71
+ teardown?: Step[];
72
+ flaky?: boolean;
73
+ skip?: boolean | string;
74
+ }
75
+ /**
76
+ * Protocol era negotiation.
77
+ * - `auto` (default): probe `server/discover`, fall back to the 2025 `initialize` handshake.
78
+ * - `legacy`: 2025 `initialize` handshake only.
79
+ * - `modern`: require the 2026-07-28 era (sugar for `{ pin: "2026-07-28" }`).
80
+ * - `{ pin }`: modern era at exactly this revision; no fallback.
81
+ */
82
+ type ProtocolMode = 'auto' | 'legacy' | 'modern' | {
83
+ pin: string;
84
+ };
85
+ interface ServerCommon {
86
+ protocol?: ProtocolMode;
87
+ /** Max time for spawn + handshake (stdio) or connect (http). Default: clamp(timeoutMs, 2000, 10000). */
88
+ spawnTimeoutMs?: number;
89
+ }
90
+ interface StdioServerConfig {
91
+ cmd: string;
92
+ args?: string[];
93
+ cwd?: string;
94
+ env?: Record<string, string>;
95
+ }
96
+ interface HttpServerConfig {
97
+ url: string;
98
+ headers?: Record<string, string>;
99
+ }
100
+ type ServerConfig = (StdioServerConfig | HttpServerConfig) & ServerCommon;
101
+ type ResolvedStdioServer = StdioServerConfig & ServerCommon;
102
+ type ResolvedHttpServer = HttpServerConfig & ServerCommon;
103
+ declare function isHttpServerConfig(server: ServerConfig): server is ResolvedHttpServer;
104
+ declare function isStdioServerConfig(server: ServerConfig): server is ResolvedStdioServer;
105
+ interface NormalizeConfig {
106
+ /** Glob-ish keys under `_meta` to strip. Default `["io.modelcontextprotocol/*"]`. */
107
+ stripMeta?: string[];
108
+ /** Top-level result keys to drop. Default `["resultType"]`. */
109
+ dropKeys?: string[];
110
+ /** List results' `ttlMs`/`cacheScope`. Default `ignore`. */
111
+ ttlMs?: 'keep' | 'ignore';
112
+ }
113
+ interface ShardSpec {
114
+ /** 1-based shard index. */
115
+ index: number;
116
+ total: number;
117
+ }
118
+ interface SuiteHooks {
119
+ setup?: Step[];
120
+ teardown?: Step[];
121
+ }
122
+ interface RunOptions {
123
+ servers: Record<string, ServerConfig>;
124
+ defaultServer: string;
125
+ globs: string[];
126
+ /** Path; default tests/integration/__manifest__.json. */
127
+ manifest?: string;
128
+ normalize: NormalizeConfig;
129
+ timeoutMs: number;
130
+ budgetMs?: number;
131
+ failFast: boolean;
132
+ allowEmpty: boolean;
133
+ /** Reporter names; default ["console"]. */
134
+ reporters: string[];
135
+ tags?: string[];
136
+ excludeTags?: string[];
137
+ grep?: string;
138
+ repeat?: number;
139
+ shard?: ShardSpec;
140
+ concurrency?: number;
141
+ update?: boolean;
142
+ trace?: boolean;
143
+ check?: boolean;
144
+ baseline?: string;
145
+ /** Suite-level setup/teardown steps. */
146
+ hooks?: SuiteHooks;
147
+ /** `vib check` / `--check` tuning: skipped check ids and per-check severity overrides. */
148
+ checks?: {
149
+ allow?: string[];
150
+ severity?: Record<string, 'failed' | 'warned'>;
151
+ };
152
+ /**
153
+ * Dotted manifest paths ignored by the lock diff. Filled by loadConfig:
154
+ * ["server.version", "protocolVersion", "listMeta"].
155
+ */
156
+ manifestIgnore?: string[];
157
+ /** Fail the run when a server's manifest lock file is missing (CI). Default false (warn). */
158
+ requireManifest?: boolean;
159
+ /** Reporter artifact directory (relative to rootDir). Unset unless configured; the runner defaults to "reports". */
160
+ outDir?: string;
161
+ /** Directory the config was loaded from; relative paths resolve against it. */
162
+ rootDir: string;
163
+ }
164
+ interface ToolSummary$1 {
165
+ name: string;
166
+ title?: string;
167
+ description?: string;
168
+ inputSchema: JsonValue;
169
+ outputSchema?: JsonValue;
170
+ annotations?: JsonValue;
171
+ icons?: JsonValue;
172
+ execution?: JsonValue;
173
+ _meta?: JsonValue;
174
+ }
175
+ interface ResourceSummary$1 {
176
+ uri: string;
177
+ name: string;
178
+ title?: string;
179
+ description?: string;
180
+ mimeType?: string;
181
+ size?: number;
182
+ annotations?: JsonValue;
183
+ icons?: JsonValue;
184
+ _meta?: JsonValue;
185
+ }
186
+ interface ResourceTemplateSummary$1 {
187
+ uriTemplate: string;
188
+ name: string;
189
+ title?: string;
190
+ description?: string;
191
+ mimeType?: string;
192
+ annotations?: JsonValue;
193
+ icons?: JsonValue;
194
+ _meta?: JsonValue;
195
+ }
196
+ interface PromptArgumentSummary$1 {
197
+ name: string;
198
+ title?: string;
199
+ description?: string;
200
+ required?: boolean;
201
+ }
202
+ interface PromptSummary$1 {
203
+ name: string;
204
+ title?: string;
205
+ description?: string;
206
+ arguments?: PromptArgumentSummary$1[];
207
+ icons?: JsonValue;
208
+ _meta?: JsonValue;
209
+ }
210
+ /** A list verb's aggregated result plus the list-level cache hints (2026-07-28). */
211
+ interface ListResult<T> {
212
+ items: T[];
213
+ /** `ttlMs` from the (first page of the) list result, when the server sent one. */
214
+ ttlMs?: number;
215
+ /** `cacheScope` from the list result, when the server sent one. */
216
+ cacheScope?: string;
217
+ /** List-level `_meta` (e.g. the stamped serverInfo), when present. */
218
+ _meta?: JsonValue;
219
+ }
220
+ interface ServerIdentity {
221
+ name?: string;
222
+ version?: string;
223
+ title?: string;
224
+ [key: string]: JsonValue | undefined;
225
+ }
226
+ interface DiscoverInfo {
227
+ era: 'legacy' | 'modern';
228
+ /** Negotiated revision, e.g. "2026-07-28" or "2025-11-25". */
229
+ protocolVersion: string;
230
+ /** Revisions the server advertises (modern) or `[protocolVersion]` (legacy). */
231
+ supportedVersions: string[];
232
+ /** Modern: `_meta["io.modelcontextprotocol/serverInfo"]`; legacy: `initialize.serverInfo`. */
233
+ serverInfo?: ServerIdentity;
234
+ capabilities: JsonValue;
235
+ /** Keys of `capabilities.extensions` (e.g. "io.modelcontextprotocol/tasks"). */
236
+ extensions: string[];
237
+ instructions?: string;
238
+ /** True when built from the legacy `initialize` result instead of `server/discover`. */
239
+ synthesized: boolean;
240
+ /** The raw wire result (`server/discover` or `initialize`). */
241
+ raw: JsonValue;
242
+ }
243
+ /** One JSON-RPC message (or stderr chunk) observed by the session. */
244
+ interface TranscriptEntry {
245
+ /** `out` = client → server, `in` = server → client, `stderr` = server stderr text. */
246
+ dir: 'in' | 'out' | 'stderr';
247
+ /** Epoch milliseconds. */
248
+ ts: number;
249
+ message?: JsonValue;
250
+ text?: string;
251
+ }
252
+
253
+ /**
254
+ * Shared run-event model. Reporters consume these; the runner emits them.
255
+ * This file is the contract between runner and reporters — change with care.
256
+ */
257
+
258
+ interface CaseRef {
259
+ /** Stable id: relative case file path, plus `#<index>` for repeated runs. */
260
+ id: string;
261
+ name: string;
262
+ file: string;
263
+ tags: string[];
264
+ server: string;
265
+ /** Case declared `flaky: true` (pass-rate floor applies under --repeat). */
266
+ flaky?: boolean;
267
+ /** 1-based line of the case in its file when known (for annotations). */
268
+ line?: number;
269
+ }
270
+ interface StepRef {
271
+ index: number;
272
+ name: string;
273
+ /** 'tool' | 'resource' | 'resourceTemplate' | 'prompt' */
274
+ kind: string;
275
+ target: string;
276
+ }
277
+ interface Mismatch {
278
+ path: string;
279
+ message: string;
280
+ expected?: unknown;
281
+ actual?: unknown;
282
+ /** Which case field the mismatch belongs to (machine-patchable failures). */
283
+ field?: 'expect' | 'expectError' | 'expectInputRequired' | 'expectTask' | 'structuredContent';
284
+ /** RFC 6901 JSON Pointer into the case file (e.g. "/steps/1/expect/content/0/text"). */
285
+ pointer?: string;
286
+ /** 0-based step index for flows. */
287
+ stepIndex?: number;
288
+ }
289
+ type CaseStatus = 'passed' | 'failed' | 'skipped' | 'updated';
290
+ interface CaseOutcome {
291
+ status: CaseStatus;
292
+ durationMs: number;
293
+ /** Human-readable one-line reason for failure / skip. */
294
+ reason?: string;
295
+ /** Distinguishes an assertion failure from an error/timeout (JUnit <failure> vs <error>). */
296
+ errorKind?: 'assertion' | 'error' | 'timeout' | 'budget' | 'load';
297
+ /** Path to captured server stderr for this case, if any. */
298
+ stderrPath?: string;
299
+ mismatches?: Mismatch[];
300
+ expected?: JsonValue;
301
+ actual?: JsonValue;
302
+ /** Path to a transcript written for this case, if any. */
303
+ tracePath?: string;
304
+ /** Per-step timings (flows). */
305
+ steps?: Array<{
306
+ step: StepRef;
307
+ durationMs: number;
308
+ status: CaseStatus;
309
+ reason?: string;
310
+ }>;
311
+ /** Budget breach, if `budgetMs` was set and exceeded. */
312
+ budget?: {
313
+ budgetMs: number;
314
+ actualMs: number;
315
+ };
316
+ /** Under --update: why this failing case was not rewritten (matchers, expectError, ...). */
317
+ updateSkipped?: {
318
+ reason: string;
319
+ };
320
+ }
321
+ interface CheckOutcome {
322
+ id: string;
323
+ server?: string;
324
+ title: string;
325
+ status: 'passed' | 'failed' | 'warned';
326
+ detail?: string;
327
+ }
328
+ interface RunSummary {
329
+ /** Case executions (cases × repeat). Distinct cases = distinct case ids (without `#<n>`). */
330
+ total: number;
331
+ /** Status counts are per execution and sum to `total`; per-case verdicts: passRates / ok. */
332
+ passed: number;
333
+ failed: number;
334
+ skipped: number;
335
+ updated: number;
336
+ durationMs: number;
337
+ /** Present when --repeat > 1: executions (= total; cases × repeat, fewer on failFast). */
338
+ runs?: number;
339
+ /** Present when --repeat > 1: pass rate per case id. */
340
+ passRates?: Record<string, number>;
341
+ }
342
+ type RunEvent = {
343
+ type: 'run:start';
344
+ /** Case runs announced (cases × repeat). */
345
+ totalCases: number;
346
+ servers: string[];
347
+ startedAt: number;
348
+ repeat?: number;
349
+ /** Present when the run is one shard of a suite (`--shard i/n`). */
350
+ shard?: {
351
+ index: number;
352
+ total: number;
353
+ };
354
+ } | {
355
+ type: 'session:start';
356
+ server: string;
357
+ era: 'legacy' | 'modern';
358
+ protocolVersion: string;
359
+ serverInfo?: {
360
+ name?: string;
361
+ version?: string;
362
+ };
363
+ } | {
364
+ type: 'session:end';
365
+ server: string;
366
+ } | {
367
+ type: 'manifest:ok';
368
+ path: string;
369
+ server?: string;
370
+ } | {
371
+ type: 'manifest:diff';
372
+ path: string;
373
+ summary: string;
374
+ diff: JsonValue;
375
+ server?: string;
376
+ } | {
377
+ type: 'check';
378
+ outcome: CheckOutcome;
379
+ } | {
380
+ type: 'case:start';
381
+ case: CaseRef;
382
+ } | {
383
+ type: 'step:start';
384
+ case: CaseRef;
385
+ step: StepRef;
386
+ } | {
387
+ type: 'step:end';
388
+ case: CaseRef;
389
+ step: StepRef;
390
+ durationMs: number;
391
+ status: CaseStatus;
392
+ reason?: string;
393
+ } | {
394
+ type: 'case:end';
395
+ case: CaseRef;
396
+ outcome: CaseOutcome;
397
+ } | {
398
+ type: 'warning';
399
+ message: string;
400
+ }
401
+ /** `ok` mirrors the runner's exit decision so every reporter agrees with it. */
402
+ | {
403
+ type: 'run:end';
404
+ summary: RunSummary;
405
+ ok?: boolean;
406
+ };
407
+ interface Reporter {
408
+ readonly name: string;
409
+ onEvent(event: RunEvent): void | Promise<void>;
410
+ /** Called once after run:end; flush files here. */
411
+ close?(): void | Promise<void>;
412
+ }
413
+ interface ReporterContext {
414
+ rootDir: string;
415
+ /** Directory for reporter artifacts (junit.xml, results.json, traces/...). Default: reports/ */
416
+ outDir: string;
417
+ pretty: boolean;
418
+ /** Write to stdout; injectable for tests. */
419
+ stdout: (line: string) => void;
420
+ stderr: (line: string) => void;
421
+ }
422
+
423
+ type OpKind = 'tool' | 'resource' | 'resourceTemplate' | 'prompt';
424
+ declare const OP_KINDS: readonly OpKind[];
425
+ declare const STEP_KEYS: readonly ["name", "tool", "resource", "resourceTemplate", "prompt", "args", "expect", "expectError", "respond", "expectInputRequired", "expectTask", "await", "capture", "budgetMs", "timeoutMs", "ignoreErrors", "validateOutput", "normalize"];
426
+ declare const CASE_KEYS: readonly ["$schema", "name", "tool", "resource", "resourceTemplate", "prompt", "args", "expect", "expectError", "respond", "expectInputRequired", "expectTask", "await", "capture", "budgetMs", "timeoutMs", "ignoreErrors", "validateOutput", "normalize", "tags", "server", "steps", "setup", "teardown", "flaky", "skip"];
427
+ interface LoadedCase {
428
+ /** Stable reference (id = root-relative posix path; `line` 1). */
429
+ ref: CaseRef;
430
+ /** Absolute path of the case file. */
431
+ path: string;
432
+ /** The case with `$ref`s expanded. Undefined when the file failed to load. */
433
+ data?: TestCase;
434
+ /** The case exactly as parsed (no `$ref` expansion): the basis for `--update` rewrites. */
435
+ source?: TestCase;
436
+ /** Raw file text. */
437
+ text?: string;
438
+ /** Load / validation problem: the case is reported failed with errorKind `load`. */
439
+ error?: string;
440
+ /** Set when the case declares `skip: true | "reason"`. */
441
+ skip?: string;
442
+ /**
443
+ * The file is not a case, and loadCases drops it: a manifest lock, or a `$ref` fragment (no
444
+ * operation / `steps`, and referenced by another matched file or kept in `shared/` /
445
+ * `fragments/`).
446
+ */
447
+ notACase?: 'manifest' | 'fragment';
448
+ }
449
+ interface CaseFilters {
450
+ /** Keep cases carrying at least one of these tags. */
451
+ tags?: string[];
452
+ /** Drop cases carrying any of these tags. */
453
+ excludeTags?: string[];
454
+ /** Keep cases whose name or id matches (case-insensitive regex, else substring). */
455
+ grep?: string;
456
+ /** Keep every `total`-th case (1-based `index`) of the id-sorted selection. */
457
+ shard?: ShardSpec;
458
+ }
459
+ interface LoadCasesOptions extends CaseFilters {
460
+ rootDir: string;
461
+ globs: string[];
462
+ /** Server for cases without `server`. Default `"default"`. */
463
+ defaultServer?: string;
464
+ /** Known server names; a case naming another server fails to load. */
465
+ servers?: string[];
466
+ /** Suite hooks: their captures are valid `{{names}}` in every case. */
467
+ hooks?: SuiteHooks;
468
+ /** Environment for `{{env.X}}` checks. Default process.env. */
469
+ env?: Record<string, string | undefined>;
470
+ /**
471
+ * Config `manifest` path (default tests/integration/__manifest__.json). The lock files (this
472
+ * path for the default server and the `<name>.<server>.json` siblings for `servers`) are
473
+ * never loaded as cases, even when the globs match them. Files shaped like a manifest
474
+ * (`"vibrissa": 2` + a `tools` array) are skipped too.
475
+ */
476
+ manifest?: string;
477
+ /** Extra files (absolute, or relative to rootDir) that are never cases. */
478
+ exclude?: string[];
479
+ }
480
+ interface LoadCasesResult {
481
+ /** Selected cases, sorted by id. */
482
+ cases: LoadedCase[];
483
+ /** Number of case files the globs matched (before filters). */
484
+ matched: number;
485
+ /** Non-fatal loader notes (ignored top-level op fields, ...). */
486
+ warnings: string[];
487
+ /** Ids of `$ref` fragment files the globs matched and that were skipped (not cases). */
488
+ fragments?: string[];
489
+ }
490
+ /** Resolve case globs (relative to rootDir; `!` negates) to absolute, sorted file paths. */
491
+ declare function resolveCaseFiles(globs: string[], rootDir: string): Promise<string[]>;
492
+ /** Root-relative posix path when the file is inside rootDir, else the absolute posix path. */
493
+ declare function caseIdFor(file: string, rootDir: string): string;
494
+ /** Parse JSONC (comments and trailing commas allowed). Throws "<where>: invalid JSON ...". */
495
+ declare function parseCaseText(text: string, where: string): unknown;
496
+ /** Shape problems for a parsed case ("<where>: <problem>. <hint>" without the file). */
497
+ declare function validateCaseShape(raw: unknown): {
498
+ problems: string[];
499
+ warnings: string[];
500
+ };
501
+ type StepPhase = 'setup' | 'main' | 'teardown';
502
+ interface PlannedStep {
503
+ phase: StepPhase;
504
+ step: Step;
505
+ /** Where the step lives in the case file (e.g. `["steps", 1]`, `[]` = the case itself). */
506
+ source: Array<string | number>;
507
+ /** Matcher-path label of the step: `steps[1]`, `setup[0]`, or `` for a single-op case. */
508
+ at: string;
509
+ }
510
+ /** setup → steps (or the case's own op) → teardown, in run order. */
511
+ declare function planSteps(data: TestCase): PlannedStep[];
512
+ interface InterpolationScope {
513
+ vars: Record<string, JsonValue>;
514
+ env?: Record<string, string | undefined>;
515
+ }
516
+ declare class InterpolationError extends Error {
517
+ /** The placeholder name (`sid`, `env.TOKEN`). */
518
+ readonly placeholder: string;
519
+ readonly at: string;
520
+ constructor(message: string,
521
+ /** The placeholder name (`sid`, `env.TOKEN`). */
522
+ placeholder: string, at: string);
523
+ }
524
+ /**
525
+ * Replace `{{name}}` / `{{env.X}}` in every string of `value` (keys untouched). A string that
526
+ * is exactly one placeholder becomes the raw captured JSON value; otherwise values are
527
+ * inlined (strings as-is, everything else as JSON). Unknown names throw InterpolationError.
528
+ */
529
+ declare function interpolate(value: JsonValue, scope: InterpolationScope, at?: string): JsonValue;
530
+ /** Every `{{name}}` used in `value`, with where it was found. */
531
+ declare function placeholdersIn(value: unknown, at?: string): Array<{
532
+ name: string;
533
+ at: string;
534
+ }>;
535
+ /** Interpolate a step's interpolated fields. */
536
+ declare function interpolateStep(step: Step, scope: InterpolationScope, at: string): Step;
537
+ interface LoadCaseFileOptions {
538
+ rootDir: string;
539
+ defaultServer?: string;
540
+ servers?: string[];
541
+ hooks?: SuiteHooks;
542
+ env?: Record<string, string | undefined>;
543
+ }
544
+ /** A manifest lock (`vib manifest record` output), not a case. */
545
+ declare function looksLikeManifest(raw: unknown): boolean;
546
+ /**
547
+ * Load one case file. Never throws: problems land in `error` (and `warnings`). A file shaped
548
+ * like a manifest lock comes back with `notACase: 'manifest'` (and no data).
549
+ */
550
+ declare function loadCaseFile(file: string, opts: LoadCaseFileOptions, warnings?: string[]): LoadedCase;
551
+ /** Apply tag / exclude-tag / grep filters, then the shard, to id-sorted cases. */
552
+ declare function filterCases(cases: LoadedCase[], filters: CaseFilters): LoadedCase[];
553
+ /** Keep every `total`-th case of the (already sorted) list, starting at `index` (1-based). */
554
+ declare function shardCases<T>(cases: T[], shard: ShardSpec | undefined): T[];
555
+ /** Resolve globs, load every case, apply filters. Never throws for bad case files. */
556
+ declare function loadCases(options: LoadCasesOptions): Promise<LoadCasesResult>;
557
+ /** Synchronous loadCases (for test-runner bridges that register tests at import time). */
558
+ declare function loadCasesSync(options: LoadCasesOptions): LoadCasesResult;
559
+ /** Split `a.b[0].c` / `a.b.0.c` into segments. */
560
+ declare function parsePath(dotted: string): string[];
561
+ /** Read a dotted path (`structuredContent.items[0].id`) from a JSON value. */
562
+ declare function getPath(value: JsonValue | undefined, dotted: string): JsonValue | undefined;
563
+
564
+ /**
565
+ * Minimal input shapes for the lint module. They mirror the field names in
566
+ * docs/V2_ARCHITECTURE.md (ToolSummary, Manifest, ...) and are deliberately
567
+ * permissive (mostly optional) so that the foundation's richer types — and raw
568
+ * JSON read from a manifest file — are assignable to them. Every check treats
569
+ * these values as untrusted server output and re-validates them at runtime.
570
+ */
571
+
572
+ interface ToolAnnotationsLike {
573
+ title?: string;
574
+ readOnlyHint?: boolean;
575
+ destructiveHint?: boolean;
576
+ idempotentHint?: boolean;
577
+ openWorldHint?: boolean;
578
+ }
579
+ interface ToolSummary {
580
+ name: string;
581
+ title?: string;
582
+ description?: string;
583
+ inputSchema?: JsonValue;
584
+ outputSchema?: JsonValue;
585
+ annotations?: ToolAnnotationsLike | JsonValue;
586
+ }
587
+ interface ResourceSummary {
588
+ uri: string;
589
+ name?: string;
590
+ title?: string;
591
+ description?: string;
592
+ mimeType?: string;
593
+ }
594
+ interface ResourceTemplateSummary {
595
+ uriTemplate: string;
596
+ name?: string;
597
+ title?: string;
598
+ description?: string;
599
+ mimeType?: string;
600
+ }
601
+ interface PromptArgumentSummary {
602
+ name: string;
603
+ title?: string;
604
+ description?: string;
605
+ required?: boolean;
606
+ }
607
+ interface PromptSummary {
608
+ name: string;
609
+ title?: string;
610
+ description?: string;
611
+ arguments?: PromptArgumentSummary[];
612
+ }
613
+ /** Cache metadata carried by a 2026-07-28 list result. Raw wire values; validated by spec checks. */
614
+ interface ListMetaEntry {
615
+ ttlMs?: JsonValue;
616
+ cacheScope?: JsonValue;
617
+ }
618
+ interface ListMeta {
619
+ tools?: ListMetaEntry;
620
+ resources?: ListMetaEntry;
621
+ resourceTemplates?: ListMetaEntry;
622
+ prompts?: ListMetaEntry;
623
+ }
624
+ /**
625
+ * Anything shaped like a recorded Manifest (src/manifest.ts). `listMeta` holds the
626
+ * `ttlMs` / `cacheScope` of each list result; leave a list's entry undefined when
627
+ * that list was not fetched. `instructions` is the optional server instructions
628
+ * string from discover/initialize (model-visible, so it is linted too).
629
+ */
630
+ interface ManifestLike {
631
+ protocolVersion?: string;
632
+ server?: {
633
+ name?: string;
634
+ version?: string;
635
+ };
636
+ capabilities?: JsonValue;
637
+ instructions?: string;
638
+ tools: ToolSummary[];
639
+ resources?: ResourceSummary[];
640
+ resourceTemplates?: ResourceTemplateSummary[];
641
+ prompts?: PromptSummary[];
642
+ listMeta?: ListMeta;
643
+ /** How the session reached the server. Decides whether stdout hygiene applies. */
644
+ transport?: 'stdio' | 'http';
645
+ /** Non-JSON-RPC lines a stdio server wrote to stdout (from `session.stdoutNoise`). */
646
+ stdoutNoise?: StdoutNoise$1;
647
+ }
648
+ interface StdoutNoise$1 {
649
+ lines: number;
650
+ sample: string[];
651
+ }
652
+
653
+ type ToolErrorKind = 'protocol' | 'tool' | 'transport' | 'timeout';
654
+ interface ToolError {
655
+ /**
656
+ * - `protocol`: JSON-RPC error response (or an SDK-level protocol violation, see `local`)
657
+ * - `tool`: the tool ran and returned `isError: true`
658
+ * - `transport`: the connection failed / closed
659
+ * - `timeout`: no response within the time budget
660
+ */
661
+ kind: ToolErrorKind;
662
+ /** Numeric JSON-RPC code, when the error has one. */
663
+ code?: number;
664
+ /** Stable human name: `InvalidParams`, `MethodNotFound`, `RequestTimeout`, `ConnectionClosed`, ... */
665
+ codeName?: string;
666
+ message: string;
667
+ /** JSON-RPC `error.data`, when present. */
668
+ data?: JsonValue;
669
+ /** True when raised by the client SDK rather than received from the server. */
670
+ local?: boolean;
671
+ }
672
+ declare function errorCodeName(code: number): string | undefined;
673
+ /** Canonical code name for a user-supplied name (handles aliases); undefined when unknown. */
674
+ declare function canonicalCodeName(name: string): string;
675
+ /** Every code name known to Vibrissa (numeric + SDK), for docs / schema / migrate. */
676
+ declare function knownCodeNames(): string[];
677
+ /**
678
+ * Does an observed error satisfy `expectError.code`? Accepts names (with aliases) and
679
+ * numbers. `ResourceNotFound` also matches the 2026-07-28 `-32602 + { uri }` shape.
680
+ */
681
+ declare function errorMatchesCode(error: ToolError, expected: string | number): boolean;
682
+
683
+ declare const MODERN_PROTOCOL_VERSION = "2026-07-28";
684
+ declare const TASKS_EXTENSION = "io.modelcontextprotocol/tasks";
685
+ /** Non-JSON-RPC lines a stdio server wrote to stdout (the SDK drops them silently). */
686
+ interface StdoutNoise {
687
+ lines: number;
688
+ /** The first few offending lines (truncated). */
689
+ sample: string[];
690
+ }
691
+ /**
692
+ * Answers one embedded input request (MRTR round or legacy server→client request).
693
+ * Return the bare result (e.g. `{ action: "accept", content: {...} }`), or `undefined`
694
+ * to leave it unanswered (the call then resolves to `kind: 'input_required'`).
695
+ */
696
+ type InputResponder = (method: string, params: JsonValue) => JsonValue | undefined | Promise<JsonValue | undefined>;
697
+ interface SessionOptions {
698
+ /** Default per-request timeout (ms). Also bounds a whole MRTR flow. */
699
+ timeoutMs: number;
700
+ /** Called for every JSON-RPC message in/out (and stderr chunks when piped). */
701
+ transcript?: (entry: TranscriptEntry) => void;
702
+ /** Session-level responder; `CallOptions.respond` is consulted first. */
703
+ inputResponder?: InputResponder;
704
+ /**
705
+ * Server stderr (stdio only): `inherit` (default) forwards to this process's stderr,
706
+ * `capture` keeps it out of the console (transcript / onStderr only), `ignore` drops it.
707
+ */
708
+ stderr?: 'inherit' | 'capture' | 'ignore';
709
+ onStderr?: (text: string) => void;
710
+ /**
711
+ * Called for every non-JSON-RPC line a stdio server writes to stdout (e.g. a stray
712
+ * `console.log`). Such lines corrupt the protocol stream; see also `session.stdoutNoise`.
713
+ */
714
+ onStdoutNoise?: (line: string) => void;
715
+ /** Declare the tasks extension (servers may then answer with tasks). Default true. */
716
+ tasks?: boolean;
717
+ clientInfo?: {
718
+ name: string;
719
+ version: string;
720
+ };
721
+ }
722
+ interface CallOptions {
723
+ timeoutMs?: number;
724
+ /** Per-call responder: a function, or a map keyed by request method (`elicitation/create`). */
725
+ respond?: InputResponder | Record<string, JsonValue>;
726
+ /** Tasks extension: poll `tasks/get` until terminal. `true` = defaults. */
727
+ await?: AwaitOptions | boolean;
728
+ }
729
+ interface OutcomeBase {
730
+ durationMs: number;
731
+ /** Every InputRequests map the server sent during this call, one per round, in order. */
732
+ inputRequests?: JsonValue[];
733
+ /** The CreateTaskResult, when the server answered with a task. */
734
+ task?: JsonValue;
735
+ }
736
+ type Outcome = (OutcomeBase & {
737
+ kind: 'result';
738
+ result: JsonValue;
739
+ }) | (OutcomeBase & {
740
+ kind: 'error';
741
+ error: ToolError;
742
+ result?: JsonValue;
743
+ }) | (OutcomeBase & {
744
+ kind: 'input_required';
745
+ requests: JsonValue;
746
+ requestState?: string;
747
+ });
748
+ type SessionStartErrorCode = 'spawn' | 'exited' | 'timeout' | 'negotiation' | 'http' | 'config';
749
+ /** Thrown by McpSession.start when the server cannot be reached / spawned / negotiated. */
750
+ declare class SessionStartError extends Error {
751
+ readonly code: SessionStartErrorCode;
752
+ readonly stderr?: string | undefined;
753
+ constructor(message: string, code: SessionStartErrorCode, stderr?: string | undefined, options?: {
754
+ cause?: unknown;
755
+ });
756
+ }
757
+ /** Thrown by list/discover verbs (which return plain data, not an Outcome). */
758
+ declare class SessionRequestError extends Error {
759
+ readonly error: ToolError;
760
+ constructor(message: string, error: ToolError);
761
+ }
762
+ /** Expand an RFC 6570 URI template with case args (non-strings are stringified). */
763
+ declare function expandUriTemplate(template: string, args?: Record<string, JsonValue>): string;
764
+ declare class McpSession {
765
+ private readonly client;
766
+ private readonly transport;
767
+ readonly server: ServerConfig;
768
+ private readonly opts;
769
+ private readonly stdio?;
770
+ private readonly httpClient?;
771
+ private queue;
772
+ private active;
773
+ private readonly toolDefs;
774
+ private toolsListed;
775
+ private initializeRaw;
776
+ private stderrBuffer;
777
+ private isClosed;
778
+ private _era;
779
+ private _protocolVersion;
780
+ private readonly noise;
781
+ private stdoutPending;
782
+ private readonly stdoutDecoder;
783
+ /** A request was left unanswered (timeout): the server may be hung, so close() kills at once. */
784
+ private hungRequest;
785
+ private closing;
786
+ private constructor();
787
+ /** Non-JSON-RPC lines the stdio server wrote to stdout so far (protocol pollution). */
788
+ get stdoutNoise(): StdoutNoise;
789
+ /** Negotiated era: `modern` = 2026-07-28+ via server/discover, `legacy` = 2025 initialize. */
790
+ get era(): 'legacy' | 'modern';
791
+ /** Negotiated protocol revision, e.g. "2026-07-28". */
792
+ get protocolVersion(): string;
793
+ /** True once the connection closed (server exit, transport failure, or close()). */
794
+ get closed(): boolean;
795
+ /** The last few KB of server stderr (stdio, unless stderr: 'ignore'). */
796
+ get stderrTail(): string;
797
+ /** Escape hatch: the underlying SDK client. Calls made here bypass serialization/capture. */
798
+ get sdk(): Client;
799
+ static start(server: ServerConfig, opts: SessionOptions): Promise<McpSession>;
800
+ /**
801
+ * See every raw stdout chunk (in order, before the SDK parses it) by wrapping the stdio
802
+ * transport's line buffer. The SDK silently drops lines that are not JSON-RPC.
803
+ */
804
+ private watchStdout;
805
+ private scanStdout;
806
+ /** Install transport taps, stderr capture, and the input-request handlers. */
807
+ private wire;
808
+ private tap;
809
+ private answerInputRequest;
810
+ private connect;
811
+ private startError;
812
+ /** Run `fn` exclusively, with a fresh wire tracker. */
813
+ private exclusive;
814
+ /** `server/discover` on modern; synthesized from the `initialize` result on legacy. */
815
+ discover(): Promise<DiscoverInfo>;
816
+ private synthesizeDiscover;
817
+ listTools(): Promise<ToolSummary$1[]>;
818
+ listResources(): Promise<ResourceSummary$1[]>;
819
+ listResourceTemplates(): Promise<ResourceTemplateSummary$1[]>;
820
+ listPrompts(): Promise<PromptSummary$1[]>;
821
+ listToolsResult(): Promise<ListResult<ToolSummary$1>>;
822
+ listResourcesResult(): Promise<ListResult<ResourceSummary$1>>;
823
+ listResourceTemplatesResult(): Promise<ListResult<ResourceTemplateSummary$1>>;
824
+ listPromptsResult(): Promise<ListResult<PromptSummary$1>>;
825
+ /** Walk all pages through the SDK, then rebuild the aggregate from the raw wire pages. */
826
+ private listPages;
827
+ callTool(name: string, args?: Record<string, JsonValue>, opts?: CallOptions): Promise<Outcome>;
828
+ readResource(uri: string, opts?: CallOptions): Promise<Outcome>;
829
+ /** Expand `template` with `args`, then `resources/read`. */
830
+ readResourceTemplate(template: string, args?: Record<string, JsonValue>, opts?: CallOptions): Promise<Outcome>;
831
+ getPrompt(name: string, args?: Record<string, JsonValue>, opts?: CallOptions): Promise<Outcome>;
832
+ /**
833
+ * Liveness round trip; false when the server is gone or does not answer in time.
834
+ * `ping` does not exist on 2026-07-28, so modern sessions use `server/discover`.
835
+ */
836
+ ping(timeoutMs?: number): Promise<boolean>;
837
+ /**
838
+ * Close the connection and release every OS resource, so the process can exit naturally.
839
+ * - stdio: stdin EOF, then SIGTERM after 500ms (at once if a call was left hanging), then
840
+ * SIGKILL after another 500ms (Windows: kill the process tree).
841
+ * - HTTP: terminate a legacy server session (DELETE) when one exists, close the transport,
842
+ * then destroy the session's sockets.
843
+ * Idempotent; concurrent callers share one teardown.
844
+ */
845
+ close(): Promise<void>;
846
+ private teardown;
847
+ /**
848
+ * The tool definition handed to the SDK: our cached one with `outputSchema` removed, so the
849
+ * SDK never validates structuredContent itself (the runner does, with better errors and a
850
+ * per-case opt-out). On modern HTTP, list once so SEP-2243 `Mcp-Param-*` mirroring has data.
851
+ */
852
+ private toolDefinitionFor;
853
+ /** Turn what happened on the wire (and what the SDK threw) into an Outcome. */
854
+ private finish;
855
+ private resultOutcome;
856
+ /** Tasks extension: poll `tasks/get` until completed / failed / cancelled. */
857
+ private awaitTask;
858
+ private taskRequest;
859
+ }
860
+
861
+ declare const MANIFEST_VERSION = 2;
862
+ /**
863
+ * Dotted paths ignored by diffManifest unless overridden (`manifestIgnore`): the server's
864
+ * release version, and what differs only by protocol era (`protocolVersion`, `listMeta`), so
865
+ * one lock serves both eras. `vib manifest diff --strict` compares everything.
866
+ */
867
+ declare const DEFAULT_MANIFEST_IGNORE: readonly string[];
868
+ interface Manifest {
869
+ vibrissa: 2;
870
+ protocolVersion: string;
871
+ server: {
872
+ name?: string;
873
+ version?: string;
874
+ };
875
+ capabilities: JsonValue;
876
+ instructions?: string;
877
+ /** Sorted by name. */
878
+ tools: ToolSummary$1[];
879
+ /** Sorted by uri. */
880
+ resources: ResourceSummary$1[];
881
+ /** Sorted by uriTemplate. */
882
+ resourceTemplates: ResourceTemplateSummary$1[];
883
+ /** Sorted by name. */
884
+ prompts: PromptSummary$1[];
885
+ /** `ttlMs` / `cacheScope` of each list result that was fetched. */
886
+ listMeta: ListMeta;
887
+ }
888
+ interface CaptureManifestOptions {
889
+ /** How many tools/list results to collect for the stability check. Default 1. */
890
+ toolsListSamples?: number;
891
+ }
892
+ interface CapturedManifest {
893
+ manifest: Manifest;
894
+ /** Raw tools/list results in server order (the first one built the manifest). */
895
+ toolsListSamples: ToolSummary$1[][];
896
+ discover: DiscoverInfo;
897
+ }
898
+ /** Assemble a Manifest from already-fetched data (pure). */
899
+ declare function manifestFrom(input: {
900
+ protocolVersion: string;
901
+ discover: Pick<DiscoverInfo, 'serverInfo' | 'capabilities' | 'instructions'>;
902
+ tools?: ListResult<ToolSummary$1>;
903
+ resources?: ListResult<ResourceSummary$1>;
904
+ resourceTemplates?: ListResult<ResourceTemplateSummary$1>;
905
+ prompts?: ListResult<PromptSummary$1>;
906
+ }): Manifest;
907
+ /**
908
+ * Snapshot the live server: discover + tools/list (+ resources, templates, prompts when the
909
+ * server declares them). Lists the server does not implement (MethodNotFound) are empty.
910
+ * Throws SessionRequestError when a list call fails for another reason.
911
+ */
912
+ declare function captureManifest(session: McpSession, opts?: CaptureManifestOptions): Promise<CapturedManifest>;
913
+ /** Snapshot the live server's manifest. */
914
+ declare function buildManifest(session: McpSession): Promise<Manifest>;
915
+ /**
916
+ * Lock file for `server`: the configured `manifest` path for the default server, and a
917
+ * sibling `<name>.<server>.json` for any other server (e.g. `__manifest__.staging.json`).
918
+ */
919
+ declare function manifestPathFor(options: Pick<RunOptions, 'manifest' | 'rootDir' | 'defaultServer'>, server?: string): string;
920
+ declare class ManifestFileError extends Error {
921
+ constructor(message: string);
922
+ }
923
+ /** Read and sanity-check a manifest lock file. */
924
+ declare function readManifest(file: string): Manifest;
925
+ /** Write a manifest as 2-space JSON (creating directories). */
926
+ declare function writeManifest(file: string, manifest: Manifest): void;
927
+ interface FieldChange {
928
+ /**
929
+ * Dotted sub-path of what changed, as deep as it goes: `description`,
930
+ * `inputSchema.required`, `inputSchema.properties.city.type`, `capabilities.tools`.
931
+ */
932
+ path: string;
933
+ /** Absent when the field was added. */
934
+ before?: JsonValue;
935
+ /** Absent when the field was removed. */
936
+ after?: JsonValue;
937
+ /**
938
+ * Would existing clients / cases break? True for: a removed or renamed input property, a
939
+ * newly required property or prompt argument, a type change, removed enum values, a tightened
940
+ * `additionalProperties`, any outputSchema change, a changed resource mimeType, a removed
941
+ * capability, a protocolVersion change. Descriptions, titles and annotations are not breaking.
942
+ */
943
+ breaking: boolean;
944
+ }
945
+ interface ItemChange {
946
+ /** Tool / prompt name, resource uri, or uriTemplate. */
947
+ key: string;
948
+ /** Any of `fields` is breaking. */
949
+ breaking: boolean;
950
+ fields: FieldChange[];
951
+ }
952
+ /** Only non-empty lists are present. Every removed item is a breaking change. */
953
+ interface SectionDiff {
954
+ added?: string[];
955
+ removed?: string[];
956
+ changed?: ItemChange[];
957
+ }
958
+ declare const MANIFEST_SECTIONS: readonly ["tools", "resources", "resourceTemplates", "prompts"];
959
+ type ManifestSection = (typeof MANIFEST_SECTIONS)[number];
960
+ /** Machine shape: empty lists and unchanged sections are omitted. */
961
+ interface ManifestDiff {
962
+ identical: boolean;
963
+ /** At least one breaking change (see FieldChange.breaking) or removed item. */
964
+ breaking: boolean;
965
+ /** Server-level changes: protocolVersion, instructions, server.*, capabilities.*, listMeta.* */
966
+ changes?: FieldChange[];
967
+ tools?: SectionDiff;
968
+ resources?: SectionDiff;
969
+ resourceTemplates?: SectionDiff;
970
+ prompts?: SectionDiff;
971
+ }
972
+ interface DiffManifestOptions {
973
+ /**
974
+ * Dotted paths to ignore. Sections are keyed by item name / uri (`tools.*.annotations`,
975
+ * `tools.notes.search.description`); prompt arguments by name
976
+ * (`prompts.*.arguments.*.description`); `*` matches any key. Default
977
+ * `["server.version", "protocolVersion", "listMeta"]` (per-release and per-era noise); pass
978
+ * `[]` to compare everything (`vib manifest diff --strict`).
979
+ */
980
+ ignore?: readonly string[];
981
+ }
982
+ /** Deep equality ignoring object key order. */
983
+ declare function jsonEqual(a: unknown, b: unknown): boolean;
984
+ /** Structured diff of a locked manifest against the live one. */
985
+ declare function diffManifest(locked: Manifest, live: Manifest, opts?: DiffManifestOptions): ManifestDiff;
986
+ /**
987
+ * One-line summary: `1 tool changed (echo: description); 1 prompt added (greet)`, ending in
988
+ * ` (breaking)` when any change is breaking.
989
+ */
990
+ declare function summarizeDiff(diff: ManifestDiff): string;
991
+ /** Word-level diff in git's `--word-diff=plain` notation: `[-removed-]{+added+}`. */
992
+ declare function wordDiff(before: string, after: string): string | undefined;
993
+ /**
994
+ * Multi-line, uncolored rendering: one line per changed sub-path (word diff for prose),
995
+ * `+` / `-` per added / removed item, `(breaking)` on breaking lines; empty sections omitted.
996
+ */
997
+ declare function formatDiff(diff: ManifestDiff): string;
998
+
999
+ /**
1000
+ * RunOptions (incl. `outDir`, `manifestIgnore`, `checks` from config) plus runner-only knobs.
1001
+ * `outDir` holds results.json, junit.xml and traces/ (relative to rootDir; default "reports").
1002
+ */
1003
+ interface RunnerOptions extends RunOptions {
1004
+ /** Allow pretty console output (still subject to TTY / NO_COLOR). Default true. */
1005
+ pretty?: boolean;
1006
+ /** Reporter output sinks. Default console.log / console.error. */
1007
+ stdout?: (line: string) => void;
1008
+ stderr?: (line: string) => void;
1009
+ /** Reporter instances added to the ones named in `reporters`. */
1010
+ extraReporters?: Reporter[];
1011
+ /** Informational lines (e.g. skipped `$ref` fragments). Default: `stdout`. */
1012
+ info?: (line: string) => void;
1013
+ /** `--update=all`: also rewrite expectations that contain matchers. */
1014
+ updateAll?: boolean;
1015
+ /** `--update-manifest`: rewrite the manifest lock on drift (or create it when missing). */
1016
+ updateManifest?: boolean;
1017
+ /** Verify the manifest lock at all. Default true. */
1018
+ lockManifest?: boolean;
1019
+ /** Minimum pass rate for `flaky: true` cases under --repeat. Default 0.5. */
1020
+ flakyFloor?: number;
1021
+ /** --baseline: fail when a passing case's durationMs grows by more than this percent. Default 50. */
1022
+ baselineLatencyPct?: number;
1023
+ /** --baseline: ignore latency growth smaller than this many ms (timer noise). Default 10. */
1024
+ baselineLatencyMinMs?: number;
1025
+ /** Server stderr for runner sessions: kept out of the console and saved with traces by default. */
1026
+ serverStderr?: 'inherit' | 'capture' | 'ignore';
1027
+ /** Environment for `{{env.X}}`. Default process.env. */
1028
+ env?: Record<string, string | undefined>;
1029
+ }
1030
+ interface ManifestResult {
1031
+ server: string;
1032
+ path: string;
1033
+ /** `missing-required`: no lock while requireManifest is on (fails the run). */
1034
+ status: 'ok' | 'diff' | 'missing' | 'missing-required' | 'updated' | 'created' | 'error';
1035
+ diff?: ManifestDiff;
1036
+ summary?: string;
1037
+ }
1038
+ interface RunResult {
1039
+ /** 0 ok, 1 failures, 2 configuration error (e.g. an invalid server entry). */
1040
+ exitCode: number;
1041
+ ok: boolean;
1042
+ summary: RunSummary;
1043
+ cases: Array<{
1044
+ ref: CaseRef;
1045
+ outcome: CaseOutcome;
1046
+ }>;
1047
+ checks: CheckOutcome[];
1048
+ manifests: ManifestResult[];
1049
+ warnings: string[];
1050
+ }
1051
+ declare const DEFAULT_FLAKY_FLOOR = 0.5;
1052
+ declare const DEFAULT_BASELINE_LATENCY_PCT = 50;
1053
+ declare const DEFAULT_BASELINE_LATENCY_MIN_MS = 10;
1054
+ /** `content[0].text` / `_meta["a/b"]` / `(root)` → path segments. */
1055
+ declare function matcherPathSegments(path: string): string[];
1056
+ /** RFC 6901 JSON Pointer for path segments (`["steps", 1, "expect"]` → `/steps/1/expect`). */
1057
+ declare function toPointer(segments: Array<string | number>): string;
1058
+ /**
1059
+ * The value `expectInputRequired` is matched against, built from every InputRequests round
1060
+ * of the call:
1061
+ *
1062
+ * - **by key** (default): `{ "<request key>": { "method": "...", "params": {...} } }`, the
1063
+ * server's own InputRequests keys merged over all rounds. On 2025-era connections the keys
1064
+ * are JSON-RPC request ids, so prefer the method view for suites that run on both eras.
1065
+ * - **by method** (when every plain key of the expectation contains `/`):
1066
+ * `{ "elicitation/create": <params> }`; a method asked several times maps to an array of
1067
+ * params in order.
1068
+ */
1069
+ declare function inputRequestView(expected: JsonValue, rounds: JsonValue[]): JsonObject;
1070
+
1071
+ interface ExecuteContext {
1072
+ session: McpSession;
1073
+ /** Run-level normalization (merged under case / step `normalize`). */
1074
+ normalize: NormalizeConfig;
1075
+ timeoutMs: number;
1076
+ /** Default case budget (config `budgetMs`). */
1077
+ budgetMs?: number;
1078
+ /** Tool definitions by name: outputSchema enforcement, args pre-validation, name hints. */
1079
+ tools?: Map<string, ToolSummary$1>;
1080
+ /** Values visible to `{{name}}` before the case captures anything (suite setup). */
1081
+ vars?: Record<string, JsonValue>;
1082
+ env?: Record<string, string | undefined>;
1083
+ /**
1084
+ * `failing` (= --update) rewrites mismatched plain leaves of `expect`; `all` (=
1085
+ * --update=all) also replaces mismatched matchers and `$ref`s with literal values.
1086
+ */
1087
+ update?: 'off' | 'failing' | 'all';
1088
+ /** Case as written in its file (no `$ref` expansion): decides what --update may rewrite. */
1089
+ source?: TestCase;
1090
+ /** Set when the case file must not be rewritten (e.g. it has comments): the reason. */
1091
+ cannotRewrite?: string;
1092
+ /** Mention `--update` in failure reasons when it would help. Default true. */
1093
+ updateHint?: boolean;
1094
+ /** Case id for messages. */
1095
+ caseId?: string;
1096
+ onStepStart?: (step: StepRef) => void | Promise<void>;
1097
+ onStepEnd?: (step: StepRef, result: {
1098
+ durationMs: number;
1099
+ status: CaseStatus;
1100
+ reason?: string;
1101
+ }) => void | Promise<void>;
1102
+ }
1103
+ interface ExpectationUpdate {
1104
+ /** Path inside the case file, e.g. `["steps", 1, "expect"]`. */
1105
+ source: Array<string | number>;
1106
+ value: JsonValue;
1107
+ }
1108
+ interface CaseExecution {
1109
+ /** tracePath / stderrPath are filled in by the runner. */
1110
+ outcome: CaseOutcome;
1111
+ /** Normalized observation of every executed step, in run order (used by `vib diff`). */
1112
+ observations: Array<{
1113
+ step: StepRef;
1114
+ observed: JsonValue;
1115
+ planned: PlannedStep;
1116
+ }>;
1117
+ /** Goldens accepted under --update (the caller writes them). */
1118
+ updates: ExpectationUpdate[];
1119
+ warnings: string[];
1120
+ /** Final `{{name}}` scope (suite setup uses this). */
1121
+ vars: Record<string, JsonValue>;
1122
+ }
1123
+ /**
1124
+ * Run one case on a session: setup → steps → teardown, with interpolation, MRTR answers,
1125
+ * tasks, outputSchema enforcement, expectations, captures and budgets. Never throws.
1126
+ */
1127
+ declare function executeCase(data: TestCase, ctx: ExecuteContext): Promise<CaseExecution>;
1128
+ /** The indent the file uses (`" "`, `" "`, `"\t"`), for rewrites that keep its format. */
1129
+ declare function detectIndent(text: string | undefined): string | number;
1130
+ /** Write accepted goldens back into the case file (its own indent and EOL; key order kept). */
1131
+ declare function applyUpdates(loaded: LoadedCase, updates: ExpectationUpdate[]): void;
1132
+ /**
1133
+ * Trace file name (relative, no extension) for a case id: `cases/echo.basic.json#2` →
1134
+ * `cases/echo.basic.run2`. Cases outside rootDir (absolute ids) get a flat
1135
+ * `_external/<basename>-<hash>` name instead of mirroring the absolute path.
1136
+ */
1137
+ declare function traceName(caseId: string): string;
1138
+ declare class Runner {
1139
+ readonly options: RunnerOptions;
1140
+ private reporter;
1141
+ private ctx;
1142
+ private rootDir;
1143
+ private readonly results;
1144
+ private readonly checks;
1145
+ private readonly manifests;
1146
+ private readonly warnings;
1147
+ private readonly flakyIds;
1148
+ /** Case ids that regressed against --baseline (set once the baseline was applied). */
1149
+ private readonly baselineFailures;
1150
+ private baselineActive;
1151
+ private stopped;
1152
+ private fatal;
1153
+ /** Every session started per server (restarts included), for the stdout-noise report. */
1154
+ private readonly sessionsByServer;
1155
+ constructor(options: RunnerOptions);
1156
+ /** Run and return the exit code (0 ok, 1 failures, 2 configuration error). */
1157
+ run(): Promise<number>;
1158
+ execute(): Promise<RunResult>;
1159
+ private emit;
1160
+ private warn;
1161
+ private runAll;
1162
+ private orderServers;
1163
+ private startWorker;
1164
+ private runServer;
1165
+ /** stdio servers must keep stdout for JSON-RPC: report stray lines (even without --check). */
1166
+ private warnStdoutNoise;
1167
+ private toolDefinitions;
1168
+ private suiteSetup;
1169
+ private suiteTeardown;
1170
+ private executeContext;
1171
+ private workerLoop;
1172
+ /** Emit a case that needs no session: load errors, skips, or a dead server. */
1173
+ private runImmediate;
1174
+ private runCase;
1175
+ private writeTrace;
1176
+ private verifyManifest;
1177
+ /** ` --server <name>` for non-default servers (CLI hints). */
1178
+ private serverFlag;
1179
+ private missingLock;
1180
+ private runServerChecks;
1181
+ private applyBaseline;
1182
+ private caseVerdicts;
1183
+ private finish;
1184
+ }
1185
+ /** v1-compatible partial options: `server` (singular) is sugar for `servers.default`. */
1186
+ type RunCasesInput = Partial<RunnerOptions> & {
1187
+ server?: ServerConfig;
1188
+ };
1189
+ /** Fill defaults (and map v1 `server` → `servers.default`). */
1190
+ declare function resolveRunnerOptions(input?: RunCasesInput): RunnerOptions;
1191
+ /** Run cases and resolve to the exit code (0 ok, 1 failures, 2 configuration error). */
1192
+ declare function runCases(options?: RunCasesInput): Promise<number>;
1193
+
1194
+ export { type ManifestResult as $, type AwaitOptions as A, type InterpolationScope as B, type CaseStatus as C, DEFAULT_BASELINE_LATENCY_MIN_MS as D, type ExecuteContext as E, type FieldChange as F, type ItemChange as G, type HttpServerConfig as H, type InputResponder as I, type JsonValue as J, type JsonObject as K, type ListResult as L, type Mismatch as M, type NormalizeConfig as N, type LoadCasesOptions as O, type PromptSummary as P, type LoadCasesResult as Q, type Reporter as R, type ServerConfig as S, type ToolSummary as T, type LoadedCase as U, MANIFEST_SECTIONS as V, MANIFEST_VERSION as W, MODERN_PROTOCOL_VERSION as X, type Manifest as Y, type ManifestDiff as Z, ManifestFileError as _, type ReporterContext as a, resolveRunnerOptions as a$, type ManifestSection as a0, McpSession as a1, OP_KINDS as a2, type OpKind as a3, type Operation as a4, type Outcome as a5, type PlannedStep as a6, type PromptSummary$1 as a7, type ResourceSummary$1 as a8, type ResourceTemplateSummary$1 as a9, errorCodeName as aA, errorMatchesCode as aB, executeCase as aC, expandUriTemplate as aD, filterCases as aE, formatDiff as aF, getPath as aG, inputRequestView as aH, interpolate as aI, interpolateStep as aJ, isHttpServerConfig as aK, isStdioServerConfig as aL, jsonEqual as aM, knownCodeNames as aN, loadCaseFile as aO, loadCases as aP, loadCasesSync as aQ, looksLikeManifest as aR, manifestFrom as aS, manifestPathFor as aT, matcherPathSegments as aU, parseCaseText as aV, parsePath as aW, placeholdersIn as aX, planSteps as aY, readManifest as aZ, resolveCaseFiles as a_, type RunCasesInput as aa, type RunResult as ab, type RunSummary as ac, Runner as ad, type RunnerOptions as ae, STEP_KEYS as af, type SectionDiff as ag, type SessionOptions as ah, SessionRequestError as ai, SessionStartError as aj, type SessionStartErrorCode as ak, type StdioServerConfig as al, type Step as am, type StepRef as an, TASKS_EXTENSION as ao, type TestCase as ap, type ToolError as aq, type ToolSummary$1 as ar, type TranscriptEntry as as, applyUpdates as at, buildManifest as au, canonicalCodeName as av, captureManifest as aw, caseIdFor as ax, detectIndent as ay, diffManifest as az, type RunEvent as b, runCases as b0, shardCases as b1, summarizeDiff as b2, toPointer as b3, traceName as b4, validateCaseShape as b5, wordDiff as b6, writeManifest as b7, type CheckOutcome as c, type ResourceSummary as d, type ResourceTemplateSummary as e, type ManifestLike as f, type ShardSpec as g, type SuiteHooks as h, type RunOptions as i, type ProtocolMode as j, CASE_KEYS as k, type CallOptions as l, type CaptureManifestOptions as m, type CapturedManifest as n, type CaseExecution as o, type CaseFilters as p, type CaseOutcome as q, type CaseRef as r, DEFAULT_BASELINE_LATENCY_PCT as s, DEFAULT_FLAKY_FLOOR as t, DEFAULT_MANIFEST_IGNORE as u, type DiffManifestOptions as v, type DiscoverInfo as w, type ExpectError as x, type ExpectationUpdate as y, InterpolationError as z };