Win-10-MCP-Server---Simple-persistant-logging
ローカルWin-10-MCP-Server---Simple-persistant-logging について
Windows 10向けのこの改変は、ファイルシステムサーバーのwrite_file操作に永続ログと日時付きバックアップを追加します。書き込みが不完全な場合に元のファイルを復元し、変更を確認したいユーザー向けです。
主な機能
- 01既存の対象ファイルをバックアップ
- 02バックアップに日時を付与
- 03書き込んだファイル名を記録
- 04Claude Desktopにバックアップ名を返却
活用シーン
- 01不完全な書き込み後にファイルを復元
- 02日時付きバックアップと変更を照合
MCP 対応
ファイルシステムMCPサーバーのwrite_fileツールを変更し、バックアップを作成してその名前を返します。READMEではserver-filesystemの実装変更が説明されています。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
Win-10-MCP-Server---Simple-persistant-logging
MCP Inspector には MCP サーバー間のすべての通信が表示されますが、HDD にログを永続的に保存したいと思っています。
具体的には、server-filesystem/dist 内の index.js にある「write-file」ケースを変更したためです。
問題:Claude は非常に怠け者になってしまい、作業中のファイル全体を書き込まないことがあります。
たとえば、ヘッダーを書き込んだあとに、次のように言うことがあります。 [ rest of the code stays the same ... ]
これでファイルが上書きされてしまいます……
次の 2 つのことを行いました。
- write_file ツールが呼び出されるたびに、対象ファイルが存在する場合は、日時のタイムスタンプを付けてコピーを作成します。
- ファイル名をログに記録し、Claude Desktop App に返します(return 文)。これにより、ユーザーはバックアップファイル名を確認できます。
その後、手動で diff ツールを使って変更点を確認します……あとから Claude の間違いを修正します。
ここはエージェントの優れたユースケースです……ヒント、ヒント。
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 が完全に音声モードになる前に、今のうちに何か学んでおくのもよいかもしれません……
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。