Tool Diff
What changed in a tool’s contract between two server versions, with breaking changes classified rather than just listed. Adding an optional parameter and making one required are both edits to `properties`; only one breaks every existing call.
src/components/ui/tool-diff.tsxAbout
From the component's own source, where it was written.
What changed in a tool's contract between two versions of a server.
MCP servers update underneath you. A parameter gets renamed, a previously optional field becomes required, an enum loses a value — and the agent carries on calling the tool the old way until something fails in a way that looks like a model problem.
**Breaking changes are classified, not just listed.** Adding an optional parameter and making an existing one required are both "a change to properties", and only one of them will break every existing call. The rule this encodes: removals and newly-required fields break; additions and relaxed requirements do not.
A type change is always breaking, even integer to number, because the caller is a model that was shown the old schema and will keep producing the old shape.
Install
npx astralyx-ui add tool-diff
Usage
import { ToolDiff } from '@/components/ui/tool-diff'
<ToolDiff tool="read_file" before={old.inputSchema} after={next.inputSchema} />Composer
Change the props and the source below follows.
read_file0.6.2 → 0.7.03 breakingpathdescriptionFile to read.Absolute path, inside the granted roots.encodingvalues changedutf-8, base64, latin1utf-8, base64encodingnow requiredmax_bytesremovedintegerlimitaddedinteger
No props to configure.
<ToolDiff
tool="read_file"
before={old.inputSchema}
after={next.inputSchema}
beforeLabel="0.6.2"
afterLabel="0.7.0"
/>Examples
A server upgrade that breaks three things
<ToolDiff tool="read_file" before={before} after={after} />API
Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.
ToolDiff
Extends Omit<ComponentProps<'div'>, 'children'>.
| Prop | Type | Default | Description |
|---|---|---|---|
| tool | string | required | — |
| before | JsonSchema | required | The two input schemas. Compared one level deep, which is where tool parameters live. |
| after | JsonSchema | required | The two input schemas. Compared one level deep, which is where tool parameters live. |
| beforeLabel | ReactNode | 'before' | — |
| afterLabel | ReactNode | 'after' | — |
| breakingLabel | string | 'breaking' | — |
| identicalLabel | string | 'This tool’s contract is unchanged.' | — |
| changes | ToolChange[] | — | Supply your own comparison instead of the built-in one. |
| rules | encoded, not guessed | — | Removals, newly-required fields, type changes and removed enum values break. Additions, relaxed requirements and description edits do not. |
| type | always breaking | — | Even integer → number. The caller is a model that was shown the old schema and will keep producing the old shape. |
Functions
| Function | Signature | Description |
|---|---|---|
| toolChangeBreaking | Record<'type' | 'added' | 'removed' | 'required' | 'enum' | 'optional' | 'description', boolean> | — |
| diffToolSchemas | (before: JsonSchema, after: JsonSchema) => ToolChange[] | Exported, so CI can fail a server upgrade that breaks a contract. |
Types
ToolChange
What changed in a tool's contract between two versions of a server.
| Field | Type | Description |
|---|---|---|
| path | string | — |
| kind | 'added' | 'removed' | 'type' | 'required' | 'optional' | 'enum' | 'description' | — |
| before? | string | — |
| after? | string | — |
| breaking | boolean | Removals, newly-required fields, type changes and removed enum values break. Additions, relaxed requirements and description edits do not. |