collect_feedback
Gather user feedback via an Electron app with customizable prompts, titles, and time formats, enabling efficient collection and processing of detailed responses.
Instructions
Collect feedback from the user through an Electron app
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| prompt | No | The message to display to the user in the feedback window | Please provide your feedback or describe your issue: |
| time_format | No | The format for time information | full |
| timezone | No | The timezone to use (defaults to local) | |
| title | No | The title of the feedback window | AI Feedback Collection |
Implementation Reference
- mcp-server.js:148-247 (registration)Registration of the 'collect_feedback' tool with McpServer using server.tool(name, description, inputSchema, handlerFn). Includes the Zod input schema and the complete handler function.server.tool( "collect_feedback", "Collect feedback from the user through an Electron app", { prompt: z.string({ description: "The message to display to the user in the feedback window" }).default("Please provide your feedback or describe your issue:"), title: z.string({ description: "The title of the feedback window" }).default("AI Feedback Collection"), time_format: z.enum(["full", "iso", "date", "time", "unix"], { description: "The format for time information" }).default("full"), timezone: z.string({ description: "The timezone to use (defaults to local)" }).optional() }, async ({ prompt, title, time_format, timezone }) => { try { // Launch a new Electron app instance for each request const { sessionId, port } = await launchAppInstance(); // Send the request to the Electron app and wait for feedback try { const feedback = await sendRequestToApp({ prompt, title, time_format, timezone, port }); // Get time information const { formattedTime, additionalInfo } = getTimeInfo(time_format, timezone); // Format the time information as requested const timeInfo = Object.entries(additionalInfo) .map(([key, value]) => `${key}: ${value}`) .join('\n'); // Create the response content array with requested format let responseContent = []; // Add feedback text if (typeof feedback === 'string') { // Simple string feedback (old format) responseContent.push({ type: "text", text: feedback }); } else { // Object with text and possibly image (new format) responseContent.push({ type: "text", text: feedback.text }); // Add image if present if (feedback.hasImage && feedback.imagePath) { try { const imageBuffer = fs.readFileSync(feedback.imagePath); const base64Image = imageBuffer.toString('base64'); // Add the image responseContent.push({ type: "image", data: base64Image, mimeType: feedback.imageType || "image/png" }); } catch (error) { console.error("Error processing image:", error.message); responseContent.push({ type: "text", text: `Note: User attached an image, but it could not be processed. Error: ${error.message}` }); } } } // Add time information responseContent.push({ type: "text", text: timeInfo }); return { content: responseContent }; } finally { // Clean up the app instance when done const instance = appInstances.get(sessionId); if (instance && instance.process) { try { instance.process.kill(); } catch (error) { console.error(`Error killing app process (sessionId: ${sessionId}):`, error); } appInstances.delete(sessionId); } } } catch (error) { console.error("Error collecting feedback:", error); return { content: [{ type: "text", text: `Error collecting feedback: ${error.message}` }], isError: true }; } } );
- mcp-server.js:165-246 (handler)The core handler logic for executing the 'collect_feedback' tool. Launches Electron app, sends HTTP request for feedback, handles text and image response, adds time info, cleans up process.async ({ prompt, title, time_format, timezone }) => { try { // Launch a new Electron app instance for each request const { sessionId, port } = await launchAppInstance(); // Send the request to the Electron app and wait for feedback try { const feedback = await sendRequestToApp({ prompt, title, time_format, timezone, port }); // Get time information const { formattedTime, additionalInfo } = getTimeInfo(time_format, timezone); // Format the time information as requested const timeInfo = Object.entries(additionalInfo) .map(([key, value]) => `${key}: ${value}`) .join('\n'); // Create the response content array with requested format let responseContent = []; // Add feedback text if (typeof feedback === 'string') { // Simple string feedback (old format) responseContent.push({ type: "text", text: feedback }); } else { // Object with text and possibly image (new format) responseContent.push({ type: "text", text: feedback.text }); // Add image if present if (feedback.hasImage && feedback.imagePath) { try { const imageBuffer = fs.readFileSync(feedback.imagePath); const base64Image = imageBuffer.toString('base64'); // Add the image responseContent.push({ type: "image", data: base64Image, mimeType: feedback.imageType || "image/png" }); } catch (error) { console.error("Error processing image:", error.message); responseContent.push({ type: "text", text: `Note: User attached an image, but it could not be processed. Error: ${error.message}` }); } } } // Add time information responseContent.push({ type: "text", text: timeInfo }); return { content: responseContent }; } finally { // Clean up the app instance when done const instance = appInstances.get(sessionId); if (instance && instance.process) { try { instance.process.kill(); } catch (error) { console.error(`Error killing app process (sessionId: ${sessionId}):`, error); } appInstances.delete(sessionId); } } } catch (error) { console.error("Error collecting feedback:", error); return { content: [{ type: "text", text: `Error collecting feedback: ${error.message}` }], isError: true }; } }
- mcp-server.js:152-164 (schema)Zod schema for tool inputs: prompt, title, time_format, timezone with descriptions and defaults.prompt: z.string({ description: "The message to display to the user in the feedback window" }).default("Please provide your feedback or describe your issue:"), title: z.string({ description: "The title of the feedback window" }).default("AI Feedback Collection"), time_format: z.enum(["full", "iso", "date", "time", "unix"], { description: "The format for time information" }).default("full"), timezone: z.string({ description: "The timezone to use (defaults to local)" }).optional() },
- mcp-server.js:113-145 (helper)Helper function to launch a new Electron app instance on a random port, track it, and return sessionId and port.async function launchAppInstance() { const sessionId = randomUUID(); const port = getAvailablePort(); console.error(`Launching new Electron app instance (sessionId: ${sessionId}, port: ${port})...`); // Pass parameters to the Electron app const env = { ...process.env, MCP_SERVER_PORT: port.toString() }; const appProcess = spawn(electronAppPath, [], { env, detached: false, windowsHide: false }); appProcess.on("error", (err) => { console.error(`Failed to start Electron app (sessionId: ${sessionId}):`, err); appInstances.delete(sessionId); }); appProcess.on("exit", (code) => { console.error(`Electron app exited with code ${code} (sessionId: ${sessionId})`); appInstances.delete(sessionId); }); // Store the instance information appInstances.set(sessionId, { process: appProcess, port: port }); // Wait a bit for the app to start await new Promise(resolve => setTimeout(resolve, 1000)); return { sessionId, port }; }
- mcp-server.js:250-291 (helper)Helper to send HTTP POST request to the Electron app's /feedback endpoint and return the feedback response.async function sendRequestToApp({ prompt, title, time_format, timezone, port }) { return new Promise((resolve, reject) => { const requestData = JSON.stringify({ prompt, title, time_format, timezone }); const req = http.request({ hostname: "localhost", port: port, path: "/feedback", method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(requestData) } }, (res) => { let responseData = ""; res.on("data", (chunk) => { responseData += chunk; }); res.on("end", () => { if (res.statusCode === 200) { try { const jsonResponse = JSON.parse(responseData); resolve(jsonResponse.feedback); } catch (e) { reject(new Error("Invalid response from Electron app")); } } else { reject(new Error(`HTTP error ${res.statusCode}: ${responseData}`)); } }); }); req.on("error", (error) => { reject(error); }); req.write(requestData); req.end(); }); }