@vscode/component-explorer-cli 0.2.1-25 → 0.2.1-27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/_virtual/_build-info.js +1 -1
- package/dist/browserPage.d.ts +49 -0
- package/dist/browserPage.d.ts.map +1 -1
- package/dist/browserPage.js +75 -0
- package/dist/browserPage.js.map +1 -1
- package/dist/commands/acceptCommand.d.ts +1 -1
- package/dist/commands/acceptCommand.d.ts.map +1 -1
- package/dist/commands/acceptCommand.js +12 -5
- package/dist/commands/acceptCommand.js.map +1 -1
- package/dist/commands/artifactCommand.d.ts +3 -1
- package/dist/commands/artifactCommand.d.ts.map +1 -1
- package/dist/commands/checkStabilityCommand.d.ts +2 -0
- package/dist/commands/checkStabilityCommand.d.ts.map +1 -1
- package/dist/commands/checkStabilityCommand.js +17 -1
- package/dist/commands/checkStabilityCommand.js.map +1 -1
- package/dist/commands/compareCommand.d.ts +1 -1
- package/dist/commands/compareCommand.d.ts.map +1 -1
- package/dist/commands/compareCommand.js +12 -4
- package/dist/commands/compareCommand.js.map +1 -1
- package/dist/commands/inputArg.d.ts +33 -0
- package/dist/commands/inputArg.d.ts.map +1 -0
- package/dist/commands/inputArg.js +103 -0
- package/dist/commands/inputArg.js.map +1 -0
- package/dist/commands/renderCommand.d.ts +9 -1
- package/dist/commands/renderCommand.d.ts.map +1 -1
- package/dist/commands/renderCommand.js +300 -15
- package/dist/commands/renderCommand.js.map +1 -1
- package/dist/commands/watchCommand.d.ts +1 -0
- package/dist/commands/watchCommand.d.ts.map +1 -1
- package/dist/commands/watchCommand.js +22 -12
- package/dist/commands/watchCommand.js.map +1 -1
- package/dist/component-explorer-config.schema.json +53 -0
- package/dist/componentExplorer.d.ts +98 -7
- package/dist/componentExplorer.d.ts.map +1 -1
- package/dist/componentExplorer.js +133 -8
- package/dist/componentExplorer.js.map +1 -1
- package/dist/config.d.ts +27 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +42 -5
- package/dist/config.js.map +1 -1
- package/dist/coverage.d.ts +63 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +212 -0
- package/dist/coverage.js.map +1 -0
- package/dist/daemon/DaemonService.d.ts +14 -5
- package/dist/daemon/DaemonService.d.ts.map +1 -1
- package/dist/daemon/DaemonService.js +32 -9
- package/dist/daemon/DaemonService.js.map +1 -1
- package/dist/daemon/version.d.ts +1 -1
- package/dist/daemon/version.js +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest.schema.json +38 -16
- package/dist/mcp/McpServer.d.ts.map +1 -1
- package/dist/mcp/McpServer.js +143 -72
- package/dist/mcp/McpServer.js.map +1 -1
- package/dist/packages/common/dist/renderManifest.js +26 -5
- package/dist/packages/common/dist/renderManifest.js.map +1 -1
- package/dist/server/serverConfig.d.ts +17 -0
- package/dist/server/serverConfig.d.ts.map +1 -1
- package/dist/server/serverConfig.js +2 -0
- package/dist/server/serverConfig.js.map +1 -1
- package/dist/server/viteServer.d.ts +15 -0
- package/dist/server/viteServer.d.ts.map +1 -1
- package/dist/server/viteServer.js +83 -0
- package/dist/server/viteServer.js.map +1 -1
- package/dist/utils.d.ts +0 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -5
- package/dist/utils.js.map +1 -1
- package/package.json +6 -1
package/dist/mcp/McpServer.js
CHANGED
|
@@ -235,12 +235,12 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
235
235
|
_registerListFixtures() {
|
|
236
236
|
this._mcp.registerTool('list_fixtures', {
|
|
237
237
|
description: 'List all fixtures from a session',
|
|
238
|
-
inputSchema: {
|
|
238
|
+
inputSchema: z.strictObject({
|
|
239
239
|
fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
|
|
240
240
|
labelRegex: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
|
|
241
241
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
242
242
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
243
|
-
},
|
|
243
|
+
}),
|
|
244
244
|
annotations: { readOnlyHint: true },
|
|
245
245
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
246
246
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -264,14 +264,22 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
264
264
|
_registerScreenshot() {
|
|
265
265
|
this._mcp.registerTool('screenshot', {
|
|
266
266
|
description: 'Take a screenshot of a single fixture. ' +
|
|
267
|
-
'When stabilityCheck is true, the fixture is unmounted and re-mounted, then three screenshots are taken. '
|
|
268
|
-
|
|
267
|
+
'When stabilityCheck is true, the fixture is unmounted and re-mounted, then three screenshots are taken. ' +
|
|
268
|
+
'By default the fixture stays mounted after the screenshot (so a follow-up `evaluate_js` call can still operate on it); ' +
|
|
269
|
+
'pass `disposeAfter: true` to dispose immediately and surface any teardown errors as `currentDispose` in the result. ' +
|
|
270
|
+
'Errors / events captured while disposing the previously-mounted fixture (the implicit dispose at the start of every render) are reported as `previousDispose`.',
|
|
271
|
+
inputSchema: z.strictObject({
|
|
269
272
|
fixtureId: z.string().describe('The fixture ID'),
|
|
270
273
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
271
274
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
272
275
|
stabilityCheck: z.boolean().optional().describe('If true, takes three screenshots (at render time, ~500ms, ~3000ms) to check visual stability. Expensive (~3s extra) — only use it when you need to verify rendering stability..'),
|
|
273
276
|
includeConsoleLog: z.boolean().optional().describe('If true, include console.log events in the returned events array. Defaults to false — console.log entries are filtered out to reduce noise.'),
|
|
274
|
-
|
|
277
|
+
input: z.unknown().optional().describe('Arbitrary JSON-serializable data passed to the fixture as `RenderContext.input`. ' +
|
|
278
|
+
'The fixture decides how to interpret it (e.g. theme switch, scenario selection, mock data).'),
|
|
279
|
+
disposeAfter: z.boolean().optional().describe('If true, dispose the fixture after taking the screenshot. ' +
|
|
280
|
+
'Default: false — the fixture stays mounted until the next render, so `evaluate_js` can still operate on it. ' +
|
|
281
|
+
'Use this when validating fixture teardown: dispose errors / events appear in `currentDispose`.'),
|
|
282
|
+
}),
|
|
275
283
|
annotations: { readOnlyHint: true },
|
|
276
284
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
277
285
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -285,6 +293,8 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
285
293
|
sourceTreeId,
|
|
286
294
|
includeImage: true,
|
|
287
295
|
stabilityCheck: args.stabilityCheck,
|
|
296
|
+
input: args.input,
|
|
297
|
+
disposeAfter: args.disposeAfter,
|
|
288
298
|
});
|
|
289
299
|
const r = result;
|
|
290
300
|
this._updateSessionSourceTreeId(sessionName, r.sourceTreeId);
|
|
@@ -292,6 +302,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
292
302
|
if (r.hash && r.image) {
|
|
293
303
|
this._imageLru.put(r.hash, r.image);
|
|
294
304
|
}
|
|
305
|
+
const filterEvents = (events) => args.includeConsoleLog ? events : events.filter(e => e.type !== 'console.log');
|
|
295
306
|
const info = {
|
|
296
307
|
hash: r.hash,
|
|
297
308
|
sourceTreeId: r.sourceTreeId,
|
|
@@ -303,19 +314,37 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
303
314
|
info.error = r.error;
|
|
304
315
|
}
|
|
305
316
|
if (r.events && r.events.length > 0) {
|
|
306
|
-
const filtered =
|
|
307
|
-
? r.events
|
|
308
|
-
: r.events.filter(e => e.type !== 'console.log');
|
|
317
|
+
const filtered = filterEvents(r.events);
|
|
309
318
|
if (filtered.length > 0) {
|
|
310
319
|
info.events = filtered;
|
|
311
320
|
}
|
|
312
321
|
}
|
|
313
|
-
if (r.
|
|
314
|
-
info.
|
|
322
|
+
if (r.output !== undefined) {
|
|
323
|
+
info.output = r.output;
|
|
315
324
|
}
|
|
316
325
|
if (r.isStable !== undefined) {
|
|
317
326
|
info.isStable = r.isStable;
|
|
318
327
|
}
|
|
328
|
+
if (r.previousDispose) {
|
|
329
|
+
const filtered = filterEvents(r.previousDispose.events);
|
|
330
|
+
if (r.previousDispose.hasError || r.previousDispose.errors.length > 0 || filtered.length > 0) {
|
|
331
|
+
info.previousDispose = {
|
|
332
|
+
hasError: r.previousDispose.hasError,
|
|
333
|
+
errors: r.previousDispose.errors,
|
|
334
|
+
events: filtered,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (r.currentDispose) {
|
|
339
|
+
const filtered = filterEvents(r.currentDispose.events);
|
|
340
|
+
if (r.currentDispose.hasError || r.currentDispose.errors.length > 0 || filtered.length > 0) {
|
|
341
|
+
info.currentDispose = {
|
|
342
|
+
hasError: r.currentDispose.hasError,
|
|
343
|
+
errors: r.currentDispose.errors,
|
|
344
|
+
events: filtered,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
319
348
|
// Visual review status
|
|
320
349
|
if (r.hash) {
|
|
321
350
|
try {
|
|
@@ -359,13 +388,13 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
359
388
|
_registerCompareScreenshot() {
|
|
360
389
|
const tool = this._mcp.registerTool('compare_screenshot', {
|
|
361
390
|
description: 'Compare a fixture\'s screenshot across two sessions (e.g. baseline vs current)',
|
|
362
|
-
inputSchema: {
|
|
391
|
+
inputSchema: z.strictObject({
|
|
363
392
|
fixtureId: z.string().describe('The fixture ID'),
|
|
364
393
|
baselineSessionName: z.string().optional().describe('Baseline session name (defaults to worktree session)'),
|
|
365
394
|
currentSessionName: z.string().optional().describe('Current session name (defaults to current session)'),
|
|
366
395
|
baselineSourceTreeId: z.string().optional().describe('Baseline source tree ID (defaults to latest known)'),
|
|
367
396
|
currentSourceTreeId: z.string().optional().describe('Current source tree ID (defaults to latest known)'),
|
|
368
|
-
},
|
|
397
|
+
}),
|
|
369
398
|
annotations: { readOnlyHint: true },
|
|
370
399
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
371
400
|
const baselineSessionName = args.baselineSessionName ?? this._defaultBaselineSessionName();
|
|
@@ -399,8 +428,8 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
399
428
|
if (r.baselineEvents && r.baselineEvents.length > 0) {
|
|
400
429
|
info.baselineEvents = r.baselineEvents;
|
|
401
430
|
}
|
|
402
|
-
if (r.
|
|
403
|
-
info.
|
|
431
|
+
if (r.baselineOutput !== undefined) {
|
|
432
|
+
info.baselineOutput = r.baselineOutput;
|
|
404
433
|
}
|
|
405
434
|
if (r.currentHasError) {
|
|
406
435
|
info.currentHasError = true;
|
|
@@ -411,8 +440,8 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
411
440
|
if (r.currentEvents && r.currentEvents.length > 0) {
|
|
412
441
|
info.currentEvents = r.currentEvents;
|
|
413
442
|
}
|
|
414
|
-
if (r.
|
|
415
|
-
info.
|
|
443
|
+
if (r.currentOutput !== undefined) {
|
|
444
|
+
info.currentOutput = r.currentOutput;
|
|
416
445
|
}
|
|
417
446
|
if (r.approval) {
|
|
418
447
|
info.approval = r.approval;
|
|
@@ -435,12 +464,12 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
435
464
|
_registerApproveDiff() {
|
|
436
465
|
const tool = this._mcp.registerTool('approve_diff', {
|
|
437
466
|
description: 'Approve a visual diff so it won\'t require re-inspection next time',
|
|
438
|
-
inputSchema: {
|
|
467
|
+
inputSchema: z.strictObject({
|
|
439
468
|
fixtureId: z.string(),
|
|
440
469
|
originalHash: z.string(),
|
|
441
470
|
modifiedHash: z.string(),
|
|
442
471
|
comment: z.string().describe('Reason for approving this diff'),
|
|
443
|
-
},
|
|
472
|
+
}),
|
|
444
473
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
445
474
|
await daemon.methods.approvals.approve(args);
|
|
446
475
|
return {
|
|
@@ -458,14 +487,14 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
458
487
|
description: 'Approve or reject a fixture\'s screenshot based on its expectedVisualDescriptions. ' +
|
|
459
488
|
'You must take a screenshot first and pass the resulting hash. ' +
|
|
460
489
|
'On approve, caches (expectedVisualDescriptions, screenshotHash) so future runs auto-approve.',
|
|
461
|
-
inputSchema: {
|
|
490
|
+
inputSchema: z.strictObject({
|
|
462
491
|
fixtureId: z.string().describe('The fixture ID'),
|
|
463
492
|
screenshotHash: z.string().describe('The screenshot hash (from a prior screenshot tool call)'),
|
|
464
493
|
verdict: z.enum(['approve', 'reject']).describe('Whether the visual matches expectations'),
|
|
465
494
|
comment: z.string().describe('Reason for the verdict'),
|
|
466
495
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
467
496
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
468
|
-
},
|
|
497
|
+
}),
|
|
469
498
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
470
499
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
471
500
|
this._log('debug', { type: 'tool-call', tool: 'review_visual', fixtureId: args.fixtureId, verdict: args.verdict });
|
|
@@ -511,12 +540,12 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
511
540
|
this._mcp.registerTool('check_visuals', {
|
|
512
541
|
description: 'Batch check visual review status for fixtures that have expectedVisualDescription. ' +
|
|
513
542
|
'Returns lists of approved, needs-review, and no-expectation fixtures.',
|
|
514
|
-
inputSchema: {
|
|
543
|
+
inputSchema: z.strictObject({
|
|
515
544
|
fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
|
|
516
545
|
labelRegex: z.string().optional().describe('RegExp to filter fixtures by label'),
|
|
517
546
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
518
547
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
519
|
-
},
|
|
548
|
+
}),
|
|
520
549
|
annotations: { readOnlyHint: true },
|
|
521
550
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
522
551
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -585,12 +614,12 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
585
614
|
'Returns the expression result as JSON. The expression can return a Promise (it will be awaited). ' +
|
|
586
615
|
'Use this to inspect DOM state, computed styles, element dimensions, or component output. ' +
|
|
587
616
|
'Do NOT use this to modify the DOM — this tool is for read-only inspection and debugging only.',
|
|
588
|
-
inputSchema: {
|
|
617
|
+
inputSchema: z.strictObject({
|
|
589
618
|
expression: z.string().describe('JavaScript expression to evaluate. Can return a Promise. The result must be JSON-serializable.'),
|
|
590
619
|
fixtureId: z.string().optional().describe('If provided, renders this fixture before evaluating the expression'),
|
|
591
620
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
592
621
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
593
|
-
},
|
|
622
|
+
}),
|
|
594
623
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
595
624
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
596
625
|
this._log('debug', { type: 'tool-call', tool: 'evaluate_js', sessionName, hasFixtureId: !!args.fixtureId });
|
|
@@ -622,9 +651,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
622
651
|
description: 'Force-reload the browser page used for rendering fixtures. ' +
|
|
623
652
|
'Only use this as a last resort if screenshots or evaluate_js return stale/broken results ' +
|
|
624
653
|
'that persist after source changes. Normal HMR updates should handle most cases automatically.',
|
|
625
|
-
inputSchema: {
|
|
654
|
+
inputSchema: z.strictObject({
|
|
626
655
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
627
|
-
},
|
|
656
|
+
}),
|
|
628
657
|
annotations: { destructiveHint: true },
|
|
629
658
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
630
659
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -642,9 +671,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
642
671
|
_registerWatchAdd() {
|
|
643
672
|
this._mcp.registerTool('watch_add', {
|
|
644
673
|
description: 'Add fixtures to the watch list. Watched fixtures are automatically re-screenshotted when source changes.',
|
|
645
|
-
inputSchema: {
|
|
674
|
+
inputSchema: z.strictObject({
|
|
646
675
|
fixtureIds: z.array(z.string()).describe('Fixture IDs to add'),
|
|
647
|
-
},
|
|
676
|
+
}),
|
|
648
677
|
}, async (args) => {
|
|
649
678
|
this._watchList.add(args.fixtureIds);
|
|
650
679
|
return {
|
|
@@ -658,9 +687,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
658
687
|
_registerWatchRemove() {
|
|
659
688
|
this._mcp.registerTool('watch_remove', {
|
|
660
689
|
description: 'Remove fixtures from the watch list',
|
|
661
|
-
inputSchema: {
|
|
690
|
+
inputSchema: z.strictObject({
|
|
662
691
|
fixtureIds: z.array(z.string()).describe('Fixture IDs to remove'),
|
|
663
|
-
},
|
|
692
|
+
}),
|
|
664
693
|
}, async (args) => {
|
|
665
694
|
this._watchList.remove(args.fixtureIds);
|
|
666
695
|
return {
|
|
@@ -674,9 +703,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
674
703
|
_registerWatchSet() {
|
|
675
704
|
this._mcp.registerTool('watch_set', {
|
|
676
705
|
description: 'Replace the watch list entirely',
|
|
677
|
-
inputSchema: {
|
|
706
|
+
inputSchema: z.strictObject({
|
|
678
707
|
fixtureIds: z.array(z.string()).describe('Fixture IDs to watch'),
|
|
679
|
-
},
|
|
708
|
+
}),
|
|
680
709
|
}, async (args) => {
|
|
681
710
|
this._watchList.set(args.fixtureIds);
|
|
682
711
|
return {
|
|
@@ -690,12 +719,12 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
690
719
|
_registerWatchCompare() {
|
|
691
720
|
const tool = this._mcp.registerTool('watch_compare', {
|
|
692
721
|
description: 'Compare all watched fixtures across two sessions. Takes fresh screenshots from both sessions and reports which fixtures differ.',
|
|
693
|
-
inputSchema: {
|
|
722
|
+
inputSchema: z.strictObject({
|
|
694
723
|
baselineSessionName: z.string().optional().describe('Baseline session name (defaults to worktree session)'),
|
|
695
724
|
currentSessionName: z.string().optional().describe('Current session name (defaults to current session)'),
|
|
696
725
|
baselineSourceTreeId: z.string().optional().describe('Baseline source tree ID (defaults to latest known)'),
|
|
697
726
|
currentSourceTreeId: z.string().optional().describe('Current source tree ID (defaults to latest known)'),
|
|
698
|
-
},
|
|
727
|
+
}),
|
|
699
728
|
annotations: { readOnlyHint: true },
|
|
700
729
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
701
730
|
const ids = [...this._watchList.fixtureIds];
|
|
@@ -760,10 +789,10 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
760
789
|
'Pass the sourceTreeId you already observed — resolves immediately if it already differs, ' +
|
|
761
790
|
'otherwise waits for a source-change or ref-change event. ' +
|
|
762
791
|
'If fixtures are on the watch list, automatically re-screenshots them and reports which changed.',
|
|
763
|
-
inputSchema: {
|
|
792
|
+
inputSchema: z.strictObject({
|
|
764
793
|
sourceTreeId: z.string().describe('The sourceTreeId the client currently knows about. The call resolves once the source tree differs from this value.'),
|
|
765
794
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
766
|
-
},
|
|
795
|
+
}),
|
|
767
796
|
annotations: { readOnlyHint: true },
|
|
768
797
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
769
798
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -865,9 +894,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
865
894
|
this._mcp.registerTool('restart_session', {
|
|
866
895
|
description: 'Restart a session by disposing its browser page and dev server, then recreating them. ' +
|
|
867
896
|
'Use this when a session appears stuck (e.g. after a timeout).',
|
|
868
|
-
inputSchema: {
|
|
897
|
+
inputSchema: z.strictObject({
|
|
869
898
|
sessionName: z.string().optional().describe('Session name to restart (defaults to first session)'),
|
|
870
|
-
},
|
|
899
|
+
}),
|
|
871
900
|
annotations: { destructiveHint: true },
|
|
872
901
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
873
902
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -885,10 +914,10 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
885
914
|
'The ref can be a branch name, tag, commit SHA, or the special value "INDEX" to snapshot staged changes. ' +
|
|
886
915
|
'The daemon allocates a reusable worktree slot from a fixed pool (max configured in component-explorer.json). ' +
|
|
887
916
|
'Returns the updated session list on success.',
|
|
888
|
-
inputSchema: {
|
|
917
|
+
inputSchema: z.strictObject({
|
|
889
918
|
name: z.string().describe('Unique session name (e.g. "baseline", "bisect")'),
|
|
890
919
|
ref: z.string().describe('Git ref: branch, tag, commit SHA, or "INDEX" for staged changes'),
|
|
891
|
-
},
|
|
920
|
+
}),
|
|
892
921
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
893
922
|
this._log('info', { type: 'tool-call', tool: 'open_session', name: args.name, ref: args.ref });
|
|
894
923
|
const result = await daemon.methods.openSession({ name: args.name, ref: args.ref });
|
|
@@ -906,9 +935,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
906
935
|
this._mcp.registerTool('close_session', {
|
|
907
936
|
description: 'Close a dynamic worktree session and release its worktree slot back to the pool. ' +
|
|
908
937
|
'Cannot close static sessions configured in component-explorer.json.',
|
|
909
|
-
inputSchema: {
|
|
938
|
+
inputSchema: z.strictObject({
|
|
910
939
|
name: z.string().describe('Session name to close'),
|
|
911
|
-
},
|
|
940
|
+
}),
|
|
912
941
|
annotations: { destructiveHint: true },
|
|
913
942
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
914
943
|
this._log('info', { type: 'tool-call', tool: 'close_session', name: args.name });
|
|
@@ -929,10 +958,10 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
929
958
|
'The worktree is checked out to the new ref and Vite\'s HMR handles the incremental update (no server restart). ' +
|
|
930
959
|
'Fails if the worktree has uncommitted changes — the error will list the dirty files. ' +
|
|
931
960
|
'The ref can be a branch, tag, commit SHA, or "INDEX" for staged changes.',
|
|
932
|
-
inputSchema: {
|
|
961
|
+
inputSchema: z.strictObject({
|
|
933
962
|
name: z.string().describe('Session name to update'),
|
|
934
963
|
ref: z.string().describe('New git ref: branch, tag, commit SHA, or "INDEX"'),
|
|
935
|
-
},
|
|
964
|
+
}),
|
|
936
965
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
937
966
|
this._log('info', { type: 'tool-call', tool: 'update_session_ref', name: args.name, ref: args.ref });
|
|
938
967
|
const result = await daemon.methods.updateSessionRef({ name: args.name, ref: args.ref });
|
|
@@ -949,11 +978,11 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
949
978
|
this._mcp.registerTool('get_url', {
|
|
950
979
|
description: 'Get URL for viewing fixtures. Returns the full Component Explorer UI by default. ' +
|
|
951
980
|
'Use `embedded: true` for a minimal single-fixture view (requires fixtureId).',
|
|
952
|
-
inputSchema: {
|
|
981
|
+
inputSchema: z.strictObject({
|
|
953
982
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
954
983
|
fixtureId: z.string().optional().describe('Specific fixture ID to view. In explorer mode, pre-selects this fixture. In embedded mode (required), shows only this fixture.'),
|
|
955
984
|
embedded: z.boolean().optional().describe('If true, returns an embedded single-fixture URL (minimal UI, requires fixtureId). Default: false (full explorer UI).'),
|
|
956
|
-
},
|
|
985
|
+
}),
|
|
957
986
|
annotations: { readOnlyHint: true },
|
|
958
987
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
959
988
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -1013,14 +1042,23 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1013
1042
|
}
|
|
1014
1043
|
_registerCheckFixtureErrors() {
|
|
1015
1044
|
this._mcp.registerTool('check_fixture_errors', {
|
|
1016
|
-
description: 'Render fixtures and check for errors.
|
|
1045
|
+
description: 'Render fixtures and check for errors. Each fixture is rendered and then disposed; ' +
|
|
1046
|
+
'render exceptions appear as `error`, dispose exceptions as `disposeError`, and console / window ' +
|
|
1047
|
+
'events from both phases are collected in `events` (each tagged with `phase: "render" | "dispose"`). ' +
|
|
1048
|
+
'A fixture is considered errored if either the render or the dispose phase reports `hasError` ' +
|
|
1049
|
+
'(i.e. an exception was thrown or an event of type `console.error` / `window.error` / `window.unhandledrejection` was captured). ' +
|
|
1050
|
+
'Only errored fixtures are listed; successful fixtures are summarised via the `summary` counts. ' +
|
|
1017
1051
|
'Returns results directly if finished within ~10s, otherwise returns a taskId for polling via check_task.',
|
|
1018
|
-
inputSchema: {
|
|
1052
|
+
inputSchema: z.strictObject({
|
|
1019
1053
|
fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
|
|
1020
1054
|
labelRegex: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
|
|
1021
1055
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
1022
1056
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
1023
|
-
|
|
1057
|
+
reloadBetweenFixtures: z.boolean().optional().describe('If true, reload the browser page between fixtures. Provides cleaner isolation but is slower. ' +
|
|
1058
|
+
'Default: false (the page is only reloaded after a fixture errors).'),
|
|
1059
|
+
input: z.unknown().optional().describe('Arbitrary JSON object passed to every fixture as `RenderContext.input`. ' +
|
|
1060
|
+
'See `screenshot` for details.'),
|
|
1061
|
+
}),
|
|
1024
1062
|
annotations: { readOnlyHint: true },
|
|
1025
1063
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
1026
1064
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -1037,8 +1075,12 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1037
1075
|
}
|
|
1038
1076
|
const fixtures = filtered.fixtures;
|
|
1039
1077
|
const task = this._taskManager.startTask(async (report, signal) => {
|
|
1040
|
-
|
|
1041
|
-
|
|
1078
|
+
// Only errored fixtures are stored/reported; successful fixtures would just bloat the response.
|
|
1079
|
+
// The summary below still surfaces the total/ok/errored counts.
|
|
1080
|
+
const erroredResults = [];
|
|
1081
|
+
let okCount = 0;
|
|
1082
|
+
let processed = 0;
|
|
1083
|
+
report({ completed: 0, total: fixtures.length, partialResult: erroredResults });
|
|
1042
1084
|
for (let i = 0; i < fixtures.length; i++) {
|
|
1043
1085
|
if (signal.aborted) {
|
|
1044
1086
|
break;
|
|
@@ -1050,30 +1092,52 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1050
1092
|
sessionName,
|
|
1051
1093
|
sourceTreeId,
|
|
1052
1094
|
includeImage: false,
|
|
1095
|
+
// Skip the reload before the very first fixture — the page is already fresh.
|
|
1096
|
+
reloadBeforeRender: args.reloadBetweenFixtures && i > 0,
|
|
1097
|
+
input: args.input,
|
|
1098
|
+
// Always dispose after rendering so teardown errors / events are observable for every fixture
|
|
1099
|
+
// (including the last one). Because we explicitly dispose here, the implicit
|
|
1100
|
+
// `previousDispose` at the start of the next render is a no-op and need not be inspected.
|
|
1101
|
+
disposeAfter: true,
|
|
1053
1102
|
});
|
|
1054
1103
|
const r = result;
|
|
1055
|
-
const
|
|
1056
|
-
|
|
1057
|
-
|
|
1104
|
+
const renderEvents = (r.events ?? []).map(e => ({ phase: 'render', ...e }));
|
|
1105
|
+
const disposeEvents = (r.currentDispose?.events ?? []).map(e => ({ phase: 'dispose', ...e }));
|
|
1106
|
+
const events = [...renderEvents, ...disposeEvents];
|
|
1107
|
+
const disposeError = r.currentDispose?.errors[0];
|
|
1108
|
+
// Trust the upstream `hasError` flags so this stays in sync with the render-report rules
|
|
1109
|
+
// (exception OR an event of type console.error / window.error / window.unhandledrejection).
|
|
1110
|
+
const hasError = r.hasError || !!r.currentDispose?.hasError;
|
|
1111
|
+
if (hasError) {
|
|
1112
|
+
const entry = { fixtureId: fixture.fixtureId, hasError: true };
|
|
1113
|
+
if (r.error) {
|
|
1114
|
+
entry.error = r.error;
|
|
1115
|
+
}
|
|
1116
|
+
if (disposeError) {
|
|
1117
|
+
entry.disposeError = disposeError;
|
|
1118
|
+
}
|
|
1119
|
+
if (events.length > 0) {
|
|
1120
|
+
entry.events = events;
|
|
1121
|
+
}
|
|
1122
|
+
erroredResults.push(entry);
|
|
1058
1123
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1124
|
+
else {
|
|
1125
|
+
okCount++;
|
|
1061
1126
|
}
|
|
1062
|
-
results.push(entry);
|
|
1063
1127
|
}
|
|
1064
1128
|
catch (e) {
|
|
1065
|
-
|
|
1129
|
+
erroredResults.push({
|
|
1066
1130
|
fixtureId: fixture.fixtureId,
|
|
1067
1131
|
hasError: true,
|
|
1068
1132
|
error: { message: e instanceof Error ? e.message : String(e) },
|
|
1069
1133
|
});
|
|
1070
1134
|
}
|
|
1071
|
-
|
|
1135
|
+
processed++;
|
|
1136
|
+
report({ completed: i + 1, total: fixtures.length, partialResult: erroredResults });
|
|
1072
1137
|
}
|
|
1073
|
-
const withErrors = results.filter(r => r.hasError);
|
|
1074
1138
|
return {
|
|
1075
|
-
fixtures:
|
|
1076
|
-
summary: { total:
|
|
1139
|
+
fixtures: erroredResults,
|
|
1140
|
+
summary: { total: processed, ok: okCount, errored: erroredResults.length },
|
|
1077
1141
|
};
|
|
1078
1142
|
});
|
|
1079
1143
|
const waited = await this._taskManager.waitForTask(task.id, 10_000);
|
|
@@ -1107,12 +1171,16 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1107
1171
|
description: 'Check rendering stability of fixtures. Each fixture is unmounted, re-mounted, and screenshotted 3 times (~3s per fixture). ' +
|
|
1108
1172
|
'Returns results directly if finished within ~10s, otherwise returns a taskId for polling via check_task. ' +
|
|
1109
1173
|
'When returning a taskId, includes partial results collected so far.',
|
|
1110
|
-
inputSchema: {
|
|
1174
|
+
inputSchema: z.strictObject({
|
|
1111
1175
|
fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
|
|
1112
1176
|
labelRegex: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
|
|
1113
1177
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
1114
1178
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
1115
|
-
|
|
1179
|
+
reloadBetweenFixtures: z.boolean().optional().describe('If true, reload the browser page between fixtures. Provides cleaner isolation but is slower. ' +
|
|
1180
|
+
'Default: false (the page is only reloaded after a fixture errors).'),
|
|
1181
|
+
input: z.unknown().optional().describe('Arbitrary JSON object passed to every fixture as `RenderContext.input`. ' +
|
|
1182
|
+
'See `screenshot` for details.'),
|
|
1183
|
+
}),
|
|
1116
1184
|
annotations: { readOnlyHint: true },
|
|
1117
1185
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
1118
1186
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -1144,6 +1212,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1144
1212
|
sourceTreeId,
|
|
1145
1213
|
includeImage: false,
|
|
1146
1214
|
stabilityCheck: true,
|
|
1215
|
+
// Skip the reload before the very first fixture — the page is already fresh.
|
|
1216
|
+
reloadBeforeRender: args.reloadBetweenFixtures && i > 0,
|
|
1217
|
+
input: args.input,
|
|
1147
1218
|
});
|
|
1148
1219
|
const r = result;
|
|
1149
1220
|
results.push({
|
|
@@ -1188,9 +1259,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1188
1259
|
_registerCheckTask() {
|
|
1189
1260
|
this._mcp.registerTool('check_task', {
|
|
1190
1261
|
description: 'Check on a running task. Waits up to ~2s for completion; if still running, returns progress and new results since last check.',
|
|
1191
|
-
inputSchema: {
|
|
1262
|
+
inputSchema: z.strictObject({
|
|
1192
1263
|
taskId: z.string().describe('The task ID returned by a previous tool call'),
|
|
1193
|
-
},
|
|
1264
|
+
}),
|
|
1194
1265
|
annotations: { readOnlyHint: true },
|
|
1195
1266
|
}, async (args) => {
|
|
1196
1267
|
const waited = await this._taskManager.waitForTask(args.taskId, 2_000);
|
|
@@ -1238,9 +1309,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1238
1309
|
_registerCancelTask() {
|
|
1239
1310
|
this._mcp.registerTool('cancel_task', {
|
|
1240
1311
|
description: 'Cancel a running task',
|
|
1241
|
-
inputSchema: {
|
|
1312
|
+
inputSchema: z.strictObject({
|
|
1242
1313
|
taskId: z.string().describe('The task ID to cancel'),
|
|
1243
|
-
},
|
|
1314
|
+
}),
|
|
1244
1315
|
}, async (args) => {
|
|
1245
1316
|
const task = this._taskManager.getTask(args.taskId);
|
|
1246
1317
|
if (!task) {
|
|
@@ -1260,9 +1331,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1260
1331
|
description: 'Retrieve a recently-taken screenshot image by its hash. ' +
|
|
1261
1332
|
'Keeps the last ~10 images in an LRU cache. ' +
|
|
1262
1333
|
'Useful for debugging when screenshot hashes behave unexpectedly.',
|
|
1263
|
-
inputSchema: {
|
|
1334
|
+
inputSchema: z.strictObject({
|
|
1264
1335
|
hash: z.string().describe('The screenshot hash to look up'),
|
|
1265
|
-
},
|
|
1336
|
+
}),
|
|
1266
1337
|
annotations: { readOnlyHint: true },
|
|
1267
1338
|
}, async (args) => {
|
|
1268
1339
|
const image = this._imageLru.get(args.hash);
|
|
@@ -1286,9 +1357,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1286
1357
|
'Only use this tool when the user explicitly asks to show or hide the browser. ' +
|
|
1287
1358
|
'Do not call this tool automatically or as part of other workflows. ' +
|
|
1288
1359
|
'Note: changing visibility closes the current browser instance, so the next screenshot or evaluate_js call will relaunch it.',
|
|
1289
|
-
inputSchema: {
|
|
1360
|
+
inputSchema: z.strictObject({
|
|
1290
1361
|
visible: z.boolean().describe('true to show the browser window (headed mode), false to hide it (headless mode)'),
|
|
1291
|
-
},
|
|
1362
|
+
}),
|
|
1292
1363
|
annotations: { destructiveHint: true },
|
|
1293
1364
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
1294
1365
|
await daemon.methods.setBrowserVisibility({ visible: args.visible });
|