configuration
npx machina-cli add skill Myst4ke/mcp-to-skills-converter/configuration --openclawConfiguration Management Skill
Overview
This skill provides guidance for implementing safe configuration file management including backup systems, atomic updates, and recovery mechanisms. For US-4 (MCP Uninstaller), this skill covers:
- Creating timestamped configuration backups
- Atomic JSON file updates
- Configuration validation
- Backup restoration and cleanup
Core Capabilities
- Atomic File Operations: Write-then-rename pattern for safe file updates
- Backup Management: Create, restore, and clean up timestamped backups
- JSON Manipulation: Safe parsing, modification, and validation of JSON config files
- Error Recovery: Automatic rollback on failures
- Metadata Tracking: Include metadata in backups for audit trail
- Cleanup Policies: Implement retention policies (e.g., keep last 10 backups)
Input Requirements
Required:
- Configuration file path
- Operation type (backup, update, restore)
- Backup directory path
Optional:
- Retention policy (number of backups to keep)
- Validation schema
- Metadata to include in backup
Output Format
Returns:
- Operation result (success/failure)
- Backup file path (for backup operations)
- Validation results
- Error messages if applicable
Dependencies
- fs-extra: Enhanced file system operations
- JSON schema validator (optional): For config validation
- TypeScript 5.x: For type-safe config handling
Workflow
Step 1: Define Configuration Interfaces
Create TypeScript interfaces for config structure:
// src/interfaces/config.interface.ts
export interface ClaudeConfig {
mcpServers?: Record<string, MCPServerConfig>;
[key: string]: any;
}
export interface MCPServerConfig {
command: string;
args?: string[];
env?: Record<string, string>;
}
export interface BackupMetadata {
timestamp: string;
originalPath: string;
reason: string;
mcpName?: string;
}
Step 2: Implement Backup Service
Create backup with metadata:
// src/services/config-backup.ts
import * as fs from 'fs-extra';
import * as path from 'path';
export class ConfigBackup {
private backupDir: string;
private maxBackups: number;
constructor(backupDir: string = '.claude/backups', maxBackups: number = 10) {
this.backupDir = backupDir;
this.maxBackups = maxBackups;
}
async createBackup(configPath: string, reason: string, mcpName?: string): Promise<string> {
// Ensure backup directory exists
await fs.ensureDir(this.backupDir);
// Generate timestamp
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupFileName = `config-backup-${timestamp}.json`;
const backupPath = path.join(this.backupDir, backupFileName);
// Read original config
const config = await fs.readJson(configPath);
// Create backup with metadata
const backup = {
metadata: {
timestamp: new Date().toISOString(),
originalPath: configPath,
reason,
mcpName
},
config
};
// Write backup atomically
await this.atomicWrite(backupPath, JSON.stringify(backup, null, 2));
// Verify backup
if (!await fs.pathExists(backupPath)) {
throw new Error('Backup verification failed');
}
// Clean up old backups
await this.cleanupOldBackups();
return backupPath;
}
private async atomicWrite(filePath: string, content: string): Promise<void> {
const tempPath = `${filePath}.tmp`;
await fs.writeFile(tempPath, content, 'utf-8');
await fs.rename(tempPath, filePath);
}
private async cleanupOldBackups(): Promise<void> {
const files = await fs.readdir(this.backupDir);
const backupFiles = files
.filter(f => f.startsWith('config-backup-'))
.sort()
.reverse();
// Keep only the most recent maxBackups
const filesToDelete = backupFiles.slice(this.maxBackups);
for (const file of filesToDelete) {
await fs.remove(path.join(this.backupDir, file));
}
}
async restore(backupPath: string, targetPath: string): Promise<void> {
const backup = await fs.readJson(backupPath);
if (!backup.config) {
throw new Error('Invalid backup file: missing config data');
}
// Write config atomically
await this.atomicWrite(targetPath, JSON.stringify(backup.config, null, 2));
}
}
Step 3: Implement Configuration Update
Update config with atomic operations:
// src/services/config-updater.ts
import * as fs from 'fs-extra';
export class ConfigUpdater {
async removeMCPEntry(configPath: string, mcpName: string): Promise<void> {
// Read config
const config = await fs.readJson(configPath);
// Remove MCP entry
if (config.mcpServers && config.mcpServers[mcpName]) {
delete config.mcpServers[mcpName];
} else {
throw new Error(`MCP "${mcpName}" not found in configuration`);
}
// Validate resulting config
this.validateConfig(config);
// Write atomically
const tempPath = `${configPath}.tmp`;
await fs.writeJson(tempPath, config, { spaces: 2 });
await fs.rename(tempPath, configPath);
// Verify write
const written = await fs.readJson(configPath);
if (JSON.stringify(written) !== JSON.stringify(config)) {
throw new Error('Configuration verification failed');
}
}
private validateConfig(config: any): void {
// Basic validation
if (typeof config !== 'object' || config === null) {
throw new Error('Invalid config: must be an object');
}
// Validate mcpServers if present
if (config.mcpServers) {
if (typeof config.mcpServers !== 'object') {
throw new Error('Invalid config: mcpServers must be an object');
}
for (const [name, server] of Object.entries(config.mcpServers)) {
if (typeof server !== 'object' || server === null) {
throw new Error(`Invalid config: mcpServers.${name} must be an object`);
}
}
}
}
async updateWithRetry(configPath: string, mcpName: string, maxAttempts: number = 3): Promise<void> {
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await this.removeMCPEntry(configPath, mcpName);
return; // Success
} catch (error) {
lastError = error instanceof Error ? error : new Error('Unknown error');
if (error instanceof Error && error.message.includes('EBUSY')) {
// File is locked, wait and retry
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
} else {
// Non-retryable error
throw error;
}
}
}
throw new Error(`Config update failed after ${maxAttempts} attempts: ${lastError?.message}`);
}
}
Step 4: Integrate with Rollback
Support transaction-like operations:
// src/services/config-transaction.ts
export class ConfigTransaction {
private backup: ConfigBackup;
private updater: ConfigUpdater;
private backupPath?: string;
constructor() {
this.backup = new ConfigBackup();
this.updater = new ConfigUpdater();
}
async execute(configPath: string, mcpName: string): Promise<void> {
try {
// Step 1: Create backup
this.backupPath = await this.backup.createBackup(
configPath,
`Removing MCP: ${mcpName}`,
mcpName
);
// Step 2: Update config
await this.updater.removeMCPEntry(configPath, mcpName);
} catch (error) {
// Rollback on error
if (this.backupPath) {
await this.backup.restore(this.backupPath, configPath);
}
throw error;
}
}
async rollback(configPath: string): Promise<void> {
if (!this.backupPath) {
throw new Error('No backup available for rollback');
}
await this.backup.restore(this.backupPath, configPath);
}
}
Example Usage
Example 1: Create Backup (TASK-4.2)
import { ConfigBackup } from './services/config-backup';
const backup = new ConfigBackup('.claude/backups', 10);
// Create backup before modifications
const backupPath = await backup.createBackup(
'~/.claude/claude_desktop_config.json',
'Pre-uninstall backup',
'server-filesystem'
);
console.log(`Backup created: ${backupPath}`);
Example 2: Update Configuration (TASK-4.5)
import { ConfigUpdater } from './services/config-updater';
const updater = new ConfigUpdater();
// Remove MCP entry with retry logic
await updater.updateWithRetry(
'~/.claude/claude_desktop_config.json',
'server-filesystem',
3 // max retries
);
console.log('MCP entry removed successfully');
Example 3: Transactional Update
import { ConfigTransaction } from './services/config-transaction';
const transaction = new ConfigTransaction();
try {
// Execute with automatic backup
await transaction.execute(
'~/.claude/claude_desktop_config.json',
'server-filesystem'
);
console.log('Configuration updated successfully');
} catch (error) {
console.error('Update failed, rollback performed:', error);
}
Error Handling
File Locked Error
if (error.code === 'EBUSY' || error.code === 'EPERM') {
throw new Error('Configuration file is locked. Close Claude Code and try again.');
}
Malformed JSON Error
try {
const config = await fs.readJson(configPath);
} catch (error) {
throw new Error(`Invalid JSON in config file: ${error.message}. Please fix manually or restore from backup.`);
}
Backup Restore Error
if (!await fs.pathExists(backupPath)) {
throw new Error(`Backup file not found: ${backupPath}. Cannot perform rollback.`);
}
Integration Points
With API Services
- Backup service is used by configuration updater
- Both used by rollback mechanism
With Rollback Mechanism
- Provides backup/restore capabilities
- Supports transaction-like operations
With CLI Interface
- CLI orchestrates backup and update operations
- Displays backup paths and success messages
With Testing
- Mock file system for unit tests
- Test atomic operations and rollback
Source
git clone https://github.com/Myst4ke/mcp-to-skills-converter/blob/main/.claude/spec-implementer-generated/skills/configuration/SKILL.mdView on GitHub Overview
This skill guides safe configuration management in TypeScript, covering timestamped backups, atomic JSON updates, validation, and recovery. It emphasizes backup restoration, metadata tracking, and cleanup policies to minimize risk during config changes.
How This Skill Works
Define TypeScript interfaces for the config, implement a backup service to create metadata-rich backups, and perform write-then-rename atomic updates. The system validates JSON when updating, automatically rolls back on failure, and enforces a retention policy to keep only the desired number of backups.
When to Use It
- Implementing configuration management for a TypeScript application
- Performing atomic updates to JSON configuration files
- Backing up and restoring configurations during MCP uninstall or upgrades
- Enforcing backup retention policies and cleanup
- Validating config against a schema before loading or applying changes
Quick Start
- Step 1: Define TypeScript interfaces for your config structure (e.g., ClaudeConfig, MCPServerConfig, BackupMetadata)
- Step 2: Implement a backup service that creates a metadata-rich backup file and stores it in a backups directory
- Step 3: Perform an update using an atomic write (temp file + rename), followed by retention cleanup and optional validation
Best Practices
- Always back up before applying changes to a config file
- Use the write-then-rename atomic update pattern
- Include high-value metadata in each backup for audit trails
- Set a retention policy (e.g., keep last 10 backups) and clean up older files
- Optionally run JSON schema validation before applying changes and rollback on failure
Example Use Cases
- Create a timestamped backup of a JSON config before updating a server setting
- Apply atomic updates to a config file to prevent partial writes during deployment
- Restore the latest backup if a new config causes validation errors or corruption
- Enforce a retention policy to keep only the last 10 backups and delete older files
- Validate the config against a schema prior to loading and applying it