obsidian_diff_notes
Compare two notes or a note with supplied text to produce a structured line diff.
Instructions
Compare two notes or compare a note with supplied text and return a structured line diff.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| vault | No | Optional configured vault name. Defaults to the server default vault. | |
| leftPath | Yes | Vault-relative path. Absolute paths and traversal are rejected. | |
| rightPath | No | ||
| rightText | No | ||
| context | No | ||
| maxLines | No |
Implementation Reference
- src/tools.ts:886-908 (registration)Registration of the obsidian_diff_notes tool with McpServer. Defines the schema (vault, leftPath, rightPath, rightText, context, maxLines) and the handler that reads notes and calls diffLines.
"obsidian_diff_notes", "Compare two notes or compare a note with supplied text and return a structured line diff.", { vault: vaultArg, leftPath: pathArg, rightPath: z.string().optional(), rightText: z.string().optional(), context: z.number().int().min(0).max(20).optional().default(3), maxLines: z.number().int().min(20).max(5000).optional().default(500), }, async (args) => { const left = await vaults.readText(args.leftPath, args.vault); const right = args.rightText ?? (args.rightPath ? (await vaults.readText(args.rightPath, args.vault)).text : ""); const diff = diffLines(left.text, right); return { left: left.path, right: args.rightPath ?? "(provided text)", changedLines: diff.filter((line) => line.type !== "same").length, diff: compactDiff(diff, args.context).slice(0, args.maxLines), }; }, { readOnlyHint: true }, ); - src/tools.ts:896-906 (handler)Handler logic for obsidian_diff_notes: reads left note from vault, gets right note text (either from rightPath or rightText), computes line diff using diffLines(), and returns structured diff with changedLines count and compacted diff.
async (args) => { const left = await vaults.readText(args.leftPath, args.vault); const right = args.rightText ?? (args.rightPath ? (await vaults.readText(args.rightPath, args.vault)).text : ""); const diff = diffLines(left.text, right); return { left: left.path, right: args.rightPath ?? "(provided text)", changedLines: diff.filter((line) => line.type !== "same").length, diff: compactDiff(diff, args.context).slice(0, args.maxLines), }; }, - src/tools.ts:888-895 (schema)Zod schema for obsidian_diff_notes tool parameters: vault (optional), leftPath (required), rightPath (optional), rightText (optional), context (default 3), maxLines (default 500).
{ vault: vaultArg, leftPath: pathArg, rightPath: z.string().optional(), rightText: z.string().optional(), context: z.number().int().min(0).max(20).optional().default(3), maxLines: z.number().int().min(20).max(5000).optional().default(500), }, - src/intelligence.ts:177-203 (helper)The diffLines() function implementation. Uses a longest-common-subsequence (LCS) dynamic programming approach to produce a structured diff with 'same', 'removed', and 'added' line types.
export function diffLines(a: string, b: string): Array<{ type: "same" | "removed" | "added"; line?: number; text: string }> { const left = a.replace(/\r\n/g, "\n").split("\n"); const right = b.replace(/\r\n/g, "\n").split("\n"); const dp: number[][] = Array.from({ length: left.length + 1 }, () => Array(right.length + 1).fill(0)); for (let i = left.length - 1; i >= 0; i -= 1) { for (let j = right.length - 1; j >= 0; j -= 1) { dp[i][j] = left[i] === right[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]); } } const out: Array<{ type: "same" | "removed" | "added"; line?: number; text: string }> = []; let i = 0; let j = 0; while (i < left.length || j < right.length) { if (i < left.length && j < right.length && left[i] === right[j]) { out.push({ type: "same", line: i + 1, text: left[i] ?? "" }); i += 1; j += 1; } else if (j >= right.length || (i < left.length && dp[i + 1][j] >= dp[i][j + 1])) { out.push({ type: "removed", line: i + 1, text: left[i] ?? "" }); i += 1; } else { out.push({ type: "added", line: j + 1, text: right[j] ?? "" }); j += 1; } } return out; } - src/tools.ts:1335-1346 (helper)The compactDiff() helper function. Filters a diff array to keep only changed lines plus surrounding context lines, used by the obsidian_diff_notes handler to limit output.
function compactDiff( diff: Array<{ type: "same" | "removed" | "added"; line?: number; text: string }>, context: number, ): Array<{ type: "same" | "removed" | "added"; line?: number; text: string }> { if (context === 0) return diff.filter((line) => line.type !== "same"); const keep = new Set<number>(); diff.forEach((line, index) => { if (line.type === "same") return; for (let i = Math.max(0, index - context); i <= Math.min(diff.length - 1, index + context); i += 1) keep.add(i); }); return diff.filter((_line, index) => keep.has(index)); }