Skip to main content
Glama
ceciliomichael

Feedback Collector MCP

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
NameRequiredDescriptionDefault
promptNoThe message to display to the user in the feedback windowPlease provide your feedback or describe your issue:
time_formatNoThe format for time informationfull
timezoneNoThe timezone to use (defaults to local)
titleNoThe title of the feedback windowAI 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
          };
        }
      }
    );
  • 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
        };
      }
    }
  • 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()
    },
  • 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 };
    }
  • 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();
      });
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden but offers minimal behavioral insight. It mentions the interface (Electron app) but doesn't disclose critical traits like whether this blocks execution, requires user interaction, handles errors, or what happens to collected feedback. For a user-interactive tool with zero annotation coverage, this leaves significant gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence with zero wasted words. It's appropriately sized for a tool with good schema coverage and no siblings, front-loading the core purpose without unnecessary elaboration.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity (user interaction via GUI) and 100% schema coverage but no annotations or output schema, the description is minimally adequate. It identifies the interface but lacks details on behavioral expectations, error handling, or result format, leaving the agent to infer much about how this tool actually operates.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all 4 parameters. The description adds no parameter-specific information beyond what's in the schema, maintaining the baseline score of 3 where structured data handles parameter documentation adequately.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('collect feedback') and target ('from the user'), specifying it occurs 'through an Electron app' which adds useful context about the interface. However, it doesn't distinguish from siblings (none exist) and could be more specific about what type of feedback or how it's collected.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives, prerequisites, or constraints. The description mentions the Electron app context but doesn't explain when this collection method is appropriate or what scenarios warrant its use.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ceciliomichael/feedbackjs-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server