@cgaspard/webappmcp 0.1.2 → 0.1.4

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/browser.js CHANGED
@@ -1,20 +1,1307 @@
1
- (function (factory) {
2
- typeof define === 'function' && define.amd ? define(factory) :
3
- factory();
4
- })((function () { 'use strict';
5
-
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.WebAppMCPClient = void 0;
8
- const tslib_1 = require("tslib");
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.WebAppMCP = {}));
5
+ })(this, (function (exports) { 'use strict';
6
+
7
+ class MCPDevTools {
8
+ constructor(config = {}) {
9
+ this.container = null;
10
+ this.isExpanded = false;
11
+ this.logs = [];
12
+ this.maxLogs = 500;
13
+ this.connectionStatus = 'disconnected';
14
+ this.config = {
15
+ position: 'bottom-right',
16
+ theme: 'dark',
17
+ ...config
18
+ };
19
+ this.createDevToolsUI();
20
+ this.setupEventListeners();
21
+ }
22
+ createDevToolsUI() {
23
+ this.container = document.createElement('div');
24
+ this.container.id = 'mcp-devtools';
25
+ this.container.className = `mcp-theme-${this.config.theme} mcp-position-${this.config.position}`;
26
+ this.container.innerHTML = `
27
+ <div class="mcp-devtools-indicator" id="mcp-indicator">
28
+ <div class="mcp-status-dot" id="mcp-status-dot"></div>
29
+ <span class="mcp-label">MCP</span>
30
+ </div>
31
+ <div class="mcp-devtools-panel" id="mcp-panel" style="display: none;">
32
+ <div class="mcp-devtools-header">
33
+ <span>MCP DevTools</span>
34
+ <div class="mcp-header-controls">
35
+ <button class="mcp-theme-toggle" id="mcp-theme-btn" title="Toggle theme">🌓</button>
36
+ <button class="mcp-close-btn" id="mcp-close-btn">×</button>
37
+ </div>
38
+ </div>
39
+ <div class="mcp-devtools-content">
40
+ <div class="mcp-status-section">
41
+ <div class="mcp-status-item">
42
+ <label>WebSocket:</label>
43
+ <span id="mcp-ws-status">Disconnected</span>
44
+ </div>
45
+ <div class="mcp-status-item">
46
+ <label>MCP Server:</label>
47
+ <span id="mcp-server-status">Disconnected</span>
48
+ </div>
49
+ </div>
50
+ <div class="mcp-logs-section">
51
+ <div class="mcp-logs-header">
52
+ <span>Activity Log</span>
53
+ <div class="mcp-logs-controls">
54
+ <label class="mcp-checkbox">
55
+ <input type="checkbox" id="mcp-autoscroll" checked>
56
+ Auto-scroll
57
+ </label>
58
+ <button class="mcp-clear-btn" id="mcp-clear-btn">Clear</button>
59
+ </div>
60
+ </div>
61
+ <div class="mcp-logs-container" id="mcp-logs"></div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ `;
66
+ this.addStyles();
67
+ document.body.appendChild(this.container);
68
+ }
69
+ addStyles() {
70
+ const style = document.createElement('style');
71
+ style.textContent = `
72
+ #mcp-devtools {
73
+ position: fixed;
74
+ z-index: 10000;
75
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
76
+ }
77
+
78
+ /* Positioning */
79
+ #mcp-devtools.mcp-position-bottom-right {
80
+ bottom: 20px;
81
+ right: 20px;
82
+ }
83
+
84
+ #mcp-devtools.mcp-position-bottom-left {
85
+ bottom: 20px;
86
+ left: 20px;
87
+ }
88
+
89
+ #mcp-devtools.mcp-position-top-right {
90
+ top: 20px;
91
+ right: 20px;
92
+ }
93
+
94
+ #mcp-devtools.mcp-position-top-left {
95
+ top: 20px;
96
+ left: 20px;
97
+ }
98
+
99
+ .mcp-devtools-indicator {
100
+ cursor: pointer;
101
+ display: flex;
102
+ align-items: center;
103
+ gap: 4px;
104
+ padding: 4px 8px;
105
+ background: rgba(255, 255, 255, 0.9);
106
+ border-radius: 4px;
107
+ border: 1px solid rgba(0, 0, 0, 0.1);
108
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
109
+ }
110
+
111
+ .mcp-label {
112
+ font-size: 10px;
113
+ font-weight: 600;
114
+ color: #495057;
115
+ user-select: none;
116
+ }
117
+
118
+ .mcp-status-dot {
119
+ width: 12px;
120
+ height: 12px;
121
+ border-radius: 50%;
122
+ background-color: #dc3545;
123
+ border: 2px solid rgba(255, 255, 255, 0.8);
124
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
125
+ transition: background-color 0.3s ease;
126
+ }
127
+
128
+ .mcp-status-dot.connecting {
129
+ background-color: #ffc107;
130
+ animation: pulse 1.5s infinite;
131
+ }
132
+
133
+ .mcp-status-dot.connected {
134
+ background-color: #28a745;
135
+ }
136
+
137
+ @keyframes pulse {
138
+ 0%, 100% { opacity: 1; }
139
+ 50% { opacity: 0.5; }
140
+ }
141
+
142
+ .mcp-devtools-panel {
143
+ position: absolute;
144
+ width: 400px;
145
+ height: 300px;
146
+ background: white;
147
+ border: 1px solid #e0e0e0;
148
+ border-radius: 8px;
149
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
150
+ display: flex;
151
+ flex-direction: column;
152
+ }
153
+
154
+ /* Panel positioning based on indicator position */
155
+ .mcp-position-bottom-right .mcp-devtools-panel {
156
+ bottom: 40px;
157
+ right: 0;
158
+ }
159
+
160
+ .mcp-position-bottom-left .mcp-devtools-panel {
161
+ bottom: 40px;
162
+ left: 0;
163
+ }
164
+
165
+ .mcp-position-top-right .mcp-devtools-panel {
166
+ top: 40px;
167
+ right: 0;
168
+ }
169
+
170
+ .mcp-position-top-left .mcp-devtools-panel {
171
+ top: 40px;
172
+ left: 0;
173
+ }
174
+
175
+ .mcp-devtools-header {
176
+ padding: 12px 16px;
177
+ background: #f8f9fa;
178
+ border-bottom: 1px solid #e0e0e0;
179
+ border-radius: 8px 8px 0 0;
180
+ display: flex;
181
+ justify-content: space-between;
182
+ align-items: center;
183
+ font-weight: 600;
184
+ font-size: 14px;
185
+ }
186
+
187
+ .mcp-close-btn {
188
+ background: none;
189
+ border: none;
190
+ font-size: 18px;
191
+ cursor: pointer;
192
+ color: #6c757d;
193
+ padding: 0;
194
+ width: 20px;
195
+ height: 20px;
196
+ display: flex;
197
+ align-items: center;
198
+ justify-content: center;
199
+ }
200
+
201
+ .mcp-close-btn:hover {
202
+ color: #495057;
203
+ }
204
+
205
+ .mcp-devtools-content {
206
+ flex: 1;
207
+ display: flex;
208
+ flex-direction: column;
209
+ overflow: hidden;
210
+ }
211
+
212
+ .mcp-status-section {
213
+ padding: 12px 16px;
214
+ border-bottom: 1px solid #e0e0e0;
215
+ background: #f8f9fa;
216
+ }
217
+
218
+ .mcp-status-item {
219
+ display: flex;
220
+ justify-content: space-between;
221
+ margin-bottom: 4px;
222
+ font-size: 12px;
223
+ }
224
+
225
+ .mcp-status-item label {
226
+ font-weight: 500;
227
+ color: #495057;
228
+ }
229
+
230
+ .mcp-status-item span {
231
+ color: #6c757d;
232
+ }
233
+
234
+ .mcp-logs-section {
235
+ flex: 1;
236
+ display: flex;
237
+ flex-direction: column;
238
+ overflow: hidden;
239
+ }
240
+
241
+ .mcp-logs-header {
242
+ padding: 8px 16px;
243
+ background: #f8f9fa;
244
+ border-bottom: 1px solid #e0e0e0;
245
+ display: flex;
246
+ justify-content: space-between;
247
+ align-items: center;
248
+ font-size: 12px;
249
+ font-weight: 500;
250
+ }
251
+
252
+ .mcp-clear-btn {
253
+ background: none;
254
+ border: 1px solid #dee2e6;
255
+ border-radius: 4px;
256
+ padding: 2px 8px;
257
+ font-size: 11px;
258
+ cursor: pointer;
259
+ color: #6c757d;
260
+ }
261
+
262
+ .mcp-clear-btn:hover {
263
+ background: #e9ecef;
264
+ }
265
+
266
+ .mcp-logs-container {
267
+ flex: 1;
268
+ overflow-y: auto;
269
+ padding: 8px;
270
+ font-size: 11px;
271
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
272
+ }
273
+
274
+ .mcp-log-entry {
275
+ margin-bottom: 4px;
276
+ padding: 4px 8px;
277
+ border-radius: 4px;
278
+ white-space: pre-wrap;
279
+ word-break: break-word;
280
+ }
281
+
282
+ .mcp-log-entry.info {
283
+ background: #e7f3ff;
284
+ color: #0c5460;
285
+ }
286
+
287
+ .mcp-log-entry.warning {
288
+ background: #fff3cd;
289
+ color: #856404;
290
+ }
291
+
292
+ .mcp-log-entry.error {
293
+ background: #f8d7da;
294
+ color: #721c24;
295
+ }
296
+
297
+ .mcp-log-timestamp {
298
+ color: #6c757d;
299
+ font-size: 10px;
300
+ }
301
+
302
+ /* Header controls */
303
+ .mcp-header-controls {
304
+ display: flex;
305
+ gap: 8px;
306
+ align-items: center;
307
+ }
308
+
309
+ .mcp-theme-toggle {
310
+ background: none;
311
+ border: none;
312
+ font-size: 16px;
313
+ cursor: pointer;
314
+ padding: 0;
315
+ opacity: 0.6;
316
+ transition: opacity 0.2s;
317
+ }
318
+
319
+ .mcp-theme-toggle:hover {
320
+ opacity: 1;
321
+ }
322
+
323
+ /* Log controls */
324
+ .mcp-logs-controls {
325
+ display: flex;
326
+ gap: 8px;
327
+ align-items: center;
328
+ }
329
+
330
+ .mcp-checkbox {
331
+ display: flex;
332
+ align-items: center;
333
+ gap: 4px;
334
+ font-size: 11px;
335
+ cursor: pointer;
336
+ }
337
+
338
+ .mcp-checkbox input {
339
+ cursor: pointer;
340
+ }
341
+
342
+ /* Tool execution logs */
343
+ .mcp-log-entry.tool {
344
+ background: #e7f3ff;
345
+ color: #004085;
346
+ border-left: 3px solid #004085;
347
+ }
348
+
349
+ .mcp-tool-details {
350
+ font-size: 10px;
351
+ margin-top: 4px;
352
+ padding-left: 16px;
353
+ opacity: 0.8;
354
+ }
355
+
356
+ /* Dark theme */
357
+ .mcp-theme-dark .mcp-devtools-indicator {
358
+ background: rgba(30, 30, 30, 0.9);
359
+ border-color: rgba(255, 255, 255, 0.1);
360
+ }
361
+
362
+ .mcp-theme-dark .mcp-label {
363
+ color: #e0e0e0;
364
+ }
365
+
366
+ .mcp-theme-dark .mcp-devtools-panel {
367
+ background: #1e1e1e;
368
+ border-color: #333;
369
+ }
370
+
371
+ .mcp-theme-dark .mcp-devtools-header {
372
+ background: #2d2d2d;
373
+ border-color: #333;
374
+ color: #e0e0e0;
375
+ }
376
+
377
+ .mcp-theme-dark .mcp-devtools-content {
378
+ background: #1e1e1e;
379
+ }
380
+
381
+ .mcp-theme-dark .mcp-status-section {
382
+ background: #2d2d2d;
383
+ border-color: #333;
384
+ }
385
+
386
+ .mcp-theme-dark .mcp-status-item label {
387
+ color: #b0b0b0;
388
+ }
389
+
390
+ .mcp-theme-dark .mcp-status-item span {
391
+ color: #e0e0e0;
392
+ }
393
+
394
+ .mcp-theme-dark .mcp-logs-header {
395
+ background: #2d2d2d;
396
+ border-color: #333;
397
+ color: #e0e0e0;
398
+ }
399
+
400
+ .mcp-theme-dark .mcp-logs-container {
401
+ background: #252525;
402
+ border-color: #333;
403
+ }
404
+
405
+ .mcp-theme-dark .mcp-log-entry {
406
+ background: #2d2d2d;
407
+ color: #e0e0e0;
408
+ border-color: #333;
409
+ }
410
+
411
+ .mcp-theme-dark .mcp-log-entry.warning {
412
+ background: #4a3800;
413
+ color: #ffc107;
414
+ }
415
+
416
+ .mcp-theme-dark .mcp-log-entry.error {
417
+ background: #4a0000;
418
+ color: #ff6b6b;
419
+ }
420
+
421
+ .mcp-theme-dark .mcp-log-entry.tool {
422
+ background: #003366;
423
+ color: #66b3ff;
424
+ border-left-color: #66b3ff;
425
+ }
426
+
427
+ .mcp-theme-dark .mcp-log-timestamp {
428
+ color: #888;
429
+ }
430
+
431
+ .mcp-theme-dark .mcp-clear-btn,
432
+ .mcp-theme-dark .mcp-close-btn {
433
+ color: #e0e0e0;
434
+ border-color: #555;
435
+ }
436
+
437
+ .mcp-theme-dark .mcp-clear-btn:hover,
438
+ .mcp-theme-dark .mcp-close-btn:hover {
439
+ background: #444;
440
+ }
441
+ }
442
+ `;
443
+ document.head.appendChild(style);
444
+ }
445
+ setupEventListeners() {
446
+ const indicator = this.container?.querySelector('#mcp-indicator');
447
+ const closeBtn = this.container?.querySelector('#mcp-close-btn');
448
+ const clearBtn = this.container?.querySelector('#mcp-clear-btn');
449
+ const themeBtn = this.container?.querySelector('#mcp-theme-btn');
450
+ indicator?.addEventListener('click', () => this.togglePanel());
451
+ closeBtn?.addEventListener('click', () => this.togglePanel());
452
+ clearBtn?.addEventListener('click', () => this.clearLogs());
453
+ themeBtn?.addEventListener('click', () => this.toggleTheme());
454
+ }
455
+ toggleTheme() {
456
+ if (!this.container)
457
+ return;
458
+ this.config.theme = this.config.theme === 'light' ? 'dark' : 'light';
459
+ this.container.className = `mcp-theme-${this.config.theme} mcp-position-${this.config.position}`;
460
+ }
461
+ togglePanel() {
462
+ this.isExpanded = !this.isExpanded;
463
+ const panel = this.container?.querySelector('#mcp-panel');
464
+ if (panel) {
465
+ panel.style.display = this.isExpanded ? 'flex' : 'none';
466
+ }
467
+ }
468
+ setConnectionStatus(status) {
469
+ this.connectionStatus = status;
470
+ const dot = this.container?.querySelector('#mcp-status-dot');
471
+ const wsStatus = this.container?.querySelector('#mcp-ws-status');
472
+ const serverStatus = this.container?.querySelector('#mcp-server-status');
473
+ if (dot) {
474
+ dot.className = `mcp-status-dot ${status}`;
475
+ }
476
+ if (wsStatus && serverStatus) {
477
+ const statusText = status.charAt(0).toUpperCase() + status.slice(1);
478
+ wsStatus.textContent = statusText;
479
+ serverStatus.textContent = statusText;
480
+ }
481
+ this.addLog('info', 'client', `Connection status: ${status}`);
482
+ }
483
+ addLog(level, source, message, data) {
484
+ const log = {
485
+ timestamp: new Date().toISOString(),
486
+ level,
487
+ source,
488
+ message,
489
+ data
490
+ };
491
+ this.logs.push(log);
492
+ if (this.logs.length > this.maxLogs) {
493
+ this.logs = this.logs.slice(-this.maxLogs);
494
+ }
495
+ this.renderLogs();
496
+ }
497
+ renderLogs() {
498
+ const logsContainer = this.container?.querySelector('#mcp-logs');
499
+ if (!logsContainer)
500
+ return;
501
+ logsContainer.innerHTML = this.logs.map(log => {
502
+ const time = new Date(log.timestamp).toLocaleTimeString();
503
+ if (log.level === 'tool' && log.data) {
504
+ // Special formatting for tool logs
505
+ const details = log.data;
506
+ let detailsHtml = '';
507
+ if (details.args) {
508
+ detailsHtml += `<div class="mcp-tool-details">Args: ${this.safeStringify(details.args)}</div>`;
509
+ }
510
+ if (details.error) {
511
+ detailsHtml += `<div class="mcp-tool-details">Error: ${details.error}</div>`;
512
+ }
513
+ if (details.result && details.status === 'completed') {
514
+ const resultStr = this.safeStringify(details.result);
515
+ if (resultStr.length > 100) {
516
+ detailsHtml += `<div class="mcp-tool-details">Result: ${resultStr.substring(0, 100)}...</div>`;
517
+ }
518
+ else {
519
+ detailsHtml += `<div class="mcp-tool-details">Result: ${resultStr}</div>`;
520
+ }
521
+ }
522
+ return `
523
+ <div class="mcp-log-entry ${log.level}">
524
+ <span class="mcp-log-timestamp">[${time}]</span> [${log.source.toUpperCase()}] ${log.message}
525
+ ${detailsHtml}
526
+ </div>
527
+ `;
528
+ }
529
+ else {
530
+ // Regular log formatting
531
+ const dataStr = log.data ? ` ${this.safeStringify(log.data)}` : '';
532
+ return `
533
+ <div class="mcp-log-entry ${log.level}">
534
+ <span class="mcp-log-timestamp">[${time}]</span> [${log.source.toUpperCase()}] ${log.message}${dataStr}
535
+ </div>
536
+ `;
537
+ }
538
+ }).join('');
539
+ // Auto-scroll if enabled
540
+ const autoScrollCheckbox = this.container?.querySelector('#mcp-autoscroll');
541
+ if (autoScrollCheckbox?.checked) {
542
+ logsContainer.scrollTop = logsContainer.scrollHeight;
543
+ }
544
+ }
545
+ safeStringify(obj) {
546
+ try {
547
+ // Handle simple types
548
+ if (obj === null || obj === undefined) {
549
+ return String(obj);
550
+ }
551
+ if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') {
552
+ return String(obj);
553
+ }
554
+ // Handle arrays and objects with circular reference protection
555
+ const seen = new Set();
556
+ return JSON.stringify(obj, (key, value) => {
557
+ if (typeof value === 'object' && value !== null) {
558
+ if (seen.has(value)) {
559
+ return '[Circular Reference]';
560
+ }
561
+ seen.add(value);
562
+ }
563
+ return value;
564
+ });
565
+ }
566
+ catch (error) {
567
+ return `[Error stringifying: ${error instanceof Error ? error.message : 'Unknown error'}]`;
568
+ }
569
+ }
570
+ clearLogs() {
571
+ this.logs = [];
572
+ this.renderLogs();
573
+ }
574
+ logWebSocketEvent(event, data) {
575
+ this.addLog('info', 'websocket', event, data);
576
+ }
577
+ logMCPEvent(event, data) {
578
+ this.addLog('info', 'mcp', event, data);
579
+ }
580
+ logError(source, message, error) {
581
+ this.addLog('error', source, message, error);
582
+ }
583
+ logToolExecution(toolName, args, success, message, executionTime, result) {
584
+ const status = success === null ? 'started' : (success ? 'completed' : 'failed');
585
+ const details = {
586
+ tool: toolName,
587
+ status,
588
+ args,
589
+ executionTime: executionTime ? `${executionTime}ms` : undefined,
590
+ result: success && result ? result : undefined,
591
+ error: !success && message !== 'Success' ? message : undefined
592
+ };
593
+ const logMessage = `Tool ${toolName} ${status}${executionTime ? ` (${executionTime}ms)` : ''}`;
594
+ this.addLog(success === false ? 'error' : 'tool', 'tool', logMessage, details);
595
+ }
596
+ }
597
+
598
+ class WebAppMCPClient {
599
+ get isConnected() {
600
+ return this._isConnected && this.ws?.readyState === WebSocket.OPEN;
601
+ }
602
+ constructor(config) {
603
+ this.ws = null;
604
+ this.reconnectAttempts = 0;
605
+ this.messageHandlers = new Map();
606
+ this.consoleLogs = [];
607
+ this._isConnected = false;
608
+ this.devTools = null;
609
+ this.config = {
610
+ reconnectInterval: 5000,
611
+ maxReconnectAttempts: 10,
612
+ enableDevTools: true,
613
+ debug: false,
614
+ enableConnection: true,
615
+ interceptConsole: true,
616
+ enabledTools: [], // Empty array means all tools enabled
617
+ devToolsPosition: 'bottom-right',
618
+ devToolsTheme: 'dark',
619
+ ...config,
620
+ };
621
+ // Only intercept console if enabled
622
+ if (this.config.interceptConsole) {
623
+ this.setupConsoleInterception();
624
+ }
625
+ if (this.config.enableDevTools && this.config.enableConnection) {
626
+ this.devTools = new MCPDevTools({
627
+ position: this.config.devToolsPosition,
628
+ theme: this.config.devToolsTheme,
629
+ });
630
+ this.devTools.setConnectionStatus('disconnected');
631
+ }
632
+ }
633
+ log(...args) {
634
+ if (this.config.debug) {
635
+ console.log('[WebAppMCP Client]', ...args);
636
+ }
637
+ }
638
+ logError(...args) {
639
+ // Always log errors regardless of debug setting
640
+ console.error('[WebAppMCP Client Error]', ...args);
641
+ }
642
+ connect() {
643
+ // Bypass connection if disabled (e.g., in production)
644
+ if (!this.config.enableConnection) {
645
+ this.log('Connection disabled by configuration');
646
+ return;
647
+ }
648
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
649
+ return;
650
+ }
651
+ this.devTools?.setConnectionStatus('connecting');
652
+ this.devTools?.logWebSocketEvent('Attempting to connect', { url: this.config.serverUrl });
653
+ try {
654
+ const url = new URL(this.config.serverUrl);
655
+ const headers = {};
656
+ if (this.config.authToken) {
657
+ headers['Authorization'] = `Bearer ${this.config.authToken}`;
658
+ }
659
+ // WebSocket in browser doesn't support custom headers directly
660
+ // So we'll pass the token in the URL
661
+ if (this.config.authToken) {
662
+ url.searchParams.set('token', this.config.authToken);
663
+ }
664
+ this.ws = new WebSocket(url.toString());
665
+ this.setupWebSocketHandlers();
666
+ }
667
+ catch (error) {
668
+ this.logError('Failed to connect to WebApp MCP server:', error);
669
+ this.devTools?.logError('websocket', 'Failed to connect', error);
670
+ this.scheduleReconnect();
671
+ }
672
+ }
673
+ disconnect() {
674
+ this._isConnected = false;
675
+ if (this.reconnectTimer) {
676
+ clearTimeout(this.reconnectTimer);
677
+ this.reconnectTimer = null;
678
+ }
679
+ if (this.ws) {
680
+ this.ws.close();
681
+ this.ws = null;
682
+ }
683
+ }
684
+ setupWebSocketHandlers() {
685
+ if (!this.ws)
686
+ return;
687
+ this.ws.onopen = () => {
688
+ this.log('Connected to WebApp MCP server');
689
+ this._isConnected = true;
690
+ this.reconnectAttempts = 0;
691
+ this.devTools?.setConnectionStatus('connected');
692
+ this.devTools?.logWebSocketEvent('Connected to WebApp MCP server');
693
+ this.sendMessage({
694
+ type: 'init',
695
+ url: window.location.href,
696
+ });
697
+ };
698
+ this.ws.onmessage = (event) => {
699
+ try {
700
+ const message = JSON.parse(event.data);
701
+ this.devTools?.logWebSocketEvent('Message received', message);
702
+ this.handleMessage(message);
703
+ }
704
+ catch (error) {
705
+ this.logError('Failed to parse WebSocket message:', error);
706
+ this.devTools?.logError('websocket', 'Failed to parse message', error);
707
+ }
708
+ };
709
+ this.ws.onerror = (error) => {
710
+ this.logError('WebSocket error:', error);
711
+ this.devTools?.logError('websocket', 'WebSocket error', error);
712
+ };
713
+ this.ws.onclose = () => {
714
+ this.log('Disconnected from WebApp MCP server');
715
+ this._isConnected = false;
716
+ this.devTools?.setConnectionStatus('disconnected');
717
+ this.devTools?.logWebSocketEvent('Disconnected from WebApp MCP server');
718
+ this.scheduleReconnect();
719
+ };
720
+ }
721
+ scheduleReconnect() {
722
+ if (this.reconnectAttempts >= (this.config.maxReconnectAttempts || 10) ||
723
+ this.reconnectTimer) {
724
+ return;
725
+ }
726
+ this.reconnectAttempts++;
727
+ this.log(`Scheduling reconnect attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts}`);
728
+ this.reconnectTimer = setTimeout(() => {
729
+ this.reconnectTimer = null;
730
+ this.connect();
731
+ }, this.config.reconnectInterval);
732
+ }
733
+ sendMessage(message) {
734
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
735
+ this.ws.send(JSON.stringify(message));
736
+ }
737
+ else {
738
+ this.logError('WebSocket is not connected');
739
+ }
740
+ }
741
+ handleMessage(message) {
742
+ const { type, requestId, tool, args } = message;
743
+ this.log('[WebApp Client] Received message:', JSON.stringify(message));
744
+ if (type === 'connected') {
745
+ this.log('WebApp MCP client registered:', message.clientId);
746
+ this.devTools?.logMCPEvent('Client registered', { clientId: message.clientId });
747
+ return;
748
+ }
749
+ if (type === 'execute_tool') {
750
+ this.log(`[WebApp Client] Executing tool: ${tool} with requestId: ${requestId}`);
751
+ this.devTools?.logMCPEvent(`Executing tool: ${tool}`, { requestId, args });
752
+ this.executeToolHandler(requestId, tool, args);
753
+ return;
754
+ }
755
+ const handler = this.messageHandlers.get(type);
756
+ if (handler) {
757
+ handler(message);
758
+ }
759
+ }
760
+ async executeToolHandler(requestId, toolName, args) {
761
+ // Check if tool is enabled
762
+ if (this.config.enabledTools && this.config.enabledTools.length > 0) {
763
+ if (!this.config.enabledTools.includes(toolName)) {
764
+ const error = `Tool ${toolName} is not enabled`;
765
+ this.logError(error);
766
+ this.devTools?.logToolExecution(toolName, args, false, error);
767
+ this.sendMessage({
768
+ type: 'tool_response',
769
+ requestId,
770
+ success: false,
771
+ error,
772
+ });
773
+ return;
774
+ }
775
+ }
776
+ this.log(`[WebApp Client] Executing tool handler for ${toolName}`);
777
+ this.log(`[WebApp Client] Tool args:`, JSON.stringify(args));
778
+ this.devTools?.logToolExecution(toolName, args, null, 'Started');
779
+ const startTime = Date.now();
780
+ try {
781
+ let result;
782
+ switch (toolName) {
783
+ case 'dom_query':
784
+ result = await this.domQuery(args);
785
+ break;
786
+ case 'dom_get_properties':
787
+ result = await this.domGetProperties(args);
788
+ break;
789
+ case 'dom_get_text':
790
+ result = await this.domGetText(args);
791
+ break;
792
+ case 'dom_get_html':
793
+ result = await this.domGetHTML(args);
794
+ break;
795
+ case 'interaction_click':
796
+ result = await this.interactionClick(args);
797
+ break;
798
+ case 'interaction_type':
799
+ result = await this.interactionType(args);
800
+ break;
801
+ case 'interaction_scroll':
802
+ result = await this.interactionScroll(args);
803
+ break;
804
+ case 'interaction_hover':
805
+ result = await this.interactionHover(args);
806
+ break;
807
+ case 'capture_screenshot':
808
+ result = await this.captureScreenshot(args);
809
+ break;
810
+ case 'capture_element_screenshot':
811
+ result = await this.captureElementScreenshot(args);
812
+ break;
813
+ case 'state_get_variable':
814
+ result = await this.stateGetVariable(args);
815
+ break;
816
+ case 'state_local_storage':
817
+ result = await this.stateLocalStorage(args);
818
+ break;
819
+ case 'console_get_logs':
820
+ result = await this.consoleGetLogs(args);
821
+ break;
822
+ case 'dom_manipulate':
823
+ result = await this.domManipulate(args);
824
+ break;
825
+ case 'javascript_inject':
826
+ result = await this.javascriptInject(args);
827
+ break;
828
+ case 'webapp_list_clients':
829
+ result = await this.webappListClients(args);
830
+ break;
831
+ case 'execute_javascript':
832
+ result = await this.executeJavascript(args);
833
+ break;
834
+ default:
835
+ throw new Error(`Unknown tool: ${toolName}`);
836
+ }
837
+ const executionTime = Date.now() - startTime;
838
+ this.log(`[WebApp Client] Tool execution successful, sending response`);
839
+ this.devTools?.logToolExecution(toolName, args, true, 'Success', executionTime, result);
840
+ this.sendMessage({
841
+ type: 'tool_response',
842
+ requestId,
843
+ result,
844
+ success: true,
845
+ });
846
+ }
847
+ catch (error) {
848
+ const executionTime = Date.now() - startTime;
849
+ const errorMessage = error instanceof Error ? error.message : String(error);
850
+ this.logError(`[WebApp Client] Tool execution failed:`, error);
851
+ this.devTools?.logToolExecution(toolName, args, false, errorMessage, executionTime);
852
+ this.sendMessage({
853
+ type: 'tool_response',
854
+ requestId,
855
+ success: false,
856
+ error: errorMessage,
857
+ });
858
+ }
859
+ }
860
+ setupConsoleInterception() {
861
+ const originalConsole = {
862
+ log: console.log,
863
+ info: console.info,
864
+ warn: console.warn,
865
+ error: console.error,
866
+ };
867
+ const interceptor = (level) => {
868
+ return (...args) => {
869
+ this.consoleLogs.push({
870
+ level,
871
+ timestamp: new Date().toISOString(),
872
+ args: args.map((arg) => {
873
+ try {
874
+ return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
875
+ }
876
+ catch {
877
+ return String(arg);
878
+ }
879
+ }),
880
+ });
881
+ if (this.consoleLogs.length > 1000) {
882
+ this.consoleLogs.shift();
883
+ }
884
+ originalConsole[level](...args);
885
+ };
886
+ };
887
+ console.log = interceptor('log');
888
+ console.info = interceptor('info');
889
+ console.warn = interceptor('warn');
890
+ console.error = interceptor('error');
891
+ }
892
+ async domQuery(args) {
893
+ const { selector, limit = 10 } = args;
894
+ const elements = Array.from(document.querySelectorAll(selector)).slice(0, limit);
895
+ return {
896
+ elements: elements.map((el) => ({
897
+ selector,
898
+ tagName: el.tagName.toLowerCase(),
899
+ id: el.id || undefined,
900
+ className: el.className || undefined,
901
+ text: el.textContent?.trim().substring(0, 100),
902
+ attributes: (() => {
903
+ const attrs = {};
904
+ for (let i = 0; i < el.attributes.length; i++) {
905
+ const attr = el.attributes[i];
906
+ attrs[attr.name] = attr.value;
907
+ }
908
+ return attrs;
909
+ })(),
910
+ })),
911
+ };
912
+ }
913
+ async domGetProperties(args) {
914
+ const { selector, properties = [] } = args;
915
+ const element = document.querySelector(selector);
916
+ if (!element) {
917
+ throw new Error(`Element not found: ${selector}`);
918
+ }
919
+ const result = {};
920
+ for (const prop of properties) {
921
+ try {
922
+ result[prop] = element[prop];
923
+ }
924
+ catch {
925
+ result[prop] = undefined;
926
+ }
927
+ }
928
+ return result;
929
+ }
930
+ async domGetText(args) {
931
+ const { selector, includeHidden = false } = args;
932
+ const elements = document.querySelectorAll(selector);
933
+ const texts = [];
934
+ elements.forEach((el) => {
935
+ if (includeHidden || el.offsetParent !== null) {
936
+ const text = el.textContent?.trim();
937
+ if (text)
938
+ texts.push(text);
939
+ }
940
+ });
941
+ return { texts };
942
+ }
943
+ async domGetHTML(args) {
944
+ const { selector, outerHTML = false } = args;
945
+ const element = document.querySelector(selector);
946
+ if (!element) {
947
+ throw new Error(`Element not found: ${selector}`);
948
+ }
949
+ return {
950
+ html: outerHTML ? element.outerHTML : element.innerHTML,
951
+ };
952
+ }
953
+ async interactionClick(args) {
954
+ const { selector, button = 'left' } = args;
955
+ const element = document.querySelector(selector);
956
+ if (!element) {
957
+ throw new Error(`Element not found: ${selector}`);
958
+ }
959
+ const event = new MouseEvent('click', {
960
+ view: window,
961
+ bubbles: true,
962
+ cancelable: true,
963
+ button: button === 'right' ? 2 : button === 'middle' ? 1 : 0,
964
+ });
965
+ element.dispatchEvent(event);
966
+ return { success: true };
967
+ }
968
+ async interactionType(args) {
969
+ const { selector, text, clear = false } = args;
970
+ const element = document.querySelector(selector);
971
+ if (!element) {
972
+ throw new Error(`Element not found: ${selector}`);
973
+ }
974
+ if (clear) {
975
+ element.value = '';
976
+ }
977
+ element.focus();
978
+ element.value += text;
979
+ element.dispatchEvent(new Event('input', { bubbles: true }));
980
+ element.dispatchEvent(new Event('change', { bubbles: true }));
981
+ return { success: true };
982
+ }
983
+ async interactionScroll(args) {
984
+ const { selector, direction, amount = 100 } = args;
985
+ const element = selector ? document.querySelector(selector) : window;
986
+ if (!element && selector) {
987
+ throw new Error(`Element not found: ${selector}`);
988
+ }
989
+ const scrollOptions = {
990
+ behavior: 'smooth',
991
+ };
992
+ if (direction === 'up' || direction === 'down') {
993
+ scrollOptions.top = direction === 'down' ? amount : -amount;
994
+ }
995
+ else {
996
+ scrollOptions.left = direction === 'right' ? amount : -amount;
997
+ }
998
+ if (element === window) {
999
+ window.scrollBy(scrollOptions);
1000
+ }
1001
+ else {
1002
+ element.scrollBy(scrollOptions);
1003
+ }
1004
+ return { success: true };
1005
+ }
1006
+ async interactionHover(args) {
1007
+ const { selector } = args;
1008
+ const element = document.querySelector(selector);
1009
+ if (!element) {
1010
+ throw new Error(`Element not found: ${selector}`);
1011
+ }
1012
+ element.dispatchEvent(new MouseEvent('mouseenter', {
1013
+ view: window,
1014
+ bubbles: true,
1015
+ cancelable: true,
1016
+ }));
1017
+ element.dispatchEvent(new MouseEvent('mouseover', {
1018
+ view: window,
1019
+ bubbles: true,
1020
+ cancelable: true,
1021
+ }));
1022
+ return { success: true };
1023
+ }
1024
+ async captureScreenshot(args) {
1025
+ const { fullPage = true, format = 'png' } = args;
1026
+ try {
1027
+ // For now, we'll use a simple approach that captures the visible viewport
1028
+ // In a production environment, you'd want to use html2canvas or similar
1029
+ const canvas = document.createElement('canvas');
1030
+ const ctx = canvas.getContext('2d');
1031
+ if (!ctx) {
1032
+ throw new Error('Failed to create canvas context');
1033
+ }
1034
+ // Get dimensions
1035
+ const width = fullPage ? document.documentElement.scrollWidth : window.innerWidth;
1036
+ const height = fullPage ? document.documentElement.scrollHeight : window.innerHeight;
1037
+ canvas.width = width;
1038
+ canvas.height = height;
1039
+ // Draw a placeholder for now
1040
+ // In production, you'd use html2canvas or similar library
1041
+ ctx.fillStyle = '#f0f0f0';
1042
+ ctx.fillRect(0, 0, width, height);
1043
+ ctx.fillStyle = '#333';
1044
+ ctx.font = '20px Arial';
1045
+ ctx.textAlign = 'center';
1046
+ ctx.fillText('Screenshot placeholder - implement with html2canvas', width / 2, height / 2);
1047
+ // Convert to data URL
1048
+ const dataUrl = canvas.toDataURL(`image/${format}`);
1049
+ return {
1050
+ success: true,
1051
+ dataUrl,
1052
+ width,
1053
+ height,
1054
+ message: 'Screenshot captured (placeholder - integrate html2canvas for full implementation)'
1055
+ };
1056
+ }
1057
+ catch (error) {
1058
+ throw new Error(`Failed to capture screenshot: ${error}`);
1059
+ }
1060
+ }
1061
+ async captureElementScreenshot(args) {
1062
+ const { selector, format = 'png' } = args;
1063
+ if (!selector) {
1064
+ throw new Error('Selector is required for element screenshot');
1065
+ }
1066
+ const element = document.querySelector(selector);
1067
+ if (!element) {
1068
+ throw new Error(`Element not found: ${selector}`);
1069
+ }
1070
+ try {
1071
+ const rect = element.getBoundingClientRect();
1072
+ const canvas = document.createElement('canvas');
1073
+ const ctx = canvas.getContext('2d');
1074
+ if (!ctx) {
1075
+ throw new Error('Failed to create canvas context');
1076
+ }
1077
+ canvas.width = rect.width;
1078
+ canvas.height = rect.height;
1079
+ // Draw a placeholder
1080
+ ctx.fillStyle = '#e0e0e0';
1081
+ ctx.fillRect(0, 0, rect.width, rect.height);
1082
+ ctx.fillStyle = '#666';
1083
+ ctx.font = '16px Arial';
1084
+ ctx.textAlign = 'center';
1085
+ ctx.fillText(`Element: ${selector}`, rect.width / 2, rect.height / 2);
1086
+ const dataUrl = canvas.toDataURL(`image/${format}`);
1087
+ return {
1088
+ success: true,
1089
+ dataUrl,
1090
+ width: rect.width,
1091
+ height: rect.height,
1092
+ selector,
1093
+ message: 'Element screenshot captured (placeholder - integrate html2canvas for full implementation)'
1094
+ };
1095
+ }
1096
+ catch (error) {
1097
+ throw new Error(`Failed to capture element screenshot: ${error}`);
1098
+ }
1099
+ }
1100
+ async stateGetVariable(args) {
1101
+ const { path } = args;
1102
+ const parts = path.split('.');
1103
+ let current = window;
1104
+ for (const part of parts) {
1105
+ if (current && typeof current === 'object' && part in current) {
1106
+ current = current[part];
1107
+ }
1108
+ else {
1109
+ throw new Error(`Variable not found: ${path}`);
1110
+ }
1111
+ }
1112
+ return { value: current };
1113
+ }
1114
+ async stateLocalStorage(args) {
1115
+ const { operation, key, value } = args;
1116
+ switch (operation) {
1117
+ case 'get':
1118
+ return { value: localStorage.getItem(key) };
1119
+ case 'set':
1120
+ localStorage.setItem(key, value);
1121
+ return { success: true };
1122
+ case 'remove':
1123
+ localStorage.removeItem(key);
1124
+ return { success: true };
1125
+ case 'clear':
1126
+ localStorage.clear();
1127
+ return { success: true };
1128
+ case 'getAll':
1129
+ const items = {};
1130
+ for (let i = 0; i < localStorage.length; i++) {
1131
+ const k = localStorage.key(i);
1132
+ if (k)
1133
+ items[k] = localStorage.getItem(k) || '';
1134
+ }
1135
+ return { items };
1136
+ default:
1137
+ throw new Error(`Unknown localStorage operation: ${operation}`);
1138
+ }
1139
+ }
1140
+ async consoleGetLogs(args) {
1141
+ const { level = 'all', limit = 100 } = args;
1142
+ let logs = this.consoleLogs;
1143
+ if (level !== 'all') {
1144
+ logs = logs.filter((log) => log.level === level);
1145
+ }
1146
+ return { logs: logs.slice(-limit) };
1147
+ }
1148
+ async domManipulate(args) {
1149
+ const { action, selector, value, attribute, property } = args;
1150
+ if (!selector) {
1151
+ throw new Error('Selector is required for DOM manipulation');
1152
+ }
1153
+ const element = document.querySelector(selector);
1154
+ if (!element) {
1155
+ throw new Error(`Element not found: ${selector}`);
1156
+ }
1157
+ switch (action) {
1158
+ case 'setAttribute':
1159
+ if (!attribute || value === undefined) {
1160
+ throw new Error('Attribute name and value are required for setAttribute');
1161
+ }
1162
+ element.setAttribute(attribute, value);
1163
+ return { success: true, message: `Set attribute ${attribute}="${value}" on ${selector}` };
1164
+ case 'removeAttribute':
1165
+ if (!attribute) {
1166
+ throw new Error('Attribute name is required for removeAttribute');
1167
+ }
1168
+ element.removeAttribute(attribute);
1169
+ return { success: true, message: `Removed attribute ${attribute} from ${selector}` };
1170
+ case 'setProperty':
1171
+ if (!property || value === undefined) {
1172
+ throw new Error('Property name and value are required for setProperty');
1173
+ }
1174
+ element[property] = value;
1175
+ return { success: true, message: `Set property ${property}=${value} on ${selector}` };
1176
+ case 'addClass':
1177
+ if (!value) {
1178
+ throw new Error('Class name is required for addClass');
1179
+ }
1180
+ element.classList.add(value);
1181
+ return { success: true, message: `Added class "${value}" to ${selector}` };
1182
+ case 'removeClass':
1183
+ if (!value) {
1184
+ throw new Error('Class name is required for removeClass');
1185
+ }
1186
+ element.classList.remove(value);
1187
+ return { success: true, message: `Removed class "${value}" from ${selector}` };
1188
+ case 'setInnerHTML':
1189
+ if (value === undefined) {
1190
+ throw new Error('HTML content is required for setInnerHTML');
1191
+ }
1192
+ element.innerHTML = value;
1193
+ return { success: true, message: `Set innerHTML on ${selector}` };
1194
+ case 'setTextContent':
1195
+ if (value === undefined) {
1196
+ throw new Error('Text content is required for setTextContent');
1197
+ }
1198
+ element.textContent = value;
1199
+ return { success: true, message: `Set textContent on ${selector}` };
1200
+ case 'setStyle':
1201
+ if (!property || value === undefined) {
1202
+ throw new Error('Style property and value are required for setStyle');
1203
+ }
1204
+ element.style[property] = value;
1205
+ return { success: true, message: `Set style ${property}=${value} on ${selector}` };
1206
+ case 'remove':
1207
+ element.remove();
1208
+ return { success: true, message: `Removed element ${selector}` };
1209
+ default:
1210
+ throw new Error(`Unknown DOM manipulation action: ${action}`);
1211
+ }
1212
+ }
1213
+ async javascriptInject(args) {
1214
+ const { code, returnValue = false } = args;
1215
+ if (!code) {
1216
+ throw new Error('JavaScript code is required');
1217
+ }
1218
+ try {
1219
+ let result;
1220
+ if (returnValue) {
1221
+ // Use eval to return a value
1222
+ result = eval(code);
1223
+ }
1224
+ else {
1225
+ // Use Function constructor for execution without return
1226
+ const func = new Function(code);
1227
+ result = func();
1228
+ }
1229
+ return {
1230
+ success: true,
1231
+ result: result !== undefined ? result : null,
1232
+ message: 'JavaScript executed successfully'
1233
+ };
1234
+ }
1235
+ catch (error) {
1236
+ throw new Error(`JavaScript execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1237
+ }
1238
+ }
1239
+ async webappListClients(args) {
1240
+ // This returns information about the current client
1241
+ return {
1242
+ clients: [{
1243
+ id: 'browser-client',
1244
+ type: 'browser',
1245
+ url: window.location.href,
1246
+ userAgent: navigator.userAgent,
1247
+ connected: this.isConnected,
1248
+ timestamp: new Date().toISOString()
1249
+ }]
1250
+ };
1251
+ }
1252
+ async executeJavascript(args) {
1253
+ const { code, returnValue = false, async = false } = args;
1254
+ if (!code) {
1255
+ throw new Error('JavaScript code is required');
1256
+ }
1257
+ try {
1258
+ let result;
1259
+ if (async) {
1260
+ // Execute asynchronously
1261
+ if (returnValue) {
1262
+ result = await eval(`(async () => { return ${code}; })()`);
1263
+ }
1264
+ else {
1265
+ result = await eval(`(async () => { ${code}; })()`);
1266
+ }
1267
+ }
1268
+ else {
1269
+ // Execute synchronously
1270
+ if (returnValue) {
1271
+ result = eval(`(() => { return ${code}; })()`);
1272
+ }
1273
+ else {
1274
+ eval(code);
1275
+ result = undefined;
1276
+ }
1277
+ }
1278
+ return {
1279
+ success: true,
1280
+ result: result !== undefined ? result : null,
1281
+ message: 'JavaScript executed successfully',
1282
+ executionTime: new Date().toISOString()
1283
+ };
1284
+ }
1285
+ catch (error) {
1286
+ throw new Error(`JavaScript execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
1287
+ }
1288
+ }
1289
+ }
1290
+ if (typeof window !== 'undefined') {
1291
+ window.WebAppMCP = { WebAppMCPClient };
1292
+ }
1293
+
9
1294
  // Browser-only exports
10
- const index_1 = tslib_1.__importDefault(require("./client/index"));
11
- exports.WebAppMCPClient = index_1.default;
12
1295
  // Also attach to window for script tag usage
13
1296
  if (typeof window !== 'undefined') {
1297
+ window.WebAppMCPClient = WebAppMCPClient;
1298
+ // Also keep the namespaced version for compatibility
14
1299
  window.WebAppMCP = {
15
- WebAppMCPClient: index_1.default
1300
+ WebAppMCPClient
16
1301
  };
17
1302
  }
18
1303
 
1304
+ exports.WebAppMCPClient = WebAppMCPClient;
1305
+
19
1306
  }));
20
1307
  //# sourceMappingURL=browser.js.map