react-devtools-bridge 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1099 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { EventEmitter } from 'events';
3
+
4
+ /**
5
+ * React DevTools MCP - Type Definitions
6
+ *
7
+ * Complete TypeScript types for the DevTools bridge protocol
8
+ * and MCP tool interfaces.
9
+ */
10
+ interface ConnectionConfig {
11
+ host: string;
12
+ port: number;
13
+ timeout: number;
14
+ autoReconnect: boolean;
15
+ }
16
+ interface ProtocolCapabilities {
17
+ bridgeProtocolVersion: number;
18
+ backendVersion: string | null;
19
+ supportsInspectElementPaths: boolean;
20
+ supportsProfilingChangeDescriptions: boolean;
21
+ supportsTimeline: boolean;
22
+ supportsNativeStyleEditor: boolean;
23
+ supportsErrorBoundaryTesting: boolean;
24
+ supportsTraceUpdates: boolean;
25
+ isBackendStorageAPISupported: boolean;
26
+ isSynchronousXHRSupported: boolean;
27
+ }
28
+ interface Renderer {
29
+ id: number;
30
+ version: string;
31
+ packageName: string;
32
+ rootIDs: Set<number>;
33
+ elementIDs: Set<number>;
34
+ }
35
+ type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error';
36
+ interface ConnectionStatus {
37
+ state: ConnectionState;
38
+ rendererCount: number;
39
+ reactVersion: string | null;
40
+ error: string | null;
41
+ }
42
+ type ElementType = 'class' | 'function' | 'forward_ref' | 'memo' | 'context' | 'suspense' | 'profiler' | 'host' | 'root' | 'portal' | 'fragment' | 'lazy' | 'cache' | 'activity' | 'virtual';
43
+ interface Element {
44
+ id: number;
45
+ parentID: number | null;
46
+ displayName: string;
47
+ type: ElementType;
48
+ key: string | number | null;
49
+ depth: number;
50
+ weight: number;
51
+ ownerID: number | null;
52
+ hasChildren: boolean;
53
+ env: string | null;
54
+ hocDisplayNames: string[] | null;
55
+ }
56
+ interface SerializedElement {
57
+ id: number;
58
+ displayName: string;
59
+ type: ElementType;
60
+ key: string | number | null;
61
+ env: string | null;
62
+ hocDisplayNames: string[] | null;
63
+ }
64
+ interface InspectedElement {
65
+ id: number;
66
+ displayName: string;
67
+ type: ElementType;
68
+ key: string | number | null;
69
+ props: DehydratedData | null;
70
+ state: DehydratedData | null;
71
+ hooks: HooksTree | null;
72
+ context: DehydratedData | null;
73
+ source: SourceLocation | null;
74
+ stack: string | null;
75
+ owners: SerializedElement[] | null;
76
+ rootType: string | null;
77
+ isErrored: boolean;
78
+ errors: Array<[string, number]>;
79
+ warnings: Array<[string, number]>;
80
+ isSuspended: boolean | null;
81
+ suspendedBy: SuspenseInfo | null;
82
+ canToggleError: boolean;
83
+ canToggleSuspense: boolean;
84
+ canEditHooks: boolean;
85
+ canEditFunctionProps: boolean;
86
+ canEditHooksAndDeletePaths: boolean;
87
+ canEditHooksAndRenamePaths: boolean;
88
+ hasLegacyContext: boolean;
89
+ env: string | null;
90
+ rendererPackageName: string | null;
91
+ rendererVersion: string | null;
92
+ nativeTag: number | null;
93
+ }
94
+ /**
95
+ * InspectElementPayload - Response from backend for element inspection.
96
+ *
97
+ * Official React DevTools protocol includes:
98
+ * - `id`: The element ID being inspected
99
+ * - `responseID`: Echoes back the requestID from the request (used for correlation)
100
+ *
101
+ * Note: Some backends may omit responseID, so we handle fallback to element id.
102
+ */
103
+ type InspectElementPayload = {
104
+ type: 'full-data';
105
+ id: number;
106
+ responseID?: number;
107
+ element: InspectedElement;
108
+ } | {
109
+ type: 'hydrated-path';
110
+ id: number;
111
+ responseID?: number;
112
+ path: Array<string | number>;
113
+ value: unknown;
114
+ } | {
115
+ type: 'no-change';
116
+ id: number;
117
+ responseID?: number;
118
+ } | {
119
+ type: 'not-found';
120
+ id: number;
121
+ responseID?: number;
122
+ } | {
123
+ type: 'error';
124
+ id: number;
125
+ responseID?: number;
126
+ errorType: string;
127
+ message: string;
128
+ stack?: string;
129
+ };
130
+ interface HooksTree {
131
+ hooks: HookInfo[];
132
+ }
133
+ interface HookInfo {
134
+ index: number;
135
+ name: HookType;
136
+ value: DehydratedData;
137
+ subHooks: HookInfo[];
138
+ isEditable: boolean;
139
+ debugLabel: string | null;
140
+ }
141
+ type HookType = 'useState' | 'useReducer' | 'useContext' | 'useRef' | 'useEffect' | 'useInsertionEffect' | 'useLayoutEffect' | 'useCallback' | 'useMemo' | 'useImperativeHandle' | 'useDebugValue' | 'useDeferredValue' | 'useTransition' | 'useSyncExternalStore' | 'useId' | 'useCacheRefresh' | 'useOptimistic' | 'useFormStatus' | 'useActionState' | 'use' | 'custom';
142
+ interface SourceLocation {
143
+ fileName: string;
144
+ lineNumber: number;
145
+ columnNumber?: number;
146
+ }
147
+ interface SuspenseInfo {
148
+ resource: string | null;
149
+ source: SourceLocation | null;
150
+ }
151
+ interface ProfilingData {
152
+ roots: ProfilingDataForRoot[];
153
+ timelineData: TimelineData | null;
154
+ }
155
+ interface ProfilingDataForRoot {
156
+ rootID: number;
157
+ displayName: string;
158
+ commits: CommitData[];
159
+ initialTreeBaseDurations: Map<number, number>;
160
+ }
161
+ interface CommitData {
162
+ timestamp: number;
163
+ duration: number;
164
+ effectDuration: number;
165
+ passiveEffectDuration: number;
166
+ priorityLevel: string | null;
167
+ fiberActualDurations: Map<number, number>;
168
+ fiberSelfDurations: Map<number, number>;
169
+ updaters: SerializedElement[] | null;
170
+ changeDescriptions: Map<number, ChangeDescription> | null;
171
+ }
172
+ interface ChangeDescription {
173
+ isFirstMount: boolean;
174
+ props: string[] | null;
175
+ state: string[] | null;
176
+ hooks: number[] | null;
177
+ context: boolean | null;
178
+ didHooksChange: boolean;
179
+ }
180
+ interface TimelineData {
181
+ events: TimelineEvent[];
182
+ }
183
+ interface TimelineEvent {
184
+ type: string;
185
+ timestamp: number;
186
+ duration?: number;
187
+ data?: unknown;
188
+ }
189
+ interface DehydratedData {
190
+ cleaned: CleanedValue[];
191
+ data: unknown;
192
+ unserializable: Array<{
193
+ path: Array<string | number>;
194
+ reason: string;
195
+ }>;
196
+ }
197
+ type CleanedValue = ['function', string] | ['symbol', string] | ['circular'] | ['date', string] | ['regexp', string] | ['map', string] | ['set', string] | ['iterator', string] | ['html_element', string] | ['html_all_collection'] | ['typed_array', string] | ['array_buffer'] | ['data_view'] | ['infinity'] | ['nan'] | ['undefined'] | ['unknown'];
198
+ interface RootTree {
199
+ rootID: number;
200
+ displayName: string;
201
+ elements: Element[];
202
+ }
203
+ interface ComponentFilter {
204
+ type: 'name' | 'location' | 'type' | 'hoc';
205
+ value: string;
206
+ isEnabled: boolean;
207
+ isRegex?: boolean;
208
+ }
209
+ type ErrorCode = 'NOT_CONNECTED' | 'ELEMENT_NOT_FOUND' | 'NOT_EDITABLE' | 'INVALID_PATH' | 'SERIALIZATION_ERROR' | 'TIMEOUT' | 'INTERNAL_ERROR';
210
+ interface MCPError {
211
+ code: ErrorCode;
212
+ message: string;
213
+ data?: unknown;
214
+ }
215
+ type BridgeOutgoing = {
216
+ type: 'inspectElement';
217
+ id: number;
218
+ rendererID: number;
219
+ forceFullData?: boolean;
220
+ path?: Array<string | number> | null;
221
+ } | {
222
+ type: 'getComponentTree';
223
+ } | {
224
+ type: 'searchForComponent';
225
+ query: string;
226
+ caseSensitive?: boolean;
227
+ isRegex?: boolean;
228
+ } | {
229
+ type: 'getOwnersList';
230
+ id: number;
231
+ } | {
232
+ type: 'highlightHostInstance';
233
+ id: number;
234
+ } | {
235
+ type: 'clearHostInstanceHighlight';
236
+ } | {
237
+ type: 'scrollToHostInstance';
238
+ id: number;
239
+ } | {
240
+ type: 'logElementToConsole';
241
+ id: number;
242
+ } | {
243
+ type: 'storeAsGlobal';
244
+ id: number;
245
+ path: Array<string | number>;
246
+ count: number;
247
+ } | {
248
+ type: 'viewElementSource';
249
+ id: number;
250
+ } | {
251
+ type: 'overrideValueAtPath';
252
+ target: OverrideTarget;
253
+ id: number;
254
+ hookIndex?: number;
255
+ path: Array<string | number>;
256
+ value: unknown;
257
+ } | {
258
+ type: 'deletePath';
259
+ target: OverrideTarget;
260
+ id: number;
261
+ hookIndex?: number;
262
+ path: Array<string | number>;
263
+ } | {
264
+ type: 'renamePath';
265
+ target: OverrideTarget;
266
+ id: number;
267
+ hookIndex?: number;
268
+ path: Array<string | number>;
269
+ oldKey: string;
270
+ newKey: string;
271
+ } | {
272
+ type: 'overrideError';
273
+ id: number;
274
+ isErrored: boolean;
275
+ } | {
276
+ type: 'overrideSuspense';
277
+ id: number;
278
+ isSuspended: boolean;
279
+ } | {
280
+ type: 'startProfiling';
281
+ recordTimeline?: boolean;
282
+ recordChangeDescriptions?: boolean;
283
+ } | {
284
+ type: 'stopProfiling';
285
+ } | {
286
+ type: 'getProfilingData';
287
+ } | {
288
+ type: 'getProfilingStatus';
289
+ } | {
290
+ type: 'updateComponentFilters';
291
+ filters: ComponentFilter[];
292
+ } | {
293
+ type: 'setTraceUpdatesEnabled';
294
+ enabled: boolean;
295
+ } | {
296
+ type: 'clearErrorsAndWarnings';
297
+ } | {
298
+ type: 'clearErrorsForElementID';
299
+ id: number;
300
+ } | {
301
+ type: 'clearWarningsForElementID';
302
+ id: number;
303
+ } | {
304
+ type: 'NativeStyleEditor_measure';
305
+ id: number;
306
+ } | {
307
+ type: 'NativeStyleEditor_setValue';
308
+ id: number;
309
+ property: string;
310
+ value: unknown;
311
+ } | {
312
+ type: 'isBackendStorageAPISupported';
313
+ } | {
314
+ type: 'isSynchronousXHRSupported';
315
+ } | {
316
+ type: 'getSupportedRendererInterfaces';
317
+ } | {
318
+ type: 'saveToClipboard';
319
+ value: string;
320
+ } | {
321
+ type: 'viewAttributeSource';
322
+ id: number;
323
+ rendererID: number;
324
+ path: Array<string | number>;
325
+ } | {
326
+ type: 'overrideContext';
327
+ id: number;
328
+ rendererID: number;
329
+ path: Array<string | number>;
330
+ value: unknown;
331
+ } | {
332
+ type: 'startInspectingNative';
333
+ } | {
334
+ type: 'stopInspectingNative';
335
+ selectNextElement: boolean;
336
+ } | {
337
+ type: 'captureScreenshot';
338
+ id: number;
339
+ rendererID: number;
340
+ };
341
+ type OverrideTarget = 'props' | 'state' | 'hooks' | 'context';
342
+ /**
343
+ * BridgeIncoming - Messages from React DevTools backend.
344
+ *
345
+ * Response correlation: Official protocol uses `responseID` to echo back the `requestID`.
346
+ * We support both `responseID` (official) and fallback to element `id` for compatibility.
347
+ */
348
+ type BridgeIncoming = {
349
+ type: 'inspectedElement';
350
+ payload: InspectElementPayload;
351
+ } | {
352
+ type: 'operations';
353
+ operations: number[];
354
+ } | {
355
+ type: 'ownersList';
356
+ id: number;
357
+ responseID?: number;
358
+ owners: SerializedElement[];
359
+ } | {
360
+ type: 'profilingData';
361
+ data: ProfilingData;
362
+ } | {
363
+ type: 'profilingStatus';
364
+ isProfiling: boolean;
365
+ } | {
366
+ type: 'backendVersion';
367
+ version: string;
368
+ } | {
369
+ type: 'bridgeProtocol';
370
+ version: number;
371
+ } | {
372
+ type: 'NativeStyleEditor_styleAndLayout';
373
+ id: number;
374
+ responseID?: number;
375
+ style: Record<string, unknown>;
376
+ layout: {
377
+ x: number;
378
+ y: number;
379
+ width: number;
380
+ height: number;
381
+ };
382
+ } | {
383
+ type: 'isBackendStorageAPISupported';
384
+ isSupported: boolean;
385
+ } | {
386
+ type: 'isSynchronousXHRSupported';
387
+ isSupported: boolean;
388
+ } | {
389
+ type: 'getSupportedRendererInterfaces';
390
+ rendererInterfaces: RendererInterface[];
391
+ } | {
392
+ type: 'updateComponentFilters';
393
+ } | {
394
+ type: 'savedToClipboard';
395
+ responseID?: number;
396
+ } | {
397
+ type: 'viewAttributeSourceResult';
398
+ id: number;
399
+ responseID?: number;
400
+ source: SourceLocation | null;
401
+ } | {
402
+ type: 'overrideContextResult';
403
+ id: number;
404
+ responseID?: number;
405
+ success: boolean;
406
+ } | {
407
+ type: 'inspectingNativeStarted';
408
+ } | {
409
+ type: 'inspectingNativeStopped';
410
+ elementID: number | null;
411
+ } | {
412
+ type: 'captureScreenshotResult';
413
+ id: number;
414
+ responseID?: number;
415
+ screenshot: string | null;
416
+ };
417
+ interface RendererInterface {
418
+ id: number;
419
+ renderer: string;
420
+ version: string;
421
+ bundleType: 'development' | 'production';
422
+ hasOwnerMetadata: boolean;
423
+ hasManyWarnings: boolean;
424
+ }
425
+ interface ConnectParams {
426
+ host?: string;
427
+ port?: number;
428
+ timeout?: number;
429
+ }
430
+ interface ConnectResult {
431
+ success: boolean;
432
+ status: ConnectionStatus;
433
+ }
434
+ interface GetComponentTreeParams {
435
+ rootID?: number;
436
+ maxDepth?: number;
437
+ }
438
+ interface GetComponentTreeResult {
439
+ roots: RootTree[];
440
+ }
441
+ interface SearchComponentsParams {
442
+ query: string;
443
+ caseSensitive?: boolean;
444
+ isRegex?: boolean;
445
+ }
446
+ interface SearchComponentsResult {
447
+ matches: Element[];
448
+ totalCount: number;
449
+ }
450
+ interface InspectElementParams {
451
+ id: number;
452
+ paths?: Array<Array<string | number>>;
453
+ }
454
+ interface InspectElementResult {
455
+ success: boolean;
456
+ element: InspectedElement | null;
457
+ error: {
458
+ type: string;
459
+ message: string;
460
+ stack?: string;
461
+ } | null;
462
+ }
463
+ interface GetOwnersListParams {
464
+ id: number;
465
+ }
466
+ interface GetOwnersListResult {
467
+ owners: SerializedElement[];
468
+ }
469
+ interface OverrideParams {
470
+ id: number;
471
+ path: Array<string | number>;
472
+ value: unknown;
473
+ }
474
+ interface OverrideHooksParams extends OverrideParams {
475
+ hookIndex: number;
476
+ }
477
+ interface OverrideResult {
478
+ success: boolean;
479
+ error?: string;
480
+ }
481
+ interface DeletePathParams {
482
+ id: number;
483
+ target: OverrideTarget;
484
+ hookIndex?: number;
485
+ path: Array<string | number>;
486
+ }
487
+ interface RenamePathParams {
488
+ id: number;
489
+ target: OverrideTarget;
490
+ hookIndex?: number;
491
+ path: Array<string | number>;
492
+ oldKey: string;
493
+ newKey: string;
494
+ }
495
+ interface StartProfilingParams {
496
+ recordTimeline?: boolean;
497
+ recordChangeDescriptions?: boolean;
498
+ }
499
+ interface StartProfilingResult {
500
+ success: boolean;
501
+ requiresReload: boolean;
502
+ }
503
+ interface StopProfilingResult {
504
+ success: boolean;
505
+ data: ProfilingData | null;
506
+ }
507
+ interface GetProfilingStatusResult {
508
+ isProfiling: boolean;
509
+ recordTimeline: boolean;
510
+ recordChangeDescriptions: boolean;
511
+ }
512
+ interface GetErrorsAndWarningsResult {
513
+ errors: Map<number, Array<[string, number]>>;
514
+ warnings: Map<number, Array<[string, number]>>;
515
+ }
516
+ interface ToggleErrorParams {
517
+ id: number;
518
+ isErrored: boolean;
519
+ }
520
+ interface ToggleSuspenseParams {
521
+ id: number;
522
+ isSuspended: boolean;
523
+ }
524
+ interface HighlightElementParams {
525
+ id: number;
526
+ duration?: number;
527
+ }
528
+ interface ScrollToElementParams {
529
+ id: number;
530
+ }
531
+ interface LogToConsoleParams {
532
+ id: number;
533
+ }
534
+ interface StoreAsGlobalParams {
535
+ id: number;
536
+ path: Array<string | number>;
537
+ globalName: string;
538
+ }
539
+ interface GetNativeStyleParams {
540
+ id: number;
541
+ }
542
+ interface GetNativeStyleResult {
543
+ style: Record<string, unknown> | null;
544
+ layout: {
545
+ x: number;
546
+ y: number;
547
+ width: number;
548
+ height: number;
549
+ } | null;
550
+ }
551
+ interface SetNativeStyleParams {
552
+ id: number;
553
+ property: string;
554
+ value: unknown;
555
+ }
556
+
557
+ /**
558
+ * Logging Infrastructure
559
+ *
560
+ * Configurable structured logging for DevTools bridge and MCP server.
561
+ */
562
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
563
+ interface LogEntry {
564
+ level: LogLevel;
565
+ message: string;
566
+ timestamp: number;
567
+ prefix?: string;
568
+ meta?: Record<string, unknown>;
569
+ }
570
+ interface Logger {
571
+ debug(message: string, meta?: Record<string, unknown>): void;
572
+ info(message: string, meta?: Record<string, unknown>): void;
573
+ warn(message: string, meta?: Record<string, unknown>): void;
574
+ error(message: string, meta?: Record<string, unknown>): void;
575
+ child(prefix: string): Logger;
576
+ }
577
+ interface LoggerOptions {
578
+ level: LogLevel;
579
+ prefix?: string;
580
+ output?: (entry: LogEntry) => void;
581
+ }
582
+ /**
583
+ * Create a logger instance
584
+ */
585
+ declare function createLogger(options?: Partial<LoggerOptions>): Logger;
586
+ /**
587
+ * No-op logger for testing or when logging disabled
588
+ */
589
+ declare const noopLogger: Logger;
590
+ /**
591
+ * Get log level from environment
592
+ */
593
+ declare function getLogLevelFromEnv(): LogLevel;
594
+
595
+ /**
596
+ * React DevTools Bridge
597
+ *
598
+ * Manages WebSocket connection to React DevTools backend and maintains
599
+ * component tree state. Translates between MCP requests and DevTools protocol.
600
+ *
601
+ * Phase 1 Fixes Implemented:
602
+ * - Logging infrastructure
603
+ * - Connection race condition fix (deduplication)
604
+ * - Automatic reconnection with exponential backoff
605
+ * - Bounds checking in operations parser
606
+ * - Memory leak fix in request handling
607
+ * - Request/response ID correlation
608
+ */
609
+
610
+ interface BridgeOptions extends Partial<ConnectionConfig> {
611
+ logger?: Logger;
612
+ }
613
+ declare class DevToolsBridge extends EventEmitter {
614
+ private config;
615
+ private logger;
616
+ private ws;
617
+ private state;
618
+ private error;
619
+ private connectPromise;
620
+ private reconnectAttempts;
621
+ private reconnectTimer;
622
+ private manualDisconnect;
623
+ private elements;
624
+ private rootIDs;
625
+ private renderers;
626
+ private elementToRenderer;
627
+ private pendingRequests;
628
+ private requestIdCounter;
629
+ private staleRequestCleanupTimer;
630
+ /**
631
+ * Unified fallback key mapping for request/response correlation.
632
+ * Maps element-based keys to requestID-based keys.
633
+ *
634
+ * Flow:
635
+ * 1. Request sent with requestID=123 for elementID=456
636
+ * 2. Store mapping: "inspect_456" -> "inspect_123"
637
+ * 3. Response arrives with responseID=123 OR just id=456
638
+ * 4. Try "inspect_123" first, fall back to mapping["inspect_456"]
639
+ * 5. Clean up mapping after resolving
640
+ *
641
+ * Needed because some React DevTools backends don't echo responseID reliably.
642
+ */
643
+ private responseFallbackKeys;
644
+ private elementErrors;
645
+ private elementWarnings;
646
+ private isProfiling;
647
+ private profilingData;
648
+ private backendVersion;
649
+ private capabilities;
650
+ private capabilitiesNegotiated;
651
+ private lastMessageAt;
652
+ private isInspectingNative;
653
+ private externalSendFn;
654
+ private isExternallyAttached;
655
+ constructor(options?: BridgeOptions);
656
+ /**
657
+ * Attach to an external message source (e.g., HeadlessDevToolsServer).
658
+ * When attached, the bridge receives messages from the external source
659
+ * instead of connecting via WebSocket.
660
+ */
661
+ attachToExternal(sendFn: (event: string, payload: unknown) => void, onDetach?: () => void): {
662
+ receiveMessage: (data: string) => void;
663
+ detach: () => void;
664
+ };
665
+ /**
666
+ * Check if bridge is attached to an external source
667
+ */
668
+ isAttachedExternally(): boolean;
669
+ /**
670
+ * Connect to DevTools backend.
671
+ * Handles deduplication of concurrent connect calls (Phase 1.2).
672
+ */
673
+ connect(): Promise<ConnectionStatus>;
674
+ /**
675
+ * Internal connection logic
676
+ */
677
+ private doConnect;
678
+ /**
679
+ * Called when connection is established
680
+ */
681
+ private onConnected;
682
+ /**
683
+ * Negotiate protocol capabilities with backend (Phase 2.2)
684
+ */
685
+ private negotiateCapabilities;
686
+ /**
687
+ * Handle WebSocket close event
688
+ */
689
+ private handleClose;
690
+ /**
691
+ * Schedule a reconnection attempt with exponential backoff (Phase 1.3)
692
+ */
693
+ private scheduleReconnect;
694
+ /**
695
+ * Disconnect from DevTools backend
696
+ */
697
+ disconnect(): void;
698
+ /**
699
+ * Get current connection status
700
+ */
701
+ getStatus(): ConnectionStatus;
702
+ /**
703
+ * Check if connected
704
+ */
705
+ isConnected(): boolean;
706
+ private setState;
707
+ private setError;
708
+ private reset;
709
+ /**
710
+ * Generate unique request ID (Phase 1.6)
711
+ */
712
+ private nextRequestId;
713
+ /**
714
+ * Create a pending request with proper cleanup (Phase 1.5)
715
+ */
716
+ private createPending;
717
+ /**
718
+ * Resolve a pending request
719
+ */
720
+ private resolvePending;
721
+ /**
722
+ * Resolve a correlated request using responseID/requestID/fallback pattern.
723
+ * Handles the common pattern of: responseID -> requestID -> element ID fallback.
724
+ *
725
+ * @param prefix - Key prefix (e.g., 'inspect', 'owners', 'nativeStyle')
726
+ * @param payload - Response payload with optional responseID, requestID, and id
727
+ * @param result - Value to resolve the promise with
728
+ */
729
+ private resolveCorrelatedRequest;
730
+ /**
731
+ * Store a fallback key mapping for request correlation.
732
+ * Call this when sending a request that uses requestID.
733
+ *
734
+ * @param prefix - Key prefix (e.g., 'inspect', 'owners')
735
+ * @param requestID - The requestID being sent
736
+ * @param elementID - The element ID (used as fallback key)
737
+ */
738
+ private storeFallbackKey;
739
+ /**
740
+ * Start periodic cleanup of stale requests (Phase 1.5)
741
+ */
742
+ private startStaleRequestCleanup;
743
+ /**
744
+ * Stop stale request cleanup
745
+ */
746
+ private stopStaleRequestCleanup;
747
+ private send;
748
+ private handleMessage;
749
+ private handleRenderer;
750
+ private handleStorageSupport;
751
+ private handleXHRSupport;
752
+ private handleRendererInterfaces;
753
+ private checkCapabilitiesComplete;
754
+ private handleAttributeSourceResult;
755
+ private handleInspectingNativeStopped;
756
+ private handleNativeStyleResponse;
757
+ private handleClipboardResponse;
758
+ private handleOverrideContextResponse;
759
+ private handleScreenshotResponse;
760
+ /**
761
+ * Decode UTF-8 string from operations array
762
+ * Based on react-devtools-shared/src/utils.js utfDecodeStringWithRanges
763
+ */
764
+ private utfDecodeString;
765
+ private handleOperations;
766
+ /**
767
+ * Process ADD operation with string table lookup
768
+ * Based on react-devtools-shared/src/devtools/store.js onBridgeOperations
769
+ *
770
+ * Root format: [id, type=11, isStrictModeCompliant, profilerFlags, supportsStrictMode, hasOwnerMetadata]
771
+ * Non-root format: [id, type, parentID, ownerID, displayNameStringID, keyStringID, namePropStringID]
772
+ */
773
+ private processAddOperation;
774
+ /**
775
+ * Process REMOVE operation with bounds checking
776
+ */
777
+ private processRemoveOperation;
778
+ /**
779
+ * Process REORDER operation with bounds checking
780
+ */
781
+ private processReorderOperation;
782
+ /**
783
+ * Process ERRORS/WARNINGS operation with bounds checking
784
+ */
785
+ private processErrorsWarningsOperation;
786
+ private handleInspectedElement;
787
+ private handleOwnersList;
788
+ private handleProfilingData;
789
+ private handleProfilingStatus;
790
+ getComponentTree(rootID?: number, maxDepth?: number): RootTree[];
791
+ getElementById(id: number): Element | null;
792
+ searchComponents(query: string, caseSensitive?: boolean, isRegex?: boolean): Element[];
793
+ /**
794
+ * Inspect element with request ID correlation (Phase 1.6)
795
+ */
796
+ inspectElement(id: number, paths?: Array<Array<string | number>>): Promise<InspectElementPayload>;
797
+ /**
798
+ * Get owners list with request ID correlation (Phase 1.6)
799
+ */
800
+ getOwnersList(id: number): Promise<SerializedElement[]>;
801
+ highlightElement(id: number): void;
802
+ clearHighlight(): void;
803
+ scrollToElement(id: number): void;
804
+ logToConsole(id: number): void;
805
+ storeAsGlobal(id: number, path: Array<string | number>, count: number): void;
806
+ viewElementSource(id: number): void;
807
+ overrideValueAtPath(target: OverrideTarget, id: number, path: Array<string | number>, value: unknown, hookIndex?: number): void;
808
+ deletePath(target: OverrideTarget, id: number, path: Array<string | number>, hookIndex?: number): void;
809
+ renamePath(target: OverrideTarget, id: number, path: Array<string | number>, oldKey: string, newKey: string, hookIndex?: number): void;
810
+ overrideError(id: number, isErrored: boolean): void;
811
+ overrideSuspense(id: number, isSuspended: boolean): void;
812
+ clearErrorsAndWarnings(id?: number): void;
813
+ getErrorsAndWarnings(): {
814
+ errors: Map<number, Array<[string, number]>>;
815
+ warnings: Map<number, Array<[string, number]>>;
816
+ };
817
+ startProfiling(recordTimeline?: boolean, recordChangeDescriptions?: boolean): void;
818
+ stopProfiling(): void;
819
+ getProfilingData(): Promise<ProfilingData | null>;
820
+ getProfilingStatus(): {
821
+ isProfiling: boolean;
822
+ };
823
+ setComponentFilters(filters: ComponentFilter[]): void;
824
+ setTraceUpdatesEnabled(enabled: boolean): void;
825
+ getNativeStyle(id: number): Promise<{
826
+ style: Record<string, unknown> | null;
827
+ layout: {
828
+ x: number;
829
+ y: number;
830
+ width: number;
831
+ height: number;
832
+ } | null;
833
+ }>;
834
+ setNativeStyle(id: number, property: string, value: unknown): void;
835
+ /**
836
+ * Save content to clipboard
837
+ */
838
+ saveToClipboard(value: string): Promise<{
839
+ success: boolean;
840
+ }>;
841
+ /**
842
+ * View attribute source location
843
+ */
844
+ viewAttributeSource(id: number, path: Array<string | number>): Promise<SourceLocation | null>;
845
+ /**
846
+ * Override context value
847
+ */
848
+ overrideContext(id: number, path: Array<string | number>, value: unknown): Promise<boolean>;
849
+ /**
850
+ * Start native element inspection mode
851
+ */
852
+ startInspectingNative(): void;
853
+ /**
854
+ * Stop native element inspection mode
855
+ * @param selectNextElement - Whether to select the next element under pointer
856
+ * @returns The ID of the selected element, or null
857
+ */
858
+ stopInspectingNative(selectNextElement?: boolean): Promise<number | null>;
859
+ /**
860
+ * Check if currently in native inspection mode
861
+ */
862
+ isInspectingNativeMode(): boolean;
863
+ /**
864
+ * Capture screenshot of an element
865
+ */
866
+ captureScreenshot(id: number): Promise<string | null>;
867
+ /**
868
+ * Get negotiated protocol capabilities
869
+ */
870
+ getCapabilities(): ProtocolCapabilities;
871
+ /**
872
+ * Check if capabilities have been negotiated
873
+ */
874
+ hasNegotiatedCapabilities(): boolean;
875
+ /**
876
+ * Wait for capabilities negotiation to complete
877
+ */
878
+ waitForCapabilities(timeout?: number): Promise<ProtocolCapabilities>;
879
+ /**
880
+ * Get all connected renderers
881
+ */
882
+ getRenderers(): Renderer[];
883
+ /**
884
+ * Get renderer by ID
885
+ */
886
+ getRenderer(id: number): Renderer | null;
887
+ /**
888
+ * Get renderer for a specific element
889
+ */
890
+ getRendererForElement(elementID: number): Renderer | null;
891
+ /**
892
+ * Get elements for a specific renderer
893
+ */
894
+ getElementsByRenderer(rendererID: number): Element[];
895
+ private ensureConnected;
896
+ /**
897
+ * Get renderer ID for an element (Phase 2.3: Multi-renderer support)
898
+ */
899
+ private getRendererIDForElement;
900
+ /**
901
+ * Get last message timestamp (for health monitoring)
902
+ */
903
+ getLastMessageTime(): number;
904
+ /**
905
+ * Get pending request count (for monitoring)
906
+ */
907
+ getPendingRequestCount(): number;
908
+ }
909
+
910
+ /**
911
+ * Headless React DevTools Server
912
+ *
913
+ * Embeds the DevTools server directly into the MCP package, allowing it to work
914
+ * without the Electron DevTools app. Supports both React Native and Web apps.
915
+ *
916
+ * Based on: react-devtools-core/src/standalone.js
917
+ * Key difference: No DOM/UI rendering - purely headless for MCP integration.
918
+ */
919
+
920
+ interface HeadlessServerOptions {
921
+ port?: number;
922
+ host?: string;
923
+ httpsOptions?: {
924
+ key: string;
925
+ cert: string;
926
+ };
927
+ logger?: Logger;
928
+ }
929
+ type ServerStatus = 'stopped' | 'starting' | 'listening' | 'connected' | 'error';
930
+ interface ServerState {
931
+ status: ServerStatus;
932
+ port: number;
933
+ host: string;
934
+ connectedAt: number | null;
935
+ error: string | null;
936
+ }
937
+ declare class HeadlessDevToolsServer extends EventEmitter {
938
+ private _options;
939
+ private _httpServer;
940
+ private _wsServer;
941
+ private _socket;
942
+ private _state;
943
+ private _backendScript;
944
+ constructor(options?: HeadlessServerOptions);
945
+ private _loadBackendScript;
946
+ get state(): ServerState;
947
+ get isConnected(): boolean;
948
+ private _externalMessageListeners;
949
+ /**
950
+ * Add an external message listener that receives all messages from React app.
951
+ * Used to relay messages to the MCP's DevToolsBridge.
952
+ */
953
+ addMessageListener(fn: (event: string, payload: unknown) => void): () => void;
954
+ /**
955
+ * Send a message to the React app via WebSocket.
956
+ * Used by the MCP's DevToolsBridge to send messages.
957
+ */
958
+ sendMessage(event: string, payload: unknown): void;
959
+ /**
960
+ * Start the headless DevTools server
961
+ */
962
+ start(): Promise<void>;
963
+ /**
964
+ * Stop the server
965
+ */
966
+ stop(): Promise<void>;
967
+ /**
968
+ * Handle HTTP requests - serve backend.js for web apps
969
+ */
970
+ private _handleHttpRequest;
971
+ /**
972
+ * Handle new WebSocket connection from React app
973
+ */
974
+ private _handleConnection;
975
+ /**
976
+ * Handle disconnection
977
+ */
978
+ private _onDisconnected;
979
+ private _setState;
980
+ }
981
+ /**
982
+ * Create and start a headless DevTools server
983
+ */
984
+ declare function startHeadlessServer(options?: HeadlessServerOptions): Promise<HeadlessDevToolsServer>;
985
+
986
+ /**
987
+ * React DevTools MCP Server
988
+ *
989
+ * Exposes React DevTools functionality via Model Context Protocol.
990
+ */
991
+
992
+ interface ServerOptions {
993
+ host?: string;
994
+ port?: number;
995
+ autoConnect?: boolean;
996
+ logger?: Logger;
997
+ /**
998
+ * Run in standalone mode with embedded DevTools server.
999
+ * When true, starts a headless WebSocket server that React apps can connect to directly.
1000
+ * No need for external `npx react-devtools` - works fully standalone.
1001
+ *
1002
+ * Default: true (can be disabled via DEVTOOLS_STANDALONE=false env var)
1003
+ */
1004
+ standalone?: boolean;
1005
+ }
1006
+ declare function createServer(options?: ServerOptions): {
1007
+ server: Server<{
1008
+ method: string;
1009
+ params?: {
1010
+ [x: string]: unknown;
1011
+ _meta?: {
1012
+ [x: string]: unknown;
1013
+ progressToken?: string | number | undefined;
1014
+ "io.modelcontextprotocol/related-task"?: {
1015
+ taskId: string;
1016
+ } | undefined;
1017
+ } | undefined;
1018
+ } | undefined;
1019
+ }, {
1020
+ method: string;
1021
+ params?: {
1022
+ [x: string]: unknown;
1023
+ _meta?: {
1024
+ [x: string]: unknown;
1025
+ progressToken?: string | number | undefined;
1026
+ "io.modelcontextprotocol/related-task"?: {
1027
+ taskId: string;
1028
+ } | undefined;
1029
+ } | undefined;
1030
+ } | undefined;
1031
+ }, {
1032
+ [x: string]: unknown;
1033
+ _meta?: {
1034
+ [x: string]: unknown;
1035
+ progressToken?: string | number | undefined;
1036
+ "io.modelcontextprotocol/related-task"?: {
1037
+ taskId: string;
1038
+ } | undefined;
1039
+ } | undefined;
1040
+ }>;
1041
+ bridge: DevToolsBridge;
1042
+ headlessServer: () => HeadlessDevToolsServer | null;
1043
+ start(): Promise<void>;
1044
+ stop(): Promise<void>;
1045
+ };
1046
+
1047
+ /**
1048
+ * Error Types
1049
+ *
1050
+ * Typed error classes for DevTools bridge operations.
1051
+ */
1052
+
1053
+ /**
1054
+ * Base error class for all DevTools errors
1055
+ */
1056
+ declare class DevToolsError extends Error {
1057
+ readonly code: ErrorCode;
1058
+ readonly details?: Record<string, unknown> | undefined;
1059
+ constructor(message: string, code: ErrorCode, details?: Record<string, unknown> | undefined);
1060
+ toJSON(): Record<string, unknown>;
1061
+ }
1062
+ /**
1063
+ * Connection-related errors
1064
+ */
1065
+ declare class ConnectionError extends DevToolsError {
1066
+ constructor(message: string, details?: Record<string, unknown>);
1067
+ }
1068
+ /**
1069
+ * Request timeout errors
1070
+ */
1071
+ declare class TimeoutError extends DevToolsError {
1072
+ constructor(operation: string, timeout: number, details?: Record<string, unknown>);
1073
+ }
1074
+ /**
1075
+ * Element not found errors
1076
+ */
1077
+ declare class ElementNotFoundError extends DevToolsError {
1078
+ constructor(elementId: number, details?: Record<string, unknown>);
1079
+ }
1080
+ /**
1081
+ * Protocol/parsing errors
1082
+ */
1083
+ declare class ProtocolError extends DevToolsError {
1084
+ constructor(message: string, details?: Record<string, unknown>);
1085
+ }
1086
+ /**
1087
+ * Validation errors for invalid inputs
1088
+ */
1089
+ declare class ValidationError extends DevToolsError {
1090
+ constructor(message: string, details?: Record<string, unknown>);
1091
+ }
1092
+ /**
1093
+ * Operation not supported/editable
1094
+ */
1095
+ declare class NotEditableError extends DevToolsError {
1096
+ constructor(operation: string, elementId: number, details?: Record<string, unknown>);
1097
+ }
1098
+
1099
+ export { type BridgeIncoming, type BridgeOptions, type BridgeOutgoing, type ChangeDescription, type CleanedValue, type CommitData, type ComponentFilter, type ConnectParams, type ConnectResult, type ConnectionConfig, ConnectionError, type ConnectionState, type ConnectionStatus, type DehydratedData, type DeletePathParams, DevToolsBridge, DevToolsError, type Element, ElementNotFoundError, type ElementType, type ErrorCode, type GetComponentTreeParams, type GetComponentTreeResult, type GetErrorsAndWarningsResult, type GetNativeStyleParams, type GetNativeStyleResult, type GetOwnersListParams, type GetOwnersListResult, type GetProfilingStatusResult, HeadlessDevToolsServer, type HeadlessServerOptions, type HighlightElementParams, type HookInfo, type HookType, type HooksTree, type InspectElementParams, type InspectElementPayload, type InspectElementResult, type InspectedElement, type LogLevel, type LogToConsoleParams, type Logger, type MCPError, NotEditableError, type OverrideHooksParams, type OverrideParams, type OverrideResult, type OverrideTarget, type ProfilingData, type ProfilingDataForRoot, type ProtocolCapabilities, ProtocolError, type RenamePathParams, type Renderer, type RendererInterface, type RootTree, type ScrollToElementParams, type SearchComponentsParams, type SearchComponentsResult, type SerializedElement, type ServerOptions, type ServerState, type ServerStatus, type SetNativeStyleParams, type SourceLocation, type StartProfilingParams, type StartProfilingResult, type StopProfilingResult, type StoreAsGlobalParams, type SuspenseInfo, type TimelineData, type TimelineEvent, TimeoutError, type ToggleErrorParams, type ToggleSuspenseParams, ValidationError, createLogger, createServer, getLogLevelFromEnv, noopLogger, startHeadlessServer };