mcpField
W
Win-10-MCP-Server---Simple-persistant-logging

Win-10-MCP-Server---Simple-persistant-logging

本地服务
github.com/trevorwilkerson文件与存储095 周前更新

关于 Win-10-MCP-Server---Simple-persistant-logging

这项 Windows 10 修改为文件系统服务的 write_file 操作增加持久日志和带时间戳的备份,适合需要检查文件变更并在写入不完整时恢复旧文件的用户。

主要功能

  1. 01备份已存在的目标文件
  2. 02为备份副本添加时间戳
  3. 03记录写入的文件名
  4. 04向 Claude Desktop 返回备份名称

适用场景

  1. 01在写入不完整后恢复文件
  2. 02对照带时间戳的备份检查变更

MCP 相关

修改文件系统 MCP 服务的 write_file 工具,使其创建备份并返回备份名称;README 描述的是对 server-filesystem 实现的修改。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

暂未收录安装方式

我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。

代码仓库

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

Win-10-MCP-Server---Simple-persistant-logging

虽然 MCP Inspector 会显示 MCP 服务的所有通信,但我希望日志能持久保存在硬盘上。 具体来说,是因为我修改了 server-filesystem/dist 中 index.js 里的“write-file”分支。

问题:Claude 变得极其懒惰,有时不会写出它正在处理的完整文件。

例如,它可能会写出标题,然后说 [ rest of the code stays the same ... ]

现在我的文件被覆盖了……

我做了两件事:

  1. 每次调用 write_file 工具时,我都会为目标文件(如果存在)创建一个副本,并加上日期时间戳。
  2. 记录文件名,并将其返回(通过返回语句)给 Claude Desktop App,这样用户就能看到备份文件名。

然后,我会手动使用差异比较工具来确认发生了哪些更改……之后再修复 Claude 的错误

这正是 Agent 大显身手的绝佳场景……提示一下,提示一下……

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:\Users\TrevorW\AppData\Roaming\npm\node_modules@modelcontextprotocol\server-filesystem\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<string>} - 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<object>} - 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,为什么不用像 cursor 这样的 IDE 来添加这个功能?

我的回答是:3 个月后我们还会有 cursor 吗?也许我现在能先学到点东西,赶在 Windows 10 完全进入语音模式之前……

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。