mcp-feedback-enhanced 0.1.55 → 0.1.65
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/index.js +346 -12
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -16,7 +16,9 @@ import * as os from 'os';
|
|
|
16
16
|
import * as fs from 'fs';
|
|
17
17
|
import * as path from 'path';
|
|
18
18
|
import * as net from 'net';
|
|
19
|
+
import * as http from 'http';
|
|
19
20
|
import { createRequire } from 'module';
|
|
21
|
+
import { exec } from 'child_process';
|
|
20
22
|
// Read version from package.json (ES module compatible)
|
|
21
23
|
const require = createRequire(import.meta.url);
|
|
22
24
|
const packageJson = require('../package.json');
|
|
@@ -173,13 +175,29 @@ async function findExtensionForProjectAsync(projectPath) {
|
|
|
173
175
|
if (servers.length === 0) {
|
|
174
176
|
return null;
|
|
175
177
|
}
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
// Get MCP server's CURSOR_TRACE_ID (if running in Cursor)
|
|
179
|
+
const myTraceId = process.env['CURSOR_TRACE_ID'] || '';
|
|
180
|
+
debug(`My CURSOR_TRACE_ID: ${myTraceId || '(not set)'}`);
|
|
181
|
+
// Strategy 0: CURSOR_TRACE_ID match (HIGHEST PRIORITY - same Cursor window)
|
|
182
|
+
if (myTraceId) {
|
|
183
|
+
const traceMatch = servers.find(s => s.cursorTraceId === myTraceId);
|
|
184
|
+
if (traceMatch) {
|
|
185
|
+
debug(`✓ CURSOR_TRACE_ID match: port=${traceMatch.port}, traceId=${myTraceId}`);
|
|
186
|
+
return traceMatch;
|
|
181
187
|
}
|
|
182
188
|
}
|
|
189
|
+
// Strategy 1: Exact workspace match + traceId filter if available
|
|
190
|
+
const workspaceMatches = servers.filter(s => s.workspaces?.includes(projectPath));
|
|
191
|
+
if (workspaceMatches.length === 1) {
|
|
192
|
+
debug(`✓ Exact workspace match (single): port=${workspaceMatches[0].port}`);
|
|
193
|
+
return workspaceMatches[0];
|
|
194
|
+
}
|
|
195
|
+
if (workspaceMatches.length > 1) {
|
|
196
|
+
// Multiple windows have this workspace - take most recent
|
|
197
|
+
const sorted = workspaceMatches.sort((a, b) => b.timestamp - a.timestamp);
|
|
198
|
+
debug(`✓ Exact workspace match (multiple, taking most recent): port=${sorted[0].port}`);
|
|
199
|
+
return sorted[0];
|
|
200
|
+
}
|
|
183
201
|
// Strategy 2: Prefix match (project is inside a workspace)
|
|
184
202
|
for (const server of servers) {
|
|
185
203
|
for (const ws of server.workspaces || []) {
|
|
@@ -206,7 +224,7 @@ async function findExtensionForProjectAsync(projectPath) {
|
|
|
206
224
|
debug(`✓ Using most recent server: port=${sorted[0].port}`);
|
|
207
225
|
debug(` Available servers:`);
|
|
208
226
|
sorted.forEach(s => {
|
|
209
|
-
debug(` - pid=${s.pid}, port=${s.port}, workspaces=${s.workspaces?.join(', ')}`);
|
|
227
|
+
debug(` - pid=${s.pid}, port=${s.port}, traceId=${s.cursorTraceId}, workspaces=${s.workspaces?.join(', ')}`);
|
|
210
228
|
});
|
|
211
229
|
return sorted[0];
|
|
212
230
|
}
|
|
@@ -217,11 +235,19 @@ function findExtensionForProject(projectPath) {
|
|
|
217
235
|
const servers = getLiveServers();
|
|
218
236
|
if (servers.length === 0)
|
|
219
237
|
return null;
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
238
|
+
// Strategy 0: CURSOR_TRACE_ID match (highest priority)
|
|
239
|
+
const myTraceId = process.env['CURSOR_TRACE_ID'] || '';
|
|
240
|
+
if (myTraceId) {
|
|
241
|
+
const traceMatch = servers.find(s => s.cursorTraceId === myTraceId);
|
|
242
|
+
if (traceMatch)
|
|
243
|
+
return traceMatch;
|
|
244
|
+
}
|
|
245
|
+
// Strategy 1: Exact workspace match
|
|
246
|
+
const workspaceMatches = servers.filter(s => s.workspaces?.includes(projectPath));
|
|
247
|
+
if (workspaceMatches.length >= 1) {
|
|
248
|
+
return workspaceMatches.sort((a, b) => b.timestamp - a.timestamp)[0];
|
|
224
249
|
}
|
|
250
|
+
// Strategy 2: Prefix match
|
|
225
251
|
for (const server of servers) {
|
|
226
252
|
for (const ws of server.workspaces || []) {
|
|
227
253
|
if (projectPath.startsWith(ws + path.sep) || ws.startsWith(projectPath + path.sep)) {
|
|
@@ -229,9 +255,11 @@ function findExtensionForProject(projectPath) {
|
|
|
229
255
|
}
|
|
230
256
|
}
|
|
231
257
|
}
|
|
258
|
+
// Strategy 3: parentPid match
|
|
232
259
|
const parentMatch = servers.find(s => s.parentPid === process.ppid);
|
|
233
260
|
if (parentMatch)
|
|
234
261
|
return parentMatch;
|
|
262
|
+
// Fallbacks
|
|
235
263
|
if (servers.length === 1)
|
|
236
264
|
return servers[0];
|
|
237
265
|
return servers.sort((a, b) => b.timestamp - a.timestamp)[0];
|
|
@@ -388,9 +416,18 @@ function handleMessage(message) {
|
|
|
388
416
|
}
|
|
389
417
|
/**
|
|
390
418
|
* Request feedback from user via Extension
|
|
419
|
+
* Falls back to browser if extension is not available
|
|
391
420
|
*/
|
|
392
421
|
async function requestFeedback(projectDirectory, summary, timeout, agentName) {
|
|
393
|
-
|
|
422
|
+
// Try to connect to extension first
|
|
423
|
+
try {
|
|
424
|
+
await ensureConnectedForProject(projectDirectory);
|
|
425
|
+
}
|
|
426
|
+
catch (e) {
|
|
427
|
+
// Extension not available, fall back to browser
|
|
428
|
+
debug(`Extension not available, falling back to browser: ${e.message}`);
|
|
429
|
+
return requestFeedbackViaBrowser(projectDirectory, summary, timeout);
|
|
430
|
+
}
|
|
394
431
|
const sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
395
432
|
return new Promise((resolve, reject) => {
|
|
396
433
|
// Set timeout
|
|
@@ -417,6 +454,303 @@ async function requestFeedback(projectDirectory, summary, timeout, agentName) {
|
|
|
417
454
|
});
|
|
418
455
|
}
|
|
419
456
|
// ============================================================================
|
|
457
|
+
// Browser Fallback
|
|
458
|
+
// ============================================================================
|
|
459
|
+
/**
|
|
460
|
+
* Open system default browser
|
|
461
|
+
*/
|
|
462
|
+
function openBrowser(url) {
|
|
463
|
+
const platform = os.platform();
|
|
464
|
+
let cmd;
|
|
465
|
+
if (platform === 'darwin') {
|
|
466
|
+
cmd = `open "${url}"`;
|
|
467
|
+
}
|
|
468
|
+
else if (platform === 'win32') {
|
|
469
|
+
cmd = `start "" "${url}"`;
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
cmd = `xdg-open "${url}"`;
|
|
473
|
+
}
|
|
474
|
+
exec(cmd, (error) => {
|
|
475
|
+
if (error) {
|
|
476
|
+
debug(`Failed to open browser: ${error.message}`);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Generate HTML page for browser feedback
|
|
482
|
+
*/
|
|
483
|
+
function generateBrowserHtml(summary, port) {
|
|
484
|
+
const escapedSummary = summary
|
|
485
|
+
.replace(/&/g, '&')
|
|
486
|
+
.replace(/</g, '<')
|
|
487
|
+
.replace(/>/g, '>')
|
|
488
|
+
.replace(/"/g, '"')
|
|
489
|
+
.replace(/\n/g, '<br>');
|
|
490
|
+
return `<!DOCTYPE html>
|
|
491
|
+
<html lang="en">
|
|
492
|
+
<head>
|
|
493
|
+
<meta charset="UTF-8">
|
|
494
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
495
|
+
<title>MCP Feedback</title>
|
|
496
|
+
<style>
|
|
497
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
498
|
+
body {
|
|
499
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
500
|
+
background: #1e1e1e;
|
|
501
|
+
color: #d4d4d4;
|
|
502
|
+
min-height: 100vh;
|
|
503
|
+
display: flex;
|
|
504
|
+
align-items: center;
|
|
505
|
+
justify-content: center;
|
|
506
|
+
padding: 20px;
|
|
507
|
+
}
|
|
508
|
+
.container {
|
|
509
|
+
max-width: 800px;
|
|
510
|
+
width: 100%;
|
|
511
|
+
background: #252526;
|
|
512
|
+
border-radius: 12px;
|
|
513
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
|
514
|
+
overflow: hidden;
|
|
515
|
+
}
|
|
516
|
+
.header {
|
|
517
|
+
background: #007acc;
|
|
518
|
+
padding: 16px 24px;
|
|
519
|
+
display: flex;
|
|
520
|
+
align-items: center;
|
|
521
|
+
gap: 12px;
|
|
522
|
+
}
|
|
523
|
+
.header h1 {
|
|
524
|
+
font-size: 18px;
|
|
525
|
+
font-weight: 600;
|
|
526
|
+
color: white;
|
|
527
|
+
}
|
|
528
|
+
.header .icon { font-size: 24px; }
|
|
529
|
+
.content { padding: 24px; }
|
|
530
|
+
.ai-message {
|
|
531
|
+
background: #2d2d2d;
|
|
532
|
+
border: 1px solid #3c3c3c;
|
|
533
|
+
border-radius: 8px;
|
|
534
|
+
padding: 16px;
|
|
535
|
+
margin-bottom: 20px;
|
|
536
|
+
max-height: 400px;
|
|
537
|
+
overflow-y: auto;
|
|
538
|
+
line-height: 1.6;
|
|
539
|
+
}
|
|
540
|
+
.label {
|
|
541
|
+
font-size: 12px;
|
|
542
|
+
color: #888;
|
|
543
|
+
margin-bottom: 8px;
|
|
544
|
+
display: flex;
|
|
545
|
+
align-items: center;
|
|
546
|
+
gap: 6px;
|
|
547
|
+
}
|
|
548
|
+
.quick-btns {
|
|
549
|
+
display: flex;
|
|
550
|
+
gap: 8px;
|
|
551
|
+
margin-bottom: 16px;
|
|
552
|
+
flex-wrap: wrap;
|
|
553
|
+
}
|
|
554
|
+
.quick-btn {
|
|
555
|
+
padding: 8px 16px;
|
|
556
|
+
background: transparent;
|
|
557
|
+
border: 1px solid #3c3c3c;
|
|
558
|
+
color: #d4d4d4;
|
|
559
|
+
border-radius: 20px;
|
|
560
|
+
cursor: pointer;
|
|
561
|
+
font-size: 13px;
|
|
562
|
+
transition: all 0.2s;
|
|
563
|
+
}
|
|
564
|
+
.quick-btn:hover {
|
|
565
|
+
background: #3c3c3c;
|
|
566
|
+
border-color: #569cd6;
|
|
567
|
+
}
|
|
568
|
+
textarea {
|
|
569
|
+
width: 100%;
|
|
570
|
+
min-height: 120px;
|
|
571
|
+
padding: 12px;
|
|
572
|
+
background: #2d2d2d;
|
|
573
|
+
border: 1px solid #3c3c3c;
|
|
574
|
+
border-radius: 8px;
|
|
575
|
+
color: #d4d4d4;
|
|
576
|
+
font-size: 14px;
|
|
577
|
+
font-family: inherit;
|
|
578
|
+
resize: vertical;
|
|
579
|
+
margin-bottom: 16px;
|
|
580
|
+
}
|
|
581
|
+
textarea:focus {
|
|
582
|
+
outline: none;
|
|
583
|
+
border-color: #007acc;
|
|
584
|
+
}
|
|
585
|
+
.submit-btn {
|
|
586
|
+
width: 100%;
|
|
587
|
+
padding: 14px;
|
|
588
|
+
background: #007acc;
|
|
589
|
+
border: none;
|
|
590
|
+
border-radius: 8px;
|
|
591
|
+
color: white;
|
|
592
|
+
font-size: 15px;
|
|
593
|
+
font-weight: 600;
|
|
594
|
+
cursor: pointer;
|
|
595
|
+
transition: background 0.2s;
|
|
596
|
+
}
|
|
597
|
+
.submit-btn:hover { background: #0098ff; }
|
|
598
|
+
.submit-btn:disabled {
|
|
599
|
+
background: #3c3c3c;
|
|
600
|
+
cursor: not-allowed;
|
|
601
|
+
}
|
|
602
|
+
.success {
|
|
603
|
+
text-align: center;
|
|
604
|
+
padding: 40px;
|
|
605
|
+
}
|
|
606
|
+
.success .icon { font-size: 48px; margin-bottom: 16px; }
|
|
607
|
+
.success h2 { color: #4ec9b0; margin-bottom: 8px; }
|
|
608
|
+
.success p { color: #888; }
|
|
609
|
+
</style>
|
|
610
|
+
</head>
|
|
611
|
+
<body>
|
|
612
|
+
<div class="container" id="feedbackForm">
|
|
613
|
+
<div class="header">
|
|
614
|
+
<span class="icon">💬</span>
|
|
615
|
+
<h1>MCP Feedback</h1>
|
|
616
|
+
</div>
|
|
617
|
+
<div class="content">
|
|
618
|
+
<div class="label">🤖 AI Summary</div>
|
|
619
|
+
<div class="ai-message">${escapedSummary}</div>
|
|
620
|
+
|
|
621
|
+
<div class="label">💬 Your Feedback</div>
|
|
622
|
+
<div class="quick-btns">
|
|
623
|
+
<button class="quick-btn" onclick="setFeedback('Continue')">▶️ Continue</button>
|
|
624
|
+
<button class="quick-btn" onclick="setFeedback('Looks good')">👍 Good</button>
|
|
625
|
+
<button class="quick-btn" onclick="setFeedback('Please fix it')">🔧 Fix</button>
|
|
626
|
+
<button class="quick-btn" onclick="setFeedback('Stop')">⏹️ Stop</button>
|
|
627
|
+
</div>
|
|
628
|
+
<textarea id="feedback" placeholder="Type your feedback here..."></textarea>
|
|
629
|
+
<button class="submit-btn" id="submitBtn" onclick="submitFeedback()">Send Feedback</button>
|
|
630
|
+
</div>
|
|
631
|
+
</div>
|
|
632
|
+
|
|
633
|
+
<div class="container" id="successMsg" style="display: none;">
|
|
634
|
+
<div class="success">
|
|
635
|
+
<div class="icon">✅</div>
|
|
636
|
+
<h2>Feedback Sent!</h2>
|
|
637
|
+
<p>You can close this window now.</p>
|
|
638
|
+
</div>
|
|
639
|
+
</div>
|
|
640
|
+
|
|
641
|
+
<script>
|
|
642
|
+
function setFeedback(text) {
|
|
643
|
+
document.getElementById('feedback').value = text;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async function submitFeedback() {
|
|
647
|
+
const feedback = document.getElementById('feedback').value.trim();
|
|
648
|
+
if (!feedback) {
|
|
649
|
+
alert('Please enter your feedback');
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const btn = document.getElementById('submitBtn');
|
|
654
|
+
btn.disabled = true;
|
|
655
|
+
btn.textContent = 'Sending...';
|
|
656
|
+
|
|
657
|
+
try {
|
|
658
|
+
const response = await fetch('/submit', {
|
|
659
|
+
method: 'POST',
|
|
660
|
+
headers: { 'Content-Type': 'application/json' },
|
|
661
|
+
body: JSON.stringify({ feedback })
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
if (response.ok) {
|
|
665
|
+
document.getElementById('feedbackForm').style.display = 'none';
|
|
666
|
+
document.getElementById('successMsg').style.display = 'block';
|
|
667
|
+
} else {
|
|
668
|
+
throw new Error('Submit failed');
|
|
669
|
+
}
|
|
670
|
+
} catch (e) {
|
|
671
|
+
btn.disabled = false;
|
|
672
|
+
btn.textContent = 'Send Feedback';
|
|
673
|
+
alert('Failed to send feedback. Please try again.');
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
</script>
|
|
677
|
+
</body>
|
|
678
|
+
</html>`;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Request feedback via browser (fallback when extension not available)
|
|
682
|
+
*/
|
|
683
|
+
async function requestFeedbackViaBrowser(projectDirectory, summary, timeout) {
|
|
684
|
+
return new Promise((resolve, reject) => {
|
|
685
|
+
// Find available port
|
|
686
|
+
const server = http.createServer();
|
|
687
|
+
server.listen(0, '127.0.0.1', () => {
|
|
688
|
+
const address = server.address();
|
|
689
|
+
if (!address || typeof address === 'string') {
|
|
690
|
+
reject(new Error('Failed to start HTTP server'));
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const port = address.port;
|
|
694
|
+
debug(`Browser feedback server started on port ${port}`);
|
|
695
|
+
let feedbackReceived = false;
|
|
696
|
+
// Handle requests
|
|
697
|
+
server.on('request', (req, res) => {
|
|
698
|
+
if (req.method === 'GET' && req.url === '/') {
|
|
699
|
+
// Serve HTML page
|
|
700
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
701
|
+
res.end(generateBrowserHtml(summary, port));
|
|
702
|
+
}
|
|
703
|
+
else if (req.method === 'POST' && req.url === '/submit') {
|
|
704
|
+
// Handle feedback submission
|
|
705
|
+
let body = '';
|
|
706
|
+
req.on('data', chunk => body += chunk);
|
|
707
|
+
req.on('end', () => {
|
|
708
|
+
try {
|
|
709
|
+
const data = JSON.parse(body);
|
|
710
|
+
feedbackReceived = true;
|
|
711
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
712
|
+
res.end(JSON.stringify({ success: true }));
|
|
713
|
+
// Close server and resolve
|
|
714
|
+
setTimeout(() => {
|
|
715
|
+
server.close();
|
|
716
|
+
resolve({
|
|
717
|
+
feedback: data.feedback || '',
|
|
718
|
+
images: []
|
|
719
|
+
});
|
|
720
|
+
}, 1000);
|
|
721
|
+
}
|
|
722
|
+
catch (e) {
|
|
723
|
+
res.writeHead(400);
|
|
724
|
+
res.end('Invalid request');
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
res.writeHead(404);
|
|
730
|
+
res.end('Not found');
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
// Open browser
|
|
734
|
+
const url = `http://127.0.0.1:${port}`;
|
|
735
|
+
debug(`Opening browser: ${url}`);
|
|
736
|
+
openBrowser(url);
|
|
737
|
+
// Timeout
|
|
738
|
+
const timeoutHandle = setTimeout(() => {
|
|
739
|
+
if (!feedbackReceived) {
|
|
740
|
+
server.close();
|
|
741
|
+
reject(new Error(`Browser feedback timeout after ${timeout} seconds`));
|
|
742
|
+
}
|
|
743
|
+
}, timeout * 1000);
|
|
744
|
+
server.on('close', () => {
|
|
745
|
+
clearTimeout(timeoutHandle);
|
|
746
|
+
});
|
|
747
|
+
});
|
|
748
|
+
server.on('error', (err) => {
|
|
749
|
+
reject(new Error(`Failed to start browser feedback server: ${err.message}`));
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
// ============================================================================
|
|
420
754
|
// MCP Server Setup
|
|
421
755
|
// ============================================================================
|
|
422
756
|
const server = new McpServer({
|
|
@@ -428,7 +762,7 @@ server.tool('interactive_feedback', 'Collect feedback from user through VSCode e
|
|
|
428
762
|
project_directory: z.string().describe('The project directory path for context'),
|
|
429
763
|
summary: z.string().describe('Summary of AI work completed for user review'),
|
|
430
764
|
timeout: z.number().optional().default(600).describe('Timeout in seconds (default: 600)'),
|
|
431
|
-
agent_name: z.string().optional().describe('
|
|
765
|
+
agent_name: z.string().optional().describe('Unique identifier for this agent/chat session. Use a stable name like the chat title or task description (e.g. "Chat 1", "Pre-credit Implementation", "Bug Fix #123"). If not provided, defaults to "Agent" and all messages will appear in the same tab. Providing a unique name allows multiple agents to have separate conversation tabs.')
|
|
432
766
|
}, async ({ project_directory, summary, timeout, agent_name }) => {
|
|
433
767
|
debug(`interactive_feedback called: project=${project_directory}, agent=${agent_name || 'default'}`);
|
|
434
768
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-feedback-enhanced",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.65",
|
|
4
4
|
"description": "MCP Feedback Enhanced Server - Interactive feedback collection for AI assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
"vscode",
|
|
18
18
|
"claude"
|
|
19
19
|
],
|
|
20
|
-
"author": "
|
|
20
|
+
"author": "yuanming.chen",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"repository": {
|
|
23
23
|
"type": "git",
|
|
24
|
-
"url": "https://github.com/
|
|
24
|
+
"url": "https://github.com/xxx/mcp-feedback-enhanced"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
27
|
"build": "tsc",
|