@vizzly-testing/cli 0.23.1 → 0.23.2

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.
Files changed (36) hide show
  1. package/README.md +54 -586
  2. package/dist/api/client.js +3 -1
  3. package/dist/api/endpoints.js +6 -7
  4. package/dist/cli.js +15 -2
  5. package/dist/commands/finalize.js +12 -0
  6. package/dist/commands/preview.js +210 -28
  7. package/dist/commands/run.js +15 -0
  8. package/dist/commands/status.js +34 -8
  9. package/dist/commands/upload.js +13 -0
  10. package/package.json +1 -2
  11. package/claude-plugin/.claude-plugin/README.md +0 -270
  12. package/claude-plugin/.claude-plugin/marketplace.json +0 -28
  13. package/claude-plugin/.claude-plugin/plugin.json +0 -14
  14. package/claude-plugin/.mcp.json +0 -12
  15. package/claude-plugin/CHANGELOG.md +0 -85
  16. package/claude-plugin/commands/setup.md +0 -137
  17. package/claude-plugin/commands/suggest-screenshots.md +0 -111
  18. package/claude-plugin/mcp/vizzly-docs-server/README.md +0 -95
  19. package/claude-plugin/mcp/vizzly-docs-server/docs-fetcher.js +0 -110
  20. package/claude-plugin/mcp/vizzly-docs-server/index.js +0 -283
  21. package/claude-plugin/mcp/vizzly-server/cloud-api-provider.js +0 -399
  22. package/claude-plugin/mcp/vizzly-server/index.js +0 -927
  23. package/claude-plugin/mcp/vizzly-server/local-tdd-provider.js +0 -455
  24. package/claude-plugin/mcp/vizzly-server/token-resolver.js +0 -185
  25. package/claude-plugin/skills/check-visual-tests/SKILL.md +0 -158
  26. package/claude-plugin/skills/debug-visual-regression/SKILL.md +0 -269
  27. package/docs/api-reference.md +0 -1003
  28. package/docs/authentication.md +0 -334
  29. package/docs/doctor-command.md +0 -44
  30. package/docs/getting-started.md +0 -131
  31. package/docs/internal/SDK-API.md +0 -1018
  32. package/docs/plugins.md +0 -557
  33. package/docs/tdd-mode.md +0 -594
  34. package/docs/test-integration.md +0 -523
  35. package/docs/tui-elements.md +0 -560
  36. package/docs/upload-command.md +0 -196
@@ -1,927 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Vizzly MCP Server
5
- * Provides Claude Code with access to Vizzly TDD state and cloud API
6
- */
7
-
8
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
9
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
- import {
11
- CallToolRequestSchema,
12
- ListToolsRequestSchema,
13
- ListResourcesRequestSchema,
14
- ReadResourceRequestSchema
15
- } from '@modelcontextprotocol/sdk/types.js';
16
- import { LocalTDDProvider } from './local-tdd-provider.js';
17
- import { CloudAPIProvider } from './cloud-api-provider.js';
18
- import { resolveToken, getUserInfo } from './token-resolver.js';
19
-
20
- class VizzlyMCPServer {
21
- constructor() {
22
- this.server = new Server(
23
- {
24
- name: 'vizzly',
25
- version: '0.1.0'
26
- },
27
- {
28
- capabilities: {
29
- tools: {},
30
- resources: {}
31
- }
32
- }
33
- );
34
-
35
- this.localProvider = new LocalTDDProvider();
36
- this.cloudProvider = new CloudAPIProvider();
37
-
38
- this.setupHandlers();
39
- }
40
-
41
- /**
42
- * Detect context - whether user is working locally or with cloud builds
43
- */
44
- async detectContext(workingDirectory) {
45
- let context = {
46
- hasLocalTDD: false,
47
- hasApiToken: false,
48
- hasAuthentication: false,
49
- mode: null,
50
- tokenSource: null,
51
- user: null
52
- };
53
-
54
- // Check for local TDD setup
55
- let vizzlyDir = await this.localProvider.findVizzlyDir(workingDirectory);
56
- if (vizzlyDir) {
57
- context.hasLocalTDD = true;
58
- context.mode = 'local';
59
- }
60
-
61
- // Check for API token using token resolver
62
- let token = await resolveToken({ workingDirectory });
63
- if (token) {
64
- context.hasApiToken = true;
65
- context.hasAuthentication = true;
66
-
67
- // Determine token source
68
- if (process.env.VIZZLY_TOKEN) {
69
- context.tokenSource = 'environment';
70
- } else if (token.startsWith('vzt_')) {
71
- context.tokenSource = 'project_mapping';
72
- } else {
73
- context.tokenSource = 'user_login';
74
- // Include user info if available
75
- let userInfo = await getUserInfo();
76
- if (userInfo) {
77
- context.user = {
78
- email: userInfo.email,
79
- name: userInfo.name
80
- };
81
- }
82
- }
83
-
84
- if (!context.mode) {
85
- context.mode = 'cloud';
86
- }
87
- }
88
-
89
- return context;
90
- }
91
-
92
- /**
93
- * Resolve API token from various sources
94
- * @param {Object} args - Tool arguments that may contain apiToken
95
- * @param {string} args.apiToken - Explicitly provided token
96
- * @param {string} args.workingDirectory - Working directory for project mapping
97
- * @returns {Promise<string|null>} Resolved token
98
- */
99
- async resolveApiToken(args = {}) {
100
- return await resolveToken({
101
- providedToken: args.apiToken,
102
- workingDirectory: args.workingDirectory || process.cwd()
103
- });
104
- }
105
-
106
- setupHandlers() {
107
- // List available tools
108
- this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
109
- tools: [
110
- {
111
- name: 'detect_context',
112
- description:
113
- "Detect whether user is working in local TDD mode or cloud collaboration mode. Use this at the start of a conversation to understand the user's workflow.",
114
- inputSchema: {
115
- type: 'object',
116
- properties: {
117
- workingDirectory: {
118
- type: 'string',
119
- description: 'Path to project directory (optional)'
120
- }
121
- }
122
- }
123
- },
124
- {
125
- name: 'get_tdd_status',
126
- description:
127
- 'Get current TDD mode status including comparison results, failed/new/passed counts, and diff information',
128
- inputSchema: {
129
- type: 'object',
130
- properties: {
131
- workingDirectory: {
132
- type: 'string',
133
- description: 'Path to project directory (optional, defaults to current directory)'
134
- },
135
- statusFilter: {
136
- type: 'string',
137
- description: 'Filter comparisons by status: "failed", "new", "passed", or "all". Defaults to "summary" (no comparisons, just counts)',
138
- enum: ['failed', 'new', 'passed', 'all', 'summary']
139
- },
140
- limit: {
141
- type: 'number',
142
- description: 'Maximum number of comparisons to return (default: unlimited when statusFilter is set)'
143
- }
144
- }
145
- }
146
- },
147
- {
148
- name: 'read_comparison_details',
149
- description:
150
- 'Read detailed comparison information for a screenshot or comparison. Automatically detects if working in local TDD mode or cloud mode. Pass either a screenshot name (e.g., "homepage_desktop") for local mode or a comparison ID (e.g., "cmp_abc123") for cloud mode. IMPORTANT: Returns both paths (for local) and URLs (for cloud). Use Read tool for paths, WebFetch for URLs. Do NOT read/fetch diff images as they cause API errors.',
151
- inputSchema: {
152
- type: 'object',
153
- properties: {
154
- identifier: {
155
- type: 'string',
156
- description: 'Screenshot name (local mode) or comparison ID (cloud mode)'
157
- },
158
- workingDirectory: {
159
- type: 'string',
160
- description: 'Path to project directory (optional, for local mode)'
161
- },
162
- apiToken: {
163
- type: 'string',
164
- description:
165
- 'Vizzly API token (optional, auto-resolves from: CLI flag > env var > project mapping > user login)'
166
- },
167
- apiUrl: {
168
- type: 'string',
169
- description: 'API base URL (optional, for cloud mode)'
170
- }
171
- },
172
- required: ['identifier']
173
- }
174
- },
175
- {
176
- name: 'list_diff_images',
177
- description:
178
- 'List all available diff images from TDD comparisons. Returns paths for reference only - do NOT read these images as they cause API errors. Use read_comparison_details to get baseline and current image paths instead.',
179
- inputSchema: {
180
- type: 'object',
181
- properties: {
182
- workingDirectory: {
183
- type: 'string',
184
- description: 'Path to project directory (optional)'
185
- }
186
- }
187
- }
188
- },
189
- {
190
- name: 'get_build_status',
191
- description: 'Get cloud build status and comparison results (requires API token)',
192
- inputSchema: {
193
- type: 'object',
194
- properties: {
195
- buildId: {
196
- type: 'string',
197
- description: 'Build ID to check status for'
198
- },
199
- apiToken: {
200
- type: 'string',
201
- description: 'Vizzly API token (optional, auto-resolves from user login or env)'
202
- },
203
- apiUrl: {
204
- type: 'string',
205
- description: 'API base URL (optional, defaults to https://app.vizzly.dev)'
206
- }
207
- },
208
- required: ['buildId']
209
- }
210
- },
211
- {
212
- name: 'list_recent_builds',
213
- description: 'List recent builds from Vizzly cloud (requires API token)',
214
- inputSchema: {
215
- type: 'object',
216
- properties: {
217
- limit: {
218
- type: 'number',
219
- description: 'Number of builds to return (default: 10)'
220
- },
221
- branch: {
222
- type: 'string',
223
- description: 'Filter by branch name (optional)'
224
- },
225
- apiToken: {
226
- type: 'string',
227
- description: 'Vizzly API token (optional, auto-resolves from user login or env)'
228
- },
229
- apiUrl: {
230
- type: 'string',
231
- description: 'API base URL (optional)'
232
- }
233
- }
234
- }
235
- },
236
- {
237
- name: 'get_comparison',
238
- description:
239
- 'Get detailed comparison information from cloud API including screenshot URLs. IMPORTANT: Use WebFetch to view ONLY baselineUrl and currentUrl - do NOT fetch diffUrl as it causes API errors.',
240
- inputSchema: {
241
- type: 'object',
242
- properties: {
243
- comparisonId: {
244
- type: 'string',
245
- description: 'Comparison ID'
246
- },
247
- apiToken: {
248
- type: 'string',
249
- description: 'Vizzly API token (optional, auto-resolves from user login or env)'
250
- },
251
- apiUrl: {
252
- type: 'string',
253
- description: 'API base URL (optional)'
254
- }
255
- },
256
- required: ['comparisonId']
257
- }
258
- },
259
- {
260
- name: 'search_comparisons',
261
- description:
262
- 'Search for comparisons by screenshot name across recent builds in the cloud. Returns matching comparisons with their build context and screenshot URLs. Use this to find all instances of a specific screenshot across different builds for debugging.',
263
- inputSchema: {
264
- type: 'object',
265
- properties: {
266
- name: {
267
- type: 'string',
268
- description: 'Screenshot/comparison name to search for (supports partial matching)'
269
- },
270
- branch: {
271
- type: 'string',
272
- description: 'Optional branch name to filter results'
273
- },
274
- limit: {
275
- type: 'number',
276
- description: 'Maximum number of results to return (default: 50)'
277
- },
278
- offset: {
279
- type: 'number',
280
- description: 'Offset for pagination (default: 0)'
281
- },
282
- apiToken: {
283
- type: 'string',
284
- description: 'Vizzly API token (optional, auto-resolves from user login or env)'
285
- },
286
- apiUrl: {
287
- type: 'string',
288
- description: 'API base URL (optional)'
289
- }
290
- },
291
- required: ['name']
292
- }
293
- },
294
- {
295
- name: 'create_build_comment',
296
- description: 'Create a comment on a build for collaboration',
297
- inputSchema: {
298
- type: 'object',
299
- properties: {
300
- buildId: {
301
- type: 'string',
302
- description: 'Build ID to comment on'
303
- },
304
- content: {
305
- type: 'string',
306
- description: 'Comment text content'
307
- },
308
- type: {
309
- type: 'string',
310
- description: 'Comment type: general, approval, rejection (default: general)'
311
- },
312
- apiToken: {
313
- type: 'string',
314
- description: 'Vizzly API token (optional, auto-resolves from user login or env)'
315
- },
316
- apiUrl: {
317
- type: 'string',
318
- description: 'API base URL (optional)'
319
- }
320
- },
321
- required: ['buildId', 'content']
322
- }
323
- },
324
- {
325
- name: 'list_build_comments',
326
- description: 'List all comments on a build',
327
- inputSchema: {
328
- type: 'object',
329
- properties: {
330
- buildId: {
331
- type: 'string',
332
- description: 'Build ID'
333
- },
334
- apiToken: {
335
- type: 'string',
336
- description: 'Vizzly API token (optional, auto-resolves from user login or env)'
337
- },
338
- apiUrl: {
339
- type: 'string',
340
- description: 'API base URL (optional)'
341
- }
342
- },
343
- required: ['buildId']
344
- }
345
- },
346
- {
347
- name: 'approve_comparison',
348
- description: 'Approve a comparison - indicates the visual change is acceptable',
349
- inputSchema: {
350
- type: 'object',
351
- properties: {
352
- comparisonId: {
353
- type: 'string',
354
- description: 'Comparison ID to approve'
355
- },
356
- comment: {
357
- type: 'string',
358
- description: 'Optional comment explaining the approval'
359
- },
360
- apiToken: {
361
- type: 'string',
362
- description: 'Vizzly API token (optional, auto-resolves from user login or env)'
363
- },
364
- apiUrl: {
365
- type: 'string',
366
- description: 'API base URL (optional)'
367
- }
368
- },
369
- required: ['comparisonId']
370
- }
371
- },
372
- {
373
- name: 'reject_comparison',
374
- description: 'Reject a comparison - indicates the visual change needs fixing',
375
- inputSchema: {
376
- type: 'object',
377
- properties: {
378
- comparisonId: {
379
- type: 'string',
380
- description: 'Comparison ID to reject'
381
- },
382
- reason: {
383
- type: 'string',
384
- description: 'Required reason for rejection (will be added as a comment)'
385
- },
386
- apiToken: {
387
- type: 'string',
388
- description: 'Vizzly API token (optional, auto-resolves from user login or env)'
389
- },
390
- apiUrl: {
391
- type: 'string',
392
- description: 'API base URL (optional)'
393
- }
394
- },
395
- required: ['comparisonId', 'reason']
396
- }
397
- },
398
- {
399
- name: 'get_review_summary',
400
- description: 'Get review status and assignments for a build',
401
- inputSchema: {
402
- type: 'object',
403
- properties: {
404
- buildId: {
405
- type: 'string',
406
- description: 'Build ID'
407
- },
408
- apiToken: {
409
- type: 'string',
410
- description: 'Vizzly API token (optional, auto-resolves from user login or env)'
411
- },
412
- apiUrl: {
413
- type: 'string',
414
- description: 'API base URL (optional)'
415
- }
416
- },
417
- required: ['buildId']
418
- }
419
- },
420
- {
421
- name: 'accept_baseline',
422
- description: 'Accept a screenshot as the new baseline in local TDD mode',
423
- inputSchema: {
424
- type: 'object',
425
- properties: {
426
- screenshotName: {
427
- type: 'string',
428
- description: 'Name of the screenshot to accept'
429
- },
430
- workingDirectory: {
431
- type: 'string',
432
- description: 'Path to project directory (optional)'
433
- }
434
- },
435
- required: ['screenshotName']
436
- }
437
- },
438
- {
439
- name: 'reject_baseline',
440
- description:
441
- 'Reject a screenshot baseline in local TDD mode (marks it for investigation)',
442
- inputSchema: {
443
- type: 'object',
444
- properties: {
445
- screenshotName: {
446
- type: 'string',
447
- description: 'Name of the screenshot to reject'
448
- },
449
- reason: {
450
- type: 'string',
451
- description: 'Reason for rejection'
452
- },
453
- workingDirectory: {
454
- type: 'string',
455
- description: 'Path to project directory (optional)'
456
- }
457
- },
458
- required: ['screenshotName', 'reason']
459
- }
460
- },
461
- {
462
- name: 'download_baselines',
463
- description: 'Download baseline screenshots from a cloud build to use in local TDD mode',
464
- inputSchema: {
465
- type: 'object',
466
- properties: {
467
- buildId: {
468
- type: 'string',
469
- description: 'Build ID to download baselines from'
470
- },
471
- screenshotNames: {
472
- type: 'array',
473
- items: {
474
- type: 'string'
475
- },
476
- description:
477
- 'Optional list of specific screenshot names to download (if not provided, downloads all)'
478
- },
479
- apiToken: {
480
- type: 'string',
481
- description: 'Vizzly API token (optional, auto-resolves from user login or env)'
482
- },
483
- apiUrl: {
484
- type: 'string',
485
- description: 'API base URL (optional)'
486
- },
487
- workingDirectory: {
488
- type: 'string',
489
- description: 'Path to project directory (optional)'
490
- }
491
- },
492
- required: ['buildId']
493
- }
494
- }
495
- ]
496
- }));
497
-
498
- // List available resources
499
- this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
500
- resources: [
501
- {
502
- uri: 'vizzly://tdd/status',
503
- name: 'TDD Status',
504
- description: 'Current TDD comparison results and statistics',
505
- mimeType: 'application/json'
506
- },
507
- {
508
- uri: 'vizzly://tdd/server-info',
509
- name: 'TDD Server Info',
510
- description: 'Information about the running TDD server',
511
- mimeType: 'application/json'
512
- }
513
- ]
514
- }));
515
-
516
- // Read resource content
517
- this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
518
- let { uri } = request.params;
519
-
520
- if (uri === 'vizzly://tdd/status') {
521
- let status = await this.localProvider.getTDDStatus();
522
- return {
523
- contents: [
524
- {
525
- uri,
526
- mimeType: 'application/json',
527
- text: JSON.stringify(status, null, 2)
528
- }
529
- ]
530
- };
531
- }
532
-
533
- if (uri === 'vizzly://tdd/server-info') {
534
- let serverInfo = await this.localProvider.getServerInfo();
535
- return {
536
- contents: [
537
- {
538
- uri,
539
- mimeType: 'application/json',
540
- text: JSON.stringify(serverInfo, null, 2)
541
- }
542
- ]
543
- };
544
- }
545
-
546
- throw new Error(`Unknown resource: ${uri}`);
547
- });
548
-
549
- // Handle tool calls
550
- this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
551
- let { name, arguments: args } = request.params;
552
-
553
- try {
554
- switch (name) {
555
- case 'detect_context': {
556
- let context = await this.detectContext(args.workingDirectory);
557
- return {
558
- content: [
559
- {
560
- type: 'text',
561
- text: JSON.stringify(context, null, 2)
562
- }
563
- ]
564
- };
565
- }
566
-
567
- case 'get_tdd_status': {
568
- let status = await this.localProvider.getTDDStatus(
569
- args.workingDirectory,
570
- args.statusFilter,
571
- args.limit
572
- );
573
- return {
574
- content: [
575
- {
576
- type: 'text',
577
- text: JSON.stringify(status, null, 2)
578
- }
579
- ]
580
- };
581
- }
582
-
583
- case 'read_comparison_details': {
584
- // Unified handler that tries local first, then cloud
585
- let details = null;
586
- let mode = null;
587
-
588
- // Try local mode first (if vizzlyDir exists)
589
- try {
590
- details = await this.localProvider.getComparisonDetails(
591
- args.identifier,
592
- args.workingDirectory
593
- );
594
- mode = 'local';
595
- } catch (localError) {
596
- // If local fails and we have API token, try cloud mode
597
- let apiToken = await this.resolveApiToken(args);
598
- if (apiToken && args.identifier.startsWith('cmp_')) {
599
- try {
600
- let cloudComparison = await this.cloudProvider.getComparison(
601
- args.identifier,
602
- apiToken,
603
- args.apiUrl
604
- );
605
-
606
- // Transform cloud response to unified format
607
- details = {
608
- mode: 'cloud',
609
- name: cloudComparison.name,
610
- status: cloudComparison.status,
611
- diffPercentage: cloudComparison.diff_percentage,
612
- threshold: cloudComparison.threshold,
613
- hasDiff: cloudComparison.has_diff,
614
- // Cloud URLs
615
- baselineUrl: cloudComparison.baseline_screenshot?.original_url,
616
- currentUrl: cloudComparison.current_screenshot?.original_url,
617
- diffUrl: cloudComparison.diff_image?.url,
618
- // Additional cloud metadata
619
- comparisonId: cloudComparison.id,
620
- buildId: cloudComparison.build_id,
621
- analysis: [
622
- `Cloud comparison (${cloudComparison.diff_percentage?.toFixed(2)}% difference)`,
623
- 'Use WebFetch to view baselineUrl and currentUrl',
624
- 'Do NOT fetch diffUrl as it causes API errors'
625
- ]
626
- };
627
- mode = 'cloud';
628
- } catch (cloudError) {
629
- throw new Error(
630
- `Failed to get comparison details: Local - ${localError.message}, Cloud - ${cloudError.message}`
631
- );
632
- }
633
- } else {
634
- throw localError;
635
- }
636
- }
637
-
638
- // Add mode to response if not already present
639
- if (mode && !details.mode) {
640
- details.mode = mode;
641
- }
642
-
643
- return {
644
- content: [
645
- {
646
- type: 'text',
647
- text: JSON.stringify(details, null, 2)
648
- }
649
- ]
650
- };
651
- }
652
-
653
- case 'list_diff_images': {
654
- let diffs = await this.localProvider.listDiffImages(args.workingDirectory);
655
- return {
656
- content: [
657
- {
658
- type: 'text',
659
- text: JSON.stringify(diffs, null, 2)
660
- }
661
- ]
662
- };
663
- }
664
-
665
- case 'get_build_status': {
666
- let apiToken = await this.resolveApiToken(args);
667
- let buildStatus = await this.cloudProvider.getBuildStatus(
668
- args.buildId,
669
- apiToken,
670
- args.apiUrl
671
- );
672
- return {
673
- content: [
674
- {
675
- type: 'text',
676
- text: JSON.stringify(buildStatus, null, 2)
677
- }
678
- ]
679
- };
680
- }
681
-
682
- case 'list_recent_builds': {
683
- let apiToken = await this.resolveApiToken(args);
684
- let builds = await this.cloudProvider.listRecentBuilds(
685
- apiToken,
686
- {
687
- limit: args.limit,
688
- branch: args.branch,
689
- apiUrl: args.apiUrl
690
- }
691
- );
692
- return {
693
- content: [
694
- {
695
- type: 'text',
696
- text: JSON.stringify(builds, null, 2)
697
- }
698
- ]
699
- };
700
- }
701
-
702
- case 'get_comparison': {
703
- let apiToken = await this.resolveApiToken(args);
704
- let comparison = await this.cloudProvider.getComparison(
705
- args.comparisonId,
706
- apiToken,
707
- args.apiUrl
708
- );
709
- return {
710
- content: [
711
- {
712
- type: 'text',
713
- text: JSON.stringify(comparison, null, 2)
714
- }
715
- ]
716
- };
717
- }
718
-
719
- case 'search_comparisons': {
720
- let apiToken = await this.resolveApiToken(args);
721
- let results = await this.cloudProvider.searchComparisons(args.name, apiToken, {
722
- branch: args.branch,
723
- limit: args.limit,
724
- offset: args.offset,
725
- apiUrl: args.apiUrl
726
- });
727
- return {
728
- content: [
729
- {
730
- type: 'text',
731
- text: JSON.stringify(results, null, 2)
732
- }
733
- ]
734
- };
735
- }
736
-
737
- case 'create_build_comment': {
738
- let apiToken = await this.resolveApiToken(args);
739
- let result = await this.cloudProvider.createBuildComment(
740
- args.buildId,
741
- args.content,
742
- args.type || 'general',
743
- apiToken,
744
- args.apiUrl
745
- );
746
- return {
747
- content: [
748
- {
749
- type: 'text',
750
- text: JSON.stringify(result, null, 2)
751
- }
752
- ]
753
- };
754
- }
755
-
756
- case 'list_build_comments': {
757
- let apiToken = await this.resolveApiToken(args);
758
- let comments = await this.cloudProvider.listBuildComments(
759
- args.buildId,
760
- apiToken,
761
- args.apiUrl
762
- );
763
- return {
764
- content: [
765
- {
766
- type: 'text',
767
- text: JSON.stringify(comments, null, 2)
768
- }
769
- ]
770
- };
771
- }
772
-
773
- case 'approve_comparison': {
774
- let apiToken = await this.resolveApiToken(args);
775
- let result = await this.cloudProvider.approveComparison(
776
- args.comparisonId,
777
- args.comment,
778
- apiToken,
779
- args.apiUrl
780
- );
781
- return {
782
- content: [
783
- {
784
- type: 'text',
785
- text: JSON.stringify(result, null, 2)
786
- }
787
- ]
788
- };
789
- }
790
-
791
- case 'reject_comparison': {
792
- let apiToken = await this.resolveApiToken(args);
793
- let result = await this.cloudProvider.rejectComparison(
794
- args.comparisonId,
795
- args.reason,
796
- apiToken,
797
- args.apiUrl
798
- );
799
- return {
800
- content: [
801
- {
802
- type: 'text',
803
- text: JSON.stringify(result, null, 2)
804
- }
805
- ]
806
- };
807
- }
808
-
809
- case 'get_review_summary': {
810
- let apiToken = await this.resolveApiToken(args);
811
- let summary = await this.cloudProvider.getReviewSummary(
812
- args.buildId,
813
- apiToken,
814
- args.apiUrl
815
- );
816
- return {
817
- content: [
818
- {
819
- type: 'text',
820
- text: JSON.stringify(summary, null, 2)
821
- }
822
- ]
823
- };
824
- }
825
-
826
- case 'accept_baseline': {
827
- let result = await this.localProvider.acceptBaseline(
828
- args.screenshotName,
829
- args.workingDirectory
830
- );
831
- return {
832
- content: [
833
- {
834
- type: 'text',
835
- text: JSON.stringify(result, null, 2)
836
- }
837
- ]
838
- };
839
- }
840
-
841
- case 'reject_baseline': {
842
- let result = await this.localProvider.rejectBaseline(
843
- args.screenshotName,
844
- args.reason,
845
- args.workingDirectory
846
- );
847
- return {
848
- content: [
849
- {
850
- type: 'text',
851
- text: JSON.stringify(result, null, 2)
852
- }
853
- ]
854
- };
855
- }
856
-
857
- case 'download_baselines': {
858
- // First get build metadata and screenshot data from cloud
859
- let apiToken = await this.resolveApiToken(args);
860
-
861
- // Get full build status for metadata
862
- let buildStatus = await this.cloudProvider.getBuildStatus(
863
- args.buildId,
864
- apiToken,
865
- args.apiUrl
866
- );
867
-
868
- // Get screenshot data
869
- let cloudData = await this.cloudProvider.downloadBaselines(
870
- args.buildId,
871
- args.screenshotNames,
872
- apiToken,
873
- args.apiUrl
874
- );
875
-
876
- // Download and save locally with build metadata
877
- let result = await this.localProvider.downloadBaselinesFromCloud(
878
- cloudData.screenshots,
879
- args.workingDirectory,
880
- buildStatus.build // Pass build metadata
881
- );
882
-
883
- return {
884
- content: [
885
- {
886
- type: 'text',
887
- text: JSON.stringify(
888
- {
889
- buildId: cloudData.buildId,
890
- buildName: cloudData.buildName,
891
- ...result
892
- },
893
- null,
894
- 2
895
- )
896
- }
897
- ]
898
- };
899
- }
900
-
901
- default:
902
- throw new Error(`Unknown tool: ${name}`);
903
- }
904
- } catch (error) {
905
- return {
906
- content: [
907
- {
908
- type: 'text',
909
- text: `Error: ${error.message}`
910
- }
911
- ],
912
- isError: true
913
- };
914
- }
915
- });
916
- }
917
-
918
- async run() {
919
- let transport = new StdioServerTransport();
920
- await this.server.connect(transport);
921
- console.error('Vizzly MCP server running on stdio');
922
- }
923
- }
924
-
925
- // Start server
926
- let server = new VizzlyMCPServer();
927
- server.run().catch(console.error);