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

by trevorwilkerson

283 downloads
Not rated
GitHub

Description

# Win-10-MCP-Server---Simple-persistant-logging Although MCP Inspector shows all communications for MCP Servers, I want a log to persist on the HDD. Specifically, because I modified the "write-file" case in the index.js inside "server-filesystem/dist" ## Problem: Claude has…

About

# Win-10-MCP-Server---Simple-persistant-logging Although MCP Inspector shows all communications for MCP Servers, I want a log to persist on the HDD. Specifically, because I modified the "write-file" case in the index.js inside "server-filesystem/dist" ## Problem: Claude has gotten extremely lazy and may not write the…

Details

Author
trevorwilkerson
Downloads
283
Categories
Other, File Management

- Creates a timestamped backup of the target file before every write_file call
- Logs all file operations to a persistent logs/KAN_server.log file
- Returns the backup filename in the response for immediate user notification
- Validates file paths to restrict writing to a controlled directory
- Runs on Node.js without any additional dependencies

Replace the standard index.js inside server-filesystem/dist with the provided modified version and place logger.mjs in your project root. The server will then log all write_file calls to logs/KAN_server.log and create a backup of any existing target file with a .MMDDYY_HH_MM__CLDBak suffix before writing.

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

Although MCP Inspector shows all communications for MCP Servers, I want a log to persist on the HDD. Specifically, because I modified the "write-file" case in the index.js inside "server-filesystem/dist"

Problem: Claude has gotten extremely lazy and may not write the whole file it is working on.

For example, it may write the headers and then say
[ rest of the code stays the same ... ]

Now my file is overwritten...

I did 2 things:
1. Every time the write_file tool is called, I make a copy of the target file ( if exists ) with a date time stamp.
2. Log the name of the file and send it back ( return statement ) to Claude Desktop App so the user will see the backup file name.

Then manually, I use a diff tool to verify what has changed.... Fixing Claudes mistakes later

Excellent use case for an Agent here..... hint, hint..

1. Create logger.mjs

Create a new file named logger.mjs in your project root:

// 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

ADD the index.js

I Changed my index.js It can be found in this directory ( Win10 ): C:\Users\TrevorW\AppData\Roaming\npm\node_modules\@modelcontextprotocol\server-filesystem\dist

3. Implementing the write_file Case

modify the write_file operation, ensuring that it handles file writing with backup creation and logs all pertinent information.

// 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});
}
}

IF you have gotten this far, you may say, hey Trevor, why don't you use an IDE like cursor to add this functionality.

my answer: Will we have cursor in 3 months ? I might learn something now before Windows 10 is totally voice mode....
No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.