@vscode/component-explorer-cli 0.2.1-16 → 0.2.1-160
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 +36 -12
- package/dist/WorktreePool.d.ts +5 -4
- package/dist/WorktreePool.d.ts.map +1 -1
- package/dist/WorktreePool.js +3 -3
- package/dist/WorktreePool.js.map +1 -1
- package/dist/_virtual/_build-info.js +1 -1
- package/dist/browserPage.d.ts +55 -1
- package/dist/browserPage.d.ts.map +1 -1
- package/dist/browserPage.js +98 -6
- 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 +21 -14
- package/dist/commands/acceptCommand.js.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 +25 -8
- 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 +31 -22
- 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/mcpCommand.d.ts.map +1 -1
- package/dist/commands/mcpCommand.js +12 -11
- package/dist/commands/mcpCommand.js.map +1 -1
- package/dist/commands/renderCommand.d.ts +10 -1
- package/dist/commands/renderCommand.d.ts.map +1 -1
- package/dist/commands/renderCommand.js +390 -61
- package/dist/commands/renderCommand.js.map +1 -1
- package/dist/commands/renderCommand.test.d.ts +2 -0
- package/dist/commands/renderCommand.test.d.ts.map +1 -0
- package/dist/commands/serveCommand.d.ts.map +1 -1
- package/dist/commands/serveCommand.js +14 -11
- package/dist/commands/serveCommand.js.map +1 -1
- package/dist/commands/serviceDiffCommitsCommand.d.ts.map +1 -1
- package/dist/commands/serviceDiffCommitsCommand.js +10 -9
- package/dist/commands/serviceDiffCommitsCommand.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 +31 -20
- package/dist/commands/watchCommand.js.map +1 -1
- package/dist/comparison.d.ts +4 -3
- package/dist/comparison.d.ts.map +1 -1
- package/dist/comparison.js +17 -14
- package/dist/comparison.js.map +1 -1
- package/dist/component-explorer-config.schema.json +55 -2
- package/dist/componentExplorer.d.ts +110 -12
- package/dist/componentExplorer.d.ts.map +1 -1
- package/dist/componentExplorer.js +177 -24
- package/dist/componentExplorer.js.map +1 -1
- package/dist/config.d.ts +33 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +64 -27
- package/dist/config.js.map +1 -1
- package/dist/coverage.d.ts +64 -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 +47 -23
- package/dist/daemon/DaemonService.js.map +1 -1
- package/dist/daemon/approvalStore.d.ts +2 -1
- package/dist/daemon/approvalStore.d.ts.map +1 -1
- package/dist/daemon/approvalStore.js +4 -3
- package/dist/daemon/approvalStore.js.map +1 -1
- package/dist/daemon/inProcessClient.js +2 -2
- package/dist/daemon/lifecycle.d.ts +2 -1
- package/dist/daemon/lifecycle.d.ts.map +1 -1
- package/dist/daemon/lifecycle.js +1 -1
- package/dist/daemon/lifecycle.js.map +1 -1
- package/dist/daemon/pipeClient.js +8 -7
- package/dist/daemon/pipeClient.js.map +1 -1
- package/dist/daemon/pipeName.d.ts +3 -3
- package/dist/daemon/pipeName.d.ts.map +1 -1
- package/dist/daemon/pipeName.js +1 -2
- package/dist/daemon/pipeName.js.map +1 -1
- package/dist/daemon/pipeServer.js +3 -3
- package/dist/daemon/version.d.ts +1 -1
- package/dist/daemon/version.js +2 -2
- package/dist/dependencyInstaller.d.ts +2 -1
- package/dist/dependencyInstaller.d.ts.map +1 -1
- package/dist/dependencyInstaller.js +4 -4
- package/dist/dependencyInstaller.js.map +1 -1
- package/dist/explorerSession.d.ts +5 -1
- package/dist/explorerSession.d.ts.map +1 -1
- package/dist/explorerSession.js +24 -7
- package/dist/explorerSession.js.map +1 -1
- package/dist/git/gitCommitResolver.d.ts +2 -1
- package/dist/git/gitCommitResolver.d.ts.map +1 -1
- package/dist/git/gitCommitResolver.js +8 -8
- package/dist/git/gitCommitResolver.js.map +1 -1
- package/dist/git/gitIndexResolver.d.ts +2 -1
- package/dist/git/gitIndexResolver.d.ts.map +1 -1
- package/dist/git/gitIndexResolver.js +7 -7
- package/dist/git/gitIndexResolver.js.map +1 -1
- package/dist/git/gitService.d.ts +5 -4
- package/dist/git/gitService.d.ts.map +1 -1
- package/dist/git/gitService.js.map +1 -1
- package/dist/git/gitUtils.d.ts +6 -5
- package/dist/git/gitUtils.d.ts.map +1 -1
- package/dist/git/gitUtils.js +5 -4
- package/dist/git/gitUtils.js.map +1 -1
- package/dist/git/gitWorktreeManager.d.ts +15 -14
- package/dist/git/gitWorktreeManager.d.ts.map +1 -1
- package/dist/git/gitWorktreeManager.js +10 -11
- package/dist/git/gitWorktreeManager.js.map +1 -1
- package/dist/git/testUtils.d.ts +2 -1
- package/dist/git/testUtils.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/js-component-explorer/packages/common/dist/explorerUrl.js +21 -0
- package/dist/js-component-explorer/packages/common/dist/explorerUrl.js.map +1 -0
- package/dist/js-component-explorer/packages/common/dist/renderManifest.js +68 -0
- package/dist/js-component-explorer/packages/common/dist/renderManifest.js.map +1 -0
- package/dist/js-component-explorer/packages/semver/dist/index.js.map +1 -0
- package/dist/js-component-explorer/packages/simple-api/dist/chunk-FJ7AVNQE.js.map +1 -0
- package/dist/js-component-explorer/packages/simple-api/dist/chunk-TTRCY65Z.js.map +1 -0
- package/dist/js-component-explorer/packages/simple-api/dist/chunk-WNXMRXWV.js.map +1 -0
- package/dist/js-component-explorer/packages/simple-api/dist/express.js.map +1 -0
- package/dist/manifest.schema.json +44 -16
- package/dist/mcp/McpServer.d.ts.map +1 -1
- package/dist/mcp/McpServer.js +183 -101
- package/dist/mcp/McpServer.js.map +1 -1
- package/dist/path.d.ts +30 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +78 -0
- package/dist/path.js.map +1 -0
- package/dist/processTree.d.ts +6 -1
- package/dist/processTree.d.ts.map +1 -1
- package/dist/processTree.js +48 -5
- package/dist/processTree.js.map +1 -1
- package/dist/renderManifest.js +1 -1
- package/dist/server/httpServer.d.ts +1 -1
- package/dist/server/httpServer.d.ts.map +1 -1
- package/dist/server/httpServer.js +4 -8
- package/dist/server/httpServer.js.map +1 -1
- package/dist/server/serverConfig.d.ts +23 -6
- package/dist/server/serverConfig.d.ts.map +1 -1
- package/dist/server/serverConfig.js +11 -18
- package/dist/server/serverConfig.js.map +1 -1
- package/dist/server/viteServer.d.ts +16 -1
- package/dist/server/viteServer.d.ts.map +1 -1
- package/dist/server/viteServer.js +86 -5
- package/dist/server/viteServer.js.map +1 -1
- package/dist/storage.d.ts +2 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +1 -1
- package/dist/storage.js.map +1 -1
- package/dist/utils.d.ts +3 -30
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +4 -24
- package/dist/utils.js.map +1 -1
- package/dist/visualCache.d.ts +2 -1
- package/dist/visualCache.d.ts.map +1 -1
- package/dist/visualCache.js +4 -3
- package/dist/visualCache.js.map +1 -1
- package/dist/viteProjectRef.d.ts +4 -12
- package/dist/viteProjectRef.d.ts.map +1 -1
- package/dist/viteProjectRef.js +9 -17
- package/dist/viteProjectRef.js.map +1 -1
- package/dist/vscode-observables/observables/dist/disposables.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/base.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/commonFacade/deps.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/debugLocation.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/debugName.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/index.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/logging/consoleObservableLogger.js.map +1 -0
- package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/logging/debugGetDependencyGraph.js +1 -1
- package/dist/vscode-observables/observables/dist/observableInternal/logging/debugGetDependencyGraph.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/logging/logging.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/observables/baseObservable.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/observables/derived.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/observables/derivedImpl.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/observables/observableValue.js.map +1 -0
- package/dist/{external/vscode-observables/observables/dist/observableInternal/reactions/autorun.js → vscode-observables/observables/dist/observableInternal/reactions/createEffect.js} +9 -5
- package/dist/vscode-observables/observables/dist/observableInternal/reactions/createEffect.js.map +1 -0
- package/dist/{external/vscode-observables/observables/dist/observableInternal/reactions/autorunImpl.js → vscode-observables/observables/dist/observableInternal/reactions/createEffectImpl.js} +1 -1
- package/dist/vscode-observables/observables/dist/observableInternal/reactions/createEffectImpl.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/transaction.js.map +1 -0
- package/dist/vscode-observables/observables/dist/observableInternal/utils/utils.js.map +1 -0
- package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/utils/utilsCancellation.js +2 -2
- package/dist/vscode-observables/observables/dist/observableInternal/utils/utilsCancellation.js.map +1 -0
- package/package.json +11 -9
- package/dist/commands/artifactCommand.d.ts +0 -14
- package/dist/commands/artifactCommand.d.ts.map +0 -1
- package/dist/external/vscode-observables/observables/dist/disposables.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/base.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/commonFacade/deps.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/debugLocation.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/debugName.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/index.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/logging/consoleObservableLogger.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/logging/debugGetDependencyGraph.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/logging/logging.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/observables/baseObservable.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/observables/derived.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/observables/derivedImpl.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/observables/observableValue.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/reactions/autorun.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/reactions/autorunImpl.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/transaction.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/utils/utils.js.map +0 -1
- package/dist/external/vscode-observables/observables/dist/observableInternal/utils/utilsCancellation.js.map +0 -1
- package/dist/packages/common/dist/renderManifest.js +0 -46
- package/dist/packages/common/dist/renderManifest.js.map +0 -1
- package/dist/packages/semver/dist/index.js.map +0 -1
- package/dist/packages/simple-api/dist/chunk-FJ7AVNQE.js.map +0 -1
- package/dist/packages/simple-api/dist/chunk-TTRCY65Z.js.map +0 -1
- package/dist/packages/simple-api/dist/chunk-WNXMRXWV.js.map +0 -1
- package/dist/packages/simple-api/dist/express.js.map +0 -1
- /package/dist/{packages → js-component-explorer/packages}/semver/dist/index.js +0 -0
- /package/dist/{packages → js-component-explorer/packages}/simple-api/dist/chunk-FJ7AVNQE.js +0 -0
- /package/dist/{packages → js-component-explorer/packages}/simple-api/dist/chunk-TTRCY65Z.js +0 -0
- /package/dist/{packages → js-component-explorer/packages}/simple-api/dist/chunk-WNXMRXWV.js +0 -0
- /package/dist/{packages → js-component-explorer/packages}/simple-api/dist/express.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/disposables.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/base.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/commonFacade/deps.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/debugLocation.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/debugName.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/index.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/logging/consoleObservableLogger.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/logging/logging.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/baseObservable.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/derived.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/derivedImpl.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/observableFromEvent.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/observableValue.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/transaction.js +0 -0
- /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/utils/utils.js +0 -0
package/dist/mcp/McpServer.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
-
import '../
|
|
5
|
-
import { Disposable } from '../
|
|
6
|
-
import '../
|
|
7
|
-
import { autorun } from '../
|
|
8
|
-
import '../
|
|
9
|
-
import '../
|
|
10
|
-
import '../
|
|
11
|
-
import
|
|
4
|
+
import '../vscode-observables/observables/dist/observableInternal/index.js';
|
|
5
|
+
import { Disposable } from '../vscode-observables/observables/dist/disposables.js';
|
|
6
|
+
import '../vscode-observables/observables/dist/observableInternal/debugLocation.js';
|
|
7
|
+
import { autorun } from '../vscode-observables/observables/dist/observableInternal/reactions/createEffect.js';
|
|
8
|
+
import '../vscode-observables/observables/dist/observableInternal/observables/derived.js';
|
|
9
|
+
import '../vscode-observables/observables/dist/observableInternal/utils/utils.js';
|
|
10
|
+
import '../vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js';
|
|
11
|
+
import '../js-component-explorer/packages/common/dist/renderManifest.js';
|
|
12
|
+
import { buildExplorerUrl } from '../js-component-explorer/packages/common/dist/explorerUrl.js';
|
|
13
|
+
import { EXPLORER_ROUTE } from '../utils.js';
|
|
12
14
|
import { TaskManager } from './TaskManager.js';
|
|
13
15
|
import { isPipeConnectionError } from '../daemon/pipeClient.js';
|
|
14
16
|
|
|
@@ -179,23 +181,23 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
179
181
|
}
|
|
180
182
|
}
|
|
181
183
|
// -- Tool registration ---------------------------------------------------
|
|
182
|
-
_filterFixtures(allFixtures,
|
|
184
|
+
_filterFixtures(allFixtures, fixtureIdRegexStr, labelRegexStr) {
|
|
183
185
|
let fixtureIdRegex;
|
|
184
|
-
if (
|
|
186
|
+
if (fixtureIdRegexStr) {
|
|
185
187
|
try {
|
|
186
|
-
fixtureIdRegex = new RegExp(
|
|
188
|
+
fixtureIdRegex = new RegExp(fixtureIdRegexStr);
|
|
187
189
|
}
|
|
188
190
|
catch {
|
|
189
|
-
return { error: `Error: Invalid
|
|
191
|
+
return { error: `Error: Invalid fixtureIdRegex: ${fixtureIdRegexStr}` };
|
|
190
192
|
}
|
|
191
193
|
}
|
|
192
194
|
let labelRegex;
|
|
193
|
-
if (
|
|
195
|
+
if (labelRegexStr) {
|
|
194
196
|
try {
|
|
195
|
-
labelRegex = new RegExp(
|
|
197
|
+
labelRegex = new RegExp(labelRegexStr);
|
|
196
198
|
}
|
|
197
199
|
catch {
|
|
198
|
-
return { error: `Error: Invalid
|
|
200
|
+
return { error: `Error: Invalid labelRegex: ${labelRegexStr}` };
|
|
199
201
|
}
|
|
200
202
|
}
|
|
201
203
|
return {
|
|
@@ -233,12 +235,12 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
233
235
|
_registerListFixtures() {
|
|
234
236
|
this._mcp.registerTool('list_fixtures', {
|
|
235
237
|
description: 'List all fixtures from a session',
|
|
236
|
-
inputSchema: {
|
|
237
|
-
|
|
238
|
-
|
|
238
|
+
inputSchema: z.strictObject({
|
|
239
|
+
fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
|
|
240
|
+
labelRegex: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
|
|
239
241
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
240
242
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
241
|
-
},
|
|
243
|
+
}),
|
|
242
244
|
annotations: { readOnlyHint: true },
|
|
243
245
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
244
246
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -246,7 +248,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
246
248
|
return this._withSourceTreeRetry(daemon, async () => {
|
|
247
249
|
const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
|
|
248
250
|
const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
|
|
249
|
-
const filtered = this._filterFixtures(listResult.fixtures, args.
|
|
251
|
+
const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
|
|
250
252
|
if ('error' in filtered) {
|
|
251
253
|
return { content: [{ type: 'text', text: filtered.error }], isError: true };
|
|
252
254
|
}
|
|
@@ -262,13 +264,22 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
262
264
|
_registerScreenshot() {
|
|
263
265
|
this._mcp.registerTool('screenshot', {
|
|
264
266
|
description: 'Take a screenshot of a single fixture. ' +
|
|
265
|
-
'When stabilityCheck is true, the fixture is unmounted and re-mounted, then three screenshots are taken. '
|
|
266
|
-
|
|
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({
|
|
267
272
|
fixtureId: z.string().describe('The fixture ID'),
|
|
268
273
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
269
274
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
270
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..'),
|
|
271
|
-
|
|
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.'),
|
|
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
|
+
}),
|
|
272
283
|
annotations: { readOnlyHint: true },
|
|
273
284
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
274
285
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -282,6 +293,8 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
282
293
|
sourceTreeId,
|
|
283
294
|
includeImage: true,
|
|
284
295
|
stabilityCheck: args.stabilityCheck,
|
|
296
|
+
input: args.input,
|
|
297
|
+
disposeAfter: args.disposeAfter,
|
|
285
298
|
});
|
|
286
299
|
const r = result;
|
|
287
300
|
this._updateSessionSourceTreeId(sessionName, r.sourceTreeId);
|
|
@@ -289,6 +302,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
289
302
|
if (r.hash && r.image) {
|
|
290
303
|
this._imageLru.put(r.hash, r.image);
|
|
291
304
|
}
|
|
305
|
+
const filterEvents = (events) => args.includeConsoleLog ? events : events.filter(e => e.type !== 'console.log');
|
|
292
306
|
const info = {
|
|
293
307
|
hash: r.hash,
|
|
294
308
|
sourceTreeId: r.sourceTreeId,
|
|
@@ -300,14 +314,37 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
300
314
|
info.error = r.error;
|
|
301
315
|
}
|
|
302
316
|
if (r.events && r.events.length > 0) {
|
|
303
|
-
|
|
317
|
+
const filtered = filterEvents(r.events);
|
|
318
|
+
if (filtered.length > 0) {
|
|
319
|
+
info.events = filtered;
|
|
320
|
+
}
|
|
304
321
|
}
|
|
305
|
-
if (r.
|
|
306
|
-
info.
|
|
322
|
+
if (r.output !== undefined) {
|
|
323
|
+
info.output = r.output;
|
|
307
324
|
}
|
|
308
325
|
if (r.isStable !== undefined) {
|
|
309
326
|
info.isStable = r.isStable;
|
|
310
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
|
+
}
|
|
311
348
|
// Visual review status
|
|
312
349
|
if (r.hash) {
|
|
313
350
|
try {
|
|
@@ -351,13 +388,13 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
351
388
|
_registerCompareScreenshot() {
|
|
352
389
|
const tool = this._mcp.registerTool('compare_screenshot', {
|
|
353
390
|
description: 'Compare a fixture\'s screenshot across two sessions (e.g. baseline vs current)',
|
|
354
|
-
inputSchema: {
|
|
391
|
+
inputSchema: z.strictObject({
|
|
355
392
|
fixtureId: z.string().describe('The fixture ID'),
|
|
356
393
|
baselineSessionName: z.string().optional().describe('Baseline session name (defaults to worktree session)'),
|
|
357
394
|
currentSessionName: z.string().optional().describe('Current session name (defaults to current session)'),
|
|
358
395
|
baselineSourceTreeId: z.string().optional().describe('Baseline source tree ID (defaults to latest known)'),
|
|
359
396
|
currentSourceTreeId: z.string().optional().describe('Current source tree ID (defaults to latest known)'),
|
|
360
|
-
},
|
|
397
|
+
}),
|
|
361
398
|
annotations: { readOnlyHint: true },
|
|
362
399
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
363
400
|
const baselineSessionName = args.baselineSessionName ?? this._defaultBaselineSessionName();
|
|
@@ -391,8 +428,8 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
391
428
|
if (r.baselineEvents && r.baselineEvents.length > 0) {
|
|
392
429
|
info.baselineEvents = r.baselineEvents;
|
|
393
430
|
}
|
|
394
|
-
if (r.
|
|
395
|
-
info.
|
|
431
|
+
if (r.baselineOutput !== undefined) {
|
|
432
|
+
info.baselineOutput = r.baselineOutput;
|
|
396
433
|
}
|
|
397
434
|
if (r.currentHasError) {
|
|
398
435
|
info.currentHasError = true;
|
|
@@ -403,8 +440,8 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
403
440
|
if (r.currentEvents && r.currentEvents.length > 0) {
|
|
404
441
|
info.currentEvents = r.currentEvents;
|
|
405
442
|
}
|
|
406
|
-
if (r.
|
|
407
|
-
info.
|
|
443
|
+
if (r.currentOutput !== undefined) {
|
|
444
|
+
info.currentOutput = r.currentOutput;
|
|
408
445
|
}
|
|
409
446
|
if (r.approval) {
|
|
410
447
|
info.approval = r.approval;
|
|
@@ -427,12 +464,12 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
427
464
|
_registerApproveDiff() {
|
|
428
465
|
const tool = this._mcp.registerTool('approve_diff', {
|
|
429
466
|
description: 'Approve a visual diff so it won\'t require re-inspection next time',
|
|
430
|
-
inputSchema: {
|
|
467
|
+
inputSchema: z.strictObject({
|
|
431
468
|
fixtureId: z.string(),
|
|
432
469
|
originalHash: z.string(),
|
|
433
470
|
modifiedHash: z.string(),
|
|
434
471
|
comment: z.string().describe('Reason for approving this diff'),
|
|
435
|
-
},
|
|
472
|
+
}),
|
|
436
473
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
437
474
|
await daemon.methods.approvals.approve(args);
|
|
438
475
|
return {
|
|
@@ -450,14 +487,14 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
450
487
|
description: 'Approve or reject a fixture\'s screenshot based on its expectedVisualDescriptions. ' +
|
|
451
488
|
'You must take a screenshot first and pass the resulting hash. ' +
|
|
452
489
|
'On approve, caches (expectedVisualDescriptions, screenshotHash) so future runs auto-approve.',
|
|
453
|
-
inputSchema: {
|
|
490
|
+
inputSchema: z.strictObject({
|
|
454
491
|
fixtureId: z.string().describe('The fixture ID'),
|
|
455
492
|
screenshotHash: z.string().describe('The screenshot hash (from a prior screenshot tool call)'),
|
|
456
493
|
verdict: z.enum(['approve', 'reject']).describe('Whether the visual matches expectations'),
|
|
457
494
|
comment: z.string().describe('Reason for the verdict'),
|
|
458
495
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
459
496
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
460
|
-
},
|
|
497
|
+
}),
|
|
461
498
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
462
499
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
463
500
|
this._log('debug', { type: 'tool-call', tool: 'review_visual', fixtureId: args.fixtureId, verdict: args.verdict });
|
|
@@ -485,13 +522,15 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
485
522
|
}
|
|
486
523
|
else {
|
|
487
524
|
return {
|
|
488
|
-
content: [{
|
|
525
|
+
content: [{
|
|
526
|
+
type: 'text', text: JSON.stringify({
|
|
489
527
|
fixtureId: args.fixtureId,
|
|
490
528
|
verdict: 'rejected',
|
|
491
529
|
comment: args.comment,
|
|
492
530
|
screenshotHash: args.screenshotHash,
|
|
493
531
|
expectedVisualDescriptions: fixture.expectedVisualDescriptions,
|
|
494
|
-
}, null, 2)
|
|
532
|
+
}, null, 2)
|
|
533
|
+
}],
|
|
495
534
|
};
|
|
496
535
|
}
|
|
497
536
|
});
|
|
@@ -501,12 +540,12 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
501
540
|
this._mcp.registerTool('check_visuals', {
|
|
502
541
|
description: 'Batch check visual review status for fixtures that have expectedVisualDescription. ' +
|
|
503
542
|
'Returns lists of approved, needs-review, and no-expectation fixtures.',
|
|
504
|
-
inputSchema: {
|
|
505
|
-
|
|
506
|
-
|
|
543
|
+
inputSchema: z.strictObject({
|
|
544
|
+
fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
|
|
545
|
+
labelRegex: z.string().optional().describe('RegExp to filter fixtures by label'),
|
|
507
546
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
508
547
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
509
|
-
},
|
|
548
|
+
}),
|
|
510
549
|
annotations: { readOnlyHint: true },
|
|
511
550
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
512
551
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -517,7 +556,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
517
556
|
if (listResult.loadError) {
|
|
518
557
|
return { content: [{ type: 'text', text: `Error: Fixture loading failed: ${listResult.loadError}\nThe fixture list may be incomplete.` }], isError: true };
|
|
519
558
|
}
|
|
520
|
-
const filtered = this._filterFixtures(listResult.fixtures, args.
|
|
559
|
+
const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
|
|
521
560
|
if ('error' in filtered) {
|
|
522
561
|
return { content: [{ type: 'text', text: filtered.error }], isError: true };
|
|
523
562
|
}
|
|
@@ -575,12 +614,12 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
575
614
|
'Returns the expression result as JSON. The expression can return a Promise (it will be awaited). ' +
|
|
576
615
|
'Use this to inspect DOM state, computed styles, element dimensions, or component output. ' +
|
|
577
616
|
'Do NOT use this to modify the DOM — this tool is for read-only inspection and debugging only.',
|
|
578
|
-
inputSchema: {
|
|
617
|
+
inputSchema: z.strictObject({
|
|
579
618
|
expression: z.string().describe('JavaScript expression to evaluate. Can return a Promise. The result must be JSON-serializable.'),
|
|
580
619
|
fixtureId: z.string().optional().describe('If provided, renders this fixture before evaluating the expression'),
|
|
581
620
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
582
621
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
583
|
-
},
|
|
622
|
+
}),
|
|
584
623
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
585
624
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
586
625
|
this._log('debug', { type: 'tool-call', tool: 'evaluate_js', sessionName, hasFixtureId: !!args.fixtureId });
|
|
@@ -612,9 +651,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
612
651
|
description: 'Force-reload the browser page used for rendering fixtures. ' +
|
|
613
652
|
'Only use this as a last resort if screenshots or evaluate_js return stale/broken results ' +
|
|
614
653
|
'that persist after source changes. Normal HMR updates should handle most cases automatically.',
|
|
615
|
-
inputSchema: {
|
|
654
|
+
inputSchema: z.strictObject({
|
|
616
655
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
617
|
-
},
|
|
656
|
+
}),
|
|
618
657
|
annotations: { destructiveHint: true },
|
|
619
658
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
620
659
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -632,9 +671,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
632
671
|
_registerWatchAdd() {
|
|
633
672
|
this._mcp.registerTool('watch_add', {
|
|
634
673
|
description: 'Add fixtures to the watch list. Watched fixtures are automatically re-screenshotted when source changes.',
|
|
635
|
-
inputSchema: {
|
|
674
|
+
inputSchema: z.strictObject({
|
|
636
675
|
fixtureIds: z.array(z.string()).describe('Fixture IDs to add'),
|
|
637
|
-
},
|
|
676
|
+
}),
|
|
638
677
|
}, async (args) => {
|
|
639
678
|
this._watchList.add(args.fixtureIds);
|
|
640
679
|
return {
|
|
@@ -648,9 +687,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
648
687
|
_registerWatchRemove() {
|
|
649
688
|
this._mcp.registerTool('watch_remove', {
|
|
650
689
|
description: 'Remove fixtures from the watch list',
|
|
651
|
-
inputSchema: {
|
|
690
|
+
inputSchema: z.strictObject({
|
|
652
691
|
fixtureIds: z.array(z.string()).describe('Fixture IDs to remove'),
|
|
653
|
-
},
|
|
692
|
+
}),
|
|
654
693
|
}, async (args) => {
|
|
655
694
|
this._watchList.remove(args.fixtureIds);
|
|
656
695
|
return {
|
|
@@ -664,9 +703,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
664
703
|
_registerWatchSet() {
|
|
665
704
|
this._mcp.registerTool('watch_set', {
|
|
666
705
|
description: 'Replace the watch list entirely',
|
|
667
|
-
inputSchema: {
|
|
706
|
+
inputSchema: z.strictObject({
|
|
668
707
|
fixtureIds: z.array(z.string()).describe('Fixture IDs to watch'),
|
|
669
|
-
},
|
|
708
|
+
}),
|
|
670
709
|
}, async (args) => {
|
|
671
710
|
this._watchList.set(args.fixtureIds);
|
|
672
711
|
return {
|
|
@@ -680,12 +719,12 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
680
719
|
_registerWatchCompare() {
|
|
681
720
|
const tool = this._mcp.registerTool('watch_compare', {
|
|
682
721
|
description: 'Compare all watched fixtures across two sessions. Takes fresh screenshots from both sessions and reports which fixtures differ.',
|
|
683
|
-
inputSchema: {
|
|
722
|
+
inputSchema: z.strictObject({
|
|
684
723
|
baselineSessionName: z.string().optional().describe('Baseline session name (defaults to worktree session)'),
|
|
685
724
|
currentSessionName: z.string().optional().describe('Current session name (defaults to current session)'),
|
|
686
725
|
baselineSourceTreeId: z.string().optional().describe('Baseline source tree ID (defaults to latest known)'),
|
|
687
726
|
currentSourceTreeId: z.string().optional().describe('Current source tree ID (defaults to latest known)'),
|
|
688
|
-
},
|
|
727
|
+
}),
|
|
689
728
|
annotations: { readOnlyHint: true },
|
|
690
729
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
691
730
|
const ids = [...this._watchList.fixtureIds];
|
|
@@ -750,10 +789,10 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
750
789
|
'Pass the sourceTreeId you already observed — resolves immediately if it already differs, ' +
|
|
751
790
|
'otherwise waits for a source-change or ref-change event. ' +
|
|
752
791
|
'If fixtures are on the watch list, automatically re-screenshots them and reports which changed.',
|
|
753
|
-
inputSchema: {
|
|
792
|
+
inputSchema: z.strictObject({
|
|
754
793
|
sourceTreeId: z.string().describe('The sourceTreeId the client currently knows about. The call resolves once the source tree differs from this value.'),
|
|
755
794
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
756
|
-
},
|
|
795
|
+
}),
|
|
757
796
|
annotations: { readOnlyHint: true },
|
|
758
797
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
759
798
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -855,9 +894,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
855
894
|
this._mcp.registerTool('restart_session', {
|
|
856
895
|
description: 'Restart a session by disposing its browser page and dev server, then recreating them. ' +
|
|
857
896
|
'Use this when a session appears stuck (e.g. after a timeout).',
|
|
858
|
-
inputSchema: {
|
|
897
|
+
inputSchema: z.strictObject({
|
|
859
898
|
sessionName: z.string().optional().describe('Session name to restart (defaults to first session)'),
|
|
860
|
-
},
|
|
899
|
+
}),
|
|
861
900
|
annotations: { destructiveHint: true },
|
|
862
901
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
863
902
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -875,10 +914,10 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
875
914
|
'The ref can be a branch name, tag, commit SHA, or the special value "INDEX" to snapshot staged changes. ' +
|
|
876
915
|
'The daemon allocates a reusable worktree slot from a fixed pool (max configured in component-explorer.json). ' +
|
|
877
916
|
'Returns the updated session list on success.',
|
|
878
|
-
inputSchema: {
|
|
917
|
+
inputSchema: z.strictObject({
|
|
879
918
|
name: z.string().describe('Unique session name (e.g. "baseline", "bisect")'),
|
|
880
919
|
ref: z.string().describe('Git ref: branch, tag, commit SHA, or "INDEX" for staged changes'),
|
|
881
|
-
},
|
|
920
|
+
}),
|
|
882
921
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
883
922
|
this._log('info', { type: 'tool-call', tool: 'open_session', name: args.name, ref: args.ref });
|
|
884
923
|
const result = await daemon.methods.openSession({ name: args.name, ref: args.ref });
|
|
@@ -896,9 +935,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
896
935
|
this._mcp.registerTool('close_session', {
|
|
897
936
|
description: 'Close a dynamic worktree session and release its worktree slot back to the pool. ' +
|
|
898
937
|
'Cannot close static sessions configured in component-explorer.json.',
|
|
899
|
-
inputSchema: {
|
|
938
|
+
inputSchema: z.strictObject({
|
|
900
939
|
name: z.string().describe('Session name to close'),
|
|
901
|
-
},
|
|
940
|
+
}),
|
|
902
941
|
annotations: { destructiveHint: true },
|
|
903
942
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
904
943
|
this._log('info', { type: 'tool-call', tool: 'close_session', name: args.name });
|
|
@@ -919,10 +958,10 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
919
958
|
'The worktree is checked out to the new ref and Vite\'s HMR handles the incremental update (no server restart). ' +
|
|
920
959
|
'Fails if the worktree has uncommitted changes — the error will list the dirty files. ' +
|
|
921
960
|
'The ref can be a branch, tag, commit SHA, or "INDEX" for staged changes.',
|
|
922
|
-
inputSchema: {
|
|
961
|
+
inputSchema: z.strictObject({
|
|
923
962
|
name: z.string().describe('Session name to update'),
|
|
924
963
|
ref: z.string().describe('New git ref: branch, tag, commit SHA, or "INDEX"'),
|
|
925
|
-
},
|
|
964
|
+
}),
|
|
926
965
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
927
966
|
this._log('info', { type: 'tool-call', tool: 'update_session_ref', name: args.name, ref: args.ref });
|
|
928
967
|
const result = await daemon.methods.updateSessionRef({ name: args.name, ref: args.ref });
|
|
@@ -939,11 +978,11 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
939
978
|
this._mcp.registerTool('get_url', {
|
|
940
979
|
description: 'Get URL for viewing fixtures. Returns the full Component Explorer UI by default. ' +
|
|
941
980
|
'Use `embedded: true` for a minimal single-fixture view (requires fixtureId).',
|
|
942
|
-
inputSchema: {
|
|
981
|
+
inputSchema: z.strictObject({
|
|
943
982
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
944
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.'),
|
|
945
984
|
embedded: z.boolean().optional().describe('If true, returns an embedded single-fixture URL (minimal UI, requires fixtureId). Default: false (full explorer UI).'),
|
|
946
|
-
},
|
|
985
|
+
}),
|
|
947
986
|
annotations: { readOnlyHint: true },
|
|
948
987
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
949
988
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
@@ -984,6 +1023,7 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
984
1023
|
const mode = args.embedded ? 'embedded' : undefined;
|
|
985
1024
|
const url = buildExplorerUrl({
|
|
986
1025
|
baseUrl,
|
|
1026
|
+
pathname: EXPLORER_ROUTE,
|
|
987
1027
|
mode,
|
|
988
1028
|
fixtureId: args.fixtureId,
|
|
989
1029
|
});
|
|
@@ -1002,32 +1042,45 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1002
1042
|
}
|
|
1003
1043
|
_registerCheckFixtureErrors() {
|
|
1004
1044
|
this._mcp.registerTool('check_fixture_errors', {
|
|
1005
|
-
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. ' +
|
|
1006
1051
|
'Returns results directly if finished within ~10s, otherwise returns a taskId for polling via check_task.',
|
|
1007
|
-
inputSchema: {
|
|
1008
|
-
|
|
1009
|
-
|
|
1052
|
+
inputSchema: z.strictObject({
|
|
1053
|
+
fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
|
|
1054
|
+
labelRegex: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
|
|
1010
1055
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
1011
1056
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
1012
|
-
|
|
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
|
+
}),
|
|
1013
1062
|
annotations: { readOnlyHint: true },
|
|
1014
1063
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
1015
1064
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
1016
|
-
this._log('debug', { type: 'tool-call', tool: 'check_fixture_errors', sessionName,
|
|
1065
|
+
this._log('debug', { type: 'tool-call', tool: 'check_fixture_errors', sessionName, fixtureIdRegex: args.fixtureIdRegex, labelRegex: args.labelRegex });
|
|
1017
1066
|
return this._withSourceTreeRetry(daemon, async () => {
|
|
1018
1067
|
const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
|
|
1019
1068
|
const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
|
|
1020
1069
|
if (listResult.loadError) {
|
|
1021
1070
|
return { content: [{ type: 'text', text: `Error: Fixture loading failed: ${listResult.loadError}\nThe fixture list may be incomplete.` }], isError: true };
|
|
1022
1071
|
}
|
|
1023
|
-
const filtered = this._filterFixtures(listResult.fixtures, args.
|
|
1072
|
+
const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
|
|
1024
1073
|
if ('error' in filtered) {
|
|
1025
1074
|
return { content: [{ type: 'text', text: filtered.error }], isError: true };
|
|
1026
1075
|
}
|
|
1027
1076
|
const fixtures = filtered.fixtures;
|
|
1028
1077
|
const task = this._taskManager.startTask(async (report, signal) => {
|
|
1029
|
-
|
|
1030
|
-
|
|
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 });
|
|
1031
1084
|
for (let i = 0; i < fixtures.length; i++) {
|
|
1032
1085
|
if (signal.aborted) {
|
|
1033
1086
|
break;
|
|
@@ -1039,30 +1092,52 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1039
1092
|
sessionName,
|
|
1040
1093
|
sourceTreeId,
|
|
1041
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,
|
|
1042
1102
|
});
|
|
1043
1103
|
const r = result;
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1046
|
-
|
|
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);
|
|
1047
1123
|
}
|
|
1048
|
-
|
|
1049
|
-
|
|
1124
|
+
else {
|
|
1125
|
+
okCount++;
|
|
1050
1126
|
}
|
|
1051
|
-
results.push(entry);
|
|
1052
1127
|
}
|
|
1053
1128
|
catch (e) {
|
|
1054
|
-
|
|
1129
|
+
erroredResults.push({
|
|
1055
1130
|
fixtureId: fixture.fixtureId,
|
|
1056
1131
|
hasError: true,
|
|
1057
1132
|
error: { message: e instanceof Error ? e.message : String(e) },
|
|
1058
1133
|
});
|
|
1059
1134
|
}
|
|
1060
|
-
|
|
1135
|
+
processed++;
|
|
1136
|
+
report({ completed: i + 1, total: fixtures.length, partialResult: erroredResults });
|
|
1061
1137
|
}
|
|
1062
|
-
const withErrors = results.filter(r => r.hasError);
|
|
1063
1138
|
return {
|
|
1064
|
-
fixtures:
|
|
1065
|
-
summary: { total:
|
|
1139
|
+
fixtures: erroredResults,
|
|
1140
|
+
summary: { total: processed, ok: okCount, errored: erroredResults.length },
|
|
1066
1141
|
};
|
|
1067
1142
|
});
|
|
1068
1143
|
const waited = await this._taskManager.waitForTask(task.id, 10_000);
|
|
@@ -1096,23 +1171,27 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1096
1171
|
description: 'Check rendering stability of fixtures. Each fixture is unmounted, re-mounted, and screenshotted 3 times (~3s per fixture). ' +
|
|
1097
1172
|
'Returns results directly if finished within ~10s, otherwise returns a taskId for polling via check_task. ' +
|
|
1098
1173
|
'When returning a taskId, includes partial results collected so far.',
|
|
1099
|
-
inputSchema: {
|
|
1100
|
-
|
|
1101
|
-
|
|
1174
|
+
inputSchema: z.strictObject({
|
|
1175
|
+
fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
|
|
1176
|
+
labelRegex: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
|
|
1102
1177
|
sessionName: z.string().optional().describe('Session name (defaults to first session)'),
|
|
1103
1178
|
sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
|
|
1104
|
-
|
|
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
|
+
}),
|
|
1105
1184
|
annotations: { readOnlyHint: true },
|
|
1106
1185
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
1107
1186
|
const sessionName = args.sessionName ?? this._defaultSessionName();
|
|
1108
|
-
this._log('debug', { type: 'tool-call', tool: 'check_stability', sessionName,
|
|
1187
|
+
this._log('debug', { type: 'tool-call', tool: 'check_stability', sessionName, fixtureIdRegex: args.fixtureIdRegex, labelRegex: args.labelRegex });
|
|
1109
1188
|
return this._withSourceTreeRetry(daemon, async () => {
|
|
1110
1189
|
const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
|
|
1111
1190
|
const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
|
|
1112
1191
|
if (listResult.loadError) {
|
|
1113
1192
|
return { content: [{ type: 'text', text: `Error: Fixture loading failed: ${listResult.loadError}\nThe fixture list may be incomplete.` }], isError: true };
|
|
1114
1193
|
}
|
|
1115
|
-
const filtered = this._filterFixtures(listResult.fixtures, args.
|
|
1194
|
+
const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
|
|
1116
1195
|
if ('error' in filtered) {
|
|
1117
1196
|
return { content: [{ type: 'text', text: filtered.error }], isError: true };
|
|
1118
1197
|
}
|
|
@@ -1133,6 +1212,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1133
1212
|
sourceTreeId,
|
|
1134
1213
|
includeImage: false,
|
|
1135
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,
|
|
1136
1218
|
});
|
|
1137
1219
|
const r = result;
|
|
1138
1220
|
results.push({
|
|
@@ -1177,9 +1259,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1177
1259
|
_registerCheckTask() {
|
|
1178
1260
|
this._mcp.registerTool('check_task', {
|
|
1179
1261
|
description: 'Check on a running task. Waits up to ~2s for completion; if still running, returns progress and new results since last check.',
|
|
1180
|
-
inputSchema: {
|
|
1262
|
+
inputSchema: z.strictObject({
|
|
1181
1263
|
taskId: z.string().describe('The task ID returned by a previous tool call'),
|
|
1182
|
-
},
|
|
1264
|
+
}),
|
|
1183
1265
|
annotations: { readOnlyHint: true },
|
|
1184
1266
|
}, async (args) => {
|
|
1185
1267
|
const waited = await this._taskManager.waitForTask(args.taskId, 2_000);
|
|
@@ -1227,9 +1309,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1227
1309
|
_registerCancelTask() {
|
|
1228
1310
|
this._mcp.registerTool('cancel_task', {
|
|
1229
1311
|
description: 'Cancel a running task',
|
|
1230
|
-
inputSchema: {
|
|
1312
|
+
inputSchema: z.strictObject({
|
|
1231
1313
|
taskId: z.string().describe('The task ID to cancel'),
|
|
1232
|
-
},
|
|
1314
|
+
}),
|
|
1233
1315
|
}, async (args) => {
|
|
1234
1316
|
const task = this._taskManager.getTask(args.taskId);
|
|
1235
1317
|
if (!task) {
|
|
@@ -1249,9 +1331,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1249
1331
|
description: 'Retrieve a recently-taken screenshot image by its hash. ' +
|
|
1250
1332
|
'Keeps the last ~10 images in an LRU cache. ' +
|
|
1251
1333
|
'Useful for debugging when screenshot hashes behave unexpectedly.',
|
|
1252
|
-
inputSchema: {
|
|
1334
|
+
inputSchema: z.strictObject({
|
|
1253
1335
|
hash: z.string().describe('The screenshot hash to look up'),
|
|
1254
|
-
},
|
|
1336
|
+
}),
|
|
1255
1337
|
annotations: { readOnlyHint: true },
|
|
1256
1338
|
}, async (args) => {
|
|
1257
1339
|
const image = this._imageLru.get(args.hash);
|
|
@@ -1275,9 +1357,9 @@ class ComponentExplorerMcpServer extends Disposable {
|
|
|
1275
1357
|
'Only use this tool when the user explicitly asks to show or hide the browser. ' +
|
|
1276
1358
|
'Do not call this tool automatically or as part of other workflows. ' +
|
|
1277
1359
|
'Note: changing visibility closes the current browser instance, so the next screenshot or evaluate_js call will relaunch it.',
|
|
1278
|
-
inputSchema: {
|
|
1360
|
+
inputSchema: z.strictObject({
|
|
1279
1361
|
visible: z.boolean().describe('true to show the browser window (headed mode), false to hide it (headless mode)'),
|
|
1280
|
-
},
|
|
1362
|
+
}),
|
|
1281
1363
|
annotations: { destructiveHint: true },
|
|
1282
1364
|
}, async (args) => this._withDaemon(async (daemon) => {
|
|
1283
1365
|
await daemon.methods.setBrowserVisibility({ visible: args.visible });
|