modelcontextprotocol/servers · error · Error

Destination already exists: ${destinationPath}

Error message

Destination already exists: ${destinationPath}

What it means

moveFile performs a non-destructive move: it first lstat's the destination and, if anything (file, directory, or symlink) already exists there, throws instead of letting fs.rename silently overwrite it. This exists to prevent data loss, since rename(2) would otherwise replace the target atomically.

Source

Thrown at src/filesystem/lib.ts:255

}


export async function moveFile(sourcePath: string, destinationPath: string): Promise<void> {
  // The move_file tool contract (and README) state the operation fails if the
  // destination already exists. fs.rename would silently overwrite it, which is
  // a data-loss bug, so reject up front when anything - file, directory, or
  // symlink - occupies the target. lstat is used so an existing symlink at the
  // destination is detected rather than followed.
  try {
    await fs.lstat(destinationPath);
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
      await fs.rename(sourcePath, destinationPath);
      return;
    }
    throw error;
  }
  throw new Error(`Destination already exists: ${destinationPath}`);
}


// File Editing Functions
interface FileEdit {
  oldText: string;
  newText: string;
}

export async function applyFileEdits(
  filePath: string,
  edits: FileEdit[],
  dryRun: boolean = false
): Promise<string> {
  // Read file content and normalize line endings
  const content = normalizeLineEndings(await fs.readFile(filePath, 'utf-8'));

  // Apply edits sequentially

View on GitHub (pinned to 579c3903f3)

Solutions

  1. Delete or rename the existing destination first, then retry the move
  2. Choose a unique destination name (e.g. append a timestamp or version suffix)
  3. If overwrite is genuinely intended, delete the destination explicitly via an allowed tool rather than expecting move_file to clobber it

Example fix

// before
await move_file('/allowed/a.txt', '/allowed/b.txt'); // b.txt exists -> error
// after
await move_file('/allowed/b.txt', '/allowed/b.txt.bak'); // or unlink first
await move_file('/allowed/a.txt', '/allowed/b.txt');
Defensive patterns

Strategy: try-catch

Validate before calling

try { await fs.lstat(dest); throw new Error(`Destination exists: ${dest}`); } catch (e) { if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e; }

Type guard

async function destinationIsFree(dest: string): Promise<boolean> {
  try { await fs.lstat(dest); return false; } catch (e) { return (e as NodeJS.ErrnoException).code === 'ENOENT'; }
}

Try / catch

try {
  await move_file(src, dest);
} catch (err) {
  if (err.message.startsWith('Destination already exists')) {
    // pick a unique name or archive the existing destination, then retry
  } else throw err;
}

Prevention

When it happens

Trigger: Calling move_file(sourcePath, destinationPath) via the move_file tool where destinationPath exists on disk — including when it is only a dangling symlink (lstat detects it without following).

Common situations: Re-running a script that moves files without cleaning up; moving into a directory that already contains same-named files; race where another process created the destination between planning and the move.

Related errors


AI-assisted analysis of modelcontextprotocol/servers@579c3903f3 (2026-09-01). Data as JSON: /api/errors/0ab28626daeeac27. Report an issue: GitHub.