Skip to content
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

### New Features

- **`ground_location_tool` now renders as a live Mapbox GL JS map** showing the reverse-geocoded origin marker + nearby POIs (numbered orange pins) + the isochrone polygons when present. Same dual-spec dispatch (MCP Apps + inline MCP-UI) and shared `renderGroundLocationAppHtml` template.
- **`map_matching_tool` now renders as a live Mapbox GL JS map** showing the raw GPS trace as a dashed orange line and the snapped matched route as a solid blue line on top. Same dual-spec dispatch (MCP Apps + inline MCP-UI) and shared `renderMapMatchingAppHtml` template.
- **`search_and_geocode_tool` and `category_search_tool` now render as a live Mapbox GL JS map** following the same dual-spec pattern as `directions_tool`/`isochrone_tool`/`optimization_tool`. Both tools share a single `SearchAppUIResource` (`ui://mapbox/search-app/index.html`) and one `renderSearchAppHtml` template that drops numbered pins on each result with name/category/address popups.
- **`optimization_tool` now renders as a live Mapbox GL JS map** following the same dual-spec pattern as `directions_tool`/`isochrone_tool`: MCP Apps via `_meta.ui.resourceUri` → `OptimizationAppUIResource`, plus an inline MCP-UI rawHtml block (gated by `ENABLE_MCP_UI`). One shared `renderOptimizationAppHtml` template renders the trip line with numbered markers (1, 2, 3, …) at each stop in the visit order.
Expand Down
2 changes: 2 additions & 0 deletions src/resources/resourceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IsochroneAppUIResource } from './ui-apps/IsochroneAppUIResource.js';
import { OptimizationAppUIResource } from './ui-apps/OptimizationAppUIResource.js';
import { SearchAppUIResource } from './ui-apps/SearchAppUIResource.js';
import { MapMatchingAppUIResource } from './ui-apps/MapMatchingAppUIResource.js';
import { GroundLocationAppUIResource } from './ui-apps/GroundLocationAppUIResource.js';
import { VersionResource } from './version/VersionResource.js';
import { httpRequest } from '../utils/httpPipeline.js';

Expand All @@ -24,6 +25,7 @@ export const ALL_RESOURCES = [
new OptimizationAppUIResource({ httpRequest }),
new SearchAppUIResource({ httpRequest }),
new MapMatchingAppUIResource({ httpRequest }),
new GroundLocationAppUIResource({ httpRequest }),
new VersionResource()
] as const;

Expand Down
79 changes: 79 additions & 0 deletions src/resources/ui-apps/GroundLocationAppUIResource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Mapbox, Inc.
// Licensed under the MIT License.

import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
import type {
ReadResourceResult,
ServerNotification,
ServerRequest
} from '@modelcontextprotocol/sdk/types.js';
import { RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
import { BaseResource } from '../BaseResource.js';
import type { HttpRequest } from '../../utils/types.js';
import { resolveMapboxPublicToken } from '../../utils/mapboxPublicToken.js';
import { renderGroundLocationAppHtml } from './groundLocationAppHtml.js';

export class GroundLocationAppUIResource extends BaseResource {
readonly name = 'Ground Location App UI';
readonly uri = 'ui://mapbox/ground-location-app/index.html';
readonly description =
'Interactive UI for visualizing a grounded location and nearby POIs (MCP Apps)';
readonly mimeType = RESOURCE_MIME_TYPE;

private readonly httpRequest: HttpRequest;
private readonly apiEndpoint: () => string;

constructor(params: {
httpRequest: HttpRequest;
apiEndpoint?: () => string;
}) {
super();
this.httpRequest = params.httpRequest;
this.apiEndpoint =
params.apiEndpoint ??
(() => process.env.MAPBOX_API_ENDPOINT || 'https://api.mapbox.com/');
}

async read(
_uri: string,
extra?: RequestHandlerExtra<ServerRequest, ServerNotification>
): Promise<ReadResourceResult> {
const accessToken =
(extra?.authInfo?.token as string | undefined) ||
process.env.MAPBOX_ACCESS_TOKEN ||
'';

const publicToken = await resolveMapboxPublicToken({
accessToken,
apiEndpoint: this.apiEndpoint(),
httpRequest: this.httpRequest
});

const html = renderGroundLocationAppHtml({
publicToken: publicToken ?? ''
});

return {
contents: [
{
uri: this.uri,
mimeType: RESOURCE_MIME_TYPE,
text: html,
_meta: {
ui: {
csp: {
connectDomains: [
'https://*.mapbox.com',
'https://events.mapbox.com'
],
resourceDomains: ['https://api.mapbox.com'],
workerDomains: ['blob:']
},
preferredSize: { width: 1000, height: 600 }
}
}
}
]
};
}
}
Loading