Skip to content

Commit c89f2ec

Browse files
Merge pull request #52 from mapbox/add-compare-styles-tool
[tools] Add compare_styles_tool for style comparison
2 parents 1d56c7c + 0e54817 commit c89f2ec

7 files changed

Lines changed: 856 additions & 0 deletions

File tree

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ https://github.com/user-attachments/assets/8b1b8ef2-9fba-4951-bc9a-beaed4f6aff6
2626
- [GeoJSON Preview tool (Beta)](#geojson-preview-tool-beta)
2727
- [Coordinate Conversion tool](#coordinate-conversion-tool)
2828
- [Bounding Box tool](#bounding-box-tool)
29+
- [compare_styles_tool](#compare_styles_tool)
2930
- [Style Optimization tool](#style-optimization-tool)
3031
- [Resources](#resources)
3132
- [Observability \& Tracing](#observability--tracing)
@@ -624,6 +625,53 @@ An array of four numbers representing the bounding box: `[minX, minY, maxX, maxY
624625
- "Calculate the bounding box of this GeoJSON file" (then upload a .geojson file)
625626
- "What's the bounding box for the coordinates in the uploaded parks.geojson file?"
626627

628+
#### compare_styles_tool
629+
630+
Compares two Mapbox styles and reports structural differences, including changes to layers, sources, and properties. This offline comparison tool performs deep object comparison without requiring API access.
631+
632+
**Parameters:**
633+
634+
- `styleA` (string or object, required): First Mapbox style to compare (JSON string or style object)
635+
- `styleB` (string or object, required): Second Mapbox style to compare (JSON string or style object)
636+
- `ignoreMetadata` (boolean, optional): If true, ignores metadata fields (id, owner, created, modified, draft, visibility) when comparing
637+
638+
**Comparison features:**
639+
640+
- Deep recursive comparison of nested structures
641+
- Layer comparison by ID (not array position)
642+
- Detailed diff reporting with JSON paths
643+
- Identifies additions, removals, and modifications
644+
- Optional metadata filtering
645+
646+
**Returns:**
647+
648+
```json
649+
{
650+
"identical": false,
651+
"differences": [
652+
{
653+
"path": "layers.water.paint.fill-color",
654+
"type": "modified",
655+
"valueA": "#a0c8f0",
656+
"valueB": "#b0d0ff",
657+
"description": "Modified property at layers.water.paint.fill-color"
658+
}
659+
],
660+
"summary": {
661+
"totalDifferences": 1,
662+
"added": 0,
663+
"removed": 0,
664+
"modified": 1
665+
}
666+
}
667+
```
668+
669+
**Example prompts:**
670+
671+
- "Compare these two Mapbox styles and show me the differences"
672+
- "What changed between my old style and new style?"
673+
- "Compare styles ignoring metadata fields"
674+
627675
#### Style Optimization tool
628676

629677
Optimizes Mapbox styles by removing redundancies, simplifying expressions, and reducing file size.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) Mapbox, Inc.
2+
// Licensed under the MIT License.
3+
4+
import { z } from 'zod';
5+
6+
export const CompareStylesInputSchema = z.object({
7+
styleA: z
8+
.union([z.string(), z.record(z.unknown())])
9+
.describe('First Mapbox style (JSON string or style object)'),
10+
styleB: z
11+
.union([z.string(), z.record(z.unknown())])
12+
.describe('Second Mapbox style (JSON string or style object)'),
13+
ignoreMetadata: z
14+
.boolean()
15+
.optional()
16+
.describe('Ignore metadata fields like id, owner, created, modified')
17+
});
18+
19+
export type CompareStylesInput = z.infer<typeof CompareStylesInputSchema>;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) Mapbox, Inc.
2+
// Licensed under the MIT License.
3+
4+
import { z } from 'zod';
5+
6+
const DifferenceSchema = z.object({
7+
path: z.string().describe('JSON path to the difference'),
8+
type: z.enum(['added', 'removed', 'modified']).describe('Type of difference'),
9+
valueA: z.unknown().optional().describe('Value in style A (if exists)'),
10+
valueB: z.unknown().optional().describe('Value in style B (if exists)'),
11+
description: z.string().optional().describe('Human-readable description')
12+
});
13+
14+
export const CompareStylesOutputSchema = z.object({
15+
identical: z.boolean().describe('Whether the styles are identical'),
16+
differences: z.array(DifferenceSchema).describe('List of differences found'),
17+
summary: z
18+
.object({
19+
totalDifferences: z.number().describe('Total number of differences'),
20+
added: z.number().describe('Number of additions in style B'),
21+
removed: z.number().describe('Number of removals from style A'),
22+
modified: z.number().describe('Number of modifications')
23+
})
24+
.describe('Summary of differences')
25+
});
26+
27+
export type CompareStylesOutput = z.infer<typeof CompareStylesOutputSchema>;
28+
export type Difference = z.infer<typeof DifferenceSchema>;

0 commit comments

Comments
 (0)