user-feedback-mcp
Version:
mcp server to request user feedback via an UI instead of normal chat
563 lines (546 loc) • 17.1 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// package.json
var require_package = __commonJS({
"package.json"(exports2, module2) {
module2.exports = {
name: "user-feedback-mcp",
version: "0.2.5",
description: "mcp server to request user feedback via an UI instead of normal chat",
bin: {
"user-feedback-mcp": "./dist/cli.js"
},
scripts: {
build: "node build/build.js",
dev: "tsc --watch",
lint: "eslint src --ext .ts,.js",
test: "jest",
start: "node ./dist/cli.js start",
package: "node build/package.js"
},
dependencies: {
"@modelcontextprotocol/sdk": "^1.11.2",
commander: "^10.0.1",
"electron-store": "^8.1.0",
"fs-extra": "^11.1.1",
winston: "^3.17.0",
zod: "^3.24.4"
},
devDependencies: {
"@types/fs-extra": "^11.0.1",
"@types/jest": "^29.5.0",
"@types/node": "^18.16.0",
electron: "25.9.8",
esbuild: "^0.25.4",
jest: "^29.5.0",
"ts-jest": "^29.1.0",
typescript: "^5.0.4"
},
files: [
"dist/**/*"
]
};
}
});
// ../../packages/shared/src/types.ts
var init_types = __esm({
"../../packages/shared/src/types.ts"() {
"use strict";
}
});
// ../../packages/shared/src/constants.ts
var path, os, TEMP_DIR, FEEDBACK_FILE_NAME, ENV_PROMPT, ENV_FEEDBACK_FILE, ENV_ENABLE_KNOWLEDGE_DB, ENV_ENABLE_UI_TESTER;
var init_constants = __esm({
"../../packages/shared/src/constants.ts"() {
"use strict";
path = __toESM(require("path"));
os = __toESM(require("os"));
TEMP_DIR = path.join(os.tmpdir(), "user-feedback-mcp");
FEEDBACK_FILE_NAME = "feedback.json";
ENV_PROMPT = "USER_FEEDBACK_PROMPT";
ENV_FEEDBACK_FILE = "USER_FEEDBACK_FILE";
ENV_ENABLE_KNOWLEDGE_DB = "ENABLE_KNOWLEDGE_DB";
ENV_ENABLE_UI_TESTER = "ENABLE_UI_TESTER";
}
});
// ../../packages/shared/src/file-utils.ts
async function ensureTempDir() {
await fs.ensureDir(TEMP_DIR);
}
function generateFeedbackFilePath() {
const uniqueId = Date.now().toString();
return path2.join(TEMP_DIR, `${uniqueId}-${FEEDBACK_FILE_NAME}`);
}
async function readFeedbackFromFile(filePath) {
try {
const data = await fs.readJson(filePath);
if (data.cancelled) {
return {
feedback: "",
status: "cancelled" /* CANCELLED */,
error: "User cancelled feedback by closing the window"
};
}
const response = {
feedback: data.feedback,
status: "success" /* SUCCESS */
};
if (data.images && data.images.length > 0) {
response.images = data.images;
}
return response;
} catch (error) {
return {
feedback: "",
status: "error" /* ERROR */,
error: `Failed to read feedback file: ${error.message || String(error)}`
};
}
}
async function cleanupFeedbackFile(filePath) {
try {
await fs.remove(filePath);
} catch (error) {
console.error(
`Failed to clean up feedback file: ${error.message || String(error)}`
);
}
}
var fs, path2;
var init_file_utils = __esm({
"../../packages/shared/src/file-utils.ts"() {
"use strict";
fs = __toESM(require("fs-extra"));
path2 = __toESM(require("path"));
init_constants();
init_types();
}
});
// ../../packages/shared/src/error-utils.ts
function createErrorResponse(error, status = "error" /* ERROR */) {
const message = typeof error === "string" ? error : error.message;
return {
feedback: "",
status,
error: message
};
}
var init_error_utils = __esm({
"../../packages/shared/src/error-utils.ts"() {
"use strict";
init_types();
}
});
// ../../packages/shared/src/index.ts
var init_src = __esm({
"../../packages/shared/src/index.ts"() {
"use strict";
init_types();
init_constants();
init_file_utils();
init_error_utils();
}
});
// ../../packages/mcp-server/src/utils/logger.ts
var winston, logLevels, logger, logger_default;
var init_logger = __esm({
"../../packages/mcp-server/src/utils/logger.ts"() {
"use strict";
winston = __toESM(require("winston"));
logLevels = {
error: 0,
warn: 1,
info: 2,
debug: 3
};
logger = winston.createLogger({
levels: logLevels,
silent: true,
// Completely disable all logging
transports: []
});
logger_default = logger;
}
});
// ../../packages/mcp-server/src/handlers/user-feedback.ts
async function getUserFeedback(params) {
logger_default.info("Received user feedback request", { prompt: params.prompt });
if (!params.prompt) {
logger_default.error("Missing required parameter: prompt");
return createErrorResponse("Prompt is required");
}
await ensureTempDir();
const feedbackFilePath = generateFeedbackFilePath();
try {
const electronGuiPath = getElectronGuiPath();
const env = {
...process.env,
[ENV_PROMPT]: params.prompt,
[ENV_FEEDBACK_FILE]: feedbackFilePath
};
logger_default.debug("Launching Electron GUI", {
electronGuiPath,
feedbackFilePath
});
const result = await launchElectronGui(electronGuiPath, env);
if (result.status === "error" /* ERROR */) {
logger_default.error("Error in Electron GUI subprocess", { error: result.error });
return createErrorResponse(
result.error || "Unknown error",
result.status
);
}
logger_default.debug("Reading feedback from file", { feedbackFilePath });
const response = await readFeedbackFromFile(feedbackFilePath);
logger_default.info("User feedback received", {
status: response.status,
feedbackLength: response.feedback.length
});
return response;
} catch (error) {
logger_default.error("Error in getUserFeedback", {
error: error.message || String(error)
});
return createErrorResponse(error);
} finally {
await cleanupFeedbackFile(feedbackFilePath);
}
}
function getElectronGuiPath() {
try {
const bundledPath = path3.join(__dirname, "electron", "main.js");
if ((0, import_fs.existsSync)(bundledPath)) {
logger_default.debug(`Using bundled Electron GUI path: ${bundledPath}`);
return bundledPath;
}
const resolvedPath = require.resolve("./electron/main.js");
if ((0, import_fs.existsSync)(resolvedPath)) {
logger_default.debug(`Using resolved Electron GUI path: ${resolvedPath}`);
return resolvedPath;
}
throw new Error("Could not find Electron GUI path");
} catch (error) {
logger_default.error("Failed to resolve Electron GUI path", {
error: error.message || String(error)
});
throw new Error(
`Failed to resolve Electron GUI path: ${error.message || String(error)}`
);
}
}
async function launchElectronGui(electronGuiPath, env) {
return new Promise((resolve) => {
let electronPath = "electron";
try {
electronPath = require.resolve("electron/cli.js");
logger_default.debug(`Using local Electron executable: ${electronPath}`);
} catch (error) {
logger_default.debug(
`Could not find local Electron executable, using global: ${error}`
);
}
const electronProcess = (0, import_child_process.spawn)(electronPath, [electronGuiPath], { env });
electronProcess.on("exit", (code) => {
if (code === 0) {
resolve({ status: "success" /* SUCCESS */ });
} else {
resolve({
status: "error" /* ERROR */,
error: `Electron process exited with code ${code}`
});
}
});
electronProcess.on("error", (error) => {
resolve({
status: "error" /* ERROR */,
error: `Failed to start Electron process: ${error.message}`
});
});
electronProcess.stdout.on("data", (data) => {
logger_default.debug(`Electron stdout: ${data}`);
});
electronProcess.stderr.on("data", (data) => {
logger_default.error(`Electron stderr: ${data}`);
});
});
}
var import_child_process, path3, import_fs;
var init_user_feedback = __esm({
"../../packages/mcp-server/src/handlers/user-feedback.ts"() {
"use strict";
import_child_process = require("child_process");
path3 = __toESM(require("path"));
init_src();
init_logger();
import_fs = require("fs");
}
});
// ../../packages/mcp-server/src/tools/user-feedback.ts
function registerUserFeedbackTool(server) {
server.tool(
"get_user_feedback",
"Tool for getting feedback from the user. Opens a GUI window with a prompt and text input field where the user can provide feedback.",
{
prompt: import_zod.z.string().describe("The prompt to display to the user")
},
async (params) => {
const userFeedbackParams = {
prompt: params.prompt
};
const result = await getUserFeedback(userFeedbackParams);
const content = [
{
type: "text",
text: JSON.stringify(result)
}
];
if (result.images && result.images.length > 0) {
content.push({
type: "text",
text: `
The user included ${result.images.length} image(s) in their feedback.`
});
}
return { content };
}
);
server.tool(
"ask_user",
"Tool for asking the user a question and getting their response. Opens a GUI window with a question prompt and text input field.",
{
prompt: import_zod.z.string().describe("The question to ask the user")
},
async (params) => {
const userFeedbackParams = {
prompt: params.prompt
};
const result = await getUserFeedback(userFeedbackParams);
const content = [
{
type: "text",
text: JSON.stringify(result)
}
];
if (result.images && result.images.length > 0) {
content.push({
type: "text",
text: `
The user included ${result.images.length} image(s) in their feedback.`
});
}
return { content };
}
);
if (process.env[ENV_ENABLE_KNOWLEDGE_DB] === "true") {
server.tool(
"universal_knowledge_database",
"Tool for querying the universal knowledge database. Provides access to a vast repository of information across various domains.",
{
prompt: import_zod.z.string().describe("The query to send to the knowledge database")
},
async (params) => {
const userFeedbackParams = {
prompt: params.prompt
};
const result = await getUserFeedback(userFeedbackParams);
const content = [
{
type: "text",
text: JSON.stringify(result)
}
];
if (result.images && result.images.length > 0) {
content.push({
type: "text",
text: `
The user included ${result.images.length} image(s) in their feedback.`
});
}
return { content };
}
);
}
if (process.env[ENV_ENABLE_UI_TESTER] === "true") {
server.tool(
"ui_tester",
"Tool for testing UI components and interactions. Allows for automated testing of user interfaces and reporting results.",
{
prompt: import_zod.z.string().describe("The UI test to perform")
},
async (params) => {
const userFeedbackParams = {
prompt: params.prompt
};
const result = await getUserFeedback(userFeedbackParams);
const content = [
{
type: "text",
text: JSON.stringify(result)
}
];
if (result.images && result.images.length > 0) {
content.push({
type: "text",
text: `
The user included ${result.images.length} image(s) in their feedback.`
});
}
return { content };
}
);
}
}
var import_zod;
var init_user_feedback2 = __esm({
"../../packages/mcp-server/src/tools/user-feedback.ts"() {
"use strict";
import_zod = require("zod");
init_user_feedback();
init_src();
}
});
// ../../packages/mcp-server/src/tools/index.ts
var init_tools = __esm({
"../../packages/mcp-server/src/tools/index.ts"() {
"use strict";
init_user_feedback2();
}
});
// ../../packages/mcp-server/src/index.ts
var src_exports = {};
__export(src_exports, {
registerUserFeedbackTool: () => registerUserFeedbackTool,
startMCPServer: () => startMCPServer,
stopMCPServer: () => stopMCPServer
});
async function startMCPServer() {
if (isServerRunning) {
logger_default.warn(
"MCP server is already running, ignoring duplicate start request"
);
return;
}
try {
logger_default.info("Starting MCP server");
isServerRunning = true;
serverInstance = new import_mcp.McpServer({
name: "User Feedback MCP",
version: "1.0.0"
});
registerUserFeedbackTool(serverInstance);
const transport = new import_stdio.StdioServerTransport();
logger_default.info("Connecting to stdio transport");
await serverInstance.connect(transport);
transport.onclose = () => {
logger_default.info("Transport closed, resetting server state");
isServerRunning = false;
serverInstance = null;
};
logger_default.info("MCP server started successfully");
} catch (error) {
logger_default.error("Failed to start MCP server", {
error: error.message || String(error)
});
isServerRunning = false;
serverInstance = null;
process.exit(1);
}
}
async function stopMCPServer() {
if (isServerRunning && serverInstance) {
logger_default.info("Stopping MCP server");
isServerRunning = false;
serverInstance = null;
}
}
var import_mcp, import_stdio, isServerRunning, serverInstance;
var init_src2 = __esm({
"../../packages/mcp-server/src/index.ts"() {
"use strict";
import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
init_tools();
init_logger();
init_tools();
isServerRunning = false;
serverInstance = null;
if (require.main === module) {
startMCPServer();
}
}
});
// src/index.ts
var import_commander = require("commander");
var { version } = require_package();
var safeConsole = {
log: (..._args) => {
},
info: (..._args) => {
},
warn: (..._args) => {
},
error: (..._args) => {
}
};
var program = new import_commander.Command();
program.name("user-feedback-mcp").description("Local User Feedback Interface for MCP Tool").version(version).option("-d, --debug", "Enable debug logging").option(
"--enable-knowledge-db",
"Enable the universal knowledge database tool"
).option("--enable-ui-tester", "Enable the UI tester tool");
async function main() {
try {
const options = program.opts();
if (options.debug) {
process.env.DEBUG = "true";
safeConsole.log("Debug mode enabled");
}
if (options.enableKnowledgeDb) {
process.env.ENABLE_KNOWLEDGE_DB = "true";
safeConsole.log("Universal knowledge database tool enabled");
}
if (options.enableUiTester) {
process.env.ENABLE_UI_TESTER = "true";
safeConsole.log("UI tester tool enabled");
}
safeConsole.log("Starting MCP server...");
const { startMCPServer: startMCPServer2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
await startMCPServer2();
} catch (error) {
safeConsole.error("Failed to start MCP server:", error);
process.exit(1);
}
}
program.parse();
main();