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
16 changes: 16 additions & 0 deletions cspell.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"version": "0.2",
"language": "en",
"words": [
"bbox",
"denoise",
"isochrone",
"mapbox",
"mmss",
"tilequery"
],
"ignorePaths": [
"node_modules",
"dist"
]
}
1,034 changes: 1,033 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

17 changes: 9 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@
"mapbox-mcp-devkit": "dist/esm/index.js"
},
"scripts": {
"lint": "eslint \"./src/**/*.{ts,tsx}\" \"./test/**/*.{ts,tsx}\"",
"lint:fix": "eslint \"./src/**/*.{ts,tsx}\" \"./test/**/*.{ts,tsx}\" --fix",
"fix-lint": "npm run lint:fix && npm run format:fix",
"build": "npm run prepare && tshy && npm run generate-version && node scripts/add-shebang.cjs",
"format": "prettier --check \"./src/**/*.{ts,tsx,js,json,md}\" \"./test/**/*.{ts,tsx,js,json,md}\"",
"format:fix": "prettier --write \"./src/**/*.{ts,tsx,js,json,md}\" \"./test/**/*.{ts,tsx,js,json,md}\"",
"prepare": "husky && node .husky/setup-hooks.js",
"test": "vitest",
"build": "npm run prepare && npm run sync-manifest && tshy && npm run generate-version && node scripts/build-helpers.cjs copy-json && node scripts/add-shebang.cjs",
"generate-version": "node scripts/build-helpers.cjs generate-version",
"inspect:build": "npm run build && npx @modelcontextprotocol/inspector -e MAPBOX_ACCESS_TOKEN=\"$MAPBOX_ACCESS_TOKEN\" node dist/esm/index.js",
"inspect:dev": "npx @modelcontextprotocol/inspector -e MAPBOX_ACCESS_TOKEN=\"$MAPBOX_ACCESS_TOKEN\" npx -y tsx src/index.ts",
"lint": "eslint \"./src/**/*.{ts,tsx}\" \"./test/**/*.{ts,tsx}\"",
"lint:fix": "eslint \"./src/**/*.{ts,tsx}\" \"./test/**/*.{ts,tsx}\" --fix",
"prepare": "husky && node .husky/setup-hooks.js",
"spellcheck": "cspell \"*.md\" \"src/**/*.ts\" \"test/**/*.ts\"",
"sync-manifest": "node scripts/sync-manifest-version.cjs",
"dev": "tsc -p tsconfig.json --watch",
"dev:inspect": "npx @modelcontextprotocol/inspector -e MAPBOX_ACCESS_TOKEN=\"$MAPBOX_ACCESS_PRIVATE_TOKEN\" npx -y tsx src/index.ts"
"test": "vitest"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": "eslint --fix",
Expand Down Expand Up @@ -52,6 +52,7 @@
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"@vitest/coverage-istanbul": "^3.2.4",
"cspell": "^9.2.1",
"eslint": "^9.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-n": "^17.21.3",
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (c) Mapbox, Inc.
// Licensed under the MIT License.

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { parseToolConfigFromArgs, filterTools } from './config/toolConfig.js';
Expand Down
223 changes: 223 additions & 0 deletions src/schemas/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// Copyright (c) Mapbox, Inc.
// Licensed under the MIT License.

import { z } from 'zod';

// Basic types
const ColorSchema = z
.string()
.describe('Color as hex, rgb, rgba, hsl, or hsla');
const CoordinatesSchema = z.tuple([z.number(), z.number()]);

// Transition schema
const TransitionSchema = z.object({
duration: z.number().optional(),
delay: z.number().optional()
});

// Light schema
const LightSchema = z.object({
anchor: z.enum(['map', 'viewport']).optional(),
position: z.tuple([z.number(), z.number(), z.number()]).optional(),
color: ColorSchema.optional(),
intensity: z.number().optional()
});

// Lights (3D) schema
const LightsSchema = z.array(
z.object({
id: z.string(),
type: z.enum(['ambient', 'directional']),
properties: z.record(z.any()).optional()
})
);

// Terrain schema
const TerrainSchema = z.object({
source: z.string(),
exaggeration: z.number().optional()
});

// Source schemas
const VectorSourceSchema = z.object({
type: z.literal('vector'),
url: z.string().optional(),
tiles: z.array(z.string()).optional(),
bounds: z.tuple([z.number(), z.number(), z.number(), z.number()]).optional(),
scheme: z.enum(['xyz', 'tms']).optional(),
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(),
volatile: z.boolean().optional()
});

const RasterSourceSchema = z.object({
type: z.literal('raster'),
url: z.string().optional(),
tiles: z.array(z.string()).optional(),
bounds: z.tuple([z.number(), z.number(), z.number(), z.number()]).optional(),
minzoom: z.number().min(0).max(22).optional(),
maxzoom: z.number().min(0).max(22).optional(),
tileSize: z.number().optional(),
scheme: z.enum(['xyz', 'tms']).optional(),
attribution: z.string().optional(),
volatile: z.boolean().optional()
});

const RasterDemSourceSchema = z.object({
type: z.literal('raster-dem'),
url: z.string().optional(),
tiles: z.array(z.string()).optional(),
bounds: z.tuple([z.number(), z.number(), z.number(), z.number()]).optional(),
minzoom: z.number().min(0).max(22).optional(),
maxzoom: z.number().min(0).max(22).optional(),
tileSize: z.number().optional(),
attribution: z.string().optional(),
encoding: z.enum(['terrarium', 'mapbox']).optional(),
volatile: z.boolean().optional()
});

const GeoJSONSourceSchema = z.object({
type: z.literal('geojson'),
data: z.union([z.string(), z.any()]), // URL or inline GeoJSON
maxzoom: z.number().min(0).max(24).optional(),
attribution: z.string().optional(),
buffer: z.number().min(0).max(512).optional(),
tolerance: z.number().optional(),
cluster: z.boolean().optional(),
clusterRadius: z.number().optional(),
clusterMaxZoom: z.number().optional(),
clusterProperties: z.record(z.any()).optional(),
lineMetrics: z.boolean().optional(),
generateId: z.boolean().optional(),
promoteId: z.union([z.string(), z.record(z.string())]).optional()
});

const ImageSourceSchema = z.object({
type: z.literal('image'),
url: z.string(),
coordinates: z.tuple([
CoordinatesSchema,
CoordinatesSchema,
CoordinatesSchema,
CoordinatesSchema
])
});

const VideoSourceSchema = z.object({
type: z.literal('video'),
urls: z.array(z.string()),
coordinates: z.tuple([
CoordinatesSchema,
CoordinatesSchema,
CoordinatesSchema,
CoordinatesSchema
])
});

const SourceSchema = z.union([
VectorSourceSchema,
RasterSourceSchema,
RasterDemSourceSchema,
GeoJSONSourceSchema,
ImageSourceSchema,
VideoSourceSchema
]);

// Layer schema (simplified - full schema would be very extensive)
const LayerSchema = z.object({
id: z.string().describe('Unique layer name'),
type: z.enum([
'fill',
'line',
'symbol',
'circle',
'heatmap',
'fill-extrusion',
'raster',
'hillshade',
'background',
'sky',
'slot',
'clip',
'model',
'raster-particle',
'building'
]),
source: z
.string()
.optional()
.describe('Source name (not required for background/sky/slot)'),
'source-layer': z
.string()
.optional()
.describe('Layer from vector tile source'),
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(),
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()
});

// Base Style properties (shared between input and output)
export const BaseStylePropertiesSchema = z.object({
// Required Style Spec properties
version: z
.literal(8)
.describe('Style specification version number. Must be 8'),
sources: z.record(SourceSchema).describe('Data source specifications'),
layers: z.array(LayerSchema).describe('Layers in draw order'),

// Optional Style Spec properties
metadata: z
.record(z.any())
.optional()
.describe('Arbitrary properties for tracking'),
center: CoordinatesSchema.optional().describe(
'Default map center [longitude, latitude]'
),
zoom: z.number().optional().describe('Default zoom level'),
bearing: z.number().optional().describe('Default bearing in degrees'),
pitch: z.number().optional().describe('Default pitch in degrees'),
sprite: z
.string()
.optional()
.describe('Base URL for sprite image and metadata'),
glyphs: z.string().optional().describe('URL template for glyph sets'),
light: LightSchema.optional()
.nullable()
.describe('Global light source (deprecated, use lights)'),
lights: LightsSchema.optional()
.nullable()
.describe('Array of 3D light sources'),
terrain: TerrainSchema.optional()
.nullable()
.describe('Global terrain elevation'),
fog: z.record(z.any()).optional().nullable().describe('Fog properties'),
projection: z
.record(z.any())
.optional()
.nullable()
.describe('Map projection'),
transition: TransitionSchema.optional()
.nullable()
.describe('Default transition timing'),
imports: z
.array(StyleImportSchema)
.optional()
.nullable()
.describe('Imported styles')
});

export type MapboxSource = z.infer<typeof SourceSchema>;
export type MapboxLayer = z.infer<typeof LayerSchema>;
Loading