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
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ jobs:
- name: Check formatting
run: npm run format

- name: Typecheck src and tests
run: npm run typecheck

- name: Compile typescript
run: npm run build

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
- `style_comparison_tool`'s `StyleComparisonUIResource` is untouched and stays separate — it renders two synced `mapboxgl.Map` instances under a swipe/compare slider, a genuinely different UI shape from "one map, different content," not just another mode to fold in.
- The GeoJSON-preview path keeps its existing eager map bootstrap (drawing the default Standard style immediately, before any tool result arrives, so an overlay has something to render onto right away); the style-preview path now reuses that same map instance via `setStyle()` when it exists, falling back to constructing its own (as before) when it doesn't. Verified live against the real Mapbox API in a real browser: the GeoJSON overlay path and the style-swap-in-place path (loading a real custom style by name) both work end-to-end on the merged resource.

### Maintenance

- **Tests are typechecked in CI (#143).** `npm run typecheck` checks `src` and `test` together and now runs before the build. The tests had 247 type errors, all already on main: 238 read `.text` off a content block without checking it was text (now `textOf`, `resourceTextOf` and `promptTextOf` in `test/utils/resultTestUtils.ts`, which fail clearly on the wrong content type), the `StyleBuilderTool` tests typed raw input with the parsed type that makes `render_type` required, and one import used a bare `src/` path. `tsconfig.test.json` gets `noEmit` so a plain `tsc` run can't write `.js` files next to the sources.

### Dependencies

- Bumped `@modelcontextprotocol/sdk` to `1.30.0`. Not adopting the `2026-07-28` spec revision this release covers (stateless request/response model, elicitation replaced by Multi Round-Trip Requests, Sampling deprecated) — that's a separate migration, tracked in #130, given this repo's own elicitation-based features depend on the mechanism being replaced. Regenerated `patches/@modelcontextprotocol+sdk+1.30.0.patch` (previously pinned to `1.29.0`) — same patch content, applies cleanly to the new version, verified live against the built server.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"spellcheck": "cspell \"*.md\" \"src/**/*.ts\" \"test/**/*.ts\" \"examples/**/*.ts\"",
"sync-manifest": "node scripts/sync-manifest-version.cjs",
"test": "vitest",
"typecheck": "tsc -p tsconfig.test.json",
"tracing:jaeger:start": "docker run --rm -d --name jaeger -p 16686:16686 -p 14250:14250 -p 4317:4317 -p 4318:4318 jaegertracing/all-in-one:latest",
"tracing:jaeger:stop": "docker stop jaeger",
"tracing:verify": "node -e \"console.log('🔍 Verifying tracing setup with Jaeger...\\n1. Copy .env.example to .env and add your MAPBOX_ACCESS_TOKEN\\n2. Start Jaeger: npm run tracing:jaeger:start\\n3. Run server: npm run inspect:build\\n4. Check traces at: http://localhost:16686')\""
Expand Down
13 changes: 7 additions & 6 deletions test/prompts/AnalyzeGeojsonPrompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { describe, it, expect } from 'vitest';
import { AnalyzeGeojsonPrompt } from '../../src/prompts/AnalyzeGeojsonPrompt.js';
import { promptTextOf } from '../utils/resultTestUtils.js';

describe('AnalyzeGeojsonPrompt', () => {
const prompt = new AnalyzeGeojsonPrompt();
Expand Down Expand Up @@ -50,7 +51,7 @@ describe('AnalyzeGeojsonPrompt', () => {
expect(result.messages[0].role).toBe('user');
expect(result.messages[0].content.type).toBe('text');

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Analyze the provided GeoJSON');
expect(text).toContain('bounding_box_tool');
expect(text).toContain('geojson_preview_tool');
Expand All @@ -61,7 +62,7 @@ describe('AnalyzeGeojsonPrompt', () => {
geojson_data: sampleGeojson
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Calculate bounding box');
expect(text).toContain('bounding_box_tool');
});
Expand All @@ -72,7 +73,7 @@ describe('AnalyzeGeojsonPrompt', () => {
show_bounds: 'false'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
// Should still mention visualization but with different step number
expect(text).toContain('Generate visualization');
});
Expand All @@ -83,7 +84,7 @@ describe('AnalyzeGeojsonPrompt', () => {
convert_coordinates: 'true'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('coordinate_conversion_tool');
expect(text).toContain('Web Mercator');
expect(text).toContain('EPSG:3857');
Expand All @@ -94,7 +95,7 @@ describe('AnalyzeGeojsonPrompt', () => {
geojson_data: sampleGeojson
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain(sampleGeojson);
});

Expand All @@ -109,7 +110,7 @@ describe('AnalyzeGeojsonPrompt', () => {
geojson_data: sampleGeojson
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Analysis guidelines');
expect(text).toContain('longitude, latitude');
expect(text).toContain('right-hand rule');
Expand Down
9 changes: 5 additions & 4 deletions test/prompts/BuildCustomMapPrompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { describe, it, expect } from 'vitest';
import { BuildCustomMapPrompt } from '../../src/prompts/BuildCustomMapPrompt.js';
import { promptTextOf } from '../utils/resultTestUtils.js';

describe('BuildCustomMapPrompt', () => {
const prompt = new BuildCustomMapPrompt();
Expand Down Expand Up @@ -37,7 +38,7 @@ describe('BuildCustomMapPrompt', () => {
expect(result.messages[0].role).toBe('user');
expect(result.messages[0].content.type).toBe('text');

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('dark cyberpunk');
expect(text).toContain('style_builder_tool');
expect(text).toContain('create_style_tool');
Expand All @@ -50,7 +51,7 @@ describe('BuildCustomMapPrompt', () => {
emphasis: 'parks and green spaces'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('parks and green spaces');
expect(text).toContain('emphasizing');
});
Expand All @@ -62,7 +63,7 @@ describe('BuildCustomMapPrompt', () => {
preview_zoom: '15'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Tokyo');
expect(text).toContain('15');
});
Expand All @@ -78,7 +79,7 @@ describe('BuildCustomMapPrompt', () => {
theme: 'retro 80s neon'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Theme interpretation tips');
expect(text).toContain('Dark cyberpunk');
expect(text).toContain('Nature-focused');
Expand Down
9 changes: 5 additions & 4 deletions test/prompts/CreateAndPreviewStylePrompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { describe, it, expect } from 'vitest';
import { CreateAndPreviewStylePrompt } from '../../src/prompts/CreateAndPreviewStylePrompt.js';
import { promptTextOf } from '../utils/resultTestUtils.js';

describe('CreateAndPreviewStylePrompt', () => {
const prompt = new CreateAndPreviewStylePrompt();
Expand Down Expand Up @@ -44,7 +45,7 @@ describe('CreateAndPreviewStylePrompt', () => {
expect(result.messages[0].role).toBe('user');
expect(result.messages[0].content.type).toBe('text');

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Test Style');
expect(text).toContain('list_tokens_tool');
expect(text).toContain('create_style_tool');
Expand All @@ -53,7 +54,7 @@ describe('CreateAndPreviewStylePrompt', () => {

it('defaults base_style to "standard", not the Classic "streets-v12"', () => {
const result = prompt.execute({ style_name: 'Test Style' });
const text = result.messages[0].content.text;
const text = promptTextOf(result);

expect(text).toContain('base_style: "standard"');
expect(text).not.toContain('streets-v12');
Expand All @@ -65,7 +66,7 @@ describe('CreateAndPreviewStylePrompt', () => {
style_name: 'Test Style',
base_style: 'streets-v12'
});
const text = result.messages[0].content.text;
const text = promptTextOf(result);

expect(text).toContain('base_style: "streets-v12"');
});
Expand All @@ -79,7 +80,7 @@ describe('CreateAndPreviewStylePrompt', () => {
preview_zoom: '14'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('A beautiful test style');
expect(text).toContain('dark-v11');
expect(text).toContain('San Francisco');
Expand Down
19 changes: 10 additions & 9 deletions test/prompts/DebugMapboxIntegrationPrompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { describe, it, expect } from 'vitest';
import { DebugMapboxIntegrationPrompt } from '../../src/prompts/DebugMapboxIntegrationPrompt.js';
import { promptTextOf } from '../utils/resultTestUtils.js';

describe('DebugMapboxIntegrationPrompt', () => {
const prompt = new DebugMapboxIntegrationPrompt();
Expand Down Expand Up @@ -39,7 +40,7 @@ describe('DebugMapboxIntegrationPrompt', () => {
expect(result.messages[0].role).toBe('user');
expect(result.messages[0].content.type).toBe('text');

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Map not loading');
expect(text).toContain('list_tokens_tool');
expect(text).toContain('Phase 1');
Expand All @@ -51,7 +52,7 @@ describe('DebugMapboxIntegrationPrompt', () => {
error_message: '401 Unauthorized'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('401 Unauthorized');
});

Expand All @@ -61,7 +62,7 @@ describe('DebugMapboxIntegrationPrompt', () => {
error_message: '401 unauthorized'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('401 Unauthorized');
expect(text).toContain('Token not set');
expect(text).toContain('Invalid token');
Expand All @@ -73,7 +74,7 @@ describe('DebugMapboxIntegrationPrompt', () => {
error_message: '403 Forbidden'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('403 Forbidden');
expect(text).toContain('URL restriction');
expect(text).toContain('Missing scope');
Expand All @@ -85,7 +86,7 @@ describe('DebugMapboxIntegrationPrompt', () => {
style_id: 'my-style-id'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('my-style-id');
expect(text).toContain('list_styles_tool');
expect(text).toContain('Style Verification');
Expand All @@ -97,7 +98,7 @@ describe('DebugMapboxIntegrationPrompt', () => {
environment: 'production'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('production');
});

Expand All @@ -107,7 +108,7 @@ describe('DebugMapboxIntegrationPrompt', () => {
error_message: 'Layer source not found'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Layer/Source Error Solutions');
});

Expand All @@ -116,7 +117,7 @@ describe('DebugMapboxIntegrationPrompt', () => {
issue_description: 'Something is wrong'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('General debugging');
expect(text).toContain('browser console');
});
Expand All @@ -126,7 +127,7 @@ describe('DebugMapboxIntegrationPrompt', () => {
issue_description: 'Issue'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Phase 1');
expect(text).toContain('Phase 2');
expect(text).toContain('Phase 3');
Expand Down
27 changes: 14 additions & 13 deletions test/prompts/DesignDataDrivenStylePrompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { describe, it, expect } from 'vitest';
import { DesignDataDrivenStylePrompt } from '../../src/prompts/DesignDataDrivenStylePrompt.js';
import { promptTextOf } from '../utils/resultTestUtils.js';

describe('DesignDataDrivenStylePrompt', () => {
const prompt = new DesignDataDrivenStylePrompt();
Expand Down Expand Up @@ -42,7 +43,7 @@ describe('DesignDataDrivenStylePrompt', () => {
expect(result.messages[0].role).toBe('user');
expect(result.messages[0].content.type).toBe('text');

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Population Map');
expect(text).toContain('City populations');
expect(text).toContain('population');
Expand All @@ -55,7 +56,7 @@ describe('DesignDataDrivenStylePrompt', () => {
property_name: 'value'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('interpolate');
expect(text).toContain('["get", "value"]');
expect(text).toContain('Expression types');
Expand All @@ -69,7 +70,7 @@ describe('DesignDataDrivenStylePrompt', () => {
visualization_type: 'color'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('fill-color');
expect(text).toContain('Color by Value');
});
Expand All @@ -82,7 +83,7 @@ describe('DesignDataDrivenStylePrompt', () => {
visualization_type: 'size'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('circle-radius');
expect(text).toContain('Size by Value');
});
Expand All @@ -95,7 +96,7 @@ describe('DesignDataDrivenStylePrompt', () => {
visualization_type: 'both'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('fill-color');
expect(text).toContain('circle-radius');
});
Expand All @@ -108,7 +109,7 @@ describe('DesignDataDrivenStylePrompt', () => {
visualization_type: 'heatmap'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('heatmap');
expect(text).toContain('heatmap-weight');
expect(text).toContain('heatmap-color');
Expand All @@ -121,7 +122,7 @@ describe('DesignDataDrivenStylePrompt', () => {
property_name: 'value'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('sequential');
});

Expand All @@ -133,7 +134,7 @@ describe('DesignDataDrivenStylePrompt', () => {
color_scheme: 'diverging'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Diverging');
expect(text).toContain('midpoint');
});
Expand All @@ -146,7 +147,7 @@ describe('DesignDataDrivenStylePrompt', () => {
color_scheme: 'categorical'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('match');
expect(text).toContain('Category');
});
Expand All @@ -158,7 +159,7 @@ describe('DesignDataDrivenStylePrompt', () => {
property_name: 'value'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Step 1');
expect(text).toContain('Step 2');
expect(text).toContain('Step 3');
Expand All @@ -176,7 +177,7 @@ describe('DesignDataDrivenStylePrompt', () => {
property_name: 'value'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Advanced Expressions');
expect(text).toContain('Zoom-Based');
expect(text).toContain('Conditional');
Expand All @@ -189,7 +190,7 @@ describe('DesignDataDrivenStylePrompt', () => {
property_name: 'value'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('Best Practices');
expect(text).toContain('DO:');
expect(text).toContain("DON'T:");
Expand All @@ -202,7 +203,7 @@ describe('DesignDataDrivenStylePrompt', () => {
property_name: 'value'
});

const text = result.messages[0].content.text;
const text = promptTextOf(result);
expect(text).toContain('style_builder_tool');
expect(text).toContain('create_style_tool');
});
Expand Down
Loading
Loading