Win-10-MCP-Server——简单持久的日志记录
虽然MCP Inspector显示了MCP服务器的所有通信,但我希望日志能保存在HDD上。 具体来说,因为我修改了index.js中“服务器文件系统/dist”中的“写入文件”案例
问题:Claude变得非常懒惰,可能无法编写它正在处理的整个文件。
例如,它可以写标题,然后说 \[其余代码保持不变…\]
现在我的文件被覆盖了。..
我做了两件事:
- 每次调用write_file工具时,我都会复制一个带有日期时间戳的目标文件(如果存在)。
- 记录文件名并将其发送回(返回语句)Claude Desktop App,以便用户看到备份文件名。
然后,我手动使用diff工具来验证发生了什么变化。…稍后修复克劳德的错误
这里是代理的绝佳用例。…提示,提示。.
1.创建 logger.mjs
创建一个名为的新文件 logger.mjs 在项目根目录中:
// logger.mjs
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
// Determine the directory of the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Define logs directory relative to the logger.mjs file
const LOG_DIR = path.join(__dirname, "logs");
// Ensure the logs directory exists
async function ensureLogDir() {
try {
await fs.mkdir(LOG_DIR, { recursive: true });
} catch (err) {
console.error("Failed to create logs directory:", err);
}
}
ensureLogDir();
// Define log file path
const LOG_FILE = path.join(LOG_DIR, "KAN_server.log");
/**
* Logs a message to the log file with the specified level.
*
* @param {string} level - The severity level of the log (e.g., INFO, ERROR).
* @param {string} message - The message to log.
*/
export async function logToFile(level, message) {
const timestamp = new Date().toISOString();
const line = `[${level.toUpperCase()}] ${timestamp} - ${message}\n`;
try {
await fs.appendFile(LOG_FILE, line);
} catch (err) {
// If writing fails, log to console.error without disrupting the main flow
console.error("Failed to write to log file:", err);
}
}
// Temporary verification (commented out to prevent interference)
// console.log("Logging to:", LOG_FILE); // Do not use, as it can interfere with JSON responses添加index.js
我更改了我的index.js 它可以在以下目录中找到(Win10): C: \\用户\\TrevorW\\AppData\\Roaming\\npm\\node_modules@modelcontextprotocol\\服务器文件系统\\dist
3.实施 write_file 案例
修改 write_file 操作,确保它处理文件写入和备份创建,并记录所有相关信息。
// index.js
// ...imports and schema definitions...
/**
* Validates the provided file path to prevent unauthorized access.
*
* @param {string} filePath - The file path to validate.
* @returns {Promise} - The validated and normalized file path.
* @throws Will throw an error if the path is invalid.
*/
async function validatePath(filePath) {
// Normalize the path to prevent directory traversal
const normalizedPath = path.normalize(filePath);
// Define allowed base directories
const allowedBase = path.resolve(process.cwd(), "Control_Charts");
// Ensure the normalized path starts with the allowed base directory
if (!normalizedPath.startsWith(allowedBase)) {
throw new Error("Invalid file path. Access denied.");
}
// Additional validations can be added here (e.g., file extension checks)
return normalizedPath;
}
/**
* Handles various operations based on the provided operation name and arguments.
*
* @param {string} operation - The name of the operation to perform.
* @param {object} args - The arguments required for the operation.
* @returns {Promise} - The response object to send back.
*/
async function handleOperation(operation, args) {
switch (operation) {
case "write_file": {
try {
// 1. Parse incoming arguments
const parsed = WriteFileArgsSchema.safeParse(args);
if (!parsed.success) {
throw new Error(`Invalid arguments for write_file: ${parsed.error}`);
}
// 2. Validate the path
const validPath = await validatePath(parsed.data.path);
let backupName = null; // Initialize backupName to track if a backup is created
// 3. Backup existing file if it exists
try {
await fs.access(validPath); // Throws if file doesn't exist
// Build date/time string in MMDDYY_HH_MM format
const now = new Date();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const yy = String(now.getFullYear()).slice(-2);
const hh = String(now.getHours()).padStart(2, '0');
const mn = String(now.getMinutes()).padStart(2, '0');
const dateString = `${mm}${dd}${yy}_${hh}_${mn}`;
// Build backup file path
const dirName = path.dirname(validPath);
const baseName = path.basename(validPath);
backupName = `${baseName}.${dateString}__CLDBak`; // Assign to backupName
const backupPath = path.join(dirName, backupName);
// Rename original file to create backup
await fs.rename(validPath, backupPath);
// Log the backup creation
await logToFile("INFO", `Backup created at ${backupPath}`);
} catch (err) {
if (err.code !== 'ENOENT') { // ENOENT: File does not exist
// Log unexpected errors during backup
await logToFile("ERROR", `Error accessing or backing up file: ${err.message}`);
throw err; // Re-throw unexpected errors
}
// File doesn't exist; proceed without backup
await logToFile("INFO", `No existing file at ${validPath}. No backup created.`);
}
// 4. Write the new content
await fs.writeFile(validPath, parsed.data.content, "utf-8");
await logToFile("INFO", `Successfully wrote to ${validPath}`);
// 5. Prepare the response
let responseText = `Successfully wrote to ${parsed.data.path}`;
if (backupName) {
responseText += `\nKAN: Backup saved to ${backupName}`;
}
// THIS IS THE KEY TO MAKE CLAUDE DESKTOP HAPPY
// The response HAS TO BE in JSON, or will bitch at you
const response = {
content: [{
type: "text",
text: responseText,
}],
};
// 6. Return the response without any additional logging
return response;
} catch (error) {
// Log the error details
await logToFile("ERROR", `write_file operation failed: ${error.message}`);
throw error; // Ensure the error is propagated appropriately
}
}
// ... handle other cases ... // Example of the lazyness I am dealing with here ....
default:
throw new Error(`Unknown operation: ${operation}`);
}
}如果你已经做到了这一点,你可能会说,嘿,Trevor,为什么不使用类似IDE的游标来添加此功能呢。
我的答案是:三个月后我们会有光标吗?在Windows 10完全进入语音模式之前,我可能会学到一些东西。...
