@oyerinde/caliper 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp.cjs ADDED
@@ -0,0 +1,2305 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
5
+ var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
6
+ var zod = require('zod');
7
+ var types_js = require('@modelcontextprotocol/sdk/types.js');
8
+ var ws = require('ws');
9
+ var events = require('events');
10
+
11
+ /**
12
+ * @name @oyerinde/caliper
13
+ * @author Daniel Oyerinde
14
+ * @license MIT
15
+ *
16
+ * Caliper - Browser Measurement Tool
17
+ *
18
+ * This source code is licensed under the MIT license found in the
19
+ * LICENSE file in the root directory of this source tree.
20
+ */
21
+
22
+ var PositionModeSchema = zod.z.enum(["static", "relative", "absolute", "fixed", "sticky"]);
23
+ var CursorContextSchema = zod.z.enum(["parent", "child", "sibling"]);
24
+ var RectSchema = zod.z.object({
25
+ top: zod.z.number(),
26
+ right: zod.z.number(),
27
+ bottom: zod.z.number(),
28
+ left: zod.z.number(),
29
+ width: zod.z.number(),
30
+ height: zod.z.number(),
31
+ x: zod.z.number(),
32
+ y: zod.z.number()
33
+ });
34
+ var ScrollStateSchema = zod.z.object({
35
+ initialScrollTop: zod.z.number(),
36
+ initialScrollLeft: zod.z.number(),
37
+ containerRect: RectSchema.nullable(),
38
+ absoluteDepth: zod.z.number(),
39
+ hasStickyAncestor: zod.z.boolean().optional()
40
+ });
41
+ var MeasurementLineSchema = zod.z.object({
42
+ type: zod.z.enum(["left", "top", "right", "bottom", "distance"]),
43
+ value: zod.z.number(),
44
+ start: zod.z.object({ x: zod.z.number(), y: zod.z.number() }),
45
+ end: zod.z.object({ x: zod.z.number(), y: zod.z.number() }),
46
+ startSync: zod.z.enum(["primary", "secondary"]).optional(),
47
+ endSync: zod.z.enum(["primary", "secondary"]).optional()
48
+ });
49
+ var StickyConfigSchema = zod.z.object({
50
+ top: zod.z.number().nullable(),
51
+ bottom: zod.z.number().nullable(),
52
+ left: zod.z.number().nullable(),
53
+ right: zod.z.number().nullable(),
54
+ naturalTop: zod.z.number(),
55
+ naturalLeft: zod.z.number(),
56
+ containerWidth: zod.z.number(),
57
+ containerHeight: zod.z.number(),
58
+ elementWidth: zod.z.number(),
59
+ elementHeight: zod.z.number(),
60
+ anchorAbsoluteDepth: zod.z.number()
61
+ });
62
+ var MeasurementResultSchema = zod.z.object({
63
+ context: CursorContextSchema.nullable(),
64
+ lines: zod.z.array(MeasurementLineSchema),
65
+ primary: RectSchema,
66
+ secondary: RectSchema.nullable(),
67
+ timestamp: zod.z.number(),
68
+ primaryHierarchy: zod.z.array(ScrollStateSchema),
69
+ secondaryHierarchy: zod.z.array(ScrollStateSchema),
70
+ primaryPosition: PositionModeSchema,
71
+ secondaryPosition: PositionModeSchema,
72
+ primaryWinX: zod.z.number(),
73
+ primaryWinY: zod.z.number(),
74
+ secondaryWinX: zod.z.number(),
75
+ secondaryWinY: zod.z.number(),
76
+ primarySticky: StickyConfigSchema.optional(),
77
+ secondarySticky: StickyConfigSchema.optional(),
78
+ primaryHasContainingBlock: zod.z.boolean().optional(),
79
+ secondaryHasContainingBlock: zod.z.boolean().optional()
80
+ });
81
+ var SelectionMetadataSchema = zod.z.object({
82
+ rect: RectSchema.nullable(),
83
+ scrollHierarchy: zod.z.array(ScrollStateSchema),
84
+ position: PositionModeSchema,
85
+ stickyConfig: StickyConfigSchema.optional(),
86
+ initialWindowX: zod.z.number(),
87
+ initialWindowY: zod.z.number(),
88
+ depth: zod.z.number(),
89
+ hasContainingBlock: zod.z.boolean().optional()
90
+ });
91
+ var ContextMetricsSchema = zod.z.object({
92
+ rootFontSize: zod.z.number(),
93
+ devicePixelRatio: zod.z.number(),
94
+ viewportWidth: zod.z.number(),
95
+ viewportHeight: zod.z.number(),
96
+ visualViewportWidth: zod.z.number().optional(),
97
+ visualViewportHeight: zod.z.number().optional(),
98
+ scrollX: zod.z.number(),
99
+ scrollY: zod.z.number(),
100
+ documentWidth: zod.z.number(),
101
+ documentHeight: zod.z.number(),
102
+ orientation: zod.z.enum(["portrait", "landscape"]),
103
+ preferences: zod.z.object({
104
+ colorScheme: zod.z.enum(["light", "dark", "no-preference"]),
105
+ reducedMotion: zod.z.boolean()
106
+ })
107
+ });
108
+ var CaliperSelectorInputSchema = zod.z.object({
109
+ selector: zod.z.string(),
110
+ // The CSS selector or data-caliper-agent-id
111
+ tag: zod.z.string(),
112
+ // e.g., "section"
113
+ timestamp: zod.z.number(),
114
+ // When the copy was made
115
+ id: zod.z.string().optional(),
116
+ // HTML id attribute
117
+ text: zod.z.string().optional(),
118
+ // Visible text snippet
119
+ classes: zod.z.array(zod.z.string()).optional(),
120
+ // CSS classes (filtered)
121
+ tabId: zod.z.string().optional(),
122
+ // Tab ID for cross-tab identification
123
+ nthChild: zod.z.number().optional(),
124
+ x: zod.z.number().optional(),
125
+ y: zod.z.number().optional(),
126
+ depth: zod.z.number().optional(),
127
+ marker: zod.z.string().optional(),
128
+ // data-caliper-marker value
129
+ // Geometry Context for re-discovery
130
+ scrollHierarchy: zod.z.array(ScrollStateSchema).optional(),
131
+ position: PositionModeSchema.optional(),
132
+ stickyConfig: StickyConfigSchema.optional(),
133
+ initialWindowX: zod.z.number().optional(),
134
+ initialWindowY: zod.z.number().optional(),
135
+ hasContainingBlock: zod.z.boolean().optional(),
136
+ rect: RectSchema.optional()
137
+ });
138
+ var BoxEdgesSchema = zod.z.object({
139
+ top: zod.z.number(),
140
+ right: zod.z.number(),
141
+ bottom: zod.z.number(),
142
+ left: zod.z.number()
143
+ });
144
+ var CaliperComputedStylesSchema = zod.z.object({
145
+ // Box Model
146
+ display: zod.z.string().optional(),
147
+ visibility: zod.z.string().optional(),
148
+ position: zod.z.string().optional(),
149
+ boxSizing: zod.z.string().optional(),
150
+ // Spacing
151
+ padding: BoxEdgesSchema.optional(),
152
+ margin: BoxEdgesSchema.optional(),
153
+ border: BoxEdgesSchema.optional(),
154
+ // Flexbox/Grid
155
+ gap: zod.z.number().nullable().optional(),
156
+ flexDirection: zod.z.string().optional(),
157
+ justifyContent: zod.z.string().optional(),
158
+ alignItems: zod.z.string().optional(),
159
+ // Typography
160
+ fontSize: zod.z.number().optional(),
161
+ fontWeight: zod.z.string().optional(),
162
+ fontFamily: zod.z.string().optional(),
163
+ lineHeight: zod.z.union([zod.z.number(), zod.z.string()]).nullable().optional(),
164
+ letterSpacing: zod.z.union([zod.z.number(), zod.z.string()]).optional(),
165
+ color: zod.z.string().optional(),
166
+ // Visual
167
+ backgroundColor: zod.z.string().optional(),
168
+ borderColor: zod.z.string().optional(),
169
+ borderRadius: zod.z.string().optional(),
170
+ boxShadow: zod.z.string().optional(),
171
+ opacity: zod.z.union([zod.z.number(), zod.z.string()]).optional(),
172
+ outline: zod.z.string().optional(),
173
+ outlineColor: zod.z.string().optional(),
174
+ zIndex: zod.z.union([zod.z.number(), zod.z.string()]).nullable().optional(),
175
+ // Overflow
176
+ overflow: zod.z.string().optional(),
177
+ overflowX: zod.z.string().optional(),
178
+ overflowY: zod.z.string().optional(),
179
+ contentVisibility: zod.z.string().optional()
180
+ });
181
+ var ParentGapSchema = zod.z.object({
182
+ top: zod.z.number(),
183
+ // Distance from parent's padding-top edge to node's margin-top
184
+ left: zod.z.number(),
185
+ // Distance from parent's padding-left edge to node's margin-left
186
+ bottom: zod.z.number(),
187
+ // Distance from node's margin-bottom to parent's padding-bottom
188
+ right: zod.z.number()
189
+ // Distance from node's margin-right to parent's padding-right
190
+ });
191
+ var SiblingGapSchema = zod.z.object({
192
+ distance: zod.z.number(),
193
+ direction: zod.z.enum(["above", "below", "left", "right"]).nullable()
194
+ }).nullable();
195
+ var CaliperMeasurementsSchema = zod.z.object({
196
+ toParent: ParentGapSchema,
197
+ toPreviousSibling: SiblingGapSchema,
198
+ toNextSibling: SiblingGapSchema,
199
+ indexInParent: zod.z.number(),
200
+ siblingCount: zod.z.number()
201
+ });
202
+ var CaliperNodeSchema = zod.z.lazy(
203
+ () => zod.z.object({
204
+ // Identity
205
+ agentId: zod.z.string(),
206
+ selector: zod.z.string(),
207
+ tag: zod.z.string(),
208
+ htmlId: zod.z.string().optional(),
209
+ classes: zod.z.array(zod.z.string()),
210
+ textContent: zod.z.string().optional(),
211
+ // Geometry (document-relative)
212
+ rect: RectSchema,
213
+ // Viewport-relative position
214
+ viewportRect: zod.z.object({
215
+ top: zod.z.number(),
216
+ left: zod.z.number()
217
+ }),
218
+ // Computed Styles
219
+ styles: CaliperComputedStylesSchema,
220
+ // Measurements
221
+ measurements: CaliperMeasurementsSchema,
222
+ // Tree structure
223
+ depth: zod.z.number(),
224
+ parentAgentId: zod.z.string().optional(),
225
+ childCount: zod.z.number(),
226
+ children: zod.z.array(CaliperNodeSchema),
227
+ marker: zod.z.string().optional(),
228
+ ariaHidden: zod.z.boolean().optional()
229
+ })
230
+ );
231
+ var WalkOptionsSchema = zod.z.object({
232
+ maxDepth: zod.z.number().optional(),
233
+ maxNodes: zod.z.number().optional(),
234
+ continueFrom: zod.z.string().optional(),
235
+ minElementSize: zod.z.number().optional(),
236
+ ignoreSelectors: zod.z.array(zod.z.string()).optional()
237
+ });
238
+ var JSONRPCRequestSchema = types_js.JSONRPCRequestSchema;
239
+ var JSONRPCNotificationSchema = types_js.JSONRPCNotificationSchema;
240
+ var JSONRPCResultResponseSchema = types_js.JSONRPCResultResponseSchema;
241
+ var JSONRPCErrorResponseSchema = types_js.JSONRPCErrorResponseSchema;
242
+ var ViewportSchema = zod.z.object({
243
+ width: zod.z.number(),
244
+ height: zod.z.number(),
245
+ scrollX: zod.z.number(),
246
+ scrollY: zod.z.number()
247
+ });
248
+ zod.z.object({
249
+ top: zod.z.number(),
250
+ left: zod.z.number(),
251
+ width: zod.z.number(),
252
+ height: zod.z.number(),
253
+ absoluteX: zod.z.number(),
254
+ absoluteY: zod.z.number(),
255
+ zIndex: zod.z.number().optional(),
256
+ agentId: zod.z.string().optional()
257
+ });
258
+ var CaliperElementSummarySchema = zod.z.object({
259
+ tagName: zod.z.string(),
260
+ id: zod.z.string().optional(),
261
+ classList: zod.z.array(zod.z.string()),
262
+ agentId: zod.z.string().optional(),
263
+ text: zod.z.string().optional()
264
+ });
265
+ var CALIPER_METHODS = {
266
+ SELECT: "CALIPER_SELECT",
267
+ MEASURE: "CALIPER_MEASURE",
268
+ INSPECT: "CALIPER_INSPECT",
269
+ FREEZE: "CALIPER_FREEZE",
270
+ CLEAR: "CALIPER_CLEAR",
271
+ WALK_DOM: "CALIPER_WALK_DOM",
272
+ WALK_AND_MEASURE: "CALIPER_WALK_AND_MEASURE",
273
+ GET_CONTEXT: "CALIPER_GET_CONTEXT",
274
+ REGISTER_TAB: "caliper/registerTab",
275
+ TAB_UPDATE: "caliper/tabUpdate",
276
+ STATE_UPDATE: "caliper/stateUpdate"
277
+ };
278
+ var CaliperMethodSchema = zod.z.enum([
279
+ CALIPER_METHODS.SELECT,
280
+ CALIPER_METHODS.MEASURE,
281
+ CALIPER_METHODS.INSPECT,
282
+ CALIPER_METHODS.FREEZE,
283
+ CALIPER_METHODS.CLEAR,
284
+ CALIPER_METHODS.WALK_DOM,
285
+ CALIPER_METHODS.WALK_AND_MEASURE,
286
+ CALIPER_METHODS.GET_CONTEXT,
287
+ CALIPER_METHODS.REGISTER_TAB,
288
+ CALIPER_METHODS.TAB_UPDATE,
289
+ CALIPER_METHODS.STATE_UPDATE
290
+ ]);
291
+ var SourceHintsSchema = zod.z.object({
292
+ stableAnchors: zod.z.array(zod.z.string()),
293
+ suggestedGrep: zod.z.string().optional(),
294
+ textContent: zod.z.string().optional(),
295
+ accessibleName: zod.z.string().optional(),
296
+ unstableClasses: zod.z.array(zod.z.string()),
297
+ tagName: zod.z.string()
298
+ });
299
+ var CaliperActionResultSchema = zod.z.union([
300
+ zod.z.object({ success: zod.z.literal(true), method: zod.z.literal(CALIPER_METHODS.SELECT), selector: zod.z.string(), selection: SelectionMetadataSchema, timestamp: zod.z.number() }),
301
+ zod.z.object({ success: zod.z.literal(true), method: zod.z.literal(CALIPER_METHODS.MEASURE), selector: zod.z.string(), measurement: MeasurementResultSchema, timestamp: zod.z.number() }),
302
+ zod.z.object({
303
+ success: zod.z.literal(true),
304
+ method: zod.z.literal(CALIPER_METHODS.INSPECT),
305
+ selector: zod.z.string(),
306
+ distances: zod.z.object({
307
+ top: zod.z.number(),
308
+ right: zod.z.number(),
309
+ bottom: zod.z.number(),
310
+ left: zod.z.number(),
311
+ horizontal: zod.z.number(),
312
+ vertical: zod.z.number()
313
+ }),
314
+ computedStyles: CaliperComputedStylesSchema,
315
+ selection: SelectionMetadataSchema,
316
+ immediateChildCount: zod.z.number().optional(),
317
+ descendantCount: zod.z.number().optional(),
318
+ descendantsTruncated: zod.z.boolean().optional(),
319
+ sourceHints: SourceHintsSchema.optional(),
320
+ timestamp: zod.z.number()
321
+ }),
322
+ zod.z.object({ success: zod.z.literal(true), method: zod.z.literal(CALIPER_METHODS.FREEZE), timestamp: zod.z.number() }),
323
+ zod.z.object({ success: zod.z.literal(true), method: zod.z.literal(CALIPER_METHODS.CLEAR), timestamp: zod.z.number() }),
324
+ zod.z.object({
325
+ success: zod.z.literal(true),
326
+ method: zod.z.literal(CALIPER_METHODS.WALK_DOM),
327
+ selector: zod.z.string(),
328
+ domContext: zod.z.object({
329
+ element: CaliperElementSummarySchema,
330
+ parent: CaliperElementSummarySchema.nullable(),
331
+ children: zod.z.array(CaliperElementSummarySchema)
332
+ }),
333
+ timestamp: zod.z.number()
334
+ }),
335
+ zod.z.object({
336
+ success: zod.z.literal(true),
337
+ method: zod.z.literal(CALIPER_METHODS.WALK_AND_MEASURE),
338
+ selector: zod.z.string(),
339
+ walkResult: zod.z.object({
340
+ root: CaliperNodeSchema.optional(),
341
+ nodeCount: zod.z.number(),
342
+ maxDepthReached: zod.z.number(),
343
+ walkDurationMs: zod.z.number(),
344
+ hasMore: zod.z.boolean().optional(),
345
+ batchInstructions: zod.z.string().optional(),
346
+ continuationToken: zod.z.string().optional()
347
+ }),
348
+ timestamp: zod.z.number(),
349
+ binaryPayload: zod.z.custom().optional()
350
+ }),
351
+ zod.z.object({
352
+ success: zod.z.literal(true),
353
+ method: zod.z.literal(CALIPER_METHODS.GET_CONTEXT),
354
+ context: ContextMetricsSchema,
355
+ timestamp: zod.z.number()
356
+ }),
357
+ zod.z.object({
358
+ success: zod.z.literal(false),
359
+ method: CaliperMethodSchema,
360
+ selector: zod.z.string().optional(),
361
+ error: zod.z.string(),
362
+ timestamp: zod.z.number(),
363
+ binaryPayload: zod.z.custom().optional()
364
+ })
365
+ ]);
366
+ var CaliperAgentStateSchema = zod.z.object({
367
+ viewport: ViewportSchema,
368
+ activeSelection: SelectionMetadataSchema.nullable(),
369
+ selectionFingerprint: CaliperSelectorInputSchema.nullable(),
370
+ lastMeasurement: MeasurementResultSchema.nullable(),
371
+ measurementFingerprint: zod.z.object({
372
+ primary: CaliperSelectorInputSchema,
373
+ secondary: CaliperSelectorInputSchema
374
+ }).nullable(),
375
+ lastUpdated: zod.z.number()
376
+ });
377
+ var IdSchema = zod.z.union([zod.z.string(), zod.z.number()]);
378
+ var RpcFactory = class {
379
+ static request(method, params, id) {
380
+ return JSONRPCRequestSchema.parse({ jsonrpc: "2.0", method, params, id });
381
+ }
382
+ static response(id, result) {
383
+ return JSONRPCResultResponseSchema.parse({ jsonrpc: "2.0", id, result });
384
+ }
385
+ static error(id, code, message, data) {
386
+ return JSONRPCErrorResponseSchema.parse({ jsonrpc: "2.0", id, error: { code, message, data } });
387
+ }
388
+ static notification(method, params) {
389
+ return JSONRPCNotificationSchema.parse({ jsonrpc: "2.0", method, params });
390
+ }
391
+ };
392
+ var isId = (value) => {
393
+ return typeof value === "string" || typeof value === "number";
394
+ };
395
+ var isCaliperActionResult = (value) => {
396
+ return CaliperActionResultSchema.safeParse(value).success;
397
+ };
398
+ var CaliperResponseSchema = zod.z.union([
399
+ zod.z.object({
400
+ jsonrpc: zod.z.literal("2.0"),
401
+ id: IdSchema,
402
+ result: CaliperActionResultSchema
403
+ }),
404
+ JSONRPCErrorResponseSchema
405
+ ]);
406
+ var CaliperNotificationSchema = zod.z.union([
407
+ zod.z.object({
408
+ jsonrpc: zod.z.literal("2.0"),
409
+ method: zod.z.literal(CALIPER_METHODS.REGISTER_TAB),
410
+ params: zod.z.object({
411
+ tabId: zod.z.string(),
412
+ url: zod.z.string(),
413
+ title: zod.z.string(),
414
+ isFocused: zod.z.boolean()
415
+ })
416
+ }),
417
+ zod.z.object({
418
+ jsonrpc: zod.z.literal("2.0"),
419
+ method: zod.z.literal(CALIPER_METHODS.TAB_UPDATE),
420
+ params: zod.z.object({
421
+ isFocused: zod.z.boolean()
422
+ })
423
+ }),
424
+ zod.z.object({
425
+ jsonrpc: zod.z.literal("2.0"),
426
+ method: zod.z.literal(CALIPER_METHODS.STATE_UPDATE),
427
+ params: CaliperAgentStateSchema
428
+ })
429
+ ]);
430
+ var BridgeMessageSchema = zod.z.union([
431
+ CaliperResponseSchema,
432
+ CaliperNotificationSchema,
433
+ JSONRPCResultResponseSchema
434
+ ]);
435
+ zod.z.object({
436
+ selector: zod.z.string()
437
+ });
438
+ zod.z.object({
439
+ primarySelector: zod.z.string(),
440
+ secondarySelector: zod.z.string()
441
+ });
442
+ zod.z.object({
443
+ selector: zod.z.string()
444
+ });
445
+ zod.z.object({
446
+ selector: zod.z.string(),
447
+ depth: zod.z.number().optional()
448
+ });
449
+ WalkOptionsSchema.extend({
450
+ selector: zod.z.string()
451
+ });
452
+ zod.z.object({});
453
+ var stringSerializer = (defaultValue = "") => ({
454
+ collectStrings: (value, collect) => collect(value),
455
+ serialize: (value, context) => {
456
+ context.view.setUint16(context.offset, context.getStringId(value));
457
+ context.offset += 2;
458
+ },
459
+ deserialize: (context) => {
460
+ const result = context.stringList[context.view.getUint16(context.offset)] || defaultValue || void 0;
461
+ context.offset += 2;
462
+ return result;
463
+ }
464
+ });
465
+ var optionalStringSerializer = () => ({
466
+ collectStrings: (value, collect) => collect(value),
467
+ serialize: (value, context) => {
468
+ context.view.setUint16(context.offset, context.getStringId(value));
469
+ context.offset += 2;
470
+ },
471
+ deserialize: (context) => {
472
+ const result = context.stringList[context.view.getUint16(context.offset)] || void 0;
473
+ context.offset += 2;
474
+ return result;
475
+ }
476
+ });
477
+ var floatSerializer = () => ({
478
+ collectStrings: () => {
479
+ },
480
+ serialize: (value, context) => {
481
+ context.view.setFloat32(context.offset, value ?? 0);
482
+ context.offset += 4;
483
+ },
484
+ deserialize: (context) => {
485
+ const result = context.view.getFloat32(context.offset);
486
+ context.offset += 4;
487
+ return result;
488
+ }
489
+ });
490
+ var boxEdgesSerializer = () => ({
491
+ collectStrings: () => {
492
+ },
493
+ serialize: (edges, context) => {
494
+ context.view.setFloat32(context.offset, edges?.top ?? 0);
495
+ context.offset += 4;
496
+ context.view.setFloat32(context.offset, edges?.right ?? 0);
497
+ context.offset += 4;
498
+ context.view.setFloat32(context.offset, edges?.bottom ?? 0);
499
+ context.offset += 4;
500
+ context.view.setFloat32(context.offset, edges?.left ?? 0);
501
+ context.offset += 4;
502
+ },
503
+ deserialize: (context) => {
504
+ const edges = {
505
+ top: context.view.getFloat32(context.offset),
506
+ right: context.view.getFloat32(context.offset + 4),
507
+ bottom: context.view.getFloat32(context.offset + 8),
508
+ left: context.view.getFloat32(context.offset + 12)
509
+ };
510
+ context.offset += 16;
511
+ if (edges.top === 0 && edges.right === 0 && edges.bottom === 0 && edges.left === 0) {
512
+ return void 0;
513
+ }
514
+ return edges;
515
+ }
516
+ });
517
+ var nullableNumberSerializer = () => ({
518
+ collectStrings: (value, collect) => collect(value == null ? null : String(value)),
519
+ serialize: (value, context) => {
520
+ context.view.setUint16(context.offset, context.getStringId(value == null ? null : String(value)));
521
+ context.offset += 2;
522
+ },
523
+ deserialize: (context) => {
524
+ const strValue = context.stringList[context.view.getUint16(context.offset)] || null;
525
+ context.offset += 2;
526
+ return strValue === null ? null : parseFloat(strValue);
527
+ }
528
+ });
529
+ var numberOrStringSerializer = (defaultValue = "") => ({
530
+ collectStrings: (value, collect) => collect(value == null ? void 0 : String(value)),
531
+ serialize: (value, context) => {
532
+ context.view.setUint16(context.offset, context.getStringId(value == null ? void 0 : String(value)));
533
+ context.offset += 2;
534
+ },
535
+ deserialize: (context) => {
536
+ const id = context.view.getUint16(context.offset);
537
+ const strValue = context.stringList[id];
538
+ context.offset += 2;
539
+ if (!strValue && id === 0) return defaultValue;
540
+ if (!strValue) return void 0;
541
+ const numValue = parseFloat(strValue);
542
+ return isNaN(numValue) ? strValue : numValue;
543
+ }
544
+ });
545
+ var nullableNumberOrStringSerializer = () => ({
546
+ collectStrings: (value, collect) => collect(value == null ? null : String(value)),
547
+ serialize: (value, context) => {
548
+ context.view.setUint16(context.offset, context.getStringId(value == null ? null : String(value)));
549
+ context.offset += 2;
550
+ },
551
+ deserialize: (context) => {
552
+ const strValue = context.stringList[context.view.getUint16(context.offset)] || null;
553
+ context.offset += 2;
554
+ if (strValue === null) return null;
555
+ const numValue = parseFloat(strValue);
556
+ return isNaN(numValue) ? strValue : numValue;
557
+ }
558
+ });
559
+ var STYLE_SERIALIZERS = {
560
+ display: stringSerializer(),
561
+ visibility: stringSerializer(),
562
+ position: stringSerializer(),
563
+ boxSizing: stringSerializer(),
564
+ padding: boxEdgesSerializer(),
565
+ margin: boxEdgesSerializer(),
566
+ border: boxEdgesSerializer(),
567
+ gap: nullableNumberSerializer(),
568
+ flexDirection: optionalStringSerializer(),
569
+ justifyContent: optionalStringSerializer(),
570
+ alignItems: optionalStringSerializer(),
571
+ fontSize: floatSerializer(),
572
+ fontWeight: stringSerializer(),
573
+ fontFamily: stringSerializer(),
574
+ lineHeight: nullableNumberOrStringSerializer(),
575
+ letterSpacing: numberOrStringSerializer(0),
576
+ color: stringSerializer(),
577
+ backgroundColor: stringSerializer(),
578
+ borderColor: optionalStringSerializer(),
579
+ borderRadius: stringSerializer("0"),
580
+ boxShadow: optionalStringSerializer(),
581
+ opacity: numberOrStringSerializer(1),
582
+ outline: optionalStringSerializer(),
583
+ outlineColor: optionalStringSerializer(),
584
+ zIndex: nullableNumberOrStringSerializer(),
585
+ overflow: stringSerializer("visible"),
586
+ overflowX: stringSerializer("visible"),
587
+ overflowY: stringSerializer("visible"),
588
+ contentVisibility: stringSerializer("visible")
589
+ };
590
+ var STYLE_KEYS = Object.keys(STYLE_SERIALIZERS);
591
+ var BitBridge = class _BitBridge {
592
+ static MAGIC = 1128352841;
593
+ // "CALI"
594
+ static serialize(root) {
595
+ const strings = /* @__PURE__ */ new Map();
596
+ const stringList = [];
597
+ const getStringId = (str) => {
598
+ if (str === void 0 || str === null) return 0;
599
+ let id = strings.get(str);
600
+ if (id === void 0) {
601
+ id = stringList.length + 1;
602
+ strings.set(str, id);
603
+ stringList.push(str);
604
+ }
605
+ return id;
606
+ };
607
+ const stack = [root];
608
+ while (stack.length > 0) {
609
+ const node = stack.pop();
610
+ getStringId(node.tag);
611
+ getStringId(node.selector);
612
+ getStringId(node.agentId);
613
+ getStringId(node.htmlId);
614
+ getStringId(node.textContent);
615
+ getStringId(node.marker);
616
+ node.classes?.forEach((className) => getStringId(className));
617
+ for (const key of STYLE_KEYS) {
618
+ const serializer = STYLE_SERIALIZERS[key];
619
+ serializer.collectStrings(
620
+ node.styles[key],
621
+ getStringId
622
+ );
623
+ }
624
+ if (node.children) {
625
+ for (let i = node.children.length - 1; i >= 0; i--) {
626
+ stack.push(node.children[i]);
627
+ }
628
+ }
629
+ }
630
+ const encoder = new TextEncoder();
631
+ const encodedStrings = stringList.map((rawString) => encoder.encode(rawString));
632
+ let dictSize = 8;
633
+ encodedStrings.forEach((bytes) => dictSize += 2 + bytes.length);
634
+ const buffer = new ArrayBuffer(dictSize + 1024 * 1024 * 5);
635
+ const view = new DataView(buffer);
636
+ let offset = 0;
637
+ view.setUint32(offset, _BitBridge.MAGIC);
638
+ offset += 4;
639
+ view.setUint32(offset, stringList.length);
640
+ offset += 4;
641
+ encodedStrings.forEach((bytes) => {
642
+ view.setUint16(offset, bytes.length);
643
+ offset += 2;
644
+ new Uint8Array(buffer, offset, bytes.length).set(bytes);
645
+ offset += bytes.length;
646
+ });
647
+ const nodeStack = [root];
648
+ while (nodeStack.length > 0) {
649
+ const node = nodeStack.pop();
650
+ view.setUint16(offset, getStringId(node.tag));
651
+ offset += 2;
652
+ view.setUint16(offset, getStringId(node.selector));
653
+ offset += 2;
654
+ view.setUint16(offset, getStringId(node.agentId));
655
+ offset += 2;
656
+ view.setUint16(offset, getStringId(node.htmlId));
657
+ offset += 2;
658
+ view.setUint16(offset, getStringId(node.textContent));
659
+ offset += 2;
660
+ view.setUint16(offset, getStringId(node.marker));
661
+ offset += 2;
662
+ view.setUint8(offset, node.ariaHidden ? 1 : 0);
663
+ offset += 1;
664
+ const classes = node.classes || [];
665
+ view.setUint16(offset, classes.length);
666
+ offset += 2;
667
+ classes.forEach((className) => {
668
+ view.setUint16(offset, getStringId(className));
669
+ offset += 2;
670
+ });
671
+ view.setFloat32(offset, node.rect.top);
672
+ offset += 4;
673
+ view.setFloat32(offset, node.rect.left);
674
+ offset += 4;
675
+ view.setFloat32(offset, node.rect.width);
676
+ offset += 4;
677
+ view.setFloat32(offset, node.rect.height);
678
+ offset += 4;
679
+ view.setFloat32(offset, node.rect.bottom);
680
+ offset += 4;
681
+ view.setFloat32(offset, node.rect.right);
682
+ offset += 4;
683
+ view.setFloat32(offset, node.viewportRect.top);
684
+ offset += 4;
685
+ view.setFloat32(offset, node.viewportRect.left);
686
+ offset += 4;
687
+ const ctx = { view, offset, getStringId };
688
+ for (const key of STYLE_KEYS) {
689
+ const serializer = STYLE_SERIALIZERS[key];
690
+ serializer.serialize(
691
+ node.styles[key],
692
+ ctx
693
+ );
694
+ }
695
+ offset = ctx.offset;
696
+ view.setUint16(offset, node.depth);
697
+ offset += 2;
698
+ const children = node.children || [];
699
+ view.setUint16(offset, children.length);
700
+ offset += 2;
701
+ for (let i = children.length - 1; i >= 0; i--) {
702
+ nodeStack.push(children[i]);
703
+ }
704
+ }
705
+ return new Uint8Array(buffer, 0, offset);
706
+ }
707
+ static deserialize(data) {
708
+ const buffer = data.buffer;
709
+ const view = new DataView(buffer, data.byteOffset, data.byteLength);
710
+ let offset = 0;
711
+ const magic = view.getUint32(offset);
712
+ offset += 4;
713
+ if (magic !== _BitBridge.MAGIC) throw new Error("Invalid BitBridge format");
714
+ const stringCount = view.getUint32(offset);
715
+ offset += 4;
716
+ const stringList = [""];
717
+ const decoder = new TextDecoder();
718
+ for (let i = 0; i < stringCount; i++) {
719
+ const len = view.getUint16(offset);
720
+ offset += 2;
721
+ const str = decoder.decode(new Uint8Array(buffer, data.byteOffset + offset, len));
722
+ stringList.push(str);
723
+ offset += len;
724
+ }
725
+ const root = this.deserializeNode(view, offset, stringList);
726
+ offset = root.newOffset;
727
+ const nodeStack = [{
728
+ node: root.node,
729
+ childrenToRead: root.childCount
730
+ }];
731
+ while (nodeStack.length > 0) {
732
+ const current = nodeStack[nodeStack.length - 1];
733
+ if (current.childrenToRead > 0) {
734
+ const childResult = this.deserializeNode(view, offset, stringList);
735
+ offset = childResult.newOffset;
736
+ childResult.node.parentAgentId = current.node.agentId;
737
+ current.node.children.push(childResult.node);
738
+ current.childrenToRead--;
739
+ if (childResult.childCount > 0) {
740
+ nodeStack.push({
741
+ node: childResult.node,
742
+ childrenToRead: childResult.childCount
743
+ });
744
+ }
745
+ } else {
746
+ nodeStack.pop();
747
+ }
748
+ }
749
+ return root.node;
750
+ }
751
+ static deserializeNode(view, offset, stringList) {
752
+ const tag = stringList[view.getUint16(offset)] || "";
753
+ offset += 2;
754
+ const selector = stringList[view.getUint16(offset)] || "";
755
+ offset += 2;
756
+ const agentId = stringList[view.getUint16(offset)] || "";
757
+ offset += 2;
758
+ const htmlId = stringList[view.getUint16(offset)] || void 0;
759
+ offset += 2;
760
+ const textContent = stringList[view.getUint16(offset)] || void 0;
761
+ offset += 2;
762
+ const marker = stringList[view.getUint16(offset)] || void 0;
763
+ offset += 2;
764
+ const ariaHidden = view.getUint8(offset) === 1;
765
+ offset += 1;
766
+ const classCount = view.getUint16(offset);
767
+ offset += 2;
768
+ const classes = [];
769
+ for (let i = 0; i < classCount; i++) {
770
+ classes.push(stringList[view.getUint16(offset)] || "");
771
+ offset += 2;
772
+ }
773
+ const rect = {
774
+ top: view.getFloat32(offset),
775
+ left: view.getFloat32(offset + 4),
776
+ width: view.getFloat32(offset + 8),
777
+ height: view.getFloat32(offset + 12),
778
+ bottom: view.getFloat32(offset + 16),
779
+ right: view.getFloat32(offset + 20),
780
+ x: 0,
781
+ y: 0
782
+ };
783
+ rect.x = rect.left;
784
+ rect.y = rect.top;
785
+ offset += 24;
786
+ const vTop = view.getFloat32(offset);
787
+ offset += 4;
788
+ const vLeft = view.getFloat32(offset);
789
+ offset += 4;
790
+ const ctx = { view, offset, stringList };
791
+ const styles = {};
792
+ for (const key of STYLE_KEYS) {
793
+ const serializer = STYLE_SERIALIZERS[key];
794
+ styles[key] = serializer.deserialize(ctx);
795
+ }
796
+ offset = ctx.offset;
797
+ const depth = view.getUint16(offset);
798
+ offset += 2;
799
+ const childCount = view.getUint16(offset);
800
+ offset += 2;
801
+ const node = {
802
+ agentId,
803
+ selector,
804
+ tag,
805
+ htmlId,
806
+ classes,
807
+ textContent,
808
+ rect,
809
+ viewportRect: { top: vTop, left: vLeft },
810
+ depth,
811
+ childCount,
812
+ children: [],
813
+ styles,
814
+ marker,
815
+ ariaHidden,
816
+ measurements: {
817
+ toParent: { top: 0, left: 0, bottom: 0, right: 0 },
818
+ toPreviousSibling: null,
819
+ toNextSibling: null,
820
+ indexInParent: 0,
821
+ siblingCount: childCount
822
+ }
823
+ };
824
+ return { node, childCount, newOffset: offset };
825
+ }
826
+ static packEnvelope(json, payload) {
827
+ const jsonBytes = new TextEncoder().encode(json);
828
+ const combined = new Uint8Array(4 + jsonBytes.byteLength + payload.byteLength);
829
+ const view = new DataView(combined.buffer);
830
+ view.setUint32(0, jsonBytes.byteLength);
831
+ combined.set(jsonBytes, 4);
832
+ combined.set(payload, 4 + jsonBytes.byteLength);
833
+ return combined;
834
+ }
835
+ static unpackEnvelope(data) {
836
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
837
+ const jsonLen = view.getUint32(0);
838
+ const jsonStr = new TextDecoder().decode(data.subarray(4, 4 + jsonLen));
839
+ const payload = data.subarray(4 + jsonLen);
840
+ return { json: jsonStr, payload };
841
+ }
842
+ };
843
+ var MAX_DESCENDANT_COUNT = 1e3;
844
+ var RECOMMENDED_PAGINATION_THRESHOLD = 40;
845
+
846
+ // ../mcp-server/src/utils/logger.ts
847
+ var Logger = class {
848
+ enabled = true;
849
+ prefix;
850
+ constructor(prefix) {
851
+ this.prefix = `[Caliper:${prefix}]`;
852
+ }
853
+ setEnabled(enabled) {
854
+ this.enabled = enabled;
855
+ }
856
+ info(...args) {
857
+ if (this.enabled) console.error(this.prefix, ...args);
858
+ }
859
+ warn(...args) {
860
+ if (this.enabled) console.error(this.prefix, "WARN:", ...args);
861
+ }
862
+ error(...args) {
863
+ if (this.enabled) console.error(this.prefix, "ERROR:", ...args);
864
+ }
865
+ debug(...args) {
866
+ if (this.enabled) console.error(this.prefix, "DEBUG:", ...args);
867
+ }
868
+ };
869
+ var createLogger = (prefix) => new Logger(prefix);
870
+
871
+ // ../mcp-server/src/services/tab-manager.ts
872
+ var logger2 = createLogger("tab-manager");
873
+ var TabManager = class {
874
+ tabs = /* @__PURE__ */ new Map();
875
+ activeTabId = null;
876
+ registerTab(tab) {
877
+ this.tabs.set(tab.id, {
878
+ ...tab,
879
+ lastActive: Date.now()
880
+ });
881
+ this.activeTabId = tab.id;
882
+ logger2.info(`Tab registered: ${tab.title} (${tab.id})`);
883
+ }
884
+ updateTab(id, isFocused) {
885
+ const tab = this.tabs.get(id);
886
+ if (tab) {
887
+ tab.lastActive = Date.now();
888
+ if (isFocused) {
889
+ this.activeTabId = id;
890
+ }
891
+ }
892
+ }
893
+ removeTab(id, ws) {
894
+ const tab = this.tabs.get(id);
895
+ if (tab && tab.ws === ws) {
896
+ this.tabs.delete(id);
897
+ if (this.activeTabId === id) {
898
+ this.activeTabId = Array.from(this.tabs.keys())[0] || null;
899
+ }
900
+ logger2.info(`Tab disconnected: ${id}`);
901
+ }
902
+ }
903
+ getActiveTab() {
904
+ if (!this.activeTabId) return null;
905
+ return this.tabs.get(this.activeTabId) || null;
906
+ }
907
+ getTabById(id) {
908
+ return this.tabs.get(id) || null;
909
+ }
910
+ getAllTabs() {
911
+ return Array.from(this.tabs.values());
912
+ }
913
+ switchTab(id) {
914
+ if (this.tabs.has(id)) {
915
+ this.activeTabId = id;
916
+ return true;
917
+ }
918
+ return false;
919
+ }
920
+ getTabCount() {
921
+ return this.tabs.size;
922
+ }
923
+ findTabByUrl(urlFragment) {
924
+ for (const tab of this.tabs.values()) {
925
+ if (tab.url.includes(urlFragment)) {
926
+ return tab;
927
+ }
928
+ }
929
+ return null;
930
+ }
931
+ };
932
+ var tabManager = new TabManager();
933
+
934
+ // ../mcp-server/src/utils/id.ts
935
+ function generateId(prefix = "") {
936
+ const randomPart = Math.random().toString(36).substring(2, 11);
937
+ return prefix ? `${prefix}-${randomPart}` : randomPart;
938
+ }
939
+
940
+ // ../mcp-server/src/utils/errors.ts
941
+ var BridgeError = class _BridgeError extends Error {
942
+ constructor(message) {
943
+ super(message);
944
+ this.name = "BridgeError";
945
+ Object.setPrototypeOf(this, _BridgeError.prototype);
946
+ }
947
+ };
948
+ var BridgeTimeoutError = class _BridgeTimeoutError extends BridgeError {
949
+ constructor(method, attempt, totalRetries) {
950
+ const message = attempt < totalRetries ? `Bridge request timed out for method: ${method}. Retrying... (Attempt ${attempt + 1}/${totalRetries + 1})` : `Bridge request timed out for method: ${method}. The bridge is connected, but the operation took too long.`;
951
+ super(message);
952
+ this.name = "BridgeTimeoutError";
953
+ Object.setPrototypeOf(this, _BridgeTimeoutError.prototype);
954
+ }
955
+ };
956
+ var BridgeValidationError = class _BridgeValidationError extends BridgeError {
957
+ constructor(message = "Data connection unauthorized or malformed.") {
958
+ super(message);
959
+ this.name = "BridgeValidationError";
960
+ Object.setPrototypeOf(this, _BridgeValidationError.prototype);
961
+ }
962
+ };
963
+
964
+ // ../mcp-server/src/shared/constants.ts
965
+ var DEFAULT_BRIDGE_PORT = 9876;
966
+ var BRIDGE_REQUEST_TIMEOUT_MS = 9e4;
967
+
968
+ // ../mcp-server/src/shared/events.ts
969
+ var BRIDGE_EVENTS = {
970
+ STATE: "state"
971
+ };
972
+
973
+ // ../mcp-server/src/services/bridge-service.ts
974
+ var logger3 = createLogger("mcp-bridge");
975
+ function getExponentialBackoff(attempt, baseDelay) {
976
+ return Math.pow(2, attempt) * baseDelay;
977
+ }
978
+ var BridgeService = class extends events.EventEmitter {
979
+ wss = null;
980
+ pendingCalls = /* @__PURE__ */ new Map();
981
+ startupError = null;
982
+ constructor() {
983
+ super();
984
+ }
985
+ async start(port2 = DEFAULT_BRIDGE_PORT, retries = 3) {
986
+ if (this.wss) return;
987
+ const attemptStart = (currentPort, remaining) => {
988
+ return new Promise((resolve, reject) => {
989
+ try {
990
+ const server2 = new ws.WebSocketServer({ port: currentPort });
991
+ server2.on("listening", () => {
992
+ this.wss = server2;
993
+ this.init();
994
+ resolve();
995
+ });
996
+ server2.on("error", (error) => {
997
+ const err = error;
998
+ if (err.code === "EADDRINUSE" && remaining > 0) {
999
+ const attemptNum = retries - remaining + 1;
1000
+ const delay = getExponentialBackoff(attemptNum - 1, 500);
1001
+ logger3.warn(
1002
+ `Port ${currentPort} is currently in use. Retrying in ${delay}ms... (Attempt ${attemptNum}/${retries})`
1003
+ );
1004
+ server2.close();
1005
+ server2.removeAllListeners();
1006
+ setTimeout(() => {
1007
+ attemptStart(currentPort, remaining - 1).then(resolve).catch(reject);
1008
+ }, delay);
1009
+ } else {
1010
+ const errorMessage = err.code === "EADDRINUSE" ? `Port ${currentPort} is already in use by another process. Please close the other instance of Caliper or use a different port.` : `WebSocket server error: ${String(error)}`;
1011
+ logger3.error(errorMessage);
1012
+ this.startupError = errorMessage;
1013
+ server2.close();
1014
+ reject(new Error(errorMessage));
1015
+ }
1016
+ });
1017
+ } catch (error) {
1018
+ reject(error);
1019
+ }
1020
+ });
1021
+ };
1022
+ try {
1023
+ await attemptStart(port2, retries);
1024
+ } catch (error) {
1025
+ if (!this.startupError) {
1026
+ this.startupError = `Failed to start WebSocket relay after multiple attempts: ${String(error)}`;
1027
+ }
1028
+ }
1029
+ }
1030
+ init() {
1031
+ if (!this.wss) return;
1032
+ this.wss.on("connection", (ws) => {
1033
+ let tabId = null;
1034
+ ws.on("message", (data) => {
1035
+ try {
1036
+ let rawMessage;
1037
+ let binaryPayload;
1038
+ if (Buffer.isBuffer(data)) {
1039
+ if (data[0] === 123) {
1040
+ rawMessage = JSON.parse(data.toString());
1041
+ } else {
1042
+ const { json, payload } = BitBridge.unpackEnvelope(new Uint8Array(data));
1043
+ rawMessage = JSON.parse(json);
1044
+ binaryPayload = payload;
1045
+ }
1046
+ } else {
1047
+ rawMessage = JSON.parse(data.toString());
1048
+ }
1049
+ const result = BridgeMessageSchema.safeParse(rawMessage);
1050
+ if (!result.success) {
1051
+ logger3.error("Invalid WS message format:", zod.z.treeifyError(result.error));
1052
+ const msgObj = rawMessage;
1053
+ if (msgObj?.id !== void 0 && isId(msgObj.id)) {
1054
+ const resolve = this.pendingCalls.get(String(msgObj.id));
1055
+ if (resolve) {
1056
+ resolve({ error: new BridgeValidationError().message });
1057
+ this.pendingCalls.delete(String(msgObj.id));
1058
+ }
1059
+ }
1060
+ return;
1061
+ }
1062
+ const message = result.data;
1063
+ if ("id" in message) {
1064
+ if ("result" in message) {
1065
+ const resolve = this.pendingCalls.get(String(message.id));
1066
+ if (resolve) {
1067
+ const finalResult = message.result;
1068
+ if (isCaliperActionResult(finalResult)) {
1069
+ if (finalResult.success && finalResult.method === CALIPER_METHODS.WALK_AND_MEASURE && binaryPayload) {
1070
+ try {
1071
+ const root = BitBridge.deserialize(binaryPayload);
1072
+ if ("walkResult" in finalResult) {
1073
+ finalResult.walkResult.root = root;
1074
+ }
1075
+ } catch (error) {
1076
+ logger3.error("Bit-Bridge reconstruction failed:", error);
1077
+ }
1078
+ }
1079
+ resolve(finalResult);
1080
+ } else if (finalResult && typeof finalResult === "object" && "error" in finalResult) {
1081
+ resolve(finalResult);
1082
+ } else {
1083
+ resolve({ error: "Unexpected result format received from bridge" });
1084
+ }
1085
+ this.pendingCalls.delete(String(message.id));
1086
+ }
1087
+ } else if ("error" in message) {
1088
+ if (message.id !== null) {
1089
+ const resolve = this.pendingCalls.get(String(message.id));
1090
+ if (resolve) {
1091
+ resolve({ error: message.error.message });
1092
+ this.pendingCalls.delete(String(message.id));
1093
+ }
1094
+ }
1095
+ }
1096
+ } else if ("method" in message) {
1097
+ if (message.method === CALIPER_METHODS.REGISTER_TAB) {
1098
+ const { tabId: newTabId, url, title, isFocused } = message.params;
1099
+ tabId = newTabId;
1100
+ tabManager.registerTab({
1101
+ id: tabId,
1102
+ ws,
1103
+ url,
1104
+ title,
1105
+ isFocused
1106
+ });
1107
+ } else if (message.method === CALIPER_METHODS.TAB_UPDATE) {
1108
+ const { isFocused } = message.params;
1109
+ if (tabId) {
1110
+ tabManager.updateTab(tabId, isFocused);
1111
+ }
1112
+ } else if (message.method === CALIPER_METHODS.STATE_UPDATE) {
1113
+ const state = message.params;
1114
+ this.emit(BRIDGE_EVENTS.STATE, state);
1115
+ }
1116
+ }
1117
+ } catch (error) {
1118
+ logger3.error("WS Message Processing Error:", error);
1119
+ }
1120
+ });
1121
+ ws.on("close", () => {
1122
+ if (tabId) {
1123
+ tabManager.removeTab(tabId, ws);
1124
+ }
1125
+ });
1126
+ });
1127
+ logger3.info(`WebSocket Relay initialized on port ${this.wss.options.port}`);
1128
+ }
1129
+ async call(method, params, retries = 0) {
1130
+ if (this.startupError) {
1131
+ throw new Error(`Caliper Bridge Unavailable: ${this.startupError}`);
1132
+ }
1133
+ const tab = tabManager.getActiveTab();
1134
+ if (!tab) {
1135
+ throw new Error(
1136
+ "No active browser tab connected to Caliper Bridge. Ensure the browser is open with Caliper enabled."
1137
+ );
1138
+ }
1139
+ const callWithTimeout = async (attempt) => {
1140
+ const callId = generateId("mcp-call");
1141
+ return new Promise((resolve, reject) => {
1142
+ const timeout = setTimeout(() => {
1143
+ this.pendingCalls.delete(callId);
1144
+ reject(new BridgeTimeoutError(method, attempt, retries));
1145
+ }, BRIDGE_REQUEST_TIMEOUT_MS);
1146
+ this.pendingCalls.set(callId, (res) => {
1147
+ clearTimeout(timeout);
1148
+ if ("error" in res && !("success" in res)) {
1149
+ if (res.error === new BridgeValidationError().message) {
1150
+ reject(new BridgeValidationError());
1151
+ } else {
1152
+ reject(new Error(res.error));
1153
+ }
1154
+ } else {
1155
+ resolve(res);
1156
+ }
1157
+ });
1158
+ tab.ws.send(JSON.stringify(RpcFactory.request(method, params, callId)));
1159
+ });
1160
+ };
1161
+ let lastError = null;
1162
+ for (let i = 0; i <= retries; i++) {
1163
+ try {
1164
+ return await callWithTimeout(i);
1165
+ } catch (err) {
1166
+ lastError = err;
1167
+ const isRetryable = lastError instanceof BridgeTimeoutError || lastError instanceof BridgeValidationError;
1168
+ if (!isRetryable) {
1169
+ throw lastError;
1170
+ }
1171
+ if (i < retries) {
1172
+ const delay = getExponentialBackoff(i, 300);
1173
+ logger3.warn(`${lastError.message}. Retrying in ${delay}ms...`);
1174
+ await new Promise((resolve) => setTimeout(resolve, delay));
1175
+ }
1176
+ }
1177
+ }
1178
+ throw lastError || new Error(`Failed bridge call: ${method}`);
1179
+ }
1180
+ async stop() {
1181
+ return new Promise((resolve) => {
1182
+ if (!this.wss) {
1183
+ resolve();
1184
+ return;
1185
+ }
1186
+ this.wss.close(() => {
1187
+ logger3.info("WebSocket relay stopped.");
1188
+ resolve();
1189
+ });
1190
+ });
1191
+ }
1192
+ };
1193
+ var bridgeService = new BridgeService();
1194
+
1195
+ // ../mcp-server/src/utils/color-utils.ts
1196
+ var NAMED_COLORS = {
1197
+ aliceblue: "#f0f8ff",
1198
+ antiquewhite: "#faebd7",
1199
+ aqua: "#00ffff",
1200
+ aquamarine: "#7fffd4",
1201
+ azure: "#f0ffff",
1202
+ beige: "#f5f5dc",
1203
+ bisque: "#ffe4c4",
1204
+ black: "#000000",
1205
+ blanchedalmond: "#ffebcd",
1206
+ blue: "#0000ff",
1207
+ blueviolet: "#8a2be2",
1208
+ brown: "#a52a2a",
1209
+ burlywood: "#deb887",
1210
+ cadetblue: "#5f9ea0",
1211
+ chartreuse: "#7fff00",
1212
+ chocolate: "#d2691e",
1213
+ coral: "#ff7f50",
1214
+ cornflowerblue: "#6495ed",
1215
+ cornsilk: "#fff8dc",
1216
+ crimson: "#dc143c",
1217
+ cyan: "#00ffff",
1218
+ darkblue: "#00008b",
1219
+ darkcyan: "#008b8b",
1220
+ darkgoldenrod: "#b8860b",
1221
+ darkgray: "#a9a9a9",
1222
+ darkgreen: "#006400",
1223
+ darkgrey: "#a9a9a9",
1224
+ darkkhaki: "#bdb76b",
1225
+ darkmagenta: "#8b008b",
1226
+ darkolivegreen: "#556b2f",
1227
+ darkorange: "#ff8c00",
1228
+ darkorchid: "#9932cc",
1229
+ darkred: "#8b0000",
1230
+ darksalmon: "#e9967a",
1231
+ darkseagreen: "#8fbc8f",
1232
+ darkslateblue: "#483d8b",
1233
+ darkslategray: "#2f4f4f",
1234
+ darkslategrey: "#2f4f4f",
1235
+ darkturquoise: "#00ced1",
1236
+ darkviolet: "#9400d3",
1237
+ deeppink: "#ff1493",
1238
+ deepskyblue: "#00bfff",
1239
+ dimgray: "#696969",
1240
+ dimgrey: "#696969",
1241
+ dodgerblue: "#1e90ff",
1242
+ firebrick: "#b22222",
1243
+ floralwhite: "#fffaf0",
1244
+ forestgreen: "#228b22",
1245
+ fuchsia: "#ff00ff",
1246
+ gainsboro: "#dcdcdc",
1247
+ ghostwhite: "#f8f8ff",
1248
+ gold: "#ffd700",
1249
+ goldenrod: "#daa520",
1250
+ gray: "#808080",
1251
+ green: "#008000",
1252
+ greenyellow: "#adff2f",
1253
+ grey: "#808080",
1254
+ honeydew: "#f0fff0",
1255
+ hotpink: "#ff69b4",
1256
+ indianred: "#cd5c5c",
1257
+ indigo: "#4b0082",
1258
+ ivory: "#fffff0",
1259
+ khaki: "#f0e68c",
1260
+ lavender: "#e6e6fa",
1261
+ lavenderblush: "#fff0f5",
1262
+ lawngreen: "#7cfc00",
1263
+ lemonchiffon: "#fffacd",
1264
+ lightblue: "#add8e6",
1265
+ lightcoral: "#f08080",
1266
+ lightcyan: "#e0ffff",
1267
+ lightgoldenrodyellow: "#fafad2",
1268
+ lightgray: "#d3d3d3",
1269
+ lightgreen: "#90ee90",
1270
+ lightgrey: "#d3d3d3",
1271
+ lightpink: "#ffb6c1",
1272
+ lightsalmon: "#ffa07a",
1273
+ lightseagreen: "#20b2aa",
1274
+ lightskyblue: "#87cefa",
1275
+ lightslategray: "#778899",
1276
+ lightslategrey: "#778899",
1277
+ lightsteelblue: "#b0c4de",
1278
+ lightyellow: "#ffffe0",
1279
+ lime: "#00ff00",
1280
+ limegreen: "#32cd32",
1281
+ linen: "#faf0e6",
1282
+ magenta: "#ff00ff",
1283
+ maroon: "#800000",
1284
+ mediumaquamarine: "#66cdaa",
1285
+ mediumblue: "#0000cd",
1286
+ mediumorchid: "#ba55d3",
1287
+ mediumpurple: "#9370db",
1288
+ mediumseagreen: "#3cb371",
1289
+ mediumslateblue: "#7b68ee",
1290
+ mediumspringgreen: "#00fa9a",
1291
+ mediumturquoise: "#48d1cc",
1292
+ mediumvioletred: "#c71585",
1293
+ midnightblue: "#191970",
1294
+ mintcream: "#f5fffa",
1295
+ mistyrose: "#ffe4e1",
1296
+ moccasin: "#ffe4b5",
1297
+ navajowhite: "#ffdead",
1298
+ navy: "#000080",
1299
+ oldlace: "#fdf5e6",
1300
+ olive: "#808000",
1301
+ olivedrab: "#6b8e23",
1302
+ orange: "#ffa500",
1303
+ orangered: "#ff4500",
1304
+ orchid: "#da70d6",
1305
+ palegoldenrod: "#eee8aa",
1306
+ palegreen: "#98fb98",
1307
+ paleturquoise: "#afeeee",
1308
+ palevioletred: "#db7093",
1309
+ papayawhip: "#ffefd5",
1310
+ peachpuff: "#ffdab9",
1311
+ peru: "#cd853f",
1312
+ pink: "#ffc0cb",
1313
+ plum: "#dda0dd",
1314
+ powderblue: "#b0e0e6",
1315
+ purple: "#800080",
1316
+ rebeccapurple: "#663399",
1317
+ red: "#ff0000",
1318
+ rosybrown: "#bc8f8f",
1319
+ royalblue: "#4169e1",
1320
+ saddlebrown: "#8b4513",
1321
+ salmon: "#fa8072",
1322
+ sandybrown: "#f4a460",
1323
+ seagreen: "#2e8b57",
1324
+ seashell: "#fff5ee",
1325
+ sienna: "#a0522d",
1326
+ silver: "#c0c0c0",
1327
+ skyblue: "#87ceeb",
1328
+ slateblue: "#6a5acd",
1329
+ slategray: "#708090",
1330
+ slategrey: "#708090",
1331
+ snow: "#fffafa",
1332
+ springgreen: "#00ff7f",
1333
+ steelblue: "#4682b4",
1334
+ tan: "#d2b48c",
1335
+ teal: "#008080",
1336
+ thistle: "#d8bfd8",
1337
+ tomato: "#ff6347",
1338
+ turquoise: "#40e0d0",
1339
+ violet: "#ee82ee",
1340
+ wheat: "#f5deb3",
1341
+ white: "#ffffff",
1342
+ whitesmoke: "#f5f5f5",
1343
+ yellow: "#ffff00",
1344
+ yellowgreen: "#9acd32"
1345
+ };
1346
+ function parseColor(colorStr) {
1347
+ const trimmed = colorStr.trim().toLowerCase();
1348
+ if (trimmed.startsWith("color-mix(")) {
1349
+ return parseColorMix(trimmed);
1350
+ }
1351
+ if (NAMED_COLORS[trimmed]) {
1352
+ return parseHex(NAMED_COLORS[trimmed]);
1353
+ }
1354
+ if (trimmed.startsWith("#")) {
1355
+ return parseHex(trimmed);
1356
+ }
1357
+ if (trimmed.startsWith("rgb")) {
1358
+ return parseRgb(trimmed);
1359
+ }
1360
+ if (trimmed.startsWith("hsl")) return parseHsl(trimmed);
1361
+ if (trimmed.startsWith("hwb")) return parseHwb(trimmed);
1362
+ if (trimmed.startsWith("oklch")) return parseOklch(trimmed);
1363
+ if (trimmed.startsWith("oklab")) return parseOklab(trimmed);
1364
+ if (trimmed.startsWith("lch")) return parseLch(trimmed);
1365
+ if (trimmed.startsWith("lab")) return parseLab(trimmed);
1366
+ if (trimmed.startsWith("color(")) return parseGenericColor(trimmed);
1367
+ return { l: 0, a: 0, b: 0, alpha: 0, raw: colorStr };
1368
+ }
1369
+ function parseNumericValue(val, scale = 1) {
1370
+ if (!val || val === "none") return 0;
1371
+ const trimmed = val.trim().toLowerCase();
1372
+ if (trimmed.endsWith("%")) return parseFloat(trimmed) / 100 * scale;
1373
+ if (trimmed.endsWith("deg")) return parseFloat(trimmed);
1374
+ if (trimmed.endsWith("rad")) return parseFloat(trimmed) * 180 / Math.PI;
1375
+ if (trimmed.endsWith("grad")) return parseFloat(trimmed) * 0.9;
1376
+ if (trimmed.endsWith("turn")) return parseFloat(trimmed) * 360;
1377
+ return parseFloat(trimmed);
1378
+ }
1379
+ function parseHex(hex) {
1380
+ let r = 0, g = 0, b = 0, a = 1;
1381
+ if (hex.length === 4) {
1382
+ r = parseInt(hex[1] + hex[1], 16);
1383
+ g = parseInt(hex[2] + hex[2], 16);
1384
+ b = parseInt(hex[3] + hex[3], 16);
1385
+ } else if (hex.length === 7) {
1386
+ r = parseInt(hex.slice(1, 3), 16);
1387
+ g = parseInt(hex.slice(3, 5), 16);
1388
+ b = parseInt(hex.slice(5, 7), 16);
1389
+ } else if (hex.length === 9) {
1390
+ r = parseInt(hex.slice(1, 3), 16);
1391
+ g = parseInt(hex.slice(3, 5), 16);
1392
+ b = parseInt(hex.slice(5, 7), 16);
1393
+ a = parseInt(hex.slice(7, 9), 16) / 255;
1394
+ }
1395
+ return rgbToNormalized(r, g, b, a, hex);
1396
+ }
1397
+ function parseRgb(rgb) {
1398
+ const parts = rgb.match(/rgba?\(([^)]+)\)/i)?.[1]?.split(/[\s,#/]+/).filter(Boolean) || [];
1399
+ if (parts.length < 3) return { l: 0, a: 0, b: 0, alpha: 1, raw: rgb };
1400
+ const r = parseNumericValue(parts[0], 255);
1401
+ const g = parseNumericValue(parts[1], 255);
1402
+ const b = parseNumericValue(parts[2], 255);
1403
+ const a = parts[3] ? parseNumericValue(parts[3], 1) : 1;
1404
+ return rgbToNormalized(r, g, b, a, rgb);
1405
+ }
1406
+ function parseHsl(hsl) {
1407
+ const parts = hsl.match(/hsla?\(([^)]+)\)/i)?.[1]?.split(/[\s,#/]+/).filter(Boolean) || [];
1408
+ if (parts.length < 3) return { l: 0, a: 0, b: 0, alpha: 1, raw: hsl };
1409
+ const h = parseNumericValue(parts[0], 360) / 360;
1410
+ const s = parseNumericValue(parts[1], 1);
1411
+ const l = parseNumericValue(parts[2], 1);
1412
+ const a = parts[3] ? parseNumericValue(parts[3], 1) : 1;
1413
+ let r, g, b;
1414
+ if (s === 0) {
1415
+ r = g = b = l;
1416
+ } else {
1417
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
1418
+ const p = 2 * l - q;
1419
+ r = hueToRgb(p, q, h + 1 / 3);
1420
+ g = hueToRgb(p, q, h);
1421
+ b = hueToRgb(p, q, h - 1 / 3);
1422
+ }
1423
+ return rgbToNormalized(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), a, hsl);
1424
+ }
1425
+ function hueToRgb(pVal, qVal, normalizedHue) {
1426
+ if (normalizedHue < 0) normalizedHue += 1;
1427
+ if (normalizedHue > 1) normalizedHue -= 1;
1428
+ if (normalizedHue < 1 / 6) return pVal + (qVal - pVal) * 6 * normalizedHue;
1429
+ if (normalizedHue < 1 / 2) return qVal;
1430
+ if (normalizedHue < 2 / 3) return pVal + (qVal - pVal) * (2 / 3 - normalizedHue) * 6;
1431
+ return pVal;
1432
+ }
1433
+ function parseHwb(hwb) {
1434
+ const parts = hwb.match(/hwb\(([^)]+)\)/i)?.[1]?.split(/[\s,#/]+/).filter(Boolean) || [];
1435
+ if (parts.length < 3) return { l: 0, a: 0, b: 0, alpha: 1, raw: hwb };
1436
+ const h = parseNumericValue(parts[0], 360) / 360;
1437
+ let w = parseNumericValue(parts[1], 1);
1438
+ let b = parseNumericValue(parts[2], 1);
1439
+ const a = parts[3] ? parseNumericValue(parts[3], 1) : 1;
1440
+ if (w + b > 1) {
1441
+ const sum = w + b;
1442
+ w /= sum;
1443
+ b /= sum;
1444
+ }
1445
+ const r_base = hueToRgb(0, 1, h + 1 / 3);
1446
+ const g_base = hueToRgb(0, 1, h);
1447
+ const b_base = hueToRgb(0, 1, h - 1 / 3);
1448
+ const factor = 1 - w - b;
1449
+ const r = r_base * factor + w;
1450
+ const g = g_base * factor + w;
1451
+ const b_final = b_base * factor + w;
1452
+ return rgbToNormalized(r * 255, g * 255, b_final * 255, a, hwb);
1453
+ }
1454
+ function parseLab(lab) {
1455
+ const parts = lab.match(/lab\(([^)]+)\)/i)?.[1]?.split(/[\s,#/]+/).filter(Boolean) || [];
1456
+ if (parts.length < 3) return { l: 0, a: 0, b: 0, alpha: 1, raw: lab };
1457
+ const L = parseNumericValue(parts[0], 100);
1458
+ const A = parseNumericValue(parts[1], 125);
1459
+ const B = parseNumericValue(parts[2], 125);
1460
+ const alpha = parts[3] ? parseNumericValue(parts[3], 1) : 1;
1461
+ const y = (L + 16) / 116;
1462
+ const x = A / 500 + y;
1463
+ const z4 = y - B / 200;
1464
+ const [X50, Y50, Z50] = [f_inv(x) * 0.96422, f_inv(y) * 1, f_inv(z4) * 0.82521];
1465
+ const X65 = X50 * 0.9555766 + Y50 * -0.0230393 + Z50 * 0.0631636;
1466
+ const Y65 = X50 * -0.0282895 + Y50 * 1.0099416 + Z50 * 0.0210077;
1467
+ const Z65 = X50 * 0.0122982 + Y50 * -0.020483 + Z50 * 1.3299098;
1468
+ return xyzToOklab(X65, Y65, Z65, alpha, lab);
1469
+ }
1470
+ function f_inv(t) {
1471
+ return t > 6 / 29 ? t ** 3 : 3 * (6 / 29) ** 2 * (t - 4 / 29);
1472
+ }
1473
+ function parseLch(lch) {
1474
+ const parts = lch.match(/lch\(([^)]+)\)/i)?.[1]?.split(/[\s,#/]+/).filter(Boolean) || [];
1475
+ if (parts.length < 3) return { l: 0, a: 0, b: 0, alpha: 1, raw: lch };
1476
+ const L = parseNumericValue(parts[0], 100);
1477
+ const C = parseNumericValue(parts[1], 150);
1478
+ const H = parseNumericValue(parts[2], 1);
1479
+ const alpha = parts[3] ? parseNumericValue(parts[3], 1) : 1;
1480
+ const hRad = H * Math.PI / 180;
1481
+ const a = C * Math.cos(hRad);
1482
+ const b = C * Math.sin(hRad);
1483
+ return parseLab(`lab(${L} ${a} ${b} / ${alpha})`);
1484
+ }
1485
+ function parseOklab(oklab) {
1486
+ const parts = oklab.match(/oklab\(([^)]+)\)/i)?.[1]?.split(/[\s,#/]+/).filter(Boolean) || [];
1487
+ if (parts.length < 3) return { l: 0, a: 0, b: 0, alpha: 1, raw: oklab };
1488
+ return {
1489
+ l: parseNumericValue(parts[0], 1),
1490
+ a: parseNumericValue(parts[1], 0.4),
1491
+ b: parseNumericValue(parts[2], 0.4),
1492
+ alpha: parts[3] ? parseNumericValue(parts[3], 1) : 1,
1493
+ raw: oklab
1494
+ };
1495
+ }
1496
+ function parseOklch(oklch) {
1497
+ const parts = oklch.match(/oklch\(([^)]+)\)/i)?.[1]?.split(/[\s,#/]+/).filter(Boolean) || [];
1498
+ if (parts.length < 3) return { l: 0, a: 0, b: 0, alpha: 1, raw: oklch };
1499
+ const L = parseNumericValue(parts[0], 1);
1500
+ const C = parseNumericValue(parts[1], 1);
1501
+ const H = parseNumericValue(parts[2], 1);
1502
+ const A = parts[3] ? parseNumericValue(parts[3], 1) : 1;
1503
+ const hRad = H * Math.PI / 180;
1504
+ const a = C * Math.cos(hRad);
1505
+ const b = C * Math.sin(hRad);
1506
+ return { l: L, a, b, alpha: A, raw: oklch };
1507
+ }
1508
+ function parseGenericColor(str) {
1509
+ const match = str.match(/color\(([^ ]+) ([^)]+)\)/i);
1510
+ if (!match) return { l: 0, a: 0, b: 0, alpha: 1, raw: str };
1511
+ const space = match[1].toLowerCase();
1512
+ const parts = match[2].split(/[\s/]+/).filter(Boolean);
1513
+ const c1 = parseNumericValue(parts[0], 1);
1514
+ const c2 = parseNumericValue(parts[1], 1);
1515
+ const c3 = parseNumericValue(parts[2], 1);
1516
+ const a = parts[3] ? parseNumericValue(parts[3], 1) : 1;
1517
+ if (space === "display-p3") {
1518
+ return p3ToNormalized(c1, c2, c3, a, str);
1519
+ }
1520
+ if (space === "srgb") {
1521
+ return rgbToNormalized(c1 * 255, c2 * 255, c3 * 255, a, str);
1522
+ }
1523
+ return rgbToNormalized(c1 * 255, c2 * 255, c3 * 255, a, str);
1524
+ }
1525
+ function parseColorMix(str) {
1526
+ const match = str.match(/color-mix\(in ([^,]+),\s*([^,]+),\s*([^)]+)\)/i);
1527
+ if (!match) return { l: 0, a: 0, b: 0, alpha: 0, raw: str };
1528
+ match[1].trim().toLowerCase();
1529
+ const c1Str = match[2].trim();
1530
+ const c2Str = match[3].trim();
1531
+ const p1Match = c1Str.match(/(.+)\s+(\d+(\.\d+)?%)/);
1532
+ const p2Match = c2Str.match(/(.+)\s+(\d+(\.\d+)?%)/);
1533
+ const c1 = parseColor(p1Match ? p1Match[1] : c1Str);
1534
+ const c2 = parseColor(p2Match ? p2Match[1] : c2Str);
1535
+ let weight = 0.5;
1536
+ if (p1Match) weight = parseFloat(p1Match[2]) / 100;
1537
+ else if (p2Match) weight = 1 - parseFloat(p2Match[2]) / 100;
1538
+ return {
1539
+ l: c1.l * weight + c2.l * (1 - weight),
1540
+ a: c1.a * weight + c2.a * (1 - weight),
1541
+ b: c1.b * weight + c2.b * (1 - weight),
1542
+ alpha: c1.alpha * weight + c2.alpha * (1 - weight),
1543
+ raw: str
1544
+ };
1545
+ }
1546
+ function xyzToOklab(x, y, z4, alpha, raw) {
1547
+ if (alpha === 0) return { l: 0, a: 0, b: 0, alpha: 0, raw };
1548
+ const lValue = 0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z4;
1549
+ const mValue = 0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z4;
1550
+ const sValue = 0.0482003018 * x + 0.2643662691 * y + 0.633851707 * z4;
1551
+ const lCubeRoot = Math.cbrt(lValue);
1552
+ const mCubeRoot = Math.cbrt(mValue);
1553
+ const sCubeRoot = Math.cbrt(sValue);
1554
+ return {
1555
+ l: 0.2104542553 * lCubeRoot + 0.793617785 * mCubeRoot - 0.0040720403 * sCubeRoot,
1556
+ a: 1.9779984951 * lCubeRoot - 2.428592205 * mCubeRoot + 0.4505937099 * sCubeRoot,
1557
+ b: 0.0259040371 * lCubeRoot + 0.7827717662 * mCubeRoot - 0.808675766 * sCubeRoot,
1558
+ alpha,
1559
+ raw
1560
+ };
1561
+ }
1562
+ function rgbToNormalized(red, green, blue, alpha, raw) {
1563
+ const linearRed = pivotRgb(red / 255);
1564
+ const linearGreen = pivotRgb(green / 255);
1565
+ const linearBlue = pivotRgb(blue / 255);
1566
+ const x = 0.4124564 * linearRed + 0.3575761 * linearGreen + 0.1804375 * linearBlue;
1567
+ const y = 0.2126729 * linearRed + 0.7151522 * linearGreen + 0.072175 * linearBlue;
1568
+ const z4 = 0.0193339 * linearRed + 0.119192 * linearGreen + 0.9503041 * linearBlue;
1569
+ return xyzToOklab(x, y, z4, alpha, raw);
1570
+ }
1571
+ function p3ToNormalized(red, green, blue, alpha, raw) {
1572
+ const lp3 = (val) => val > 0.04045 ? Math.pow((val + 0.055) / 1.055, 2.4) : val / 12.92;
1573
+ const linearRed = lp3(red);
1574
+ const linearGreen = lp3(green);
1575
+ const linearBlue = lp3(blue);
1576
+ const x = 0.4865709 * linearRed + 0.2656677 * linearGreen + 0.1982119 * linearBlue;
1577
+ const y = 0.2289748 * linearRed + 0.6917385 * linearGreen + 0.0592867 * linearBlue;
1578
+ const z4 = 0 * linearRed + 0.0451104 * linearGreen + 1.0439444 * linearBlue;
1579
+ return xyzToOklab(x, y, z4, alpha, raw);
1580
+ }
1581
+ function pivotRgb(colorValue) {
1582
+ return colorValue > 0.04045 ? Math.pow((colorValue + 0.055) / 1.055, 2.4) : colorValue / 12.92;
1583
+ }
1584
+ function calculateDeltaE(color1, color2) {
1585
+ const deltaL = color1.l - color2.l;
1586
+ const deltaA = color1.a - color2.a;
1587
+ const deltaB = color1.b - color2.b;
1588
+ const deltaAlpha = color1.alpha - color2.alpha;
1589
+ return Math.sqrt(
1590
+ deltaL * deltaL + deltaA * deltaA + deltaB * deltaB + deltaAlpha * deltaAlpha * 0.5
1591
+ );
1592
+ }
1593
+ function unpivotRgb(c) {
1594
+ return c > 31308e-7 ? 1.055 * Math.pow(c, 1 / 2.4) - 0.055 : 12.92 * c;
1595
+ }
1596
+ function oklabToSrgbComponents(color) {
1597
+ const lmsPrimeL = color.l + 0.3963377774 * color.a + 0.2158037573 * color.b;
1598
+ const lmsPrimeM = color.l - 0.1055613458 * color.a - 0.0638541728 * color.b;
1599
+ const lmsPrimeS = color.l - 0.0894841775 * color.a - 1.291485548 * color.b;
1600
+ const lmsL = lmsPrimeL * lmsPrimeL * lmsPrimeL;
1601
+ const lmsM = lmsPrimeM * lmsPrimeM * lmsPrimeM;
1602
+ const lmsS = lmsPrimeS * lmsPrimeS * lmsPrimeS;
1603
+ const linearR = 4.0767416621 * lmsL - 3.3077115913 * lmsM + 0.2309699292 * lmsS;
1604
+ const linearG = -1.2684380046 * lmsL + 2.6097574011 * lmsM - 0.3413193965 * lmsS;
1605
+ const linearB = -0.0041960863 * lmsL - 0.7034186147 * lmsM + 1.707612701 * lmsS;
1606
+ return {
1607
+ red: Math.min(1, Math.max(0, unpivotRgb(linearR))),
1608
+ green: Math.min(1, Math.max(0, unpivotRgb(linearG))),
1609
+ blue: Math.min(1, Math.max(0, unpivotRgb(linearB)))
1610
+ };
1611
+ }
1612
+ function getLuminance(color) {
1613
+ const { red, green, blue } = oklabToSrgbComponents(color);
1614
+ const rLin = red <= 0.04045 ? red / 12.92 : Math.pow((red + 0.055) / 1.055, 2.4);
1615
+ const gLin = green <= 0.04045 ? green / 12.92 : Math.pow((green + 0.055) / 1.055, 2.4);
1616
+ const bLin = blue <= 0.04045 ? blue / 12.92 : Math.pow((blue + 0.055) / 1.055, 2.4);
1617
+ return 0.2126 * rLin + 0.7152 * gLin + 0.0722 * bLin;
1618
+ }
1619
+ function calculateContrastRatio(fg, bg) {
1620
+ const l1 = getLuminance(fg);
1621
+ const l2 = getLuminance(bg);
1622
+ const lighter = Math.max(l1, l2);
1623
+ const darker = Math.min(l1, l2);
1624
+ return (lighter + 0.05) / (darker + 0.05);
1625
+ }
1626
+
1627
+ // ../mcp-server/src/services/mcp-server.ts
1628
+ var logger4 = createLogger("mcp-server");
1629
+ var CaliperMcpServer = class {
1630
+ server;
1631
+ port;
1632
+ lastState = null;
1633
+ constructor(port2 = DEFAULT_BRIDGE_PORT) {
1634
+ this.port = port2;
1635
+ this.server = new mcp_js.McpServer({
1636
+ name: "caliper-mcp-server",
1637
+ version: "0.2.0"
1638
+ });
1639
+ this.registerTools();
1640
+ this.registerResources();
1641
+ this.registerPrompts();
1642
+ }
1643
+ registerTools() {
1644
+ this.server.registerTool(
1645
+ "caliper_list_tabs",
1646
+ {
1647
+ description: "List all browser tabs currently connected to the Caliper Agent Bridge.",
1648
+ inputSchema: zod.z.object({})
1649
+ },
1650
+ async () => {
1651
+ const tabs = tabManager.getAllTabs().map((tab) => ({
1652
+ id: tab.id,
1653
+ title: tab.title,
1654
+ url: tab.url,
1655
+ isActive: tab.id === tabManager.getActiveTab()?.id
1656
+ }));
1657
+ return { content: [{ type: "text", text: JSON.stringify(tabs, null, 2) }] };
1658
+ }
1659
+ );
1660
+ this.server.registerTool(
1661
+ "caliper_switch_tab",
1662
+ {
1663
+ description: "Switch the active targeting to a specific browser tab.",
1664
+ inputSchema: zod.z.object({
1665
+ tabId: zod.z.string().describe("The ID of the tab to target")
1666
+ })
1667
+ },
1668
+ async ({ tabId }) => {
1669
+ if (tabManager.switchTab(tabId)) {
1670
+ return {
1671
+ content: [
1672
+ { type: "text", text: `Switched to tab: ${tabManager.getActiveTab()?.title}` }
1673
+ ]
1674
+ };
1675
+ }
1676
+ return {
1677
+ content: [{ type: "text", text: `Error: Tab ${tabId} not found` }],
1678
+ isError: true
1679
+ };
1680
+ }
1681
+ );
1682
+ this.server.registerTool(
1683
+ "caliper_inspect",
1684
+ {
1685
+ description: `Get full geometry and computed visibility for an element.
1686
+
1687
+ Returns comprehensive element data including:
1688
+ - distances: Position relative to viewport edges
1689
+ - computedStyles: All CSS computed values
1690
+ - selection: Scroll hierarchy and sticky configuration
1691
+ - immediateChildCount: Direct children count
1692
+ - descendantCount: Total descendants (capped at ${MAX_DESCENDANT_COUNT})
1693
+ - descendantsTruncated: True if descendant count was capped
1694
+ - sourceHints: Metadata for code discovery. Includes:
1695
+ * suggestedGrep: A reliable pattern for finding this element in the codebase.
1696
+ * stableAnchors: Prioritized list of stable attributes (data-testid, id, etc.).
1697
+ * textContent: Truncated direct text content.
1698
+ * unstableClasses: Classes that look like generated/hashed strings (to be ignored).
1699
+
1700
+ Use descendantCount to decide whether to paginate caliper_walk_and_measure.
1701
+ If descendantCount > ${RECOMMENDED_PAGINATION_THRESHOLD} or descendantsTruncated is true, use maxNodes parameter.`,
1702
+ inputSchema: zod.z.object({
1703
+ selector: zod.z.string().describe(
1704
+ "Element identifier. PRIORITIZE JSON Fingerprint, Caliper Agent ID (caliper-***), or CSS selector for maximum stabilization."
1705
+ )
1706
+ })
1707
+ },
1708
+ async ({ selector }) => {
1709
+ try {
1710
+ const result = await bridgeService.call(CALIPER_METHODS.INSPECT, { selector });
1711
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1712
+ } catch (error) {
1713
+ return {
1714
+ content: [
1715
+ {
1716
+ type: "text",
1717
+ text: `Error inspecting element: ${error instanceof Error ? error.message : String(error)}`
1718
+ }
1719
+ ],
1720
+ isError: true
1721
+ };
1722
+ }
1723
+ }
1724
+ );
1725
+ this.server.registerTool(
1726
+ "caliper_measure",
1727
+ {
1728
+ description: "Perform a high-precision measurement between two elements.",
1729
+ inputSchema: zod.z.object({
1730
+ primarySelector: zod.z.string().describe(
1731
+ "Identifier for the primary element. PRIORITIZE JSON Fingerprint, Caliper Agent ID (caliper-***), or CSS selector."
1732
+ ),
1733
+ secondarySelector: zod.z.string().describe(
1734
+ "Identifier for the target element. PRIORITIZE JSON Fingerprint, Caliper Agent ID (caliper-***), or CSS selector."
1735
+ )
1736
+ })
1737
+ },
1738
+ async ({ primarySelector, secondarySelector }) => {
1739
+ try {
1740
+ const result = await bridgeService.call(CALIPER_METHODS.MEASURE, {
1741
+ primarySelector,
1742
+ secondarySelector
1743
+ });
1744
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1745
+ } catch (error) {
1746
+ return {
1747
+ content: [
1748
+ {
1749
+ type: "text",
1750
+ text: `Error performing measurement: ${error instanceof Error ? error.message : String(error)}`
1751
+ }
1752
+ ],
1753
+ isError: true
1754
+ };
1755
+ }
1756
+ }
1757
+ );
1758
+ this.server.registerTool(
1759
+ "caliper_clear",
1760
+ {
1761
+ description: "Clear all measurements and selections in the browser UI.",
1762
+ inputSchema: zod.z.object({})
1763
+ },
1764
+ async () => {
1765
+ try {
1766
+ const result = await bridgeService.call(CALIPER_METHODS.CLEAR, {});
1767
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1768
+ } catch (error) {
1769
+ return {
1770
+ content: [
1771
+ {
1772
+ type: "text",
1773
+ text: `Error clearing UI: ${error instanceof Error ? error.message : String(error)}`
1774
+ }
1775
+ ],
1776
+ isError: true
1777
+ };
1778
+ }
1779
+ }
1780
+ );
1781
+ this.server.registerTool(
1782
+ "caliper_walk_dom",
1783
+ {
1784
+ description: "Get the semantic context of an element by traversing its parents and children. Useful for understanding component hierarchy.",
1785
+ inputSchema: zod.z.object({
1786
+ selector: zod.z.string().describe(
1787
+ "Element identifier. PRIORITIZE JSON Fingerprint, Caliper Agent ID (caliper-***), or CSS selector for maximum stabilization."
1788
+ )
1789
+ })
1790
+ },
1791
+ async ({ selector }) => {
1792
+ try {
1793
+ const result = await bridgeService.call(CALIPER_METHODS.WALK_DOM, { selector });
1794
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
1795
+ } catch (error) {
1796
+ return {
1797
+ content: [
1798
+ {
1799
+ type: "text",
1800
+ text: `DOM Walk failed: ${error instanceof Error ? error.message : String(error)}`
1801
+ }
1802
+ ],
1803
+ isError: true
1804
+ };
1805
+ }
1806
+ }
1807
+ );
1808
+ this.server.registerTool(
1809
+ "caliper_walk_and_measure",
1810
+ {
1811
+ description: `Walk the DOM tree starting from a selector and capture precise measurements.
1812
+
1813
+ This is the HARNESS tool for comprehensive audits. It:
1814
+ - Walks the DOM tree using BFS (breadth-first)
1815
+ - Captures computed styles for each node (padding, margin, gap, typography, colors)
1816
+ - Measures gaps between siblings and distance to parent edges
1817
+ - Returns a full tree structure ready for reconciliation with Figma
1818
+
1819
+ Use this BEFORE making any code changes to gather complete context.
1820
+
1821
+ SAFETY: Calling 'caliper_inspect' is REQUIRED before initiating an initial audit on a component. This provides the 'descendantCount' metrics needed to set 'maxNodes' and avoid timeouts. You may skip 'inspect' only when resuming an audit using a 'continuationToken'.
1822
+
1823
+ STYLE PRUNING: To reduce payload size, default styles are omitted. If a style is missing, assume:
1824
+ - display: "block"
1825
+ - visibility: "visible"
1826
+ - position: "static"
1827
+ - boxSizing: "border-box"
1828
+ - padding/margin/border: { top: 0, right: 0, bottom: 0, left: 0 }
1829
+ - gap: 0
1830
+ - flexDirection: "row" (flex only)
1831
+ - justifyContent: "normal" (flex only)
1832
+ - alignItems: "normal" (flex only)
1833
+ - lineHeight: "normal"
1834
+ - letterSpacing: "normal"
1835
+ - backgroundColor: "rgba(0, 0, 0, 0)" (transparent)
1836
+ - borderRadius: "0px"
1837
+ - boxShadow: "none"
1838
+ - opacity: 1
1839
+ - overflow/overflowX/overflowY: "visible"
1840
+
1841
+ The output includes:
1842
+ - root: The full measured tree (recursive CaliperNode structure)
1843
+ - nodeCount: Total nodes captured
1844
+ - maxDepthReached: Deepest level reached
1845
+ - walkDurationMs: Time taken to complete the walk
1846
+ - hasMore: Whether tree was truncated (pagination)
1847
+ - continuationToken: Selector to resume from (if hasMore is true)
1848
+ - batchInstructions: Guidance for next batch call (if hasMore is true)`,
1849
+ inputSchema: zod.z.object({
1850
+ selector: zod.z.string().describe(
1851
+ "Root element identifier. PRIORITIZE JSON Fingerprint, Caliper Agent ID (caliper-***), or CSS selector for maximum stabilization."
1852
+ ),
1853
+ maxDepth: zod.z.number().optional().describe("Maximum depth to walk (default: 5)"),
1854
+ maxNodes: zod.z.number().optional().describe(
1855
+ "Maximum nodes to return per batch (default: unlimited). Use for large trees."
1856
+ ),
1857
+ continueFrom: zod.z.string().optional().describe("Continuation token from previous call to resume pagination"),
1858
+ minElementSize: zod.z.number().optional().describe(
1859
+ "Minimum element size (width/height) to include in the walk (default: 0). Use to filter out icons or tiny decorative elements."
1860
+ ),
1861
+ ignoreSelectors: zod.z.array(zod.z.string()).optional().describe("List of CSS selectors or Agent IDs to skip during the walk.")
1862
+ })
1863
+ },
1864
+ async ({ selector, maxDepth, maxNodes, continueFrom, minElementSize, ignoreSelectors }) => {
1865
+ try {
1866
+ const auditResult = await bridgeService.call(CALIPER_METHODS.WALK_AND_MEASURE, {
1867
+ selector,
1868
+ maxDepth: maxDepth ?? 5,
1869
+ maxNodes,
1870
+ continueFrom,
1871
+ minElementSize,
1872
+ ignoreSelectors
1873
+ });
1874
+ const auditResponse = auditResult;
1875
+ let reportContent = JSON.stringify(auditResult);
1876
+ if (auditResponse.walkResult?.hasMore && auditResponse.walkResult?.batchInstructions) {
1877
+ reportContent = `${auditResponse.walkResult.batchInstructions}
1878
+
1879
+ ${reportContent}`;
1880
+ }
1881
+ return { content: [{ type: "text", text: reportContent }] };
1882
+ } catch (error) {
1883
+ return {
1884
+ content: [
1885
+ {
1886
+ type: "text",
1887
+ text: `Walk and Measure failed: ${error instanceof Error ? error.message : String(error)}`
1888
+ }
1889
+ ],
1890
+ isError: true
1891
+ };
1892
+ }
1893
+ }
1894
+ );
1895
+ this.server.registerTool(
1896
+ "caliper_get_context",
1897
+ {
1898
+ description: "Get comprehensive window, viewport, and accessibility metrics from the current browser tab.",
1899
+ inputSchema: zod.z.object({})
1900
+ },
1901
+ async () => {
1902
+ try {
1903
+ const result = await bridgeService.call(CALIPER_METHODS.GET_CONTEXT, {});
1904
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
1905
+ } catch (error) {
1906
+ return {
1907
+ content: [
1908
+ {
1909
+ type: "text",
1910
+ text: `Get Context failed: ${error instanceof Error ? error.message : String(error)}`
1911
+ }
1912
+ ],
1913
+ isError: true
1914
+ };
1915
+ }
1916
+ }
1917
+ );
1918
+ this.server.registerTool(
1919
+ "caliper_check_contrast",
1920
+ {
1921
+ description: `Check WCAG 2.1 contrast ratio between two colors.
1922
+
1923
+ Returns the contrast ratio (1-21) and pass/fail status for:
1924
+ - AA Normal Text (4.5:1)
1925
+ - AA Large Text (3:1)
1926
+ - AAA Normal Text (7:1)
1927
+ - AAA Large Text (4.5:1)`,
1928
+ inputSchema: zod.z.object({
1929
+ foreground: zod.z.string().describe("Foreground color (any CSS format: hex, rgb, hsl, oklch, etc.)"),
1930
+ background: zod.z.string().describe("Background color (any CSS format: hex, rgb, hsl, oklch, etc.)")
1931
+ })
1932
+ },
1933
+ async ({ foreground, background }) => {
1934
+ try {
1935
+ const fg = parseColor(foreground);
1936
+ const bg = parseColor(background);
1937
+ const ratio = calculateContrastRatio(fg, bg);
1938
+ return {
1939
+ content: [
1940
+ {
1941
+ type: "text",
1942
+ text: JSON.stringify(
1943
+ {
1944
+ foreground,
1945
+ background,
1946
+ ratio: Number(ratio.toFixed(2)),
1947
+ passAA: ratio >= 4.5,
1948
+ passAALarge: ratio >= 3,
1949
+ passAAA: ratio >= 7,
1950
+ passAAALarge: ratio >= 4.5
1951
+ },
1952
+ null,
1953
+ 2
1954
+ )
1955
+ }
1956
+ ]
1957
+ };
1958
+ } catch (error) {
1959
+ return {
1960
+ content: [
1961
+ {
1962
+ type: "text",
1963
+ text: `Contrast check failed: ${error instanceof Error ? error.message : String(error)}`
1964
+ }
1965
+ ],
1966
+ isError: true
1967
+ };
1968
+ }
1969
+ }
1970
+ );
1971
+ this.server.registerTool(
1972
+ "caliper_delta_e",
1973
+ {
1974
+ description: `Calculate perceptual color difference (Delta E) between two colors using Oklab.
1975
+
1976
+ Returns the Delta E value and a human-readable interpretation:
1977
+ - < 0.02: Imperceptible
1978
+ - 0.02-0.05: Just noticeable
1979
+ - 0.05-0.1: Noticeable
1980
+ - 0.1-0.3: Distinct
1981
+ - > 0.3: Very different`,
1982
+ inputSchema: zod.z.object({
1983
+ color1: zod.z.string().describe("First color (any CSS format: hex, rgb, hsl, oklch, etc.)"),
1984
+ color2: zod.z.string().describe("Second color (any CSS format: hex, rgb, hsl, oklch, etc.)")
1985
+ })
1986
+ },
1987
+ async ({ color1, color2 }) => {
1988
+ try {
1989
+ const c1 = parseColor(color1);
1990
+ const c2 = parseColor(color2);
1991
+ const deltaE = calculateDeltaE(c1, c2);
1992
+ let interpretation;
1993
+ if (deltaE < 0.02) interpretation = "Imperceptible (< 0.02)";
1994
+ else if (deltaE < 0.05) interpretation = "Just noticeable (0.02-0.05)";
1995
+ else if (deltaE < 0.1) interpretation = "Noticeable (0.05-0.1)";
1996
+ else if (deltaE < 0.3) interpretation = "Distinct (0.1-0.3)";
1997
+ else interpretation = "Very different (> 0.3)";
1998
+ return {
1999
+ content: [
2000
+ {
2001
+ type: "text",
2002
+ text: JSON.stringify(
2003
+ {
2004
+ color1,
2005
+ color2,
2006
+ deltaE: Number(deltaE.toFixed(4)),
2007
+ interpretation
2008
+ },
2009
+ null,
2010
+ 2
2011
+ )
2012
+ }
2013
+ ]
2014
+ };
2015
+ } catch (error) {
2016
+ return {
2017
+ content: [
2018
+ {
2019
+ type: "text",
2020
+ text: `Delta E calculation failed: ${error instanceof Error ? error.message : String(error)}`
2021
+ }
2022
+ ],
2023
+ isError: true
2024
+ };
2025
+ }
2026
+ }
2027
+ );
2028
+ }
2029
+ registerResources() {
2030
+ this.server.registerResource(
2031
+ "caliper-tabs",
2032
+ "caliper://tabs",
2033
+ { description: "List of all browser tabs currently connected to the bridge." },
2034
+ async () => {
2035
+ const tabs = tabManager.getAllTabs().map((tab) => ({
2036
+ id: tab.id,
2037
+ title: tab.title,
2038
+ url: tab.url,
2039
+ isActive: tab.id === tabManager.getActiveTab()?.id
2040
+ }));
2041
+ return {
2042
+ contents: [
2043
+ {
2044
+ uri: "caliper://tabs",
2045
+ mimeType: "application/json",
2046
+ text: JSON.stringify(tabs, null, 2)
2047
+ }
2048
+ ]
2049
+ };
2050
+ }
2051
+ );
2052
+ this.server.registerResource(
2053
+ "caliper-state",
2054
+ "caliper://state",
2055
+ {
2056
+ description: `Provides a real-time stream of the active browser environment. This is your primary source for "listening" to what the user is doing.
2057
+
2058
+ It contains:
2059
+ - viewport: Current scroll positions and dimensions.
2060
+ - activeSelection: Visual metadata of the currently selected element.
2061
+ - selectionFingerprint: A stable JSON identifier (agentId, tag, text content) for the active selection. Use the 'selector' property from this object as input for 'caliper_inspect' or 'caliper_walk_and_measure' to perform high-precision audits on what the user just picked.
2062
+ - lastMeasurement & measurementFingerprint: Context for the most recent distance measurement between two elements.
2063
+
2064
+ USE CASE: When you receive a notification that this resource has updated, read it to understand the user's current focus. If a 'selectionFingerprint' is present, you can immediately offer to 'Inspect' or 'Audit' that element without asking the user for a selector.`
2065
+ },
2066
+ async () => {
2067
+ return {
2068
+ contents: [
2069
+ {
2070
+ uri: "caliper://state",
2071
+ mimeType: "application/json",
2072
+ text: JSON.stringify(this.lastState, null, 2)
2073
+ }
2074
+ ]
2075
+ };
2076
+ }
2077
+ );
2078
+ bridgeService.on(BRIDGE_EVENTS.STATE, (state) => {
2079
+ this.lastState = state;
2080
+ this.server.server.sendResourceUpdated({ uri: "caliper://state" }).catch((error) => {
2081
+ logger4.warn("Failed to notify resource update", error);
2082
+ });
2083
+ });
2084
+ }
2085
+ registerPrompts() {
2086
+ this.server.registerPrompt(
2087
+ "caliper-selector-audit",
2088
+ {
2089
+ description: "Perform a comprehensive, structured audit of a specific selector: from source discovery to precision styling analysis.",
2090
+ argsSchema: {
2091
+ selector: zod.z.string().describe(
2092
+ "The Caliper Selector (JSON Fingerprint, Caliper Agent ID (caliper-***), or CSS selector) to audit. PRIORITIZE JSON Fingerprint for maximum stabilization."
2093
+ )
2094
+ }
2095
+ },
2096
+ async ({ selector }) => ({
2097
+ messages: [
2098
+ {
2099
+ role: "user",
2100
+ content: {
2101
+ type: "text",
2102
+ text: `## CALIPER SELECTOR AUDIT: ${selector}
2103
+
2104
+ You are about to perform a structured, comprehensive audit of the element '${selector}'. Follow these phases precisely.
2105
+
2106
+ ### PHASE 1: CONTEXT GATHERING
2107
+ 1. **Deep Inspection**: Call 'caliper_inspect' to get all computed styles, colors, typography, and **sourceHints**.
2108
+ 2. **Hierarchy Walk**: Call 'caliper_walk_and_measure' with maxDepth: 5 to understand its position in the tree, its children, and the spacing (gaps) to its neighbors.
2109
+ 3. **Source Discovery**: Use the \`suggestedGrep\` from \`sourceHints\` (or another stable anchor) to locate the exact source file and line number in the codebase.
2110
+
2111
+ ### PHASE 2: ANALYSIS & CONSISTENCY
2112
+ 4. **Spacing Check**: Analyze if padding, margins, and gaps follow a consistent scale (e.g., multiples of 4px/8px).
2113
+ 5. **System Alignment**: Check if font sizes, weights, and colors match the project's design system or established patterns.
2114
+ 6. **Intent Audit**: Identify hardcoded "magic numbers", layout inconsistencies, or unusual offsets.
2115
+
2116
+ ### PHASE 3: REMEDIATION
2117
+ 7. **Recommendations**: Provide a clear report of discrepancies and the exact CSS/Code fixes needed.
2118
+ 8. **Verification**: After applying changes, re-run 'caliper_inspect' or 'caliper_walk_and_measure' to confirm parity.
2119
+
2120
+ ---
2121
+ BEGIN PHASE 1 NOW. Do not skip any steps.`
2122
+ }
2123
+ }
2124
+ ]
2125
+ })
2126
+ );
2127
+ this.server.registerPrompt(
2128
+ "caliper-selectors-compare",
2129
+ {
2130
+ description: "Compare two selections (A and B) to learn from A and fix B. Supports cross-tab comparisons.",
2131
+ argsSchema: {
2132
+ selectorA: zod.z.string().describe(
2133
+ "Caliper Selector for the REFERENCE element. PRIORITIZE JSON Fingerprint, Caliper Agent ID (caliper-***), or CSS selector for stabilization."
2134
+ ),
2135
+ selectorB: zod.z.string().describe(
2136
+ "Caliper Selector for the TARGET element. PRIORITIZE JSON Fingerprint, Caliper Agent ID (caliper-***), or CSS selector for stabilization."
2137
+ ),
2138
+ tabIdA: zod.z.string().optional().describe("Tab ID containing Selection A (use caliper_list_tabs to find IDs)"),
2139
+ tabIdB: zod.z.string().optional().describe("Tab ID containing Selection B (defaults to same tab as A)"),
2140
+ properties: zod.z.array(zod.z.enum(["spacing", "typography", "colors", "layout", "all"])).default(["all"]).describe("Which properties to compare")
2141
+ }
2142
+ },
2143
+ async ({ selectorA, selectorB, tabIdA, tabIdB, properties }) => ({
2144
+ messages: [
2145
+ {
2146
+ role: "user",
2147
+ content: {
2148
+ type: "text",
2149
+ text: `## CALIPER SELECTORS COMPARISON
2150
+
2151
+ You are comparing TWO elements to understand the styling of one (A) and apply corrections to the other (B).
2152
+
2153
+ **Inputs:**
2154
+ - Selection A (REFERENCE): \`${selectorA}\`${tabIdA ? ` in Tab: ${tabIdA}` : ""}
2155
+ - Selection B (TARGET): \`${selectorB}\`${tabIdB ? ` in Tab: ${tabIdB}` : " (same tab as A)"}
2156
+ - Properties to Compare: ${properties.join(", ")}
2157
+
2158
+ ---
2159
+
2160
+ ### IMPORTANT: TAB MANAGEMENT
2161
+
2162
+ ${tabIdA || tabIdB ? `
2163
+ You are working across multiple tabs. The agent-ID is **tab-specific** - if you send a command to the wrong tab, it will fail.
2164
+
2165
+ **Before Each Command:**
2166
+ 1. Use \`caliper_list_tabs\` to see all connected tabs
2167
+ 2. Use \`caliper_switch_tab\` to switch to the correct tab BEFORE calling walk/inspect
2168
+ ` : `
2169
+ Both selections are on the SAME tab. No tab switching required.
2170
+ `}
2171
+
2172
+ ### PHASE 1: WALK SELECTION A (REFERENCE)
2173
+
2174
+ ${tabIdA ? `1. **Switch to Tab A**
2175
+ Call \`caliper_switch_tab\` with tabId: "${tabIdA}"
2176
+
2177
+ 2. ` : "1. "}**Walk and Measure A**
2178
+ Call \`caliper_walk_and_measure\` with:
2179
+ - selector: "${selectorA}"
2180
+ - maxDepth: 5
2181
+
2182
+ Record A's styles as the REFERENCE:
2183
+ ${properties.includes("all") || properties.includes("spacing") ? "- padding, margin, gap values" : ""}
2184
+ ${properties.includes("all") || properties.includes("typography") ? "- font-size, font-weight, line-height" : ""}
2185
+ ${properties.includes("all") || properties.includes("colors") ? "- background-color, color, border-color" : ""}
2186
+ ${properties.includes("all") || properties.includes("layout") ? "- display, flex-direction, justify-content, align-items" : ""}
2187
+
2188
+ ### PHASE 2: WALK SELECTION B (TARGET)
2189
+
2190
+ ${tabIdB ? `${tabIdA ? "3" : "2"}. **Switch to Tab B**
2191
+ Call \`caliper_switch_tab\` with tabId: "${tabIdB}"
2192
+
2193
+ ${tabIdA ? "4" : "3"}. ` : `${tabIdA ? "3" : "2"}. `}**Walk and Measure B**
2194
+ Call \`caliper_walk_and_measure\` with:
2195
+ - selector: "${selectorB}"
2196
+ - maxDepth: 5
2197
+
2198
+ ### PHASE 3: COMPARE AND ANALYZE
2199
+
2200
+ ${tabIdB ? tabIdA ? "5" : "4" : tabIdA ? "4" : "3"}. **Generate Comparison Report**
2201
+ For each property category, list:
2202
+ - Property name
2203
+ - Value in A (REFERENCE)
2204
+ - Value in B (TARGET)
2205
+ - Delta (difference)
2206
+ - Recommended fix for B
2207
+
2208
+ Focus on making B match A's styling approach.
2209
+
2210
+ ### PHASE 4: FIND SOURCE FOR B
2211
+
2212
+ ${tabIdB ? tabIdA ? "6" : "5" : tabIdA ? "5" : "4"}. **Locate B's Source**
2213
+ ${tabIdB ? `Switch back to Tab B if needed.` : ""}
2214
+ Use internal agent search (grep) with B's best anchor.
2215
+ Get the exact file path and line number.
2216
+
2217
+ ### PHASE 5: APPLY FIXES TO B
2218
+
2219
+ ${tabIdB ? tabIdA ? "7" : "6" : tabIdA ? "6" : "5"}. **Generate CSS Fixes**
2220
+ Create CSS rules that will make B match A:
2221
+ \`\`\`css
2222
+ ${selectorB.startsWith("#") ? selectorB : `.${selectorB}`} {
2223
+ /* Fixes to match Selection A */
2224
+ }
2225
+ \`\`\`
2226
+
2227
+ ${tabIdB ? tabIdA ? "8" : "7" : tabIdA ? "7" : "6"}. **Edit Source File**
2228
+ Apply the CSS fixes to B's source file.
2229
+
2230
+ ${tabIdB ? tabIdA ? "9" : "8" : tabIdA ? "8" : "7"}. **Verify**
2231
+ Re-run \`caliper_walk_and_measure\` on B to confirm the fix.
2232
+
2233
+ ---
2234
+ **BEGIN PHASE 1 NOW. Remember to switch tabs before each walk command if working across tabs.**`
2235
+ }
2236
+ }
2237
+ ]
2238
+ })
2239
+ );
2240
+ }
2241
+ async start() {
2242
+ try {
2243
+ await bridgeService.start(this.port);
2244
+ } catch (err) {
2245
+ logger4.error("Bridge startup failed:", err);
2246
+ }
2247
+ try {
2248
+ const transport = new stdio_js.StdioServerTransport();
2249
+ await this.server.connect(transport);
2250
+ logger4.info("Server running on STDIO transport");
2251
+ } catch (err) {
2252
+ logger4.error("MCP Startup failed:", err);
2253
+ process.exit(1);
2254
+ }
2255
+ }
2256
+ async stop() {
2257
+ await bridgeService.stop();
2258
+ logger4.info("Server stopped.");
2259
+ }
2260
+ };
2261
+
2262
+ // ../mcp-server/src/index.ts
2263
+ function parseArgs() {
2264
+ const args = process.argv.slice(2);
2265
+ let port2 = DEFAULT_BRIDGE_PORT;
2266
+ for (let i = 0; i < args.length; i++) {
2267
+ if (args[i] === "--port" || args[i] === "-p") {
2268
+ const nextArg = args[i + 1];
2269
+ if (nextArg) {
2270
+ const val = parseInt(nextArg, 10);
2271
+ if (!isNaN(val) && val > 0 && val < 65536) {
2272
+ port2 = val;
2273
+ }
2274
+ }
2275
+ i++;
2276
+ } else if (args[i] === "--help" || args[i] === "-h") {
2277
+ console.error(`
2278
+ Caliper MCP Server - AI-powered UI measurement tool
2279
+
2280
+ Usage:
2281
+ npx @oyerinde/caliper-mcp [options]
2282
+
2283
+ Options:
2284
+ -p, --port <number> WebSocket relay port (default: ${DEFAULT_BRIDGE_PORT})
2285
+ -d, --docs Open documentation: https://caliper.danieloyerinde.com/
2286
+ -h, --help Show this help message
2287
+ `);
2288
+ process.exit(0);
2289
+ } else if (args[i] === "--docs" || args[i] === "-d") {
2290
+ console.log("\n\u{1F4DA} View Documentation: https://caliper.danieloyerinde.com/\n");
2291
+ process.exit(0);
2292
+ }
2293
+ }
2294
+ return { port: port2 };
2295
+ }
2296
+ var { port } = parseArgs();
2297
+ var server = new CaliperMcpServer(port);
2298
+ server.start();
2299
+ var shutdown = async () => {
2300
+ await server.stop();
2301
+ process.exit(0);
2302
+ };
2303
+ process.on("SIGINT", shutdown);
2304
+ process.on("SIGTERM", shutdown);
2305
+ process.stdin.on("end", shutdown);