obsidian_update_links
Rewrite wiki-links and Markdown links from one path to another within an Obsidian vault. Supports dry-run mode.
Instructions
Rewrite incoming wiki-links and Markdown links from one target path to another. Dry-run by default.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| vault | No | Optional configured vault name. Defaults to the server default vault. | |
| from | Yes | Vault-relative path. Absolute paths and traversal are rejected. | |
| to | Yes | ||
| dryRun | No |
Implementation Reference
- src/tools.ts:585-595 (registration)Registration of the obsidian_update_links tool in the MCP server with its Zod schema and handler that delegates to updateLinksAcrossVault.
tool( "obsidian_update_links", "Rewrite incoming wiki-links and Markdown links from one target path to another. Dry-run by default.", { vault: vaultArg, from: pathArg, to: pathArg, dryRun: z.boolean().optional().default(true), }, async (args) => ({ rewrites: await updateLinksAcrossVault(vaults, args.vault, vaults.notePath(args.from), vaults.notePath(args.to), { dryRun: args.dryRun }) }), ); - src/ops.ts:55-91 (handler)Core handler that iterates all Markdown files in the vault, rewrites wiki-links ([[...]]) and Markdown links ([...](...)) from the old path to the new path, with dry-run support.
export async function updateLinksAcrossVault( vaults: VaultManager, vault: string | undefined, fromPath: string, toPath: string, options: { dryRun?: boolean } = {}, ): Promise<Array<{ path: string; changed: number; preview?: string }>> { const files = await vaults.markdownFiles(vault); const fromNoExt = fromPath.replace(/\.md$/i, ""); const fromBase = path.posix.basename(fromNoExt); const toNoExt = toPath.replace(/\.md$/i, ""); const rewrites: Array<{ path: string; changed: number; preview?: string }> = []; for (const file of files) { const read = await vaults.readText(file, vault); let changed = 0; let next = read.text.replace(/(!?)\[\[([^\]\n]+)\]\]/g, (full, embed: string, inner: string) => { const [targetAndSection, display] = inner.split("|", 2); const sectionMatch = /([#^].*)$/.exec(targetAndSection); const section = sectionMatch?.[1] ?? ""; const target = targetAndSection.replace(/[#^].*$/, "").trim().replace(/\.md$/i, ""); if (target !== fromNoExt && target !== fromBase && target !== fromPath) return full; changed += 1; return `${embed}[[${toNoExt}${section}${display ? `|${display}` : ""}]]`; }); next = next.replace(/\[([^\]\n]+)\]\(([^)\n]+)\)/g, (full, label: string, target: string) => { const clean = decodeURIComponent(target).replace(/^\.\//, ""); if (clean !== fromPath && clean !== fromNoExt && clean !== `${fromNoExt}.md`) return full; changed += 1; return `[${label}](${encodeURI(toPath)})`; }); if (changed > 0) { rewrites.push({ path: read.path, changed, preview: options.dryRun ? previewDiff(read.text, next) : undefined }); if (!options.dryRun) await vaults.writeText(read.path, next, vault, { overwrite: true }); } } return rewrites; } - src/ops.ts:195-207 (helper)Helper used by updateLinksAcrossVault to generate a preview diff of changed lines for dry-run mode.
function previewDiff(before: string, after: string): string { const beforeLines = before.split("\n"); const afterLines = after.split("\n"); const out: string[] = []; const max = Math.max(beforeLines.length, afterLines.length); for (let i = 0; i < max && out.length < 20; i += 1) { if (beforeLines[i] !== afterLines[i]) { if (beforeLines[i] !== undefined) out.push(`-${beforeLines[i]}`); if (afterLines[i] !== undefined) out.push(`+${afterLines[i]}`); } } return out.join("\n"); } - src/tools.ts:588-594 (schema)Zod input schema for obsidian_update_links: vault (optional), from (path), to (path), dryRun (boolean, default true).
{ vault: vaultArg, from: pathArg, to: pathArg, dryRun: z.boolean().optional().default(true), }, async (args) => ({ rewrites: await updateLinksAcrossVault(vaults, args.vault, vaults.notePath(args.from), vaults.notePath(args.to), { dryRun: args.dryRun }) }), - src/tools.ts:28-28 (registration)Import of updateLinksAcrossVault from ops.ts into tools.ts registration file.
import { batchRename, deleteFolder, pruneEmptyDirs, regexReplaceAcrossVault, updateLinksAcrossVault } from "./ops.js";