mcp-feedback-enhanced 0.1.45

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/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # MCP Feedback Enhanced Server
2
+
3
+ **Version: 0.1.44**
4
+
5
+ MCP Server component that connects to the VSCode Extension's WebSocket server to collect user feedback.
6
+
7
+ ## Architecture
8
+
9
+ ```
10
+ ┌─────────────────┐ ┌─────────────────┐
11
+ │ Cursor/AI │ stdio │ MCP Server │
12
+ │ (AI Client) │ ←────→ │ (This) │
13
+ └─────────────────┘ └────────┬────────┘
14
+ │ WebSocket
15
+
16
+ ┌─────────────────┐
17
+ │ VSCode Extension│
18
+ │ (WS Server) │
19
+ └────────┬────────┘
20
+
21
+
22
+ ┌─────────────────┐
23
+ │ Feedback Panel │
24
+ │ (Webview) │
25
+ └─────────────────┘
26
+ ```
27
+
28
+ ## How It Works
29
+
30
+ 1. **Extension starts** → Creates WebSocket server → Writes `~/.config/mcp-feedback-enhanced/servers/<pid>.json`
31
+ 2. **MCP Server starts** → Waits for `interactive_feedback` call
32
+ 3. **AI calls tool** → MCP Server reads server files → Finds matching Extension by workspace path → Connects
33
+ 4. **User submits** → Feedback flows back to AI
34
+
35
+ ## Server Discovery
36
+
37
+ MCP Server finds the correct Extension using multi-strategy matching:
38
+
39
+ 1. **Exact workspace match** - `project_directory` in server's `workspaces` array
40
+ 2. **Prefix match** - Project is inside a workspace directory
41
+ 3. **parentPid match** - Same parent process (backward compatibility)
42
+ 4. **Single server** - Only one server running
43
+ 5. **Most recent** - Last registered server
44
+
45
+ ## Tools
46
+
47
+ ### interactive_feedback
48
+
49
+ Collect feedback from user through the VSCode sidebar panel.
50
+
51
+ ```typescript
52
+ {
53
+ project_directory: string; // Project path for context & server matching
54
+ summary: string; // AI summary for user review
55
+ timeout?: number; // Timeout in seconds (default: 600)
56
+ }
57
+ ```
58
+
59
+ ### get_system_info
60
+
61
+ Returns system environment information.
62
+
63
+ ## Configuration
64
+
65
+ Add to `~/.cursor/mcp.json`:
66
+
67
+ ```json
68
+ {
69
+ "mcpServers": {
70
+ "mcp-feedback-enhanced": {
71
+ "command": "node",
72
+ "args": ["/path/to/mcp-server/dist/index.js"],
73
+ "env": {
74
+ "MCP_FEEDBACK_DEBUG": "true"
75
+ },
76
+ "timeout": 89400,
77
+ "autoApprove": ["interactive_feedback"]
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ ## Environment Variables
84
+
85
+ | Variable | Default | Description |
86
+ |----------|---------|-------------|
87
+ | `MCP_FEEDBACK_DEBUG` | `false` | Enable debug logging to stderr |
88
+
89
+ ## Debug Mode
90
+
91
+ Set `MCP_FEEDBACK_DEBUG=true` to see:
92
+ - Server discovery process
93
+ - WebSocket connection status
94
+ - Message flow between components
95
+
96
+ ## Troubleshooting
97
+
98
+ ### "No MCP Feedback Extension found for project"
99
+
100
+ 1. Ensure VSCode extension is installed and activated
101
+ 2. Open the MCP Feedback panel (click sidebar icon)
102
+ 3. Check `~/.config/mcp-feedback-enhanced/servers/` for server files
103
+ 4. Verify your project path is in the `workspaces` array
104
+
105
+ ### Connection timeout
106
+
107
+ 1. Check if Extension's WebSocket server is running (port in server file)
108
+ 2. Verify no firewall blocking localhost connections
109
+ 3. Try reloading the Cursor window
110
+
111
+ ## License
112
+
113
+ MIT
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Feedback Server - TypeScript Implementation
4
+ *
5
+ * This server provides the interactive_feedback tool that collects
6
+ * user feedback through a VSCode extension sidebar panel.
7
+ *
8
+ * NEW ARCHITECTURE: MCP Server connects to Extension's WebSocket Server
9
+ * (instead of running its own WebSocket Server)
10
+ */
11
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,361 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Feedback Server - TypeScript Implementation
4
+ *
5
+ * This server provides the interactive_feedback tool that collects
6
+ * user feedback through a VSCode extension sidebar panel.
7
+ *
8
+ * NEW ARCHITECTURE: MCP Server connects to Extension's WebSocket Server
9
+ * (instead of running its own WebSocket Server)
10
+ */
11
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
13
+ import WebSocket from 'ws';
14
+ import * as z from 'zod';
15
+ import * as os from 'os';
16
+ import * as fs from 'fs';
17
+ import * as path from 'path';
18
+ import { createRequire } from 'module';
19
+ // Read version from package.json (ES module compatible)
20
+ const require = createRequire(import.meta.url);
21
+ const packageJson = require('../package.json');
22
+ const VERSION = packageJson.version || '0.0.0';
23
+ // Configuration
24
+ const DEBUG = process.env.MCP_FEEDBACK_DEBUG === 'true';
25
+ const CONFIG_DIR = path.join(os.homedir(), '.config', 'mcp-feedback-enhanced');
26
+ const SERVERS_DIR = path.join(CONFIG_DIR, 'servers');
27
+ const CONNECTION_TIMEOUT_MS = 5000; // 5 seconds to connect
28
+ const HEARTBEAT_INTERVAL_MS = 30000; // 30 seconds heartbeat
29
+ // Debug logging
30
+ function debug(message) {
31
+ if (DEBUG) {
32
+ console.error(`[MCP Feedback] ${message}`);
33
+ }
34
+ }
35
+ // State
36
+ let ws = null;
37
+ let isConnected = false;
38
+ let pendingFeedbackResolvers = new Map();
39
+ /**
40
+ * Get all live Extension servers
41
+ */
42
+ function getLiveServers() {
43
+ try {
44
+ if (!fs.existsSync(SERVERS_DIR)) {
45
+ debug('Servers directory not found');
46
+ return [];
47
+ }
48
+ const files = fs.readdirSync(SERVERS_DIR);
49
+ const servers = [];
50
+ for (const file of files) {
51
+ if (!file.endsWith('.json'))
52
+ continue;
53
+ try {
54
+ const data = JSON.parse(fs.readFileSync(path.join(SERVERS_DIR, file), 'utf-8'));
55
+ // Verify process is still alive
56
+ try {
57
+ process.kill(data.pid, 0);
58
+ servers.push({
59
+ ...data,
60
+ workspaces: data.workspaces || [] // Handle old format
61
+ });
62
+ }
63
+ catch {
64
+ // Process dead, skip
65
+ debug(`Skipping dead server: pid=${data.pid}`);
66
+ }
67
+ }
68
+ catch {
69
+ // Skip invalid files
70
+ }
71
+ }
72
+ return servers;
73
+ }
74
+ catch (e) {
75
+ debug(`Error reading servers: ${e}`);
76
+ return [];
77
+ }
78
+ }
79
+ /**
80
+ * Find the best Extension server for a given project path
81
+ * Uses workspace matching as primary strategy
82
+ */
83
+ function findExtensionForProject(projectPath) {
84
+ const servers = getLiveServers();
85
+ debug(`Looking for Extension for project: ${projectPath}`);
86
+ debug(`Found ${servers.length} live Extension server(s)`);
87
+ if (servers.length === 0) {
88
+ return null;
89
+ }
90
+ // Strategy 1: Exact workspace match
91
+ for (const server of servers) {
92
+ if (server.workspaces?.includes(projectPath)) {
93
+ debug(`✓ Exact workspace match: port=${server.port}`);
94
+ return server;
95
+ }
96
+ }
97
+ // Strategy 2: Prefix match (project is inside a workspace)
98
+ for (const server of servers) {
99
+ for (const ws of server.workspaces || []) {
100
+ if (projectPath.startsWith(ws + path.sep) || ws.startsWith(projectPath + path.sep)) {
101
+ debug(`✓ Prefix workspace match: port=${server.port}, workspace=${ws}`);
102
+ return server;
103
+ }
104
+ }
105
+ }
106
+ // Strategy 3: parentPid match (backward compatibility)
107
+ const myParentPid = process.ppid;
108
+ const parentMatch = servers.find(s => s.parentPid === myParentPid);
109
+ if (parentMatch) {
110
+ debug(`✓ parentPid match: port=${parentMatch.port}`);
111
+ return parentMatch;
112
+ }
113
+ // Strategy 4: Single server fallback
114
+ if (servers.length === 1) {
115
+ debug(`✓ Single server fallback: port=${servers[0].port}`);
116
+ return servers[0];
117
+ }
118
+ // Strategy 5: Most recent server
119
+ const sorted = servers.sort((a, b) => b.timestamp - a.timestamp);
120
+ debug(`✓ Using most recent server: port=${sorted[0].port}`);
121
+ debug(` Available servers:`);
122
+ sorted.forEach(s => {
123
+ debug(` - pid=${s.pid}, port=${s.port}, workspaces=${s.workspaces?.join(', ')}`);
124
+ });
125
+ return sorted[0];
126
+ }
127
+ // Track which server we're connected to
128
+ let connectedPort = null;
129
+ /**
130
+ * Connect to Extension's WebSocket Server for a specific project
131
+ */
132
+ function connectToExtension(projectPath) {
133
+ return new Promise((resolve, reject) => {
134
+ const server = findExtensionForProject(projectPath);
135
+ if (!server) {
136
+ reject(new Error(`No MCP Feedback Extension found for project: ${projectPath}. Please ensure the extension is installed and a Cursor window is open with this project.`));
137
+ return;
138
+ }
139
+ const url = `ws://127.0.0.1:${server.port}/ws`;
140
+ debug(`Connecting to Extension at ${url} for project ${projectPath}`);
141
+ ws = new WebSocket(url);
142
+ ws.on('open', () => {
143
+ debug('Connected to Extension WebSocket Server');
144
+ isConnected = true;
145
+ connectedPort = server.port;
146
+ // Register as MCP Server
147
+ ws?.send(JSON.stringify({
148
+ type: 'register',
149
+ clientType: 'mcp-server',
150
+ pid: process.pid,
151
+ parentPid: process.ppid
152
+ }));
153
+ resolve();
154
+ });
155
+ ws.on('message', (data) => {
156
+ try {
157
+ const message = JSON.parse(data.toString());
158
+ handleMessage(message);
159
+ }
160
+ catch (e) {
161
+ debug(`Parse error: ${e}`);
162
+ }
163
+ });
164
+ ws.on('close', () => {
165
+ debug('Disconnected from Extension');
166
+ isConnected = false;
167
+ connectedPort = null;
168
+ ws = null;
169
+ // Reject all pending feedback requests
170
+ pendingFeedbackResolvers.forEach((resolver, sessionId) => {
171
+ clearTimeout(resolver.timeout);
172
+ resolver.reject(new Error('Connection to Extension lost'));
173
+ });
174
+ pendingFeedbackResolvers.clear();
175
+ });
176
+ ws.on('error', (err) => {
177
+ debug(`WebSocket error: ${err.message}`);
178
+ if (!isConnected) {
179
+ reject(err);
180
+ }
181
+ });
182
+ // Timeout for initial connection
183
+ setTimeout(() => {
184
+ if (!isConnected) {
185
+ ws?.close();
186
+ reject(new Error(`Connection timeout to ${url}`));
187
+ }
188
+ }, CONNECTION_TIMEOUT_MS);
189
+ });
190
+ }
191
+ /**
192
+ * Ensure connected to the right Extension for this project
193
+ */
194
+ async function ensureConnectedForProject(projectPath) {
195
+ // Check if we need to reconnect (different project might need different Extension)
196
+ const server = findExtensionForProject(projectPath);
197
+ if (!server) {
198
+ throw new Error(`No MCP Feedback Extension found for project: ${projectPath}. Please open the project in Cursor with the MCP Feedback extension installed.`);
199
+ }
200
+ // If already connected to the right server, reuse connection
201
+ if (isConnected && ws?.readyState === WebSocket.OPEN && connectedPort === server.port) {
202
+ return;
203
+ }
204
+ // Close existing connection if any
205
+ if (ws) {
206
+ ws.close();
207
+ ws = null;
208
+ isConnected = false;
209
+ connectedPort = null;
210
+ }
211
+ // Connect to the right server
212
+ await connectToExtension(projectPath);
213
+ }
214
+ /**
215
+ * Handle messages from Extension
216
+ */
217
+ function handleMessage(message) {
218
+ debug(`Received: ${message.type}`);
219
+ switch (message.type) {
220
+ case 'connection_established':
221
+ debug(`Connected to Extension v${message.version}`);
222
+ break;
223
+ case 'feedback_result':
224
+ // User submitted feedback
225
+ const resolver = pendingFeedbackResolvers.get(message.session_id);
226
+ if (resolver) {
227
+ clearTimeout(resolver.timeout);
228
+ resolver.resolve({
229
+ feedback: message.feedback,
230
+ images: message.images || []
231
+ });
232
+ pendingFeedbackResolvers.delete(message.session_id);
233
+ }
234
+ break;
235
+ case 'feedback_error':
236
+ // Error getting feedback
237
+ const errorResolver = pendingFeedbackResolvers.get(message.session_id);
238
+ if (errorResolver) {
239
+ clearTimeout(errorResolver.timeout);
240
+ errorResolver.reject(new Error(message.error));
241
+ pendingFeedbackResolvers.delete(message.session_id);
242
+ }
243
+ break;
244
+ case 'pong':
245
+ // Heartbeat response
246
+ break;
247
+ }
248
+ }
249
+ /**
250
+ * Request feedback from user via Extension
251
+ */
252
+ async function requestFeedback(projectDirectory, summary, timeout) {
253
+ await ensureConnectedForProject(projectDirectory);
254
+ const sessionId = `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
255
+ return new Promise((resolve, reject) => {
256
+ // Set timeout
257
+ const timeoutHandle = setTimeout(() => {
258
+ pendingFeedbackResolvers.delete(sessionId);
259
+ reject(new Error(`Feedback timeout after ${timeout} seconds`));
260
+ }, timeout * 1000);
261
+ // Store resolver
262
+ pendingFeedbackResolvers.set(sessionId, {
263
+ resolve,
264
+ reject,
265
+ timeout: timeoutHandle
266
+ });
267
+ // Send request to Extension
268
+ ws?.send(JSON.stringify({
269
+ type: 'feedback_request',
270
+ session_id: sessionId,
271
+ project_directory: projectDirectory,
272
+ summary,
273
+ timeout
274
+ }));
275
+ debug(`Feedback request sent: session=${sessionId}`);
276
+ });
277
+ }
278
+ // ============================================================================
279
+ // MCP Server Setup
280
+ // ============================================================================
281
+ const server = new McpServer({
282
+ name: 'mcp-feedback-enhanced',
283
+ version: VERSION
284
+ });
285
+ // Register the interactive_feedback tool
286
+ server.tool('interactive_feedback', 'Collect feedback from user through VSCode extension panel. The feedback panel will display the AI summary and wait for user input.', {
287
+ project_directory: z.string().describe('The project directory path for context'),
288
+ summary: z.string().describe('Summary of AI work completed for user review'),
289
+ timeout: z.number().optional().default(600).describe('Timeout in seconds (default: 600)')
290
+ }, async ({ project_directory, summary, timeout }) => {
291
+ debug(`interactive_feedback called: project=${project_directory}`);
292
+ try {
293
+ const result = await requestFeedback(project_directory, summary, timeout || 600);
294
+ // Format response
295
+ let response = `User Feedback:\n${result.feedback}`;
296
+ if (result.images && result.images.length > 0) {
297
+ response += `\n\n[${result.images.length} image(s) attached]`;
298
+ }
299
+ return {
300
+ content: [{ type: 'text', text: response }]
301
+ };
302
+ }
303
+ catch (error) {
304
+ debug(`Feedback error: ${error.message}`);
305
+ return {
306
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
307
+ isError: true
308
+ };
309
+ }
310
+ });
311
+ // Register get_system_info tool
312
+ server.tool('get_system_info', 'Get system environment information', {}, async () => {
313
+ const info = {
314
+ platform: os.platform(),
315
+ hostname: os.hostname(),
316
+ user: os.userInfo().username,
317
+ homeDir: os.homedir(),
318
+ nodeVersion: process.version,
319
+ pid: process.pid,
320
+ parentPid: process.ppid,
321
+ extensionConnected: isConnected
322
+ };
323
+ return {
324
+ content: [{ type: 'text', text: JSON.stringify(info, null, 2) }]
325
+ };
326
+ });
327
+ // ============================================================================
328
+ // Main Entry Point
329
+ // ============================================================================
330
+ async function main() {
331
+ debug('MCP Feedback Server starting...');
332
+ debug(`PID: ${process.pid}, Parent PID: ${process.ppid}`);
333
+ debug(`Version: ${VERSION}`);
334
+ // Log environment for debugging
335
+ if (DEBUG) {
336
+ console.error('[MCP Feedback] Environment:');
337
+ Object.keys(process.env).filter(k => k.includes('CURSOR') || k.includes('VSCODE') || k.includes('MCP')).forEach(k => console.error(` ${k}=${process.env[k]}`));
338
+ // Show available servers
339
+ const servers = getLiveServers();
340
+ console.error(`[MCP Feedback] Available servers: ${servers.length}`);
341
+ servers.forEach(s => {
342
+ console.error(` - port=${s.port}, workspaces=${s.workspaces?.join(', ')}`);
343
+ });
344
+ }
345
+ // Don't connect at startup - we'll connect on demand when interactive_feedback is called
346
+ // This allows us to use project_directory for server matching
347
+ // Start MCP server on stdio
348
+ const transport = new StdioServerTransport();
349
+ await server.connect(transport);
350
+ debug('MCP Server running on stdio, will connect to Extension on first feedback request');
351
+ // Heartbeat to keep connection alive
352
+ setInterval(() => {
353
+ if (ws?.readyState === WebSocket.OPEN) {
354
+ ws.send(JSON.stringify({ type: 'ping' }));
355
+ }
356
+ }, HEARTBEAT_INTERVAL_MS);
357
+ }
358
+ main().catch((error) => {
359
+ console.error('Fatal error:', error);
360
+ process.exit(1);
361
+ });
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "mcp-feedback-enhanced",
3
+ "version": "0.1.45",
4
+ "description": "MCP Feedback Enhanced Server - Interactive feedback collection for AI assistants",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "mcp-feedback-enhanced": "dist/index.js",
9
+ "mcp-feedback-server": "dist/index.js"
10
+ },
11
+ "keywords": [
12
+ "mcp",
13
+ "model-context-protocol",
14
+ "ai",
15
+ "feedback",
16
+ "cursor",
17
+ "vscode",
18
+ "claude"
19
+ ],
20
+ "author": "Minidoracat",
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/Minidoracat/mcp-feedback-enhanced"
25
+ },
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "start": "node dist/index.js",
29
+ "dev": "tsx src/index.ts",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md"
35
+ ],
36
+ "dependencies": {
37
+ "@modelcontextprotocol/sdk": "^1.0.0",
38
+ "ws": "^8.18.0",
39
+ "zod": "^3.23.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^20.0.0",
43
+ "@types/ws": "^8.5.0",
44
+ "tsx": "^4.0.0",
45
+ "typescript": "^5.0.0"
46
+ },
47
+ "engines": {
48
+ "node": ">=18.0.0"
49
+ }
50
+ }