Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Unreleased

### Dependencies

- **Upgrade `zod` from `^3.25.42` to `^4.3.6`** — migrated all `z.record()` calls to require explicit key schema (`z.string()`), updated test assertions for changed error message format

## 0.6.0 - 2026-04-01

### Security
Expand Down
6 changes: 4 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"@opentelemetry/sdk-node": "^0.56.0",
"@opentelemetry/sdk-trace-base": "^1.30.1",
"@opentelemetry/semantic-conventions": "^1.30.1",
"zod": "^3.25.42"
"zod": "^4.3.6"
},
"devDependencies": {
"@eslint/js": "^9.27.0",
Expand Down
30 changes: 18 additions & 12 deletions src/schemas/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const LightsSchema = z.array(
z.object({
id: z.string(),
type: z.enum(['ambient', 'directional']),
properties: z.record(z.any()).optional()
properties: z.record(z.string(), z.any()).optional()
})
);

Expand All @@ -48,7 +48,7 @@ const VectorSourceSchema = z.object({
minzoom: z.number().min(0).max(22).optional(),
maxzoom: z.number().min(0).max(22).optional(),
attribution: z.string().optional(),
promoteId: z.union([z.string(), z.record(z.string())]).optional(),
promoteId: z.union([z.string(), z.record(z.string(), z.string())]).optional(),
volatile: z.boolean().optional()
});

Expand Down Expand Up @@ -88,10 +88,10 @@ const GeoJSONSourceSchema = z.object({
cluster: z.boolean().optional(),
clusterRadius: z.number().optional(),
clusterMaxZoom: z.number().optional(),
clusterProperties: z.record(z.any()).optional(),
clusterProperties: z.record(z.string(), z.any()).optional(),
lineMetrics: z.boolean().optional(),
generateId: z.boolean().optional(),
promoteId: z.union([z.string(), z.record(z.string())]).optional()
promoteId: z.union([z.string(), z.record(z.string(), z.string())]).optional()
});

const ImageSourceSchema = z.object({
Expand Down Expand Up @@ -156,17 +156,17 @@ const LayerSchema = z.object({
minzoom: z.number().min(0).max(24).optional(),
maxzoom: z.number().min(0).max(24).optional(),
filter: z.any().optional().describe('Expression for filtering features'),
layout: z.record(z.any()).optional(),
paint: z.record(z.any()).optional(),
metadata: z.record(z.any()).optional(),
layout: z.record(z.string(), z.any()).optional(),
paint: z.record(z.string(), z.any()).optional(),
metadata: z.record(z.string(), z.any()).optional(),
slot: z.string().optional().describe('Slot this layer is assigned to')
});

// Style import schema
const StyleImportSchema = z.object({
id: z.string(),
url: z.string(),
config: z.record(z.any()).optional()
config: z.record(z.string(), z.any()).optional()
});

// Base Style properties (shared between input and output)
Expand All @@ -175,12 +175,14 @@ export const BaseStylePropertiesSchema = z.object({
version: z
.literal(8)
.describe('Style specification version number. Must be 8'),
sources: z.record(SourceSchema).describe('Data source specifications'),
sources: z
.record(z.string(), SourceSchema)
.describe('Data source specifications'),
layers: z.array(LayerSchema).describe('Layers in draw order'),

// Optional Style Spec properties
metadata: z
.record(z.any())
.record(z.string(), z.any())
.optional()
.describe('Arbitrary properties for tracking'),
center: CoordinatesSchema.optional().describe(
Expand All @@ -203,9 +205,13 @@ export const BaseStylePropertiesSchema = z.object({
terrain: TerrainSchema.optional()
.nullable()
.describe('Global terrain elevation'),
fog: z.record(z.any()).optional().nullable().describe('Fog properties'),
fog: z
.record(z.string(), z.any())
.optional()
.nullable()
.describe('Fog properties'),
projection: z
.record(z.any())
.record(z.string(), z.any())
.optional()
.nullable()
.describe('Map projection'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { z } from 'zod';

export const CompareStylesInputSchema = z.object({
styleA: z
.union([z.string(), z.record(z.unknown())])
.union([z.string(), z.record(z.string(), z.unknown())])
.describe('First Mapbox style (JSON string or style object)'),
styleB: z
.union([z.string(), z.record(z.unknown())])
.union([z.string(), z.record(z.string(), z.unknown())])
.describe('Second Mapbox style (JSON string or style object)'),
ignoreMetadata: z
.boolean()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { z } from 'zod';
export const CreateStyleInputSchema = z.object({
name: z.string().describe('Human-readable name for the style'),
style: z
.record(z.any())
.record(z.string(), z.any())
.describe(
'Complete Mapbox Style Specification object. Must include: version (8), sources, layers. Optional: sprite, glyphs, center, zoom, bearing, pitch, metadata, etc. See https://docs.mapbox.com/mapbox-gl-js/style-spec/'
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ const StyleMetadataSchema = z.object({
pitch: z.number().optional().describe('Default pitch in degrees'),

// Sources and layers may or may not be included in list responses
sources: z.record(z.any()).optional().describe('Style data sources'),
sources: z
.record(z.string(), z.any())
.optional()
.describe('Style data sources'),
layers: z.array(z.any()).optional().describe('Style layers'),

// Additional metadata fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { z } from 'zod';

export const OptimizeStyleInputSchema = z.object({
style: z
.union([z.string(), z.record(z.unknown())])
.union([z.string(), z.record(z.string(), z.unknown())])
.describe('Mapbox style to optimize (JSON string or style object)'),
optimizations: z
.array(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ const OptimizationSchema = z.object({
});

export const OptimizeStyleOutputSchema = z.object({
optimizedStyle: z.record(z.unknown()).describe('The optimized Mapbox style'),
optimizedStyle: z
.record(z.string(), z.unknown())
.describe('The optimized Mapbox style'),
optimizations: z
.array(OptimizationSchema)
.describe('List of optimizations that were applied'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const LayerConfigSchema = z.object({
z.number(),
z.boolean(),
z.array(z.unknown()),
z.record(z.unknown())
z.record(z.string(), z.unknown())
])
.optional()
.describe('Custom filter expression'),
Expand Down Expand Up @@ -109,7 +109,7 @@ const LayerConfigSchema = z.object({
z.number(),
z.boolean(),
z.array(z.unknown()),
z.record(z.unknown())
z.record(z.string(), z.unknown())
])
.optional()
.describe('Custom Mapbox expression for advanced styling'),
Expand Down
2 changes: 1 addition & 1 deletion src/tools/tilequery-tool/TilequeryTool.output.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const VectorTilequeryFeatureSchema = z.object({
.describe('The vector tile layer of the feature result')
})
})
.and(z.record(z.any())) // Allow additional properties from the original feature
.and(z.record(z.string(), z.any())) // Allow additional properties from the original feature
});

// Rasterarray Tileset Feature Schema
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const UpdateStyleInputSchema = z.object({
styleId: z.string().describe('Style ID to update'),
name: z.string().optional().describe('New name for the style'),
style: z
.record(z.any())
.record(z.string(), z.any())
.optional()
.describe(
'Complete Mapbox Style Specification object to update. Must include: version (8), sources, layers. Optional: sprite, glyphs, center, zoom, bearing, pitch, metadata, etc. See https://docs.mapbox.com/mapbox-gl-js/style-spec/'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { z } from 'zod';

export const ValidateGeojsonInputSchema = z.object({
geojson: z
.union([z.string(), z.record(z.unknown())])
.union([z.string(), z.record(z.string(), z.unknown())])
.describe('GeoJSON object or JSON string to validate')
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { z } from 'zod';
*/
export const ValidateStyleInputSchema = z.object({
style: z
.union([z.string(), z.record(z.unknown())])
.union([z.string(), z.record(z.string(), z.unknown())])
.describe(
'Mapbox style JSON object or JSON string to validate against the Mapbox Style Specification'
)
Expand Down
4 changes: 2 additions & 2 deletions test/tools/create-token-tool/CreateTokenTool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('CreateTokenTool', () => {
expect(result.isError).toBe(true);
expect(result.content[0]).toHaveProperty('type', 'text');
const errorText = (result.content[0] as TextContent).text;
expect(errorText).toContain('Required');
expect(errorText).toContain('invalid_type');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create token tool doesn't show any changes in this PR. Assume this is just a downstream effect of the version change, flagging it just in case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct — this is a direct result of the zod v4 error message format change. In v3, missing required fields produced a 'Required' message. In v4 that changed to 'Invalid input: expected string, received undefined' (code invalid_type). The test assertion was updated to match the new format while still verifying that validation fails and the error references the correct field (feedback_id / note / scopes).

});

it('validates allowedUrls array length', async () => {
Expand Down Expand Up @@ -93,7 +93,7 @@ describe('CreateTokenTool', () => {
expect(result.isError).toBe(true);
expect(result.content[0]).toHaveProperty('type', 'text');
const errorText = (result.content[0] as TextContent).text;
expect(errorText).toContain('Invalid enum value');
expect(errorText).toContain('invalid_value');
});

it('throws error when unable to extract username from token', async () => {
Expand Down
2 changes: 1 addition & 1 deletion test/tools/get-feedback-tool/GetFeedbackTool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ describe('GetFeedbackTool', () => {

// Should fail validation
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Required');
expect(result.content[0].text).toContain('invalid_type');
expect(result.content[0].text).toContain('feedback_id');
});
});
2 changes: 1 addition & 1 deletion test/tools/list-tokens-tool/ListTokensTool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('ListTokensTool', () => {
expect(result.isError).toBe(true);
expect(result.content[0]).toHaveProperty('type', 'text');
const errorText = (result.content[0] as TextContent).text;
expect(errorText).toContain('Number must be less than or equal to 100');
expect(errorText).toContain('<=100');
});

it('validates sortby enum values', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('StyleComparisonTool', () => {
expect(result.isError).toBe(true);
expect(
(result.content[0] as { type: 'text'; text: string }).text
).toContain('Required');
).toContain('invalid_type');
});

it('should handle full style URLs', async () => {
Expand Down
Loading