Skip to content
Draft
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
Expand Up @@ -14,6 +14,10 @@
- `render_map_tool` is now titled **Mapbox Map** (was "Render Map"), since the title is what the tab and the entrypoint show. Hosts without the extension ignore the `openai/ui` metadata and `document.modelContext`.
- **Discovery:** the result of a `render_map_tool` call that opens a new map asks the model to call `read_map` once on the open map, if available, because the model only started using the map's tools after calling one. A `redraw_map` call relays only the first text of the result, so it is not asked again.
- **Base map points of interest:** a click on a point of interest of the Standard base map (a shop, a station, a statue) selects it like a pin, so "what is this?" works. The base map has no `mapbox_id` for it, so the model gets its name, category and coordinates.
- **Tileset explorer: `tileset_explorer_tool` (#335).** Previews a tileset's tiles on an interactive map (`ui://mapbox/tileset-explorer/index.html`). Takes a `tileset_id` (`username.id`; several vector tilesets joined with commas), reads its TileJSON and returns the source layers, their fields and the zoom range.
- **The map:** every source layer is drawn in its own color (polygons, lines and points) over a blank background, framed to the tileset's bounds and never below its minzoom. The panel switches layers, lists their fields, shows tile boundaries and adds a base map. A click shows a feature's layer, geometry, ID and properties. A raster tileset is one raster layer.
- **In ChatGPT:** opens as a tab next to the chat, and is a thread entrypoint (**Mapbox Tileset Explorer**) that starts on `mapbox.mapbox-streets-v8`. An open explorer shares the tileset, its hidden layers and the view as background context, and the selected feature as a titled item the user can remove. It registers two WebMCP tools: `load_tileset` (calls `tileset_explorer_tool` and shows the result in the open explorer) and `read_tileset_view` (features in view per source layer, with up to three samples each).
- **Tokens:** the TileJSON `tiles` URLs carry the caller's token, so only metadata is returned. The page loads tiles through `mapbox://` with the account's public token; without one it still lists layers and fields.
- Both tools use a square Mapbox mark icon, since the sidebar would otherwise fall back to the wide server logo. `BaseTool` now passes `icons` and typed `_meta` (`ui.visibility`, `openai/ui` entrypoints and quick actions) through to `registerTool`. Hosts without the extension ignore the `openai/ui` metadata.

## 0.17.1-dev.1 - 2026-11-09
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ export CLIENT_NEEDS_RESOURCE_FALLBACK=true

## Tools

### Tileset explorer (`tileset_explorer_tool`)

Previews a Mapbox tileset's tiles on an interactive map. Pass a `tileset_id` (`username.id`, for example `mapbox.mapbox-streets-v8`, `mapbox.satellite` or one of your own; join several vector tilesets with commas). The tool returns the tileset's source layers, their fields and its zoom range, and the map draws every source layer in its own color over a blank background. The panel switches layers on and off, lists each layer's fields, shows tile boundaries, and puts a light or dark base map underneath. Click a feature to see its properties. Raster tilesets are drawn as they are.

In ChatGPT the explorer opens as a tab next to the chat, and also from "+" → "More tools" → **Mapbox Tileset Explorer**, where it starts on Mapbox Streets. While open it shares the tileset, the view and the selected feature with the conversation, and in agent mode it registers two WebMCP tools, `load_tileset` and `read_tileset_view`, so the model switches tilesets and reads the features in view in the open explorer instead of opening another.

The server reads the TileJSON with the caller's token and passes on only its metadata, never the tile URLs, which carry that token. The map loads tiles with the account's public token, so a private tileset shows only for its own account.

### Utility Tools

#### Resource Reader Tool
Expand Down
3 changes: 3 additions & 0 deletions src/resources/resourceRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ToolResponseResource } from './tool-response/ToolResponseResource.js';
import { MapAppUIResource } from './ui-apps/MapAppUIResource.js';
import { StaticMapViewerUIResource } from './ui-apps/StaticMapViewerUIResource.js';
import { HomeUIResource } from './ui-apps/HomeUIResource.js';
import { TilesetExplorerUIResource } from './ui-apps/TilesetExplorerUIResource.js';
import { VersionResource } from './version/VersionResource.js';
import { PlacePhotoResource } from './place-photo/PlacePhotoResource.js';
import { httpRequest } from '../utils/httpPipeline.js';
Expand All @@ -28,6 +29,8 @@ export const ALL_RESOURCES = [
new StaticMapViewerUIResource(),
// ChatGPT sidebar home page, targeted by mapbox_home_tool.
new HomeUIResource({ httpRequest }),
// Tileset preview, targeted by tileset_explorer_tool.
new TilesetExplorerUIResource({ httpRequest }),
// Photos for the map app's results carousel, which its CSP can't load directly.
new PlacePhotoResource({ httpRequest }),
new VersionResource()
Expand Down
73 changes: 73 additions & 0 deletions src/resources/ui-apps/TilesetExplorerUIResource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) Mapbox, Inc.
// Licensed under the MIT License.

import type {
ReadResourceResult,
ServerContext
} from '@modelcontextprotocol/server';
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 { renderTilesetExplorerHtml } from './tilesetExplorerHtml.js';
import { mapAppCsp } from './mapAppCsp.js';

export const TILESET_EXPLORER_URI = 'ui://mapbox/tileset-explorer/index.html';

/** UI for `tileset_explorer_tool`. */
export class TilesetExplorerUIResource extends BaseResource {
readonly name = 'Mapbox Tileset Explorer UI';
readonly uri = TILESET_EXPLORER_URI;
readonly description =
"Interactive preview of a Mapbox tileset's tiles, source layers and features (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?: ServerContext): Promise<ReadResourceResult> {
const accessToken =
(extra?.http?.authInfo?.token as string | undefined) ||
process.env.MAPBOX_ACCESS_TOKEN ||
'';

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

return {
contents: [
{
uri: this.uri,
mimeType: RESOURCE_MIME_TYPE,
text: renderTilesetExplorerHtml({ publicToken: publicToken ?? '' }),
_meta: {
ui: {
csp: mapAppCsp(this.apiEndpoint()),
preferredSize: { width: 1000, height: 600 }
},
'openai/ui': {
preferredDisplayMode: 'fullscreen',
availableDisplayModes: ['inline', 'fullscreen']
}
}
}
]
};
}
}
Loading