Skip to content
devtoolbox.cc

json_diff MCP tool: compare JSON files from an AI assistant

· 6 min read

Get the devtoolbox.cc MCP serverOpen the JSON Diff tool

“What changed between these two configs?” is a common thing to ask an assistant, and a risky one. A model comparing two long JSON files by eye is good at spotting the obvious change and bad at proving there are no others. It may also report a “change” that is only a different key order, or miss that an array item moved instead of disappearing.

The devtoolbox.cc MCP server has a tool for this, json_diff. It compares two JSON documents by structure, on your computer, and returns every difference with its path. It shares its code with the JSON Diff tool on this site. Install instructions are on the MCP page.

How the comparison works

The two documents are parsed and compared as trees, not as lines of text:

  • Object key order is ignored. {"a":1,"b":2} and {"b":2,"a":1} are equal.
  • Numbers are compared by exact value, so 1.0 equals 1, and 9007199254740993 doesn’t equal 9007199254740992.
  • Whitespace and formatting never matter.

Arrays need a decision, because order in a JSON array can be meaningful or not. The arrays argument picks one of four modes:

Mode Arrays are compared Good for
lcs By aligning matching items (longest common subsequence) Most data; the default
index Position by position Fixed-size tuples, coordinates
unordered As multisets: same items, any order Tags, permissions, sets
key By an identifying field such as id Lists of records

The result is a summary with one line per change, marked + (added), - (removed) or ~ (changed), and optionally an RFC 6902 JSON Patch that turns the first document into the second.

Arguments

Argument Type Meaning
left string The original JSON. Use this or left_path.
left_path string A local file with the original.
right string The modified JSON. Use this or right_path.
right_path string A local file with the modified version.
arrays "lcs", "index", "unordered", "key" How to compare arrays. Default "lcs".
array_key string The identifying field for "key" mode. Default "id".
output "summary", "patch" or "both" Default "summary".
output_path string Write the result to a file instead of returning it.

Examples

These examples were run against the real tool. Two versions of a service config:

{
  "service": "api",
  "port": 8080,
  "replicas": 2,
  "features": ["search", "export"],
  "limits": { "rps": 100, "burst": 20 }
}
{
  "limits": { "burst": 20, "rps": 250 },
  "service": "api",
  "port": 8080,
  "features": ["export", "search", "audit"],
  "debug": true
}

The keys were reordered as well as changed. With arrays compared as unordered sets, only the real changes remain:

{
  "left_path": "/home/dev/deploy/config.old.json",
  "right_path": "/home/dev/deploy/config.json",
  "arrays": "unordered"
}
2 added, 1 removed, 1 changed
- $.replicas: 2
+ $.features[2]: "audit"
~ $.limits.rps: 100 → 250
+ $.debug: true

The same comparison as a JSON Patch, which an agent can apply or show in a pull request. A patch needs a defined array order, so it uses the default lcs mode:

{
  "left_path": "/home/dev/deploy/config.old.json",
  "right_path": "/home/dev/deploy/config.json",
  "output": "patch"
}
[
  {
    "op": "remove",
    "path": "/replicas"
  },
  {
    "op": "remove",
    "path": "/features/0"
  },
  {
    "op": "add",
    "path": "/features/1",
    "value": "search"
  },
  {
    "op": "add",
    "path": "/features/2",
    "value": "audit"
  },
  {
    "op": "replace",
    "path": "/limits/rps",
    "value": 250
  },
  {
    "op": "add",
    "path": "/debug",
    "value": true
  }
]

In lcs mode “export” and “search” swapped places, so the patch removes “search” at index 0 and adds it back after “export”. Asking for a patch in unordered mode fails, because a set has no positions to patch:

{
  "left_path": "/home/dev/deploy/config.old.json",
  "right_path": "/home/dev/deploy/config.json",
  "arrays": "unordered",
  "output": "patch"
}
A JSON Patch needs array order: use arrays "lcs" or "index".

Lists of records are where key mode helps. Two user exports in a different order:

[
  { "id": 1, "name": "Anna", "role": "admin" },
  { "id": 2, "name": "Boris", "role": "editor" },
  { "id": 3, "name": "Chen", "role": "viewer" }
]
[
  { "id": 3, "name": "Chen", "role": "editor" },
  { "id": 1, "name": "Anna", "role": "admin" },
  { "id": 4, "name": "Dana", "role": "viewer" }
]
{
  "left_path": "/home/dev/data/users-monday.json",
  "right_path": "/home/dev/data/users-tuesday.json",
  "arrays": "key",
  "array_key": "id"
}
1 added, 1 removed, 1 changed
- $[1]: {"id":2,"name":"Boris","role":"editor"}
~ $[2].role → $[0].role: "viewer" → "editor"
+ $[2]: {"id":4,"name":"Dana","role":"viewer"}

Matched by id, the result reads like what happened: Chen was promoted, Boris left, Dana joined. Compared by position instead, almost every field would show as changed.

Large arrays

The lcs mode aligns two arrays with a table whose size is the product of their lengths. For two arrays of 2,000 items each that’s 4,000,000 cells, which is where the tool stops. A larger array is compared by position instead, and the summary ends with a line naming it (“Large arrays compared by position: …”), so an agent knows that part of the result is coarser. For big lists of records, key mode avoids the problem altogether, because it matches items by their ID.

Things to ask your assistant

  • “Compare staging.json and production.json and tell me which settings differ. Ignore array order.”
  • “What changed between yesterday’s catalog.json and today’s? Match products by sku.”
  • “Make a JSON Patch that turns v1.json into v2.json and save it as migration.patch.json.”
  • “Did the API response change after my refactor? Compare before.json and after.json.”

To see a diff side by side with highlighting, use the JSON Diff tool in the browser. It has the same array modes, and it can download the patch. The JSON formatter helps when one of the files arrives minified.

Why not diff as text

A line-based diff of two JSON files reports noise: a reordered key, a re-indented block or a trailing comma moved by a formatter all show up as changes. On large files the noise hides the one value that matters. A model reading a text diff inherits that noise. A structural diff starts from the data, so its output is the list of real changes and nothing else.

Exact numbers matter here as well. Many diff tools parse JSON with standard float parsing first, so two different 19-digit IDs can compare as equal. This one compares the digits.

json_diff runs inside the devtoolbox.cc MCP server, locally and without network access, so configs and data exports stay on your machine. Setup for each MCP client is on the MCP install page.

← All posts