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.0equals1, 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.jsonandproduction.jsonand tell me which settings differ. Ignore array order.” - “What changed between yesterday’s
catalog.jsonand today’s? Match products bysku.” - “Make a JSON Patch that turns
v1.jsonintov2.jsonand save it asmigration.patch.json.” - “Did the API response change after my refactor? Compare
before.jsonandafter.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.