From b47f7ebf997ee624f9e5e0ab959e36cf5f86dc05 Mon Sep 17 00:00:00 2001 From: Jean-Luc Deziel Date: Wed, 18 Feb 2026 15:38:27 -0500 Subject: [PATCH 01/12] mcp server --- packages/itwin/map-layers-mcp/README.md | 59 ++++ packages/itwin/map-layers-mcp/dist/index.d.ts | 3 + packages/itwin/map-layers-mcp/dist/index.js | 9 + .../itwin/map-layers-mcp/dist/index.js.map | 1 + .../itwin/map-layers-mcp/dist/server.d.ts | 4 + packages/itwin/map-layers-mcp/dist/server.js | 172 ++++++++++ .../itwin/map-layers-mcp/dist/server.js.map | 1 + packages/itwin/map-layers-mcp/dist/tools.d.ts | 78 +++++ packages/itwin/map-layers-mcp/dist/tools.js | 215 +++++++++++++ .../itwin/map-layers-mcp/dist/tools.js.map | 1 + packages/itwin/map-layers-mcp/package.json | 19 ++ packages/itwin/map-layers-mcp/src/index.ts | 22 ++ packages/itwin/map-layers-mcp/src/server.ts | 247 ++++++++++++++ packages/itwin/map-layers-mcp/src/tools.ts | 301 ++++++++++++++++++ packages/itwin/map-layers-mcp/tsconfig.json | 15 + 15 files changed, 1147 insertions(+) create mode 100644 packages/itwin/map-layers-mcp/README.md create mode 100644 packages/itwin/map-layers-mcp/dist/index.d.ts create mode 100644 packages/itwin/map-layers-mcp/dist/index.js create mode 100644 packages/itwin/map-layers-mcp/dist/index.js.map create mode 100644 packages/itwin/map-layers-mcp/dist/server.d.ts create mode 100644 packages/itwin/map-layers-mcp/dist/server.js create mode 100644 packages/itwin/map-layers-mcp/dist/server.js.map create mode 100644 packages/itwin/map-layers-mcp/dist/tools.d.ts create mode 100644 packages/itwin/map-layers-mcp/dist/tools.js create mode 100644 packages/itwin/map-layers-mcp/dist/tools.js.map create mode 100644 packages/itwin/map-layers-mcp/package.json create mode 100644 packages/itwin/map-layers-mcp/src/index.ts create mode 100644 packages/itwin/map-layers-mcp/src/server.ts create mode 100644 packages/itwin/map-layers-mcp/src/tools.ts create mode 100644 packages/itwin/map-layers-mcp/tsconfig.json diff --git a/packages/itwin/map-layers-mcp/README.md b/packages/itwin/map-layers-mcp/README.md new file mode 100644 index 000000000..357945733 --- /dev/null +++ b/packages/itwin/map-layers-mcp/README.md @@ -0,0 +1,59 @@ +# @itwin/map-layers-mcp + +MCP (Model Context Protocol) server that exposes iTwin.js map-layer operations as tools. + +## Tools + +| Tool | Description | +|------|-------------| +| `open_map_layers_widget` | Opens the Map Layers widget panel in the frontstage UI | +| `toggle_background_map` | Toggles the background map on/off | +| `set_base_map_type` | Changes the base map (aerial, hybrid, street, or solid color) | +| `set_map_transparency` | Sets background map transparency (0–1) | +| `toggle_terrain` | Toggles terrain display on/off | +| `get_map_layer_info` | Returns info about all attached map layers | +| `attach_map_layer` | Attaches a new map layer by URL (WMS, WMTS, ArcGIS, TileURL) | +| `detach_map_layer` | Detaches a map layer by name | +| `set_map_layer_visibility` | Sets visibility of a specific map layer | + +## Usage + +### As a stdio MCP server + +```bash +npm run build +node dist/server.js +``` + +Configure in your MCP client (e.g. VS Code `mcp.json`, Claude Desktop `claude_desktop_config.json`): + +```json +{ + "mcpServers": { + "map-layers": { + "command": "node", + "args": ["path/to/packages/itwin/map-layers-mcp/dist/server.js"] + } + } +} +``` + +### In-process (inside an iTwin.js app) + +```typescript +import { setViewportAccessor } from "@itwin/map-layers-mcp"; +import { IModelApp } from "@itwin/core-frontend"; + +// Provide the viewport accessor so tool functions can operate on the live viewport +setViewportAccessor(() => IModelApp.viewManager.selectedView); +``` + +## Architecture + +The server uses the **MCP SDK** (`@modelcontextprotocol/sdk`) with a **stdio transport**. + +- **`src/tools.ts`** — Pure functions that operate on an iTwin.js `ScreenViewport`. They use `any` types to avoid hard dependencies on `@itwin/core-frontend` / `@itwin/core-common` — the host app provides these at runtime. +- **`src/server.ts`** — MCP server that registers all 9 tools with zod schemas and wires them to the tool functions. +- **`src/index.ts`** — Public API barrel for in-process usage. + +When running standalone (stdio), the tool functions that need a viewport will return structured JSON payloads describing the intended action. The host iTwin.js application should use `setViewportAccessor()` to provide real viewport access. diff --git a/packages/itwin/map-layers-mcp/dist/index.d.ts b/packages/itwin/map-layers-mcp/dist/index.d.ts new file mode 100644 index 000000000..0a656a8c8 --- /dev/null +++ b/packages/itwin/map-layers-mcp/dist/index.d.ts @@ -0,0 +1,3 @@ +export { openMapLayersWidget, toggleBackgroundMap, setBaseMapType, setMapTransparency, toggleTerrain, getMapLayerInfo, attachMapLayer, detachMapLayer, setMapLayerVisibility, } from "./tools.js"; +export type { MapLayerInfo } from "./tools.js"; +export { setViewportAccessor } from "./server.js"; diff --git a/packages/itwin/map-layers-mcp/dist/index.js b/packages/itwin/map-layers-mcp/dist/index.js new file mode 100644 index 000000000..810b4f12f --- /dev/null +++ b/packages/itwin/map-layers-mcp/dist/index.js @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * @itwin/map-layers-mcp + * + * Public API surface – re-exports the tool functions (for in-process use) + * and the viewport accessor setter. + *--------------------------------------------------------------------------------------------*/ +export { openMapLayersWidget, toggleBackgroundMap, setBaseMapType, setMapTransparency, toggleTerrain, getMapLayerInfo, attachMapLayer, detachMapLayer, setMapLayerVisibility, } from "./tools.js"; +export { setViewportAccessor } from "./server.js"; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/itwin/map-layers-mcp/dist/index.js.map b/packages/itwin/map-layers-mcp/dist/index.js.map new file mode 100644 index 000000000..52114b365 --- /dev/null +++ b/packages/itwin/map-layers-mcp/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;gGAKgG;AAEhG,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,cAAc,EACd,cAAc,EACd,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"} \ No newline at end of file diff --git a/packages/itwin/map-layers-mcp/dist/server.d.ts b/packages/itwin/map-layers-mcp/dist/server.d.ts new file mode 100644 index 000000000..b6eb87c54 --- /dev/null +++ b/packages/itwin/map-layers-mcp/dist/server.d.ts @@ -0,0 +1,4 @@ +type ViewportAccessor = () => any; +/** Set the viewport accessor (for in-process usage). */ +export declare function setViewportAccessor(fn: ViewportAccessor): void; +export {}; diff --git a/packages/itwin/map-layers-mcp/dist/server.js b/packages/itwin/map-layers-mcp/dist/server.js new file mode 100644 index 000000000..a6d057f0a --- /dev/null +++ b/packages/itwin/map-layers-mcp/dist/server.js @@ -0,0 +1,172 @@ +/*--------------------------------------------------------------------------------------------- + * Map Layers MCP Server + * + * Exposes iTwin.js map-layer operations as MCP tools over stdio transport. + * The server communicates with a host iTwin.js application that provides + * viewport access — the host is expected to inject or relay viewport calls. + * + * Usage: + * node dist/server.js + * + * In practice the host application registers this as an MCP server (stdio) + * and implements the viewport resolution on its side. + *--------------------------------------------------------------------------------------------*/ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; +let _getViewport = () => undefined; +/** Set the viewport accessor (for in-process usage). */ +export function setViewportAccessor(fn) { + _getViewport = fn; +} +// --------------------------------------------------------------------------- +// Server +// --------------------------------------------------------------------------- +const server = new McpServer({ + name: "map-layers-mcp", + version: "1.0.0", +}); +// ── 1. open_map_layers_widget ──────────────────────────────────────────────── +server.tool("open_map_layers_widget", "Opens the Map Layers widget panel in the iTwin.js frontstage UI so the user can manage map layers visually.", {}, async () => { + try { + const { openMapLayersWidget } = await import("./tools.js"); + const msg = await openMapLayersWidget(); + return { content: [{ type: "text", text: msg }] }; + } + catch (e) { + return { content: [{ type: "text", text: JSON.stringify({ action: "open_map_layers_widget" }) }] }; + } +}); +// ── 2. toggle_background_map ───────────────────────────────────────────────── +server.tool("toggle_background_map", "Toggles the background map on or off. If 'enabled' is provided, sets it to that state; otherwise toggles.", { + enabled: z.boolean().optional().describe("If provided, force the background map to this state (true = on, false = off). Omit to toggle."), +}, async ({ enabled }) => { + try { + const { toggleBackgroundMap } = await import("./tools.js"); + const result = toggleBackgroundMap(_getViewport(), enabled); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + catch (e) { + return { content: [{ type: "text", text: JSON.stringify({ action: "toggle_background_map", enabled }) }] }; + } +}); +// ── 3. set_base_map_type ───────────────────────────────────────────────────── +server.tool("set_base_map_type", "Changes the base map to one of the well-known types: aerial, hybrid, street, or a solid color fill.", { + type: z.enum(["aerial", "hybrid", "street", "color"]).describe("The base map type to set."), + colorTbgr: z.number().optional().describe("TBGR color integer when type is 'color'. Ignored otherwise."), +}, async ({ type, colorTbgr }) => { + try { + const { setBaseMapType } = await import("./tools.js"); + const result = await setBaseMapType(_getViewport(), type, colorTbgr); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + catch (e) { + return { content: [{ type: "text", text: JSON.stringify({ action: "set_base_map_type", type, colorTbgr }) }] }; + } +}); +// ── 4. set_map_transparency ────────────────────────────────────────────────── +server.tool("set_map_transparency", "Sets the transparency of the background map. 0 = fully opaque, 1 = fully transparent.", { + transparency: z.number().min(0).max(1).describe("Transparency value from 0.0 (opaque) to 1.0 (transparent)."), +}, async ({ transparency }) => { + try { + const { setMapTransparency } = await import("./tools.js"); + const result = setMapTransparency(_getViewport(), transparency); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + catch (e) { + return { content: [{ type: "text", text: JSON.stringify({ action: "set_map_transparency", transparency }) }] }; + } +}); +// ── 5. toggle_terrain ──────────────────────────────────────────────────────── +server.tool("toggle_terrain", "Toggles terrain display on or off. If 'enabled' is provided, sets it to that state; otherwise toggles.", { + enabled: z.boolean().optional().describe("If provided, force terrain to this state (true = on, false = off). Omit to toggle."), +}, async ({ enabled }) => { + try { + const { toggleTerrain } = await import("./tools.js"); + const result = toggleTerrain(_getViewport(), enabled); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + catch (e) { + return { content: [{ type: "text", text: JSON.stringify({ action: "toggle_terrain", enabled }) }] }; + } +}); +// ── 6. get_map_layer_info ──────────────────────────────────────────────────── +server.tool("get_map_layer_info", "Returns detailed information about all attached map layers (background and overlay) and whether the background map is enabled.", {}, async () => { + try { + const { getMapLayerInfo } = await import("./tools.js"); + const result = getMapLayerInfo(_getViewport()); + return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; + } + catch (e) { + return { content: [{ type: "text", text: JSON.stringify({ action: "get_map_layer_info" }) }] }; + } +}); +// ── 7. attach_map_layer ────────────────────────────────────────────────────── +server.tool("attach_map_layer", "Attaches a new map layer to the viewport by URL. Supports WMS, WMTS, ArcGIS, ArcGISFeature, and TileURL formats.", { + url: z.string().describe("The map layer service URL."), + name: z.string().describe("Display name for the layer."), + formatId: z + .enum(["WMS", "WMTS", "ArcGIS", "ArcGISFeature", "TileURL"]) + .optional() + .describe("The map service format. Defaults to 'WMS'."), + isOverlay: z.boolean().optional().describe("If true, attach as overlay; otherwise as background layer. Default: false."), + userName: z.string().optional().describe("Optional username for authenticated layers."), + password: z.string().optional().describe("Optional password for authenticated layers."), +}, async ({ url, name, formatId, isOverlay, userName, password }) => { + try { + const { attachMapLayer } = await import("./tools.js"); + const result = await attachMapLayer(_getViewport(), url, name, formatId, isOverlay, userName, password); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + catch (e) { + return { + content: [{ type: "text", text: JSON.stringify({ action: "attach_map_layer", url, name, formatId, isOverlay, error: e.message }) }], + }; + } +}); +// ── 8. detach_map_layer ────────────────────────────────────────────────────── +server.tool("detach_map_layer", "Detaches (removes) a map layer by name. If multiple layers share the same name, all are removed.", { + name: z.string().describe("The name of the map layer to detach."), + isOverlay: z.boolean().optional().describe("If specified, only detach from overlay or background layers. Omit to search both."), +}, async ({ name, isOverlay }) => { + try { + const { detachMapLayer } = await import("./tools.js"); + const result = detachMapLayer(_getViewport(), name, isOverlay); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + catch (e) { + return { + content: [{ type: "text", text: JSON.stringify({ action: "detach_map_layer", name, isOverlay, error: e.message }) }], + }; + } +}); +// ── 9. set_map_layer_visibility ────────────────────────────────────────────── +server.tool("set_map_layer_visibility", "Sets the visibility of a specific map layer identified by name.", { + name: z.string().describe("The name of the map layer to update."), + visible: z.boolean().describe("Whether the layer should be visible."), + isOverlay: z.boolean().optional().describe("If specified, only search overlay or background layers. Omit to search both."), +}, async ({ name, visible, isOverlay }) => { + try { + const { setMapLayerVisibility } = await import("./tools.js"); + const result = setMapLayerVisibility(_getViewport(), name, visible, isOverlay); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + catch (e) { + return { + content: [{ type: "text", text: JSON.stringify({ action: "set_map_layer_visibility", name, visible, isOverlay, error: e.message }) }], + }; + } +}); +// --------------------------------------------------------------------------- +// Start +// --------------------------------------------------------------------------- +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("Map Layers MCP server running on stdio"); +} +main().catch((err) => { + console.error("Fatal error starting MCP server:", err); + process.exit(1); +}); +//# sourceMappingURL=server.js.map \ No newline at end of file diff --git a/packages/itwin/map-layers-mcp/dist/server.js.map b/packages/itwin/map-layers-mcp/dist/server.js.map new file mode 100644 index 000000000..8588ec982 --- /dev/null +++ b/packages/itwin/map-layers-mcp/dist/server.js.map @@ -0,0 +1 @@ +{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/itwin/map-layers-mcp/dist/tools.d.ts b/packages/itwin/map-layers-mcp/dist/tools.d.ts new file mode 100644 index 000000000..a0256d671 --- /dev/null +++ b/packages/itwin/map-layers-mcp/dist/tools.d.ts @@ -0,0 +1,78 @@ +export interface MapLayerInfo { + name: string; + source: string; + visible: boolean; + transparency: number; + isOverlay: boolean; + layerIndex: number; + subLayers?: any[]; +} +/** + * Opens (activates) the Map Layers widget in the frontstage. + * Must run in an iTwin.js environment where `@itwin/appui-react` is loaded. + */ +export declare function openMapLayersWidget(): Promise; +/** + * Toggles background map visibility on/off, or sets it to a specific state. + */ +export declare function toggleBackgroundMap(vp: any, enabled?: boolean): { + backgroundMapEnabled: boolean; +}; +/** + * Sets the base map to one of the well-known Bing providers: + * "aerial" | "hybrid" | "street" + * Or to a solid color fill when type is "color" + optional colorDef (TBGR integer). + */ +export declare function setBaseMapType(vp: any, type: "aerial" | "hybrid" | "street" | "color", colorTbgr?: number): Promise<{ + baseMap: string; +}>; +/** + * Sets the background map transparency (0 = fully opaque, 1 = fully transparent). + */ +export declare function setMapTransparency(vp: any, transparency: number): { + transparency: number; +}; +/** + * Toggles terrain display on/off, or sets it to a specific state. + */ +export declare function toggleTerrain(vp: any, enabled?: boolean): { + terrainEnabled: boolean; +}; +/** + * Returns information about all attached map layers (both background and overlay). + */ +export declare function getMapLayerInfo(vp: any): { + backgroundLayers: MapLayerInfo[]; + overlayLayers: MapLayerInfo[]; + backgroundMapEnabled: boolean; +}; +/** + * Attaches a new map layer to the viewport by URL. + * + * @param url The layer service URL (WMS, WMTS, ArcGIS, TileURL, etc.) + * @param name Display name for the layer + * @param formatId Format identifier: "WMS", "WMTS", "ArcGIS", "ArcGISFeature", "TileURL" + * @param isOverlay If true the layer is added as an overlay; otherwise as a background layer + * @param userName Optional credentials + * @param password Optional credentials + */ +export declare function attachMapLayer(vp: any, url: string, name: string, formatId?: string, isOverlay?: boolean, userName?: string, password?: string): Promise<{ + attached: boolean; + name: string; + isOverlay: boolean; +}>; +/** + * Detaches a map layer by name (and optionally by overlay flag). + * If multiple layers match, all are detached. + */ +export declare function detachMapLayer(vp: any, name: string, isOverlay?: boolean): { + detached: string[]; +}; +/** + * Sets the visibility of a specific map layer identified by name. + */ +export declare function setMapLayerVisibility(vp: any, name: string, visible: boolean, isOverlay?: boolean): { + name: string; + visible: boolean; + updated: number; +}; diff --git a/packages/itwin/map-layers-mcp/dist/tools.js b/packages/itwin/map-layers-mcp/dist/tools.js new file mode 100644 index 000000000..976b0d1ed --- /dev/null +++ b/packages/itwin/map-layers-mcp/dist/tools.js @@ -0,0 +1,215 @@ +/*--------------------------------------------------------------------------------------------- + * Map Layers MCP Tool Functions + * + * These functions operate on an iTwin.js ScreenViewport. They are designed to + * be called from within a running iTwin.js application where `IModelApp` is + * initialized and a viewport is available. + * + * The functions accept a generic viewport reference (typed as `any` to avoid + * hard dependency on @itwin/core-frontend in the MCP server package). The host + * application is responsible for providing the correctly-typed ScreenViewport. + *--------------------------------------------------------------------------------------------*/ +// ── Helpers ────────────────────────────────────────────────────────────────── +function getViewport(vp) { + if (!vp) { + throw new Error("No active viewport available."); + } + return vp; +} +function gatherLayers(vp, isOverlay) { + const layers = isOverlay + ? vp.displayStyle.settings.mapImagery.overlayLayers + : vp.displayStyle.settings.mapImagery.backgroundLayers; + return layers.map((layer, idx) => ({ + name: layer.name, + source: layer.source ?? layer.modelId ?? "", + visible: layer.visible, + transparency: layer.transparency, + isOverlay, + layerIndex: idx, + subLayers: layer.subLayers?.map((s) => (typeof s.toJSON === "function" ? s.toJSON() : s)) ?? undefined, + })); +} +// ── 1. open_map_layers_widget ──────────────────────────────────────────────── +/** + * Opens (activates) the Map Layers widget in the frontstage. + * Must run in an iTwin.js environment where `@itwin/appui-react` is loaded. + */ +export async function openMapLayersWidget() { + // Dynamic import so this module is only required at runtime inside iTwin.js + const { UiFramework } = await import("@itwin/appui-react"); + const widgetId = "map-layers:mapLayersWidget"; + UiFramework.frontstages.activeFrontstageDef?.findWidgetDef(widgetId)?.setWidgetState(1 /* WidgetState.Open */); + return `Map Layers widget opened (id: ${widgetId}).`; +} +// ── 2. toggle_background_map ───────────────────────────────────────────────── +/** + * Toggles background map visibility on/off, or sets it to a specific state. + */ +export function toggleBackgroundMap(vp, enabled) { + const viewport = getViewport(vp); + const newState = enabled ?? !viewport.viewFlags.backgroundMap; + viewport.viewFlags = viewport.viewFlags.with("backgroundMap", newState); + return { backgroundMapEnabled: newState }; +} +// ── 3. set_base_map_type ───────────────────────────────────────────────────── +/** + * Sets the base map to one of the well-known Bing providers: + * "aerial" | "hybrid" | "street" + * Or to a solid color fill when type is "color" + optional colorDef (TBGR integer). + */ +export async function setBaseMapType(vp, type, colorTbgr) { + const coreCommon = await import("@itwin/core-common"); + const { BackgroundMapProvider, BackgroundMapType, BaseLayerSettings, ColorDef } = coreCommon; + const viewport = getViewport(vp); + if (type === "color") { + const color = colorTbgr !== undefined ? ColorDef.fromJSON(colorTbgr) : ColorDef.fromJSON(0); + viewport.displayStyle.backgroundMapBase = color; + return { baseMap: `color (TBGR: ${color.toJSON()})` }; + } + const bgTypeMap = { + aerial: BackgroundMapType.Aerial, + hybrid: BackgroundMapType.Hybrid, + street: BackgroundMapType.Street, + }; + const bgType = bgTypeMap[type] ?? BackgroundMapType.Hybrid; + const provider = BackgroundMapProvider.fromJSON({ name: "BingProvider", type: bgType }); + const settings = BaseLayerSettings.fromProvider(provider); + viewport.displayStyle.backgroundMapBase = settings; + return { baseMap: type }; +} +// ── 4. set_map_transparency ────────────────────────────────────────────────── +/** + * Sets the background map transparency (0 = fully opaque, 1 = fully transparent). + */ +export function setMapTransparency(vp, transparency) { + const viewport = getViewport(vp); + const clamped = Math.max(0, Math.min(1, transparency)); + viewport.changeBackgroundMapProps({ transparency: clamped }); + return { transparency: clamped }; +} +// ── 5. toggle_terrain ──────────────────────────────────────────────────────── +/** + * Toggles terrain display on/off, or sets it to a specific state. + */ +export function toggleTerrain(vp, enabled) { + const viewport = getViewport(vp); + const currentlyEnabled = viewport.view?.getDisplayStyle3d?.()?.settings?.backgroundMap?.applyTerrain ?? false; + const newState = enabled ?? !currentlyEnabled; + viewport.changeBackgroundMapProps({ applyTerrain: newState }); + return { terrainEnabled: newState }; +} +// ── 6. get_map_layer_info ──────────────────────────────────────────────────── +/** + * Returns information about all attached map layers (both background and overlay). + */ +export function getMapLayerInfo(vp) { + const viewport = getViewport(vp); + const backgroundLayers = gatherLayers(viewport, false); + const overlayLayers = gatherLayers(viewport, true); + return { + backgroundLayers, + overlayLayers, + backgroundMapEnabled: viewport.viewFlags.backgroundMap, + }; +} +// ── 7. attach_map_layer ────────────────────────────────────────────────────── +/** + * Attaches a new map layer to the viewport by URL. + * + * @param url The layer service URL (WMS, WMTS, ArcGIS, TileURL, etc.) + * @param name Display name for the layer + * @param formatId Format identifier: "WMS", "WMTS", "ArcGIS", "ArcGISFeature", "TileURL" + * @param isOverlay If true the layer is added as an overlay; otherwise as a background layer + * @param userName Optional credentials + * @param password Optional credentials + */ +export async function attachMapLayer(vp, url, name, formatId, isOverlay, userName, password) { + const { MapLayerSource } = await import("@itwin/core-frontend"); + const viewport = getViewport(vp); + const overlay = isOverlay ?? false; + const source = MapLayerSource.fromJSON({ + url, + name, + formatId: formatId ?? "WMS", + }); + if (!source) { + throw new Error(`Failed to create map layer source from URL: ${url}`); + } + if (userName) + source.userName = userName; + if (password) + source.password = password; + const settings = source.toLayerSettings(); + if (!settings) { + throw new Error(`Failed to create layer settings for source: ${name}`); + } + viewport.displayStyle.attachMapLayer({ + settings, + mapLayerIndex: { index: -1, isOverlay: overlay }, + }); + return { attached: true, name, isOverlay: overlay }; +} +// ── 8. detach_map_layer ────────────────────────────────────────────────────── +/** + * Detaches a map layer by name (and optionally by overlay flag). + * If multiple layers match, all are detached. + */ +export function detachMapLayer(vp, name, isOverlay) { + const viewport = getViewport(vp); + const detached = []; + const tryDetach = (overlay) => { + const layers = overlay + ? viewport.displayStyle.settings.mapImagery.overlayLayers + : viewport.displayStyle.settings.mapImagery.backgroundLayers; + // Iterate in reverse so index removal is safe + for (let i = layers.length - 1; i >= 0; i--) { + if (layers[i].name === name) { + viewport.displayStyle.detachMapLayerByIndex({ index: i, isOverlay: overlay }); + detached.push(`${name} (${overlay ? "overlay" : "background"}, index ${i})`); + } + } + }; + if (isOverlay === undefined) { + tryDetach(false); + tryDetach(true); + } + else { + tryDetach(isOverlay); + } + if (detached.length === 0) { + throw new Error(`No map layer found with name "${name}".`); + } + return { detached }; +} +// ── 9. set_map_layer_visibility ────────────────────────────────────────────── +/** + * Sets the visibility of a specific map layer identified by name. + */ +export function setMapLayerVisibility(vp, name, visible, isOverlay) { + const viewport = getViewport(vp); + let updated = 0; + const trySetVisibility = (overlay) => { + const layers = overlay + ? viewport.displayStyle.settings.mapImagery.overlayLayers + : viewport.displayStyle.settings.mapImagery.backgroundLayers; + for (let i = 0; i < layers.length; i++) { + if (layers[i].name === name) { + viewport.displayStyle.changeMapLayerProps({ visible }, { index: i, isOverlay: overlay }); + updated++; + } + } + }; + if (isOverlay === undefined) { + trySetVisibility(false); + trySetVisibility(true); + } + else { + trySetVisibility(isOverlay); + } + if (updated === 0) { + throw new Error(`No map layer found with name "${name}".`); + } + return { name, visible, updated }; +} +//# sourceMappingURL=tools.js.map \ No newline at end of file diff --git a/packages/itwin/map-layers-mcp/dist/tools.js.map b/packages/itwin/map-layers-mcp/dist/tools.js.map new file mode 100644 index 000000000..1899b473b --- /dev/null +++ b/packages/itwin/map-layers-mcp/dist/tools.js.map @@ -0,0 +1 @@ +{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/itwin/map-layers-mcp/package.json b/packages/itwin/map-layers-mcp/package.json new file mode 100644 index 000000000..fb15ae491 --- /dev/null +++ b/packages/itwin/map-layers-mcp/package.json @@ -0,0 +1,19 @@ +{ + "name": "@itwin/map-layers-mcp", + "version": "1.0.0", + "description": "MCP server exposing map layer tools for iTwin.js viewer", + "main": "dist/server.js", + "type": "module", + "scripts": { + "build": "tsc", + "start": "node dist/server.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.1", + "zod": "^3.24.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "~5.6.0" + } +} diff --git a/packages/itwin/map-layers-mcp/src/index.ts b/packages/itwin/map-layers-mcp/src/index.ts new file mode 100644 index 000000000..6a66d0d59 --- /dev/null +++ b/packages/itwin/map-layers-mcp/src/index.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * @itwin/map-layers-mcp + * + * Public API surface – re-exports the tool functions (for in-process use) + * and the viewport accessor setter. + *--------------------------------------------------------------------------------------------*/ + +export { + openMapLayersWidget, + toggleBackgroundMap, + setBaseMapType, + setMapTransparency, + toggleTerrain, + getMapLayerInfo, + attachMapLayer, + detachMapLayer, + setMapLayerVisibility, +} from "./tools.js"; + +export type { MapLayerInfo } from "./tools.js"; + +export { setViewportAccessor } from "./server.js"; diff --git a/packages/itwin/map-layers-mcp/src/server.ts b/packages/itwin/map-layers-mcp/src/server.ts new file mode 100644 index 000000000..82a366fc1 --- /dev/null +++ b/packages/itwin/map-layers-mcp/src/server.ts @@ -0,0 +1,247 @@ +/*--------------------------------------------------------------------------------------------- + * Map Layers MCP Server + * + * Exposes iTwin.js map-layer operations as MCP tools over stdio transport. + * The server communicates with a host iTwin.js application that provides + * viewport access — the host is expected to inject or relay viewport calls. + * + * Usage: + * node dist/server.js + * + * In practice the host application registers this as an MCP server (stdio) + * and implements the viewport resolution on its side. + *--------------------------------------------------------------------------------------------*/ + +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; + +// --------------------------------------------------------------------------- +// Viewport accessor +// --------------------------------------------------------------------------- +// The MCP server runs in its own process and cannot directly access the +// iTwin.js ScreenViewport. The host application must provide a viewport +// accessor. For stdio-based deployments the tools encode the *intent* and +// the host application applies them. When running **in-process** (e.g. via +// an adapter) the host can set this accessor before starting the server. +// +// For the stdio transport case, each tool simply returns a structured JSON +// payload that the host can interpret and apply to its viewport. +// --------------------------------------------------------------------------- + +type ViewportAccessor = () => /* ScreenViewport | undefined */ any; +let _getViewport: ViewportAccessor = () => undefined; + +/** Set the viewport accessor (for in-process usage). */ +export function setViewportAccessor(fn: ViewportAccessor) { + _getViewport = fn; +} + +// --------------------------------------------------------------------------- +// Server +// --------------------------------------------------------------------------- + +const server = new McpServer({ + name: "map-layers-mcp", + version: "1.0.0", +}); + +// ── 1. open_map_layers_widget ──────────────────────────────────────────────── + +server.tool( + "open_map_layers_widget", + "Opens the Map Layers widget panel in the iTwin.js frontstage UI so the user can manage map layers visually.", + {}, + async () => { + try { + const { openMapLayersWidget } = await import("./tools.js"); + const msg = await openMapLayersWidget(); + return { content: [{ type: "text", text: msg }] }; + } catch (e: any) { + return { content: [{ type: "text", text: JSON.stringify({ action: "open_map_layers_widget" }) }] }; + } + }, +); + +// ── 2. toggle_background_map ───────────────────────────────────────────────── + +server.tool( + "toggle_background_map", + "Toggles the background map on or off. If 'enabled' is provided, sets it to that state; otherwise toggles.", + { + enabled: z.boolean().optional().describe("If provided, force the background map to this state (true = on, false = off). Omit to toggle."), + }, + async ({ enabled }) => { + try { + const { toggleBackgroundMap } = await import("./tools.js"); + const result = toggleBackgroundMap(_getViewport(), enabled); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } catch (e: any) { + return { content: [{ type: "text", text: JSON.stringify({ action: "toggle_background_map", enabled }) }] }; + } + }, +); + +// ── 3. set_base_map_type ───────────────────────────────────────────────────── + +server.tool( + "set_base_map_type", + "Changes the base map to one of the well-known types: aerial, hybrid, street, or a solid color fill.", + { + type: z.enum(["aerial", "hybrid", "street", "color"]).describe("The base map type to set."), + colorTbgr: z.number().optional().describe("TBGR color integer when type is 'color'. Ignored otherwise."), + }, + async ({ type, colorTbgr }) => { + try { + const { setBaseMapType } = await import("./tools.js"); + const result = await setBaseMapType(_getViewport(), type, colorTbgr); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } catch (e: any) { + return { content: [{ type: "text", text: JSON.stringify({ action: "set_base_map_type", type, colorTbgr }) }] }; + } + }, +); + +// ── 4. set_map_transparency ────────────────────────────────────────────────── + +server.tool( + "set_map_transparency", + "Sets the transparency of the background map. 0 = fully opaque, 1 = fully transparent.", + { + transparency: z.number().min(0).max(1).describe("Transparency value from 0.0 (opaque) to 1.0 (transparent)."), + }, + async ({ transparency }) => { + try { + const { setMapTransparency } = await import("./tools.js"); + const result = setMapTransparency(_getViewport(), transparency); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } catch (e: any) { + return { content: [{ type: "text", text: JSON.stringify({ action: "set_map_transparency", transparency }) }] }; + } + }, +); + +// ── 5. toggle_terrain ──────────────────────────────────────────────────────── + +server.tool( + "toggle_terrain", + "Toggles terrain display on or off. If 'enabled' is provided, sets it to that state; otherwise toggles.", + { + enabled: z.boolean().optional().describe("If provided, force terrain to this state (true = on, false = off). Omit to toggle."), + }, + async ({ enabled }) => { + try { + const { toggleTerrain } = await import("./tools.js"); + const result = toggleTerrain(_getViewport(), enabled); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } catch (e: any) { + return { content: [{ type: "text", text: JSON.stringify({ action: "toggle_terrain", enabled }) }] }; + } + }, +); + +// ── 6. get_map_layer_info ──────────────────────────────────────────────────── + +server.tool( + "get_map_layer_info", + "Returns detailed information about all attached map layers (background and overlay) and whether the background map is enabled.", + {}, + async () => { + try { + const { getMapLayerInfo } = await import("./tools.js"); + const result = getMapLayerInfo(_getViewport()); + return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; + } catch (e: any) { + return { content: [{ type: "text", text: JSON.stringify({ action: "get_map_layer_info" }) }] }; + } + }, +); + +// ── 7. attach_map_layer ────────────────────────────────────────────────────── + +server.tool( + "attach_map_layer", + "Attaches a new map layer to the viewport by URL. Supports WMS, WMTS, ArcGIS, ArcGISFeature, and TileURL formats.", + { + url: z.string().describe("The map layer service URL."), + name: z.string().describe("Display name for the layer."), + formatId: z + .enum(["WMS", "WMTS", "ArcGIS", "ArcGISFeature", "TileURL"]) + .optional() + .describe("The map service format. Defaults to 'WMS'."), + isOverlay: z.boolean().optional().describe("If true, attach as overlay; otherwise as background layer. Default: false."), + userName: z.string().optional().describe("Optional username for authenticated layers."), + password: z.string().optional().describe("Optional password for authenticated layers."), + }, + async ({ url, name, formatId, isOverlay, userName, password }) => { + try { + const { attachMapLayer } = await import("./tools.js"); + const result = await attachMapLayer(_getViewport(), url, name, formatId, isOverlay, userName, password); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } catch (e: any) { + return { + content: [{ type: "text", text: JSON.stringify({ action: "attach_map_layer", url, name, formatId, isOverlay, error: e.message }) }], + }; + } + }, +); + +// ── 8. detach_map_layer ────────────────────────────────────────────────────── + +server.tool( + "detach_map_layer", + "Detaches (removes) a map layer by name. If multiple layers share the same name, all are removed.", + { + name: z.string().describe("The name of the map layer to detach."), + isOverlay: z.boolean().optional().describe("If specified, only detach from overlay or background layers. Omit to search both."), + }, + async ({ name, isOverlay }) => { + try { + const { detachMapLayer } = await import("./tools.js"); + const result = detachMapLayer(_getViewport(), name, isOverlay); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } catch (e: any) { + return { + content: [{ type: "text", text: JSON.stringify({ action: "detach_map_layer", name, isOverlay, error: e.message }) }], + }; + } + }, +); + +// ── 9. set_map_layer_visibility ────────────────────────────────────────────── + +server.tool( + "set_map_layer_visibility", + "Sets the visibility of a specific map layer identified by name.", + { + name: z.string().describe("The name of the map layer to update."), + visible: z.boolean().describe("Whether the layer should be visible."), + isOverlay: z.boolean().optional().describe("If specified, only search overlay or background layers. Omit to search both."), + }, + async ({ name, visible, isOverlay }) => { + try { + const { setMapLayerVisibility } = await import("./tools.js"); + const result = setMapLayerVisibility(_getViewport(), name, visible, isOverlay); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } catch (e: any) { + return { + content: [{ type: "text", text: JSON.stringify({ action: "set_map_layer_visibility", name, visible, isOverlay, error: e.message }) }], + }; + } + }, +); + +// --------------------------------------------------------------------------- +// Start +// --------------------------------------------------------------------------- + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error("Map Layers MCP server running on stdio"); +} + +main().catch((err) => { + console.error("Fatal error starting MCP server:", err); + process.exit(1); +}); diff --git a/packages/itwin/map-layers-mcp/src/tools.ts b/packages/itwin/map-layers-mcp/src/tools.ts new file mode 100644 index 000000000..f36e01ef6 --- /dev/null +++ b/packages/itwin/map-layers-mcp/src/tools.ts @@ -0,0 +1,301 @@ +/*--------------------------------------------------------------------------------------------- + * Map Layers MCP Tool Functions + * + * These functions operate on an iTwin.js ScreenViewport. They are designed to + * be called from within a running iTwin.js application where `IModelApp` is + * initialized and a viewport is available. + * + * The functions accept a generic viewport reference (typed as `any` to avoid + * hard dependency on @itwin/core-frontend in the MCP server package). The host + * application is responsible for providing the correctly-typed ScreenViewport. + *--------------------------------------------------------------------------------------------*/ + +// We intentionally use `any` for viewport types so the MCP server package +// does not require @itwin/core-frontend or @itwin/core-common as dependencies. +// The host iTwin.js application that wires up the viewport accessor will have +// these types available at runtime. + +// ── Types ──────────────────────────────────────────────────────────────────── + +export interface MapLayerInfo { + name: string; + source: string; + visible: boolean; + transparency: number; + isOverlay: boolean; + layerIndex: number; + subLayers?: any[]; +} + +// ── Helpers ────────────────────────────────────────────────────────────────── + +function getViewport(vp: any): any { + if (!vp) { + throw new Error("No active viewport available."); + } + return vp; +} + +function gatherLayers(vp: any, isOverlay: boolean): MapLayerInfo[] { + const layers = isOverlay + ? vp.displayStyle.settings.mapImagery.overlayLayers + : vp.displayStyle.settings.mapImagery.backgroundLayers; + + return layers.map((layer: any, idx: number) => ({ + name: layer.name, + source: layer.source ?? layer.modelId ?? "", + visible: layer.visible, + transparency: layer.transparency, + isOverlay, + layerIndex: idx, + subLayers: layer.subLayers?.map((s: any) => (typeof s.toJSON === "function" ? s.toJSON() : s)) ?? undefined, + })); +} + +// ── 1. open_map_layers_widget ──────────────────────────────────────────────── + +/** + * Opens (activates) the Map Layers widget in the frontstage. + * Must run in an iTwin.js environment where `@itwin/appui-react` is loaded. + */ +export async function openMapLayersWidget(): Promise { + // Dynamic import so this module is only required at runtime inside iTwin.js + const { UiFramework } = await import("@itwin/appui-react" as string); + const widgetId = "map-layers:mapLayersWidget"; + UiFramework.frontstages.activeFrontstageDef?.findWidgetDef(widgetId)?.setWidgetState(1 /* WidgetState.Open */); + return `Map Layers widget opened (id: ${widgetId}).`; +} + +// ── 2. toggle_background_map ───────────────────────────────────────────────── + +/** + * Toggles background map visibility on/off, or sets it to a specific state. + */ +export function toggleBackgroundMap( + vp: any, + enabled?: boolean, +): { backgroundMapEnabled: boolean } { + const viewport = getViewport(vp); + const newState = enabled ?? !viewport.viewFlags.backgroundMap; + viewport.viewFlags = viewport.viewFlags.with("backgroundMap", newState); + return { backgroundMapEnabled: newState }; +} + +// ── 3. set_base_map_type ───────────────────────────────────────────────────── + +/** + * Sets the base map to one of the well-known Bing providers: + * "aerial" | "hybrid" | "street" + * Or to a solid color fill when type is "color" + optional colorDef (TBGR integer). + */ +export async function setBaseMapType( + vp: any, + type: "aerial" | "hybrid" | "street" | "color", + colorTbgr?: number, +): Promise<{ baseMap: string }> { + const coreCommon = await import("@itwin/core-common" as string); + const { BackgroundMapProvider, BackgroundMapType, BaseLayerSettings, ColorDef } = coreCommon; + + const viewport = getViewport(vp); + + if (type === "color") { + const color = colorTbgr !== undefined ? ColorDef.fromJSON(colorTbgr) : ColorDef.fromJSON(0); + viewport.displayStyle.backgroundMapBase = color; + return { baseMap: `color (TBGR: ${color.toJSON()})` }; + } + + const bgTypeMap: Record = { + aerial: BackgroundMapType.Aerial, + hybrid: BackgroundMapType.Hybrid, + street: BackgroundMapType.Street, + }; + const bgType = bgTypeMap[type] ?? BackgroundMapType.Hybrid; + const provider = BackgroundMapProvider.fromJSON({ name: "BingProvider", type: bgType }); + const settings = BaseLayerSettings.fromProvider(provider); + viewport.displayStyle.backgroundMapBase = settings; + return { baseMap: type }; +} + +// ── 4. set_map_transparency ────────────────────────────────────────────────── + +/** + * Sets the background map transparency (0 = fully opaque, 1 = fully transparent). + */ +export function setMapTransparency( + vp: any, + transparency: number, +): { transparency: number } { + const viewport = getViewport(vp); + const clamped = Math.max(0, Math.min(1, transparency)); + viewport.changeBackgroundMapProps({ transparency: clamped }); + return { transparency: clamped }; +} + +// ── 5. toggle_terrain ──────────────────────────────────────────────────────── + +/** + * Toggles terrain display on/off, or sets it to a specific state. + */ +export function toggleTerrain( + vp: any, + enabled?: boolean, +): { terrainEnabled: boolean } { + const viewport = getViewport(vp); + const currentlyEnabled = viewport.view?.getDisplayStyle3d?.()?.settings?.backgroundMap?.applyTerrain ?? false; + const newState = enabled ?? !currentlyEnabled; + viewport.changeBackgroundMapProps({ applyTerrain: newState }); + return { terrainEnabled: newState }; +} + +// ── 6. get_map_layer_info ──────────────────────────────────────────────────── + +/** + * Returns information about all attached map layers (both background and overlay). + */ +export function getMapLayerInfo( + vp: any, +): { backgroundLayers: MapLayerInfo[]; overlayLayers: MapLayerInfo[]; backgroundMapEnabled: boolean } { + const viewport = getViewport(vp); + const backgroundLayers = gatherLayers(viewport, false); + const overlayLayers = gatherLayers(viewport, true); + return { + backgroundLayers, + overlayLayers, + backgroundMapEnabled: viewport.viewFlags.backgroundMap, + }; +} + +// ── 7. attach_map_layer ────────────────────────────────────────────────────── + +/** + * Attaches a new map layer to the viewport by URL. + * + * @param url The layer service URL (WMS, WMTS, ArcGIS, TileURL, etc.) + * @param name Display name for the layer + * @param formatId Format identifier: "WMS", "WMTS", "ArcGIS", "ArcGISFeature", "TileURL" + * @param isOverlay If true the layer is added as an overlay; otherwise as a background layer + * @param userName Optional credentials + * @param password Optional credentials + */ +export async function attachMapLayer( + vp: any, + url: string, + name: string, + formatId?: string, + isOverlay?: boolean, + userName?: string, + password?: string, +): Promise<{ attached: boolean; name: string; isOverlay: boolean }> { + const { MapLayerSource } = await import("@itwin/core-frontend" as string); + + const viewport = getViewport(vp); + const overlay = isOverlay ?? false; + + const source = MapLayerSource.fromJSON({ + url, + name, + formatId: formatId ?? "WMS", + }); + if (!source) { + throw new Error(`Failed to create map layer source from URL: ${url}`); + } + + if (userName) source.userName = userName; + if (password) source.password = password; + + const settings = source.toLayerSettings(); + if (!settings) { + throw new Error(`Failed to create layer settings for source: ${name}`); + } + + viewport.displayStyle.attachMapLayer({ + settings, + mapLayerIndex: { index: -1, isOverlay: overlay }, + }); + + return { attached: true, name, isOverlay: overlay }; +} + +// ── 8. detach_map_layer ────────────────────────────────────────────────────── + +/** + * Detaches a map layer by name (and optionally by overlay flag). + * If multiple layers match, all are detached. + */ +export function detachMapLayer( + vp: any, + name: string, + isOverlay?: boolean, +): { detached: string[] } { + const viewport = getViewport(vp); + const detached: string[] = []; + + const tryDetach = (overlay: boolean) => { + const layers = overlay + ? viewport.displayStyle.settings.mapImagery.overlayLayers + : viewport.displayStyle.settings.mapImagery.backgroundLayers; + + // Iterate in reverse so index removal is safe + for (let i = layers.length - 1; i >= 0; i--) { + if (layers[i].name === name) { + viewport.displayStyle.detachMapLayerByIndex({ index: i, isOverlay: overlay }); + detached.push(`${name} (${overlay ? "overlay" : "background"}, index ${i})`); + } + } + }; + + if (isOverlay === undefined) { + tryDetach(false); + tryDetach(true); + } else { + tryDetach(isOverlay); + } + + if (detached.length === 0) { + throw new Error(`No map layer found with name "${name}".`); + } + return { detached }; +} + +// ── 9. set_map_layer_visibility ────────────────────────────────────────────── + +/** + * Sets the visibility of a specific map layer identified by name. + */ +export function setMapLayerVisibility( + vp: any, + name: string, + visible: boolean, + isOverlay?: boolean, +): { name: string; visible: boolean; updated: number } { + const viewport = getViewport(vp); + let updated = 0; + + const trySetVisibility = (overlay: boolean) => { + const layers = overlay + ? viewport.displayStyle.settings.mapImagery.overlayLayers + : viewport.displayStyle.settings.mapImagery.backgroundLayers; + + for (let i = 0; i < layers.length; i++) { + if (layers[i].name === name) { + viewport.displayStyle.changeMapLayerProps( + { visible }, + { index: i, isOverlay: overlay }, + ); + updated++; + } + } + }; + + if (isOverlay === undefined) { + trySetVisibility(false); + trySetVisibility(true); + } else { + trySetVisibility(isOverlay); + } + + if (updated === 0) { + throw new Error(`No map layer found with name "${name}".`); + } + return { name, visible, updated }; +} diff --git a/packages/itwin/map-layers-mcp/tsconfig.json b/packages/itwin/map-layers-mcp/tsconfig.json new file mode 100644 index 000000000..699837d26 --- /dev/null +++ b/packages/itwin/map-layers-mcp/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "sourceMap": true + }, + "include": ["src/**/*"] +} From 85263ec51cbedbcc23ec6271b265a494aa7ec6c1 Mon Sep 17 00:00:00 2001 From: Jean-Luc Deziel Date: Thu, 19 Feb 2026 11:59:12 -0500 Subject: [PATCH 02/12] clean ups --- packages/itwin/map-layers-mcp/.gitignore | 2 + packages/itwin/map-layers-mcp/README.md | 8 +- packages/itwin/map-layers-mcp/dist/index.d.ts | 3 - packages/itwin/map-layers-mcp/dist/index.js | 9 - .../itwin/map-layers-mcp/dist/index.js.map | 1 - .../itwin/map-layers-mcp/dist/server.d.ts | 4 - packages/itwin/map-layers-mcp/dist/server.js | 172 -------------- .../itwin/map-layers-mcp/dist/server.js.map | 1 - packages/itwin/map-layers-mcp/dist/tools.d.ts | 78 ------- packages/itwin/map-layers-mcp/dist/tools.js | 215 ------------------ .../itwin/map-layers-mcp/dist/tools.js.map | 1 - packages/itwin/map-layers-mcp/package.json | 7 +- packages/itwin/map-layers-mcp/src/cli.ts | 18 ++ packages/itwin/map-layers-mcp/src/index.ts | 3 +- packages/itwin/map-layers-mcp/src/server.ts | 38 +--- packages/itwin/map-layers-mcp/src/viewport.ts | 33 +++ 16 files changed, 72 insertions(+), 521 deletions(-) create mode 100644 packages/itwin/map-layers-mcp/.gitignore delete mode 100644 packages/itwin/map-layers-mcp/dist/index.d.ts delete mode 100644 packages/itwin/map-layers-mcp/dist/index.js delete mode 100644 packages/itwin/map-layers-mcp/dist/index.js.map delete mode 100644 packages/itwin/map-layers-mcp/dist/server.d.ts delete mode 100644 packages/itwin/map-layers-mcp/dist/server.js delete mode 100644 packages/itwin/map-layers-mcp/dist/server.js.map delete mode 100644 packages/itwin/map-layers-mcp/dist/tools.d.ts delete mode 100644 packages/itwin/map-layers-mcp/dist/tools.js delete mode 100644 packages/itwin/map-layers-mcp/dist/tools.js.map create mode 100644 packages/itwin/map-layers-mcp/src/cli.ts create mode 100644 packages/itwin/map-layers-mcp/src/viewport.ts diff --git a/packages/itwin/map-layers-mcp/.gitignore b/packages/itwin/map-layers-mcp/.gitignore new file mode 100644 index 000000000..1eae0cf67 --- /dev/null +++ b/packages/itwin/map-layers-mcp/.gitignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ diff --git a/packages/itwin/map-layers-mcp/README.md b/packages/itwin/map-layers-mcp/README.md index 357945733..5d366f5af 100644 --- a/packages/itwin/map-layers-mcp/README.md +++ b/packages/itwin/map-layers-mcp/README.md @@ -22,7 +22,7 @@ MCP (Model Context Protocol) server that exposes iTwin.js map-layer operations a ```bash npm run build -node dist/server.js +node dist/cli.js ``` Configure in your MCP client (e.g. VS Code `mcp.json`, Claude Desktop `claude_desktop_config.json`): @@ -32,7 +32,7 @@ Configure in your MCP client (e.g. VS Code `mcp.json`, Claude Desktop `claude_de "mcpServers": { "map-layers": { "command": "node", - "args": ["path/to/packages/itwin/map-layers-mcp/dist/server.js"] + "args": ["path/to/packages/itwin/map-layers-mcp/dist/cli.js"] } } } @@ -53,7 +53,9 @@ setViewportAccessor(() => IModelApp.viewManager.selectedView); The server uses the **MCP SDK** (`@modelcontextprotocol/sdk`) with a **stdio transport**. - **`src/tools.ts`** — Pure functions that operate on an iTwin.js `ScreenViewport`. They use `any` types to avoid hard dependencies on `@itwin/core-frontend` / `@itwin/core-common` — the host app provides these at runtime. -- **`src/server.ts`** — MCP server that registers all 9 tools with zod schemas and wires them to the tool functions. +- **`src/server.ts`** — MCP server that registers all 9 tools with zod schemas and wires them to the tool functions. Does **not** auto-start; call `startServer()` or use the CLI. +- **`src/viewport.ts`** — Shared viewport accessor module (no side effects). +- **`src/cli.ts`** — CLI entry point that starts the server on stdio. - **`src/index.ts`** — Public API barrel for in-process usage. When running standalone (stdio), the tool functions that need a viewport will return structured JSON payloads describing the intended action. The host iTwin.js application should use `setViewportAccessor()` to provide real viewport access. diff --git a/packages/itwin/map-layers-mcp/dist/index.d.ts b/packages/itwin/map-layers-mcp/dist/index.d.ts deleted file mode 100644 index 0a656a8c8..000000000 --- a/packages/itwin/map-layers-mcp/dist/index.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { openMapLayersWidget, toggleBackgroundMap, setBaseMapType, setMapTransparency, toggleTerrain, getMapLayerInfo, attachMapLayer, detachMapLayer, setMapLayerVisibility, } from "./tools.js"; -export type { MapLayerInfo } from "./tools.js"; -export { setViewportAccessor } from "./server.js"; diff --git a/packages/itwin/map-layers-mcp/dist/index.js b/packages/itwin/map-layers-mcp/dist/index.js deleted file mode 100644 index 810b4f12f..000000000 --- a/packages/itwin/map-layers-mcp/dist/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * @itwin/map-layers-mcp - * - * Public API surface – re-exports the tool functions (for in-process use) - * and the viewport accessor setter. - *--------------------------------------------------------------------------------------------*/ -export { openMapLayersWidget, toggleBackgroundMap, setBaseMapType, setMapTransparency, toggleTerrain, getMapLayerInfo, attachMapLayer, detachMapLayer, setMapLayerVisibility, } from "./tools.js"; -export { setViewportAccessor } from "./server.js"; -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/itwin/map-layers-mcp/dist/index.js.map b/packages/itwin/map-layers-mcp/dist/index.js.map deleted file mode 100644 index 52114b365..000000000 --- a/packages/itwin/map-layers-mcp/dist/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;gGAKgG;AAEhG,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,cAAc,EACd,cAAc,EACd,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"} \ No newline at end of file diff --git a/packages/itwin/map-layers-mcp/dist/server.d.ts b/packages/itwin/map-layers-mcp/dist/server.d.ts deleted file mode 100644 index b6eb87c54..000000000 --- a/packages/itwin/map-layers-mcp/dist/server.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -type ViewportAccessor = () => any; -/** Set the viewport accessor (for in-process usage). */ -export declare function setViewportAccessor(fn: ViewportAccessor): void; -export {}; diff --git a/packages/itwin/map-layers-mcp/dist/server.js b/packages/itwin/map-layers-mcp/dist/server.js deleted file mode 100644 index a6d057f0a..000000000 --- a/packages/itwin/map-layers-mcp/dist/server.js +++ /dev/null @@ -1,172 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Map Layers MCP Server - * - * Exposes iTwin.js map-layer operations as MCP tools over stdio transport. - * The server communicates with a host iTwin.js application that provides - * viewport access — the host is expected to inject or relay viewport calls. - * - * Usage: - * node dist/server.js - * - * In practice the host application registers this as an MCP server (stdio) - * and implements the viewport resolution on its side. - *--------------------------------------------------------------------------------------------*/ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { z } from "zod"; -let _getViewport = () => undefined; -/** Set the viewport accessor (for in-process usage). */ -export function setViewportAccessor(fn) { - _getViewport = fn; -} -// --------------------------------------------------------------------------- -// Server -// --------------------------------------------------------------------------- -const server = new McpServer({ - name: "map-layers-mcp", - version: "1.0.0", -}); -// ── 1. open_map_layers_widget ──────────────────────────────────────────────── -server.tool("open_map_layers_widget", "Opens the Map Layers widget panel in the iTwin.js frontstage UI so the user can manage map layers visually.", {}, async () => { - try { - const { openMapLayersWidget } = await import("./tools.js"); - const msg = await openMapLayersWidget(); - return { content: [{ type: "text", text: msg }] }; - } - catch (e) { - return { content: [{ type: "text", text: JSON.stringify({ action: "open_map_layers_widget" }) }] }; - } -}); -// ── 2. toggle_background_map ───────────────────────────────────────────────── -server.tool("toggle_background_map", "Toggles the background map on or off. If 'enabled' is provided, sets it to that state; otherwise toggles.", { - enabled: z.boolean().optional().describe("If provided, force the background map to this state (true = on, false = off). Omit to toggle."), -}, async ({ enabled }) => { - try { - const { toggleBackgroundMap } = await import("./tools.js"); - const result = toggleBackgroundMap(_getViewport(), enabled); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - } - catch (e) { - return { content: [{ type: "text", text: JSON.stringify({ action: "toggle_background_map", enabled }) }] }; - } -}); -// ── 3. set_base_map_type ───────────────────────────────────────────────────── -server.tool("set_base_map_type", "Changes the base map to one of the well-known types: aerial, hybrid, street, or a solid color fill.", { - type: z.enum(["aerial", "hybrid", "street", "color"]).describe("The base map type to set."), - colorTbgr: z.number().optional().describe("TBGR color integer when type is 'color'. Ignored otherwise."), -}, async ({ type, colorTbgr }) => { - try { - const { setBaseMapType } = await import("./tools.js"); - const result = await setBaseMapType(_getViewport(), type, colorTbgr); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - } - catch (e) { - return { content: [{ type: "text", text: JSON.stringify({ action: "set_base_map_type", type, colorTbgr }) }] }; - } -}); -// ── 4. set_map_transparency ────────────────────────────────────────────────── -server.tool("set_map_transparency", "Sets the transparency of the background map. 0 = fully opaque, 1 = fully transparent.", { - transparency: z.number().min(0).max(1).describe("Transparency value from 0.0 (opaque) to 1.0 (transparent)."), -}, async ({ transparency }) => { - try { - const { setMapTransparency } = await import("./tools.js"); - const result = setMapTransparency(_getViewport(), transparency); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - } - catch (e) { - return { content: [{ type: "text", text: JSON.stringify({ action: "set_map_transparency", transparency }) }] }; - } -}); -// ── 5. toggle_terrain ──────────────────────────────────────────────────────── -server.tool("toggle_terrain", "Toggles terrain display on or off. If 'enabled' is provided, sets it to that state; otherwise toggles.", { - enabled: z.boolean().optional().describe("If provided, force terrain to this state (true = on, false = off). Omit to toggle."), -}, async ({ enabled }) => { - try { - const { toggleTerrain } = await import("./tools.js"); - const result = toggleTerrain(_getViewport(), enabled); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - } - catch (e) { - return { content: [{ type: "text", text: JSON.stringify({ action: "toggle_terrain", enabled }) }] }; - } -}); -// ── 6. get_map_layer_info ──────────────────────────────────────────────────── -server.tool("get_map_layer_info", "Returns detailed information about all attached map layers (background and overlay) and whether the background map is enabled.", {}, async () => { - try { - const { getMapLayerInfo } = await import("./tools.js"); - const result = getMapLayerInfo(_getViewport()); - return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; - } - catch (e) { - return { content: [{ type: "text", text: JSON.stringify({ action: "get_map_layer_info" }) }] }; - } -}); -// ── 7. attach_map_layer ────────────────────────────────────────────────────── -server.tool("attach_map_layer", "Attaches a new map layer to the viewport by URL. Supports WMS, WMTS, ArcGIS, ArcGISFeature, and TileURL formats.", { - url: z.string().describe("The map layer service URL."), - name: z.string().describe("Display name for the layer."), - formatId: z - .enum(["WMS", "WMTS", "ArcGIS", "ArcGISFeature", "TileURL"]) - .optional() - .describe("The map service format. Defaults to 'WMS'."), - isOverlay: z.boolean().optional().describe("If true, attach as overlay; otherwise as background layer. Default: false."), - userName: z.string().optional().describe("Optional username for authenticated layers."), - password: z.string().optional().describe("Optional password for authenticated layers."), -}, async ({ url, name, formatId, isOverlay, userName, password }) => { - try { - const { attachMapLayer } = await import("./tools.js"); - const result = await attachMapLayer(_getViewport(), url, name, formatId, isOverlay, userName, password); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - } - catch (e) { - return { - content: [{ type: "text", text: JSON.stringify({ action: "attach_map_layer", url, name, formatId, isOverlay, error: e.message }) }], - }; - } -}); -// ── 8. detach_map_layer ────────────────────────────────────────────────────── -server.tool("detach_map_layer", "Detaches (removes) a map layer by name. If multiple layers share the same name, all are removed.", { - name: z.string().describe("The name of the map layer to detach."), - isOverlay: z.boolean().optional().describe("If specified, only detach from overlay or background layers. Omit to search both."), -}, async ({ name, isOverlay }) => { - try { - const { detachMapLayer } = await import("./tools.js"); - const result = detachMapLayer(_getViewport(), name, isOverlay); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - } - catch (e) { - return { - content: [{ type: "text", text: JSON.stringify({ action: "detach_map_layer", name, isOverlay, error: e.message }) }], - }; - } -}); -// ── 9. set_map_layer_visibility ────────────────────────────────────────────── -server.tool("set_map_layer_visibility", "Sets the visibility of a specific map layer identified by name.", { - name: z.string().describe("The name of the map layer to update."), - visible: z.boolean().describe("Whether the layer should be visible."), - isOverlay: z.boolean().optional().describe("If specified, only search overlay or background layers. Omit to search both."), -}, async ({ name, visible, isOverlay }) => { - try { - const { setMapLayerVisibility } = await import("./tools.js"); - const result = setMapLayerVisibility(_getViewport(), name, visible, isOverlay); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - } - catch (e) { - return { - content: [{ type: "text", text: JSON.stringify({ action: "set_map_layer_visibility", name, visible, isOverlay, error: e.message }) }], - }; - } -}); -// --------------------------------------------------------------------------- -// Start -// --------------------------------------------------------------------------- -async function main() { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("Map Layers MCP server running on stdio"); -} -main().catch((err) => { - console.error("Fatal error starting MCP server:", err); - process.exit(1); -}); -//# sourceMappingURL=server.js.map \ No newline at end of file diff --git a/packages/itwin/map-layers-mcp/dist/server.js.map b/packages/itwin/map-layers-mcp/dist/server.js.map deleted file mode 100644 index 8588ec982..000000000 --- a/packages/itwin/map-layers-mcp/dist/server.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/itwin/map-layers-mcp/dist/tools.d.ts b/packages/itwin/map-layers-mcp/dist/tools.d.ts deleted file mode 100644 index a0256d671..000000000 --- a/packages/itwin/map-layers-mcp/dist/tools.d.ts +++ /dev/null @@ -1,78 +0,0 @@ -export interface MapLayerInfo { - name: string; - source: string; - visible: boolean; - transparency: number; - isOverlay: boolean; - layerIndex: number; - subLayers?: any[]; -} -/** - * Opens (activates) the Map Layers widget in the frontstage. - * Must run in an iTwin.js environment where `@itwin/appui-react` is loaded. - */ -export declare function openMapLayersWidget(): Promise; -/** - * Toggles background map visibility on/off, or sets it to a specific state. - */ -export declare function toggleBackgroundMap(vp: any, enabled?: boolean): { - backgroundMapEnabled: boolean; -}; -/** - * Sets the base map to one of the well-known Bing providers: - * "aerial" | "hybrid" | "street" - * Or to a solid color fill when type is "color" + optional colorDef (TBGR integer). - */ -export declare function setBaseMapType(vp: any, type: "aerial" | "hybrid" | "street" | "color", colorTbgr?: number): Promise<{ - baseMap: string; -}>; -/** - * Sets the background map transparency (0 = fully opaque, 1 = fully transparent). - */ -export declare function setMapTransparency(vp: any, transparency: number): { - transparency: number; -}; -/** - * Toggles terrain display on/off, or sets it to a specific state. - */ -export declare function toggleTerrain(vp: any, enabled?: boolean): { - terrainEnabled: boolean; -}; -/** - * Returns information about all attached map layers (both background and overlay). - */ -export declare function getMapLayerInfo(vp: any): { - backgroundLayers: MapLayerInfo[]; - overlayLayers: MapLayerInfo[]; - backgroundMapEnabled: boolean; -}; -/** - * Attaches a new map layer to the viewport by URL. - * - * @param url The layer service URL (WMS, WMTS, ArcGIS, TileURL, etc.) - * @param name Display name for the layer - * @param formatId Format identifier: "WMS", "WMTS", "ArcGIS", "ArcGISFeature", "TileURL" - * @param isOverlay If true the layer is added as an overlay; otherwise as a background layer - * @param userName Optional credentials - * @param password Optional credentials - */ -export declare function attachMapLayer(vp: any, url: string, name: string, formatId?: string, isOverlay?: boolean, userName?: string, password?: string): Promise<{ - attached: boolean; - name: string; - isOverlay: boolean; -}>; -/** - * Detaches a map layer by name (and optionally by overlay flag). - * If multiple layers match, all are detached. - */ -export declare function detachMapLayer(vp: any, name: string, isOverlay?: boolean): { - detached: string[]; -}; -/** - * Sets the visibility of a specific map layer identified by name. - */ -export declare function setMapLayerVisibility(vp: any, name: string, visible: boolean, isOverlay?: boolean): { - name: string; - visible: boolean; - updated: number; -}; diff --git a/packages/itwin/map-layers-mcp/dist/tools.js b/packages/itwin/map-layers-mcp/dist/tools.js deleted file mode 100644 index 976b0d1ed..000000000 --- a/packages/itwin/map-layers-mcp/dist/tools.js +++ /dev/null @@ -1,215 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Map Layers MCP Tool Functions - * - * These functions operate on an iTwin.js ScreenViewport. They are designed to - * be called from within a running iTwin.js application where `IModelApp` is - * initialized and a viewport is available. - * - * The functions accept a generic viewport reference (typed as `any` to avoid - * hard dependency on @itwin/core-frontend in the MCP server package). The host - * application is responsible for providing the correctly-typed ScreenViewport. - *--------------------------------------------------------------------------------------------*/ -// ── Helpers ────────────────────────────────────────────────────────────────── -function getViewport(vp) { - if (!vp) { - throw new Error("No active viewport available."); - } - return vp; -} -function gatherLayers(vp, isOverlay) { - const layers = isOverlay - ? vp.displayStyle.settings.mapImagery.overlayLayers - : vp.displayStyle.settings.mapImagery.backgroundLayers; - return layers.map((layer, idx) => ({ - name: layer.name, - source: layer.source ?? layer.modelId ?? "", - visible: layer.visible, - transparency: layer.transparency, - isOverlay, - layerIndex: idx, - subLayers: layer.subLayers?.map((s) => (typeof s.toJSON === "function" ? s.toJSON() : s)) ?? undefined, - })); -} -// ── 1. open_map_layers_widget ──────────────────────────────────────────────── -/** - * Opens (activates) the Map Layers widget in the frontstage. - * Must run in an iTwin.js environment where `@itwin/appui-react` is loaded. - */ -export async function openMapLayersWidget() { - // Dynamic import so this module is only required at runtime inside iTwin.js - const { UiFramework } = await import("@itwin/appui-react"); - const widgetId = "map-layers:mapLayersWidget"; - UiFramework.frontstages.activeFrontstageDef?.findWidgetDef(widgetId)?.setWidgetState(1 /* WidgetState.Open */); - return `Map Layers widget opened (id: ${widgetId}).`; -} -// ── 2. toggle_background_map ───────────────────────────────────────────────── -/** - * Toggles background map visibility on/off, or sets it to a specific state. - */ -export function toggleBackgroundMap(vp, enabled) { - const viewport = getViewport(vp); - const newState = enabled ?? !viewport.viewFlags.backgroundMap; - viewport.viewFlags = viewport.viewFlags.with("backgroundMap", newState); - return { backgroundMapEnabled: newState }; -} -// ── 3. set_base_map_type ───────────────────────────────────────────────────── -/** - * Sets the base map to one of the well-known Bing providers: - * "aerial" | "hybrid" | "street" - * Or to a solid color fill when type is "color" + optional colorDef (TBGR integer). - */ -export async function setBaseMapType(vp, type, colorTbgr) { - const coreCommon = await import("@itwin/core-common"); - const { BackgroundMapProvider, BackgroundMapType, BaseLayerSettings, ColorDef } = coreCommon; - const viewport = getViewport(vp); - if (type === "color") { - const color = colorTbgr !== undefined ? ColorDef.fromJSON(colorTbgr) : ColorDef.fromJSON(0); - viewport.displayStyle.backgroundMapBase = color; - return { baseMap: `color (TBGR: ${color.toJSON()})` }; - } - const bgTypeMap = { - aerial: BackgroundMapType.Aerial, - hybrid: BackgroundMapType.Hybrid, - street: BackgroundMapType.Street, - }; - const bgType = bgTypeMap[type] ?? BackgroundMapType.Hybrid; - const provider = BackgroundMapProvider.fromJSON({ name: "BingProvider", type: bgType }); - const settings = BaseLayerSettings.fromProvider(provider); - viewport.displayStyle.backgroundMapBase = settings; - return { baseMap: type }; -} -// ── 4. set_map_transparency ────────────────────────────────────────────────── -/** - * Sets the background map transparency (0 = fully opaque, 1 = fully transparent). - */ -export function setMapTransparency(vp, transparency) { - const viewport = getViewport(vp); - const clamped = Math.max(0, Math.min(1, transparency)); - viewport.changeBackgroundMapProps({ transparency: clamped }); - return { transparency: clamped }; -} -// ── 5. toggle_terrain ──────────────────────────────────────────────────────── -/** - * Toggles terrain display on/off, or sets it to a specific state. - */ -export function toggleTerrain(vp, enabled) { - const viewport = getViewport(vp); - const currentlyEnabled = viewport.view?.getDisplayStyle3d?.()?.settings?.backgroundMap?.applyTerrain ?? false; - const newState = enabled ?? !currentlyEnabled; - viewport.changeBackgroundMapProps({ applyTerrain: newState }); - return { terrainEnabled: newState }; -} -// ── 6. get_map_layer_info ──────────────────────────────────────────────────── -/** - * Returns information about all attached map layers (both background and overlay). - */ -export function getMapLayerInfo(vp) { - const viewport = getViewport(vp); - const backgroundLayers = gatherLayers(viewport, false); - const overlayLayers = gatherLayers(viewport, true); - return { - backgroundLayers, - overlayLayers, - backgroundMapEnabled: viewport.viewFlags.backgroundMap, - }; -} -// ── 7. attach_map_layer ────────────────────────────────────────────────────── -/** - * Attaches a new map layer to the viewport by URL. - * - * @param url The layer service URL (WMS, WMTS, ArcGIS, TileURL, etc.) - * @param name Display name for the layer - * @param formatId Format identifier: "WMS", "WMTS", "ArcGIS", "ArcGISFeature", "TileURL" - * @param isOverlay If true the layer is added as an overlay; otherwise as a background layer - * @param userName Optional credentials - * @param password Optional credentials - */ -export async function attachMapLayer(vp, url, name, formatId, isOverlay, userName, password) { - const { MapLayerSource } = await import("@itwin/core-frontend"); - const viewport = getViewport(vp); - const overlay = isOverlay ?? false; - const source = MapLayerSource.fromJSON({ - url, - name, - formatId: formatId ?? "WMS", - }); - if (!source) { - throw new Error(`Failed to create map layer source from URL: ${url}`); - } - if (userName) - source.userName = userName; - if (password) - source.password = password; - const settings = source.toLayerSettings(); - if (!settings) { - throw new Error(`Failed to create layer settings for source: ${name}`); - } - viewport.displayStyle.attachMapLayer({ - settings, - mapLayerIndex: { index: -1, isOverlay: overlay }, - }); - return { attached: true, name, isOverlay: overlay }; -} -// ── 8. detach_map_layer ────────────────────────────────────────────────────── -/** - * Detaches a map layer by name (and optionally by overlay flag). - * If multiple layers match, all are detached. - */ -export function detachMapLayer(vp, name, isOverlay) { - const viewport = getViewport(vp); - const detached = []; - const tryDetach = (overlay) => { - const layers = overlay - ? viewport.displayStyle.settings.mapImagery.overlayLayers - : viewport.displayStyle.settings.mapImagery.backgroundLayers; - // Iterate in reverse so index removal is safe - for (let i = layers.length - 1; i >= 0; i--) { - if (layers[i].name === name) { - viewport.displayStyle.detachMapLayerByIndex({ index: i, isOverlay: overlay }); - detached.push(`${name} (${overlay ? "overlay" : "background"}, index ${i})`); - } - } - }; - if (isOverlay === undefined) { - tryDetach(false); - tryDetach(true); - } - else { - tryDetach(isOverlay); - } - if (detached.length === 0) { - throw new Error(`No map layer found with name "${name}".`); - } - return { detached }; -} -// ── 9. set_map_layer_visibility ────────────────────────────────────────────── -/** - * Sets the visibility of a specific map layer identified by name. - */ -export function setMapLayerVisibility(vp, name, visible, isOverlay) { - const viewport = getViewport(vp); - let updated = 0; - const trySetVisibility = (overlay) => { - const layers = overlay - ? viewport.displayStyle.settings.mapImagery.overlayLayers - : viewport.displayStyle.settings.mapImagery.backgroundLayers; - for (let i = 0; i < layers.length; i++) { - if (layers[i].name === name) { - viewport.displayStyle.changeMapLayerProps({ visible }, { index: i, isOverlay: overlay }); - updated++; - } - } - }; - if (isOverlay === undefined) { - trySetVisibility(false); - trySetVisibility(true); - } - else { - trySetVisibility(isOverlay); - } - if (updated === 0) { - throw new Error(`No map layer found with name "${name}".`); - } - return { name, visible, updated }; -} -//# sourceMappingURL=tools.js.map \ No newline at end of file diff --git a/packages/itwin/map-layers-mcp/dist/tools.js.map b/packages/itwin/map-layers-mcp/dist/tools.js.map deleted file mode 100644 index 1899b473b..000000000 --- a/packages/itwin/map-layers-mcp/dist/tools.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/itwin/map-layers-mcp/package.json b/packages/itwin/map-layers-mcp/package.json index fb15ae491..ea776fedf 100644 --- a/packages/itwin/map-layers-mcp/package.json +++ b/packages/itwin/map-layers-mcp/package.json @@ -2,11 +2,14 @@ "name": "@itwin/map-layers-mcp", "version": "1.0.0", "description": "MCP server exposing map layer tools for iTwin.js viewer", - "main": "dist/server.js", + "main": "dist/index.js", "type": "module", + "bin": { + "map-layers-mcp": "dist/cli.js" + }, "scripts": { "build": "tsc", - "start": "node dist/server.js" + "start": "node dist/cli.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", diff --git a/packages/itwin/map-layers-mcp/src/cli.ts b/packages/itwin/map-layers-mcp/src/cli.ts new file mode 100644 index 000000000..df82f701b --- /dev/null +++ b/packages/itwin/map-layers-mcp/src/cli.ts @@ -0,0 +1,18 @@ +#!/usr/bin/env node +/*--------------------------------------------------------------------------------------------- + * Map Layers MCP Server — CLI entry point + * + * Starts the MCP server with a stdio transport. This file is the entry point + * for standalone (subprocess) usage. It is NOT imported by the library barrel + * (index.ts), so importing @itwin/map-layers-mcp will not trigger a server. + * + * Usage: + * node dist/cli.js + *--------------------------------------------------------------------------------------------*/ + +import { startServer } from "./server.js"; + +startServer().catch((err) => { + console.error("Fatal error starting MCP server:", err); + process.exit(1); +}); diff --git a/packages/itwin/map-layers-mcp/src/index.ts b/packages/itwin/map-layers-mcp/src/index.ts index 6a66d0d59..4f0acdfe8 100644 --- a/packages/itwin/map-layers-mcp/src/index.ts +++ b/packages/itwin/map-layers-mcp/src/index.ts @@ -19,4 +19,5 @@ export { export type { MapLayerInfo } from "./tools.js"; -export { setViewportAccessor } from "./server.js"; +export { setViewportAccessor } from "./viewport.js"; +export { startServer } from "./server.js"; diff --git a/packages/itwin/map-layers-mcp/src/server.ts b/packages/itwin/map-layers-mcp/src/server.ts index 82a366fc1..0393c77c4 100644 --- a/packages/itwin/map-layers-mcp/src/server.ts +++ b/packages/itwin/map-layers-mcp/src/server.ts @@ -5,37 +5,17 @@ * The server communicates with a host iTwin.js application that provides * viewport access — the host is expected to inject or relay viewport calls. * - * Usage: - * node dist/server.js - * - * In practice the host application registers this as an MCP server (stdio) - * and implements the viewport resolution on its side. + * This module only **defines** the server and its tool registrations. + * It does NOT auto-start. Use `startServer()` or the CLI entry point + * (`cli.ts` / `dist/cli.js`) to actually start the stdio transport. *--------------------------------------------------------------------------------------------*/ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; +import { getViewportAccessor } from "./viewport.js"; -// --------------------------------------------------------------------------- -// Viewport accessor -// --------------------------------------------------------------------------- -// The MCP server runs in its own process and cannot directly access the -// iTwin.js ScreenViewport. The host application must provide a viewport -// accessor. For stdio-based deployments the tools encode the *intent* and -// the host application applies them. When running **in-process** (e.g. via -// an adapter) the host can set this accessor before starting the server. -// -// For the stdio transport case, each tool simply returns a structured JSON -// payload that the host can interpret and apply to its viewport. -// --------------------------------------------------------------------------- - -type ViewportAccessor = () => /* ScreenViewport | undefined */ any; -let _getViewport: ViewportAccessor = () => undefined; - -/** Set the viewport accessor (for in-process usage). */ -export function setViewportAccessor(fn: ViewportAccessor) { - _getViewport = fn; -} +const _getViewport = getViewportAccessor; // --------------------------------------------------------------------------- // Server @@ -235,13 +215,9 @@ server.tool( // Start // --------------------------------------------------------------------------- -async function main() { +/** Start the MCP server on a stdio transport. */ +export async function startServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Map Layers MCP server running on stdio"); } - -main().catch((err) => { - console.error("Fatal error starting MCP server:", err); - process.exit(1); -}); diff --git a/packages/itwin/map-layers-mcp/src/viewport.ts b/packages/itwin/map-layers-mcp/src/viewport.ts new file mode 100644 index 000000000..b05bc5736 --- /dev/null +++ b/packages/itwin/map-layers-mcp/src/viewport.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Viewport Accessor + * + * Shared module that holds the viewport accessor used by both the MCP server + * and the in-process tool functions. This module has no side effects and is + * safe to import from any context. + *--------------------------------------------------------------------------------------------*/ + +// --------------------------------------------------------------------------- +// Viewport accessor +// --------------------------------------------------------------------------- +// The MCP server runs in its own process and cannot directly access the +// iTwin.js ScreenViewport. The host application must provide a viewport +// accessor. For stdio-based deployments the tools encode the *intent* and +// the host application applies them. When running **in-process** (e.g. via +// an adapter) the host can set this accessor before starting the server. +// +// For the stdio transport case, each tool simply returns a structured JSON +// payload that the host can interpret and apply to its viewport. +// --------------------------------------------------------------------------- + +type ViewportAccessor = () => /* ScreenViewport | undefined */ any; +let _getViewport: ViewportAccessor = () => undefined; + +/** Set the viewport accessor (for in-process usage). */ +export function setViewportAccessor(fn: ViewportAccessor) { + _getViewport = fn; +} + +/** Get the current viewport via the registered accessor. */ +export function getViewportAccessor(): any { + return _getViewport(); +} From e11cd32daf42aa78a901af96b1aefbc1842b75df Mon Sep 17 00:00:00 2001 From: Jean-Luc Deziel Date: Thu, 19 Feb 2026 12:12:16 -0500 Subject: [PATCH 03/12] moved mcp under map-layers package --- packages/itwin/map-layers-mcp/.gitignore | 2 - packages/itwin/map-layers-mcp/README.md | 61 --- packages/itwin/map-layers-mcp/package.json | 22 - packages/itwin/map-layers-mcp/src/index.ts | 23 - packages/itwin/map-layers-mcp/src/viewport.ts | 33 -- packages/itwin/map-layers-mcp/tsconfig.json | 15 - packages/itwin/map-layers/package.json | 19 +- packages/itwin/map-layers/pnpm-lock.yaml | 467 ++++++++++++++++++ .../src => map-layers/src/mcp}/cli.ts | 21 +- packages/itwin/map-layers/src/mcp/index.ts | 28 ++ .../src => map-layers/src/mcp}/server.ts | 99 ++-- .../src => map-layers/src/mcp}/tools.ts | 145 +++--- packages/itwin/map-layers/src/mcp/viewport.ts | 24 + 13 files changed, 676 insertions(+), 283 deletions(-) delete mode 100644 packages/itwin/map-layers-mcp/.gitignore delete mode 100644 packages/itwin/map-layers-mcp/README.md delete mode 100644 packages/itwin/map-layers-mcp/package.json delete mode 100644 packages/itwin/map-layers-mcp/src/index.ts delete mode 100644 packages/itwin/map-layers-mcp/src/viewport.ts delete mode 100644 packages/itwin/map-layers-mcp/tsconfig.json rename packages/itwin/{map-layers-mcp/src => map-layers/src/mcp}/cli.ts (54%) create mode 100644 packages/itwin/map-layers/src/mcp/index.ts rename packages/itwin/{map-layers-mcp/src => map-layers/src/mcp}/server.ts (65%) rename packages/itwin/{map-layers-mcp/src => map-layers/src/mcp}/tools.ts (67%) create mode 100644 packages/itwin/map-layers/src/mcp/viewport.ts diff --git a/packages/itwin/map-layers-mcp/.gitignore b/packages/itwin/map-layers-mcp/.gitignore deleted file mode 100644 index 1eae0cf67..000000000 --- a/packages/itwin/map-layers-mcp/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -dist/ -node_modules/ diff --git a/packages/itwin/map-layers-mcp/README.md b/packages/itwin/map-layers-mcp/README.md deleted file mode 100644 index 5d366f5af..000000000 --- a/packages/itwin/map-layers-mcp/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# @itwin/map-layers-mcp - -MCP (Model Context Protocol) server that exposes iTwin.js map-layer operations as tools. - -## Tools - -| Tool | Description | -|------|-------------| -| `open_map_layers_widget` | Opens the Map Layers widget panel in the frontstage UI | -| `toggle_background_map` | Toggles the background map on/off | -| `set_base_map_type` | Changes the base map (aerial, hybrid, street, or solid color) | -| `set_map_transparency` | Sets background map transparency (0–1) | -| `toggle_terrain` | Toggles terrain display on/off | -| `get_map_layer_info` | Returns info about all attached map layers | -| `attach_map_layer` | Attaches a new map layer by URL (WMS, WMTS, ArcGIS, TileURL) | -| `detach_map_layer` | Detaches a map layer by name | -| `set_map_layer_visibility` | Sets visibility of a specific map layer | - -## Usage - -### As a stdio MCP server - -```bash -npm run build -node dist/cli.js -``` - -Configure in your MCP client (e.g. VS Code `mcp.json`, Claude Desktop `claude_desktop_config.json`): - -```json -{ - "mcpServers": { - "map-layers": { - "command": "node", - "args": ["path/to/packages/itwin/map-layers-mcp/dist/cli.js"] - } - } -} -``` - -### In-process (inside an iTwin.js app) - -```typescript -import { setViewportAccessor } from "@itwin/map-layers-mcp"; -import { IModelApp } from "@itwin/core-frontend"; - -// Provide the viewport accessor so tool functions can operate on the live viewport -setViewportAccessor(() => IModelApp.viewManager.selectedView); -``` - -## Architecture - -The server uses the **MCP SDK** (`@modelcontextprotocol/sdk`) with a **stdio transport**. - -- **`src/tools.ts`** — Pure functions that operate on an iTwin.js `ScreenViewport`. They use `any` types to avoid hard dependencies on `@itwin/core-frontend` / `@itwin/core-common` — the host app provides these at runtime. -- **`src/server.ts`** — MCP server that registers all 9 tools with zod schemas and wires them to the tool functions. Does **not** auto-start; call `startServer()` or use the CLI. -- **`src/viewport.ts`** — Shared viewport accessor module (no side effects). -- **`src/cli.ts`** — CLI entry point that starts the server on stdio. -- **`src/index.ts`** — Public API barrel for in-process usage. - -When running standalone (stdio), the tool functions that need a viewport will return structured JSON payloads describing the intended action. The host iTwin.js application should use `setViewportAccessor()` to provide real viewport access. diff --git a/packages/itwin/map-layers-mcp/package.json b/packages/itwin/map-layers-mcp/package.json deleted file mode 100644 index ea776fedf..000000000 --- a/packages/itwin/map-layers-mcp/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "@itwin/map-layers-mcp", - "version": "1.0.0", - "description": "MCP server exposing map layer tools for iTwin.js viewer", - "main": "dist/index.js", - "type": "module", - "bin": { - "map-layers-mcp": "dist/cli.js" - }, - "scripts": { - "build": "tsc", - "start": "node dist/cli.js" - }, - "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.1", - "zod": "^3.24.0" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "typescript": "~5.6.0" - } -} diff --git a/packages/itwin/map-layers-mcp/src/index.ts b/packages/itwin/map-layers-mcp/src/index.ts deleted file mode 100644 index 4f0acdfe8..000000000 --- a/packages/itwin/map-layers-mcp/src/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * @itwin/map-layers-mcp - * - * Public API surface – re-exports the tool functions (for in-process use) - * and the viewport accessor setter. - *--------------------------------------------------------------------------------------------*/ - -export { - openMapLayersWidget, - toggleBackgroundMap, - setBaseMapType, - setMapTransparency, - toggleTerrain, - getMapLayerInfo, - attachMapLayer, - detachMapLayer, - setMapLayerVisibility, -} from "./tools.js"; - -export type { MapLayerInfo } from "./tools.js"; - -export { setViewportAccessor } from "./viewport.js"; -export { startServer } from "./server.js"; diff --git a/packages/itwin/map-layers-mcp/src/viewport.ts b/packages/itwin/map-layers-mcp/src/viewport.ts deleted file mode 100644 index b05bc5736..000000000 --- a/packages/itwin/map-layers-mcp/src/viewport.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Viewport Accessor - * - * Shared module that holds the viewport accessor used by both the MCP server - * and the in-process tool functions. This module has no side effects and is - * safe to import from any context. - *--------------------------------------------------------------------------------------------*/ - -// --------------------------------------------------------------------------- -// Viewport accessor -// --------------------------------------------------------------------------- -// The MCP server runs in its own process and cannot directly access the -// iTwin.js ScreenViewport. The host application must provide a viewport -// accessor. For stdio-based deployments the tools encode the *intent* and -// the host application applies them. When running **in-process** (e.g. via -// an adapter) the host can set this accessor before starting the server. -// -// For the stdio transport case, each tool simply returns a structured JSON -// payload that the host can interpret and apply to its viewport. -// --------------------------------------------------------------------------- - -type ViewportAccessor = () => /* ScreenViewport | undefined */ any; -let _getViewport: ViewportAccessor = () => undefined; - -/** Set the viewport accessor (for in-process usage). */ -export function setViewportAccessor(fn: ViewportAccessor) { - _getViewport = fn; -} - -/** Get the current viewport via the registered accessor. */ -export function getViewportAccessor(): any { - return _getViewport(); -} diff --git a/packages/itwin/map-layers-mcp/tsconfig.json b/packages/itwin/map-layers-mcp/tsconfig.json deleted file mode 100644 index 699837d26..000000000 --- a/packages/itwin/map-layers-mcp/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": true, - "sourceMap": true - }, - "include": ["src/**/*"] -} diff --git a/packages/itwin/map-layers/package.json b/packages/itwin/map-layers/package.json index 9a9e77585..7a34969ad 100644 --- a/packages/itwin/map-layers/package.json +++ b/packages/itwin/map-layers/package.json @@ -5,6 +5,21 @@ "main": "lib/cjs/map-layers.js", "module": "lib/esm/map-layers.js", "typings": "lib/cjs/map-layers", + "exports": { + ".": { + "import": "./lib/esm/map-layers.js", + "require": "./lib/cjs/map-layers.js", + "types": "./lib/cjs/map-layers.d.ts" + }, + "./mcp": { + "import": "./lib/esm/mcp/index.js", + "require": "./lib/cjs/mcp/index.js", + "types": "./lib/cjs/mcp/index.d.ts" + } + }, + "bin": { + "map-layers-mcp": "lib/cjs/mcp/cli.js" + }, "license": "MIT", "scripts": { "build": "npm run -s copy:locale && npm run -s build:cjs && npm run -s build:esm", @@ -39,7 +54,9 @@ "dependencies": { "@itwin/itwinui-icons-color-react": "^2.1.0", "@itwin/itwinui-icons-react": "^2.10.0", - "react-beautiful-dnd": "^13.1.1" + "@modelcontextprotocol/sdk": "^1.12.1", + "react-beautiful-dnd": "^13.1.1", + "zod": "^3.25.0" }, "devDependencies": { "@itwin/appui-abstract": "^5.0.0", diff --git a/packages/itwin/map-layers/pnpm-lock.yaml b/packages/itwin/map-layers/pnpm-lock.yaml index 260971c19..ccfee3ac1 100644 --- a/packages/itwin/map-layers/pnpm-lock.yaml +++ b/packages/itwin/map-layers/pnpm-lock.yaml @@ -15,9 +15,15 @@ importers: '@itwin/itwinui-icons-react': specifier: ^2.10.0 version: 2.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@modelcontextprotocol/sdk': + specifier: ^1.12.1 + version: 1.12.1 react-beautiful-dnd: specifier: ^13.1.1 version: 13.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + zod: + specifier: ^3.24.0 + version: 3.24.0 devDependencies: '@itwin/appui-abstract': specifier: ^5.0.0 @@ -723,6 +729,10 @@ packages: '@microsoft/tsdoc@0.15.1': resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + '@modelcontextprotocol/sdk@1.12.1': + resolution: {integrity: sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw==} + engines: {node: '>=18'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -764,36 +774,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -864,66 +880,79 @@ packages: resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.57.1': resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.57.1': resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.57.1': resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.57.1': resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.57.1': resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.57.1': resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.57.1': resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.57.1': resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.57.1': resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.57.1': resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.57.1': resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.57.1': resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.57.1': resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} @@ -1220,6 +1249,10 @@ packages: '@yarnpkg/lockfile@1.1.0': resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1377,6 +1410,10 @@ packages: bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1396,6 +1433,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1483,6 +1524,26 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cpx2@3.0.2: resolution: {integrity: sha512-xVmdulZJVGSV+c8KkZ9IQY+RgyL9sGeVqScI2e7NtsEY9SVKcQXM4v0/9OLU0W0YtL9nmmqrtWjs5rpvgHn9Hg==} engines: {node: '>=6.5'} @@ -1563,6 +1624,15 @@ packages: supports-color: optional: true + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@4.0.0: resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} engines: {node: '>=10'} @@ -1585,6 +1655,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1634,6 +1708,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emoji-regex-xs@1.0.0: resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} @@ -1643,6 +1720,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -1698,6 +1779,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1833,10 +1917,32 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + express-rate-limit@7.5.1: + resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1870,6 +1976,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + find-index@0.1.1: resolution: {integrity: sha512-uJ5vWrfBKMcE6y2Z8834dwEZj9mNGxYa3t3I53OwFeuZ8D9oc2E5zcsrkuhX6h4iYrjhiv0T3szQmxlAV9uxDg==} @@ -1919,6 +2029,14 @@ packages: resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} engines: {node: '>= 6'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -2079,6 +2197,10 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -2096,6 +2218,10 @@ packages: i18next@21.10.0: resolution: {integrity: sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg==} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + ignore-styles@5.0.1: resolution: {integrity: sha512-gQQmIznCETPLEzfg1UH4Cs2oRq+HBPl8quroEUNXT8oybEG7/0lqI3dGgDSRry6B9HcCXw3PVkFFS0FF3CMddg==} @@ -2139,6 +2265,10 @@ packages: inversify@6.0.2: resolution: {integrity: sha512-i9m8j/7YIv4mDuYXUAcrpKPSaju/CIly9AHK5jvCBeoiM/2KEsuCQTTP+rzSWWpLYWRukdXFSl6ZTk2/uumbiA==} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -2213,6 +2343,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -2433,9 +2566,17 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2463,10 +2604,18 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + minimatch@10.0.1: resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} engines: {node: 20 || >=22} @@ -2518,6 +2667,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} @@ -2569,6 +2722,10 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -2617,6 +2774,10 @@ packages: parse5@8.0.0: resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2640,6 +2801,9 @@ packages: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -2661,6 +2825,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -2691,6 +2859,10 @@ packages: protocols@2.0.2: resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -2702,6 +2874,10 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2714,6 +2890,14 @@ packages: randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + react-autosuggest@10.1.0: resolution: {integrity: sha512-/azBHmc6z/31s/lBf6irxPf/7eejQdR0IqnZUzjdSibtlS8+Rw/R79pgDAo6Ft5QqCUTyEQ+f0FhL+1olDQ8OA==} peerDependencies: @@ -2866,6 +3050,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -2887,6 +3075,9 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sass-embedded-android-arm64@1.87.0: resolution: {integrity: sha512-uqeZoBuXm3W2KhxolScAAfWOLHL21e50g7AxlLmG0he7WZsWw6e9kSnmq301iLIFp4kvmXYXbXbNKAeu9ItRYA==} engines: {node: '>=14.0.0'} @@ -3044,9 +3235,17 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -3059,6 +3258,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shallow-equal@1.2.1: resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==} @@ -3133,6 +3335,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} @@ -3248,6 +3454,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tough-cookie@6.0.0: resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} engines: {node: '>=16'} @@ -3299,6 +3509,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -3381,6 +3595,10 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -3400,6 +3618,10 @@ packages: varint@6.0.0: resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vfile-message@4.0.2: resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} @@ -3600,6 +3822,14 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + + zod@3.24.0: + resolution: {integrity: sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w==} + zustand@4.5.6: resolution: {integrity: sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==} engines: {node: '>=12.7.0'} @@ -4193,6 +4423,22 @@ snapshots: '@microsoft/tsdoc@0.15.1': {} + '@modelcontextprotocol/sdk@1.12.1': + dependencies: + ajv: 6.12.6 + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + express: 5.2.1 + express-rate-limit: 7.5.1(express@5.2.1) + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.24.0 + zod-to-json-schema: 3.25.1(zod@3.24.0) + transitivePeerDependencies: + - supports-color + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4685,6 +4931,11 @@ snapshots: '@yarnpkg/lockfile@1.1.0': {} + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.14.1): dependencies: acorn: 8.14.1 @@ -4857,6 +5108,20 @@ snapshots: dependencies: require-from-string: 2.0.2 + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.0 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -4877,6 +5142,8 @@ snapshots: buffer-from@1.1.2: {} + bytes@3.1.2: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4953,6 +5220,19 @@ snapshots: concat-map@0.0.1: {} + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cpx2@3.0.2: dependencies: co: 4.6.0 @@ -5063,6 +5343,10 @@ snapshots: optionalDependencies: supports-color: 8.1.1 + debug@4.4.3: + dependencies: + ms: 2.1.3 + decamelize@4.0.0: {} decimal.js@10.6.0: {} @@ -5083,6 +5367,8 @@ snapshots: delayed-stream@1.0.0: {} + depd@2.0.0: {} + dequal@2.0.3: {} detect-libc@2.1.2: @@ -5127,12 +5413,16 @@ snapshots: eastasianwidth@0.2.0: {} + ee-first@1.1.1: {} + emoji-regex-xs@1.0.0: {} emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + encodeurl@2.0.0: {} + entities@4.5.0: {} entities@6.0.0: {} @@ -5270,6 +5560,8 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} eslint-config-prettier@10.1.2(eslint@9.25.1): @@ -5473,8 +5765,53 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + expect-type@1.3.0: {} + express-rate-limit@7.5.1(express@5.2.1): + dependencies: + express: 5.2.1 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.0(supports-color@8.1.1) + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.15.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -5505,6 +5842,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.1: + dependencies: + debug: 4.4.0(supports-color@8.1.1) + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-index@0.1.1: {} find-up@5.0.0: @@ -5547,6 +5895,10 @@ snapshots: es-set-tostringtag: 2.1.0 mime-types: 2.1.35 + forwarded@0.2.0: {} + + fresh@2.0.0: {} + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -5741,6 +6093,14 @@ snapshots: html-void-elements@3.0.0: {} + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -5769,6 +6129,10 @@ snapshots: dependencies: '@babel/runtime': 7.27.1 + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ignore-styles@5.0.1: {} ignore@5.3.2: {} @@ -5804,6 +6168,8 @@ snapshots: inversify@6.0.2: optional: true + ipaddr.js@1.9.1: {} + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -5878,6 +6244,8 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-promise@4.0.0: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -6131,8 +6499,12 @@ snapshots: mdurl@2.0.0: {} + media-typer@1.1.0: {} + memoize-one@5.2.1: {} + merge-descriptors@2.0.0: {} + merge2@1.4.1: {} micromark-util-character@2.1.1: @@ -6159,10 +6531,16 @@ snapshots: mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + minimatch@10.0.1: dependencies: brace-expansion: 2.0.1 @@ -6229,6 +6607,8 @@ snapshots: natural-compare@1.4.0: {} + negotiator@1.0.0: {} + node-addon-api@7.1.1: optional: true @@ -6282,6 +6662,10 @@ snapshots: obug@2.1.1: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -6341,6 +6725,8 @@ snapshots: dependencies: entities: 6.0.0 + parseurl@1.3.3: {} + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -6359,6 +6745,8 @@ snapshots: lru-cache: 11.1.0 minipass: 7.1.2 + path-to-regexp@8.3.0: {} + path-type@4.0.0: {} pathe@2.0.3: {} @@ -6371,6 +6759,8 @@ snapshots: picomatch@4.0.3: {} + pkce-challenge@5.0.1: {} + possible-typed-array-names@1.1.0: {} postcss@8.5.6: @@ -6399,12 +6789,21 @@ snapshots: protocols@2.0.2: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: {} punycode.js@2.3.1: {} punycode@2.3.1: {} + qs@6.15.0: + dependencies: + side-channel: 1.1.0 + queue-microtask@1.2.3: {} raf-schd@4.0.3: {} @@ -6417,6 +6816,15 @@ snapshots: dependencies: safe-buffer: 5.2.1 + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + react-autosuggest@10.1.0(react@18.3.1): dependencies: es6-promise: 4.2.8 @@ -6618,6 +7026,16 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 + router@2.2.0: + dependencies: + debug: 4.4.0(supports-color@8.1.1) + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -6647,6 +7065,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safer-buffer@2.1.2: {} + sass-embedded-android-arm64@1.87.0: optional: true @@ -6772,10 +7192,35 @@ snapshots: semver@7.7.1: {} + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -6798,6 +7243,8 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 + setprototypeof@1.2.0: {} + shallow-equal@1.2.1: {} shebang-command@2.0.0: @@ -6877,6 +7324,8 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@3.10.0: {} string-argv@0.3.2: {} @@ -7009,6 +7458,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + tough-cookie@6.0.0: dependencies: tldts: 7.0.22 @@ -7060,6 +7511,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -7158,6 +7615,8 @@ snapshots: universalify@2.0.1: {} + unpipe@1.0.0: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -7175,6 +7634,8 @@ snapshots: varint@6.0.0: optional: true + vary@1.1.2: {} + vfile-message@4.0.2: dependencies: '@types/unist': 3.0.3 @@ -7380,6 +7841,12 @@ snapshots: yocto-queue@0.1.0: {} + zod-to-json-schema@3.25.1(zod@3.24.0): + dependencies: + zod: 3.24.0 + + zod@3.24.0: {} + zustand@4.5.6(@types/react@18.3.20)(immer@10.1.1)(react@18.3.1): dependencies: use-sync-external-store: 1.5.0(react@18.3.1) diff --git a/packages/itwin/map-layers-mcp/src/cli.ts b/packages/itwin/map-layers/src/mcp/cli.ts similarity index 54% rename from packages/itwin/map-layers-mcp/src/cli.ts rename to packages/itwin/map-layers/src/mcp/cli.ts index df82f701b..ced42d389 100644 --- a/packages/itwin/map-layers-mcp/src/cli.ts +++ b/packages/itwin/map-layers/src/mcp/cli.ts @@ -1,18 +1,25 @@ #!/usr/bin/env node /*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +/** * Map Layers MCP Server — CLI entry point * * Starts the MCP server with a stdio transport. This file is the entry point - * for standalone (subprocess) usage. It is NOT imported by the library barrel - * (index.ts), so importing @itwin/map-layers-mcp will not trigger a server. + * for standalone (subprocess) usage. It is NOT imported by the library barrel, + * so importing `@itwin/map-layers` or `@itwin/map-layers/mcp` will not + * trigger a server. * * Usage: - * node dist/cli.js - *--------------------------------------------------------------------------------------------*/ + * node lib/cjs/mcp/cli.js + */ -import { startServer } from "./server.js"; +import { startServer } from "./server"; -startServer().catch((err) => { +startServer().catch((err: unknown) => { console.error("Fatal error starting MCP server:", err); - process.exit(1); + + throw err; }); diff --git a/packages/itwin/map-layers/src/mcp/index.ts b/packages/itwin/map-layers/src/mcp/index.ts new file mode 100644 index 000000000..33106d9b9 --- /dev/null +++ b/packages/itwin/map-layers/src/mcp/index.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +/** + * @itwin/map-layers/mcp — public API surface. + * + * Import from `@itwin/map-layers/mcp` to get the MCP tool functions, + * viewport accessor, and startServer for in-process usage. + */ + +export { + openMapLayersWidget, + toggleBackgroundMap, + setBaseMapType, + setMapTransparency, + toggleTerrain, + getMapLayerInfo, + attachMapLayer, + detachMapLayer, + setMapLayerVisibility, +} from "./tools"; + +export type { MapLayerInfo } from "./tools"; + +export { setViewportAccessor } from "./viewport"; +export { startServer } from "./server"; diff --git a/packages/itwin/map-layers-mcp/src/server.ts b/packages/itwin/map-layers/src/mcp/server.ts similarity index 65% rename from packages/itwin/map-layers-mcp/src/server.ts rename to packages/itwin/map-layers/src/mcp/server.ts index 0393c77c4..ac6e18f8b 100644 --- a/packages/itwin/map-layers-mcp/src/server.ts +++ b/packages/itwin/map-layers/src/mcp/server.ts @@ -1,21 +1,33 @@ /*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +/** * Map Layers MCP Server * * Exposes iTwin.js map-layer operations as MCP tools over stdio transport. - * The server communicates with a host iTwin.js application that provides - * viewport access — the host is expected to inject or relay viewport calls. * * This module only **defines** the server and its tool registrations. - * It does NOT auto-start. Use `startServer()` or the CLI entry point - * (`cli.ts` / `dist/cli.js`) to actually start the stdio transport. - *--------------------------------------------------------------------------------------------*/ + * It does NOT auto-start. Call `startServer()` or use the CLI entry point + * to start the stdio transport. + */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; -import { getViewportAccessor } from "./viewport.js"; - -const _getViewport = getViewportAccessor; +import { getViewportAccessor } from "./viewport"; +import { + attachMapLayer, + detachMapLayer, + getMapLayerInfo, + openMapLayersWidget, + setBaseMapType, + setMapLayerVisibility, + setMapTransparency, + toggleBackgroundMap, + toggleTerrain, +} from "./tools"; // --------------------------------------------------------------------------- // Server @@ -34,11 +46,10 @@ server.tool( {}, async () => { try { - const { openMapLayersWidget } = await import("./tools.js"); - const msg = await openMapLayersWidget(); - return { content: [{ type: "text", text: msg }] }; + const msg = openMapLayersWidget(); + return { content: [{ type: "text" as const, text: msg }] }; } catch (e: any) { - return { content: [{ type: "text", text: JSON.stringify({ action: "open_map_layers_widget" }) }] }; + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "open_map_layers_widget", error: e.message }) }], isError: true }; } }, ); @@ -53,11 +64,10 @@ server.tool( }, async ({ enabled }) => { try { - const { toggleBackgroundMap } = await import("./tools.js"); - const result = toggleBackgroundMap(_getViewport(), enabled); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; + const result = toggleBackgroundMap(getViewportAccessor(), enabled); + return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; } catch (e: any) { - return { content: [{ type: "text", text: JSON.stringify({ action: "toggle_background_map", enabled }) }] }; + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "toggle_background_map", error: e.message }) }], isError: true }; } }, ); @@ -73,11 +83,10 @@ server.tool( }, async ({ type, colorTbgr }) => { try { - const { setBaseMapType } = await import("./tools.js"); - const result = await setBaseMapType(_getViewport(), type, colorTbgr); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; + const result = setBaseMapType(getViewportAccessor(), type, colorTbgr); + return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; } catch (e: any) { - return { content: [{ type: "text", text: JSON.stringify({ action: "set_base_map_type", type, colorTbgr }) }] }; + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "set_base_map_type", error: e.message }) }], isError: true }; } }, ); @@ -92,11 +101,10 @@ server.tool( }, async ({ transparency }) => { try { - const { setMapTransparency } = await import("./tools.js"); - const result = setMapTransparency(_getViewport(), transparency); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; + const result = setMapTransparency(getViewportAccessor(), transparency); + return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; } catch (e: any) { - return { content: [{ type: "text", text: JSON.stringify({ action: "set_map_transparency", transparency }) }] }; + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "set_map_transparency", error: e.message }) }], isError: true }; } }, ); @@ -111,11 +119,10 @@ server.tool( }, async ({ enabled }) => { try { - const { toggleTerrain } = await import("./tools.js"); - const result = toggleTerrain(_getViewport(), enabled); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; + const result = toggleTerrain(getViewportAccessor(), enabled); + return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; } catch (e: any) { - return { content: [{ type: "text", text: JSON.stringify({ action: "toggle_terrain", enabled }) }] }; + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "toggle_terrain", error: e.message }) }], isError: true }; } }, ); @@ -128,11 +135,10 @@ server.tool( {}, async () => { try { - const { getMapLayerInfo } = await import("./tools.js"); - const result = getMapLayerInfo(_getViewport()); - return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; + const result = getMapLayerInfo(getViewportAccessor()); + return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] }; } catch (e: any) { - return { content: [{ type: "text", text: JSON.stringify({ action: "get_map_layer_info" }) }] }; + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "get_map_layer_info", error: e.message }) }], isError: true }; } }, ); @@ -155,13 +161,10 @@ server.tool( }, async ({ url, name, formatId, isOverlay, userName, password }) => { try { - const { attachMapLayer } = await import("./tools.js"); - const result = await attachMapLayer(_getViewport(), url, name, formatId, isOverlay, userName, password); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; + const result = attachMapLayer(getViewportAccessor(), url, name, formatId, isOverlay, userName, password); + return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; } catch (e: any) { - return { - content: [{ type: "text", text: JSON.stringify({ action: "attach_map_layer", url, name, formatId, isOverlay, error: e.message }) }], - }; + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "attach_map_layer", error: e.message }) }], isError: true }; } }, ); @@ -177,13 +180,10 @@ server.tool( }, async ({ name, isOverlay }) => { try { - const { detachMapLayer } = await import("./tools.js"); - const result = detachMapLayer(_getViewport(), name, isOverlay); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; + const result = detachMapLayer(getViewportAccessor(), name, isOverlay); + return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; } catch (e: any) { - return { - content: [{ type: "text", text: JSON.stringify({ action: "detach_map_layer", name, isOverlay, error: e.message }) }], - }; + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "detach_map_layer", error: e.message }) }], isError: true }; } }, ); @@ -200,13 +200,10 @@ server.tool( }, async ({ name, visible, isOverlay }) => { try { - const { setMapLayerVisibility } = await import("./tools.js"); - const result = setMapLayerVisibility(_getViewport(), name, visible, isOverlay); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; + const result = setMapLayerVisibility(getViewportAccessor(), name, visible, isOverlay); + return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; } catch (e: any) { - return { - content: [{ type: "text", text: JSON.stringify({ action: "set_map_layer_visibility", name, visible, isOverlay, error: e.message }) }], - }; + return { content: [{ type: "text" as const, text: JSON.stringify({ action: "set_map_layer_visibility", error: e.message }) }], isError: true }; } }, ); @@ -216,7 +213,7 @@ server.tool( // --------------------------------------------------------------------------- /** Start the MCP server on a stdio transport. */ -export async function startServer() { +export async function startServer(): Promise { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Map Layers MCP server running on stdio"); diff --git a/packages/itwin/map-layers-mcp/src/tools.ts b/packages/itwin/map-layers/src/mcp/tools.ts similarity index 67% rename from packages/itwin/map-layers-mcp/src/tools.ts rename to packages/itwin/map-layers/src/mcp/tools.ts index f36e01ef6..b27bb50ef 100644 --- a/packages/itwin/map-layers-mcp/src/tools.ts +++ b/packages/itwin/map-layers/src/mcp/tools.ts @@ -1,19 +1,25 @@ /*--------------------------------------------------------------------------------------------- - * Map Layers MCP Tool Functions - * - * These functions operate on an iTwin.js ScreenViewport. They are designed to - * be called from within a running iTwin.js application where `IModelApp` is - * initialized and a viewport is available. - * - * The functions accept a generic viewport reference (typed as `any` to avoid - * hard dependency on @itwin/core-frontend in the MCP server package). The host - * application is responsible for providing the correctly-typed ScreenViewport. + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -// We intentionally use `any` for viewport types so the MCP server package -// does not require @itwin/core-frontend or @itwin/core-common as dependencies. -// The host iTwin.js application that wires up the viewport accessor will have -// these types available at runtime. +/** + * MCP tool functions that operate on an iTwin.js ScreenViewport. + * + * These are pure functions designed to be called from the MCP server handlers + * or directly from an in-process consumer. Since this file lives inside the + * `@itwin/map-layers` package it has direct access to iTwin.js types. + */ + +import type { ScreenViewport } from "@itwin/core-frontend"; +import { + BackgroundMapProvider, + BackgroundMapType, + BaseMapLayerSettings, + ColorDef, +} from "@itwin/core-common"; +import { MapLayerSource } from "@itwin/core-frontend"; +import { UiFramework, WidgetState } from "@itwin/appui-react"; // ── Types ──────────────────────────────────────────────────────────────────── @@ -24,31 +30,34 @@ export interface MapLayerInfo { transparency: number; isOverlay: boolean; layerIndex: number; - subLayers?: any[]; + subLayers?: unknown[]; } // ── Helpers ────────────────────────────────────────────────────────────────── -function getViewport(vp: any): any { +function requireViewport(vp: ScreenViewport | undefined): ScreenViewport { if (!vp) { throw new Error("No active viewport available."); } return vp; } -function gatherLayers(vp: any, isOverlay: boolean): MapLayerInfo[] { +function gatherLayers(vp: ScreenViewport, isOverlay: boolean): MapLayerInfo[] { const layers = isOverlay ? vp.displayStyle.settings.mapImagery.overlayLayers : vp.displayStyle.settings.mapImagery.backgroundLayers; - return layers.map((layer: any, idx: number) => ({ + return layers.map((layer, idx) => ({ name: layer.name, - source: layer.source ?? layer.modelId ?? "", + source: (layer as any).source ?? (layer as any).modelId ?? "", visible: layer.visible, transparency: layer.transparency, isOverlay, layerIndex: idx, - subLayers: layer.subLayers?.map((s: any) => (typeof s.toJSON === "function" ? s.toJSON() : s)) ?? undefined, + subLayers: + (layer as any).subLayers?.map((s: any) => + typeof s.toJSON === "function" ? s.toJSON() : s, + ) ?? undefined, })); } @@ -56,13 +65,12 @@ function gatherLayers(vp: any, isOverlay: boolean): MapLayerInfo[] { /** * Opens (activates) the Map Layers widget in the frontstage. - * Must run in an iTwin.js environment where `@itwin/appui-react` is loaded. */ -export async function openMapLayersWidget(): Promise { - // Dynamic import so this module is only required at runtime inside iTwin.js - const { UiFramework } = await import("@itwin/appui-react" as string); +export function openMapLayersWidget(): string { const widgetId = "map-layers:mapLayersWidget"; - UiFramework.frontstages.activeFrontstageDef?.findWidgetDef(widgetId)?.setWidgetState(1 /* WidgetState.Open */); + UiFramework.frontstages.activeFrontstageDef + ?.findWidgetDef(widgetId) + ?.setWidgetState(WidgetState.Open); return `Map Layers widget opened (id: ${widgetId}).`; } @@ -72,10 +80,10 @@ export async function openMapLayersWidget(): Promise { * Toggles background map visibility on/off, or sets it to a specific state. */ export function toggleBackgroundMap( - vp: any, + vp: ScreenViewport | undefined, enabled?: boolean, ): { backgroundMapEnabled: boolean } { - const viewport = getViewport(vp); + const viewport = requireViewport(vp); const newState = enabled ?? !viewport.viewFlags.backgroundMap; viewport.viewFlags = viewport.viewFlags.with("backgroundMap", newState); return { backgroundMapEnabled: newState }; @@ -84,34 +92,35 @@ export function toggleBackgroundMap( // ── 3. set_base_map_type ───────────────────────────────────────────────────── /** - * Sets the base map to one of the well-known Bing providers: - * "aerial" | "hybrid" | "street" - * Or to a solid color fill when type is "color" + optional colorDef (TBGR integer). + * Sets the base map to one of the well-known Bing providers or a solid color. */ -export async function setBaseMapType( - vp: any, +export function setBaseMapType( + vp: ScreenViewport | undefined, type: "aerial" | "hybrid" | "street" | "color", colorTbgr?: number, -): Promise<{ baseMap: string }> { - const coreCommon = await import("@itwin/core-common" as string); - const { BackgroundMapProvider, BackgroundMapType, BaseLayerSettings, ColorDef } = coreCommon; - - const viewport = getViewport(vp); +): { baseMap: string } { + const viewport = requireViewport(vp); if (type === "color") { - const color = colorTbgr !== undefined ? ColorDef.fromJSON(colorTbgr) : ColorDef.fromJSON(0); + const color = + colorTbgr !== undefined + ? ColorDef.fromJSON(colorTbgr) + : ColorDef.fromJSON(0); viewport.displayStyle.backgroundMapBase = color; return { baseMap: `color (TBGR: ${color.toJSON()})` }; } - const bgTypeMap: Record = { + const bgTypeMap: Record = { aerial: BackgroundMapType.Aerial, hybrid: BackgroundMapType.Hybrid, street: BackgroundMapType.Street, }; const bgType = bgTypeMap[type] ?? BackgroundMapType.Hybrid; - const provider = BackgroundMapProvider.fromJSON({ name: "BingProvider", type: bgType }); - const settings = BaseLayerSettings.fromProvider(provider); + const provider = BackgroundMapProvider.fromJSON({ + name: "BingProvider", + type: bgType, + }); + const settings = BaseMapLayerSettings.fromProvider(provider); viewport.displayStyle.backgroundMapBase = settings; return { baseMap: type }; } @@ -122,10 +131,10 @@ export async function setBaseMapType( * Sets the background map transparency (0 = fully opaque, 1 = fully transparent). */ export function setMapTransparency( - vp: any, + vp: ScreenViewport | undefined, transparency: number, ): { transparency: number } { - const viewport = getViewport(vp); + const viewport = requireViewport(vp); const clamped = Math.max(0, Math.min(1, transparency)); viewport.changeBackgroundMapProps({ transparency: clamped }); return { transparency: clamped }; @@ -137,11 +146,13 @@ export function setMapTransparency( * Toggles terrain display on/off, or sets it to a specific state. */ export function toggleTerrain( - vp: any, + vp: ScreenViewport | undefined, enabled?: boolean, ): { terrainEnabled: boolean } { - const viewport = getViewport(vp); - const currentlyEnabled = viewport.view?.getDisplayStyle3d?.()?.settings?.backgroundMap?.applyTerrain ?? false; + const viewport = requireViewport(vp); + const currentlyEnabled = + (viewport.view as any)?.getDisplayStyle3d?.()?.settings?.backgroundMap + ?.applyTerrain ?? false; const newState = enabled ?? !currentlyEnabled; viewport.changeBackgroundMapProps({ applyTerrain: newState }); return { terrainEnabled: newState }; @@ -152,10 +163,12 @@ export function toggleTerrain( /** * Returns information about all attached map layers (both background and overlay). */ -export function getMapLayerInfo( - vp: any, -): { backgroundLayers: MapLayerInfo[]; overlayLayers: MapLayerInfo[]; backgroundMapEnabled: boolean } { - const viewport = getViewport(vp); +export function getMapLayerInfo(vp: ScreenViewport | undefined): { + backgroundLayers: MapLayerInfo[]; + overlayLayers: MapLayerInfo[]; + backgroundMapEnabled: boolean; +} { + const viewport = requireViewport(vp); const backgroundLayers = gatherLayers(viewport, false); const overlayLayers = gatherLayers(viewport, true); return { @@ -169,26 +182,17 @@ export function getMapLayerInfo( /** * Attaches a new map layer to the viewport by URL. - * - * @param url The layer service URL (WMS, WMTS, ArcGIS, TileURL, etc.) - * @param name Display name for the layer - * @param formatId Format identifier: "WMS", "WMTS", "ArcGIS", "ArcGISFeature", "TileURL" - * @param isOverlay If true the layer is added as an overlay; otherwise as a background layer - * @param userName Optional credentials - * @param password Optional credentials */ -export async function attachMapLayer( - vp: any, +export function attachMapLayer( + vp: ScreenViewport | undefined, url: string, name: string, formatId?: string, isOverlay?: boolean, userName?: string, password?: string, -): Promise<{ attached: boolean; name: string; isOverlay: boolean }> { - const { MapLayerSource } = await import("@itwin/core-frontend" as string); - - const viewport = getViewport(vp); +): { attached: boolean; name: string; isOverlay: boolean } { + const viewport = requireViewport(vp); const overlay = isOverlay ?? false; const source = MapLayerSource.fromJSON({ @@ -223,11 +227,11 @@ export async function attachMapLayer( * If multiple layers match, all are detached. */ export function detachMapLayer( - vp: any, + vp: ScreenViewport | undefined, name: string, isOverlay?: boolean, ): { detached: string[] } { - const viewport = getViewport(vp); + const viewport = requireViewport(vp); const detached: string[] = []; const tryDetach = (overlay: boolean) => { @@ -238,8 +242,13 @@ export function detachMapLayer( // Iterate in reverse so index removal is safe for (let i = layers.length - 1; i >= 0; i--) { if (layers[i].name === name) { - viewport.displayStyle.detachMapLayerByIndex({ index: i, isOverlay: overlay }); - detached.push(`${name} (${overlay ? "overlay" : "background"}, index ${i})`); + viewport.displayStyle.detachMapLayerByIndex({ + index: i, + isOverlay: overlay, + }); + detached.push( + `${name} (${overlay ? "overlay" : "background"}, index ${i})`, + ); } } }; @@ -263,12 +272,12 @@ export function detachMapLayer( * Sets the visibility of a specific map layer identified by name. */ export function setMapLayerVisibility( - vp: any, + vp: ScreenViewport | undefined, name: string, visible: boolean, isOverlay?: boolean, ): { name: string; visible: boolean; updated: number } { - const viewport = getViewport(vp); + const viewport = requireViewport(vp); let updated = 0; const trySetVisibility = (overlay: boolean) => { diff --git a/packages/itwin/map-layers/src/mcp/viewport.ts b/packages/itwin/map-layers/src/mcp/viewport.ts new file mode 100644 index 000000000..180892ea6 --- /dev/null +++ b/packages/itwin/map-layers/src/mcp/viewport.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +/** + * Shared viewport accessor used by both the MCP server and tool functions. + * This module has no side effects and is safe to import from any context. + */ + +import type { ScreenViewport } from "@itwin/core-frontend"; + +type ViewportAccessor = () => ScreenViewport | undefined; +let _getViewport: ViewportAccessor = () => undefined; + +/** Set the viewport accessor so MCP tool functions can reach the live viewport. */ +export function setViewportAccessor(fn: ViewportAccessor): void { + _getViewport = fn; +} + +/** Get the current viewport via the registered accessor. */ +export function getViewportAccessor(): ScreenViewport | undefined { + return _getViewport(); +} From b39d0e2fa3ea753683c522e4299911674f9fefa6 Mon Sep 17 00:00:00 2001 From: Jean-Luc Deziel Date: Thu, 19 Feb 2026 12:20:13 -0500 Subject: [PATCH 04/12] fix zod version --- packages/itwin/map-layers/pnpm-lock.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/itwin/map-layers/pnpm-lock.yaml b/packages/itwin/map-layers/pnpm-lock.yaml index ccfee3ac1..d1a023810 100644 --- a/packages/itwin/map-layers/pnpm-lock.yaml +++ b/packages/itwin/map-layers/pnpm-lock.yaml @@ -22,8 +22,8 @@ importers: specifier: ^13.1.1 version: 13.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) zod: - specifier: ^3.24.0 - version: 3.24.0 + specifier: ^3.25.0 + version: 3.25.0 devDependencies: '@itwin/appui-abstract': specifier: ^5.0.0 @@ -3827,8 +3827,8 @@ packages: peerDependencies: zod: ^3.25 || ^4 - zod@3.24.0: - resolution: {integrity: sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w==} + zod@3.25.0: + resolution: {integrity: sha512-ficnZKUW0mlNivqeJkosTEkGbJ6NKCtSaOHGx5aXbtfeWMdRyzXLbAIn19my4C/KB7WPY/p9vlGPt+qpOp6c4Q==} zustand@4.5.6: resolution: {integrity: sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==} @@ -4434,8 +4434,8 @@ snapshots: express-rate-limit: 7.5.1(express@5.2.1) pkce-challenge: 5.0.1 raw-body: 3.0.2 - zod: 3.24.0 - zod-to-json-schema: 3.25.1(zod@3.24.0) + zod: 3.25.0 + zod-to-json-schema: 3.25.1(zod@3.25.0) transitivePeerDependencies: - supports-color @@ -7841,11 +7841,11 @@ snapshots: yocto-queue@0.1.0: {} - zod-to-json-schema@3.25.1(zod@3.24.0): + zod-to-json-schema@3.25.1(zod@3.25.0): dependencies: - zod: 3.24.0 + zod: 3.25.0 - zod@3.24.0: {} + zod@3.25.0: {} zustand@4.5.6(@types/react@18.3.20)(immer@10.1.1)(react@18.3.1): dependencies: From b18f0de09f7d3c1e8517fd664a0e8be53867e611 Mon Sep 17 00:00:00 2001 From: Jean-Luc Deziel Date: Thu, 19 Feb 2026 12:29:32 -0500 Subject: [PATCH 05/12] zod version fix 2 --- packages/itwin/map-layers/package.json | 2 +- packages/itwin/map-layers/pnpm-lock.yaml | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/itwin/map-layers/package.json b/packages/itwin/map-layers/package.json index 7a34969ad..8e6d3e547 100644 --- a/packages/itwin/map-layers/package.json +++ b/packages/itwin/map-layers/package.json @@ -56,7 +56,7 @@ "@itwin/itwinui-icons-react": "^2.10.0", "@modelcontextprotocol/sdk": "^1.12.1", "react-beautiful-dnd": "^13.1.1", - "zod": "^3.25.0" + "zod": "~3.24.0" }, "devDependencies": { "@itwin/appui-abstract": "^5.0.0", diff --git a/packages/itwin/map-layers/pnpm-lock.yaml b/packages/itwin/map-layers/pnpm-lock.yaml index d1a023810..d9a9229c1 100644 --- a/packages/itwin/map-layers/pnpm-lock.yaml +++ b/packages/itwin/map-layers/pnpm-lock.yaml @@ -22,8 +22,8 @@ importers: specifier: ^13.1.1 version: 13.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) zod: - specifier: ^3.25.0 - version: 3.25.0 + specifier: ~3.24.0 + version: 3.24.0 devDependencies: '@itwin/appui-abstract': specifier: ^5.0.0 @@ -3827,8 +3827,8 @@ packages: peerDependencies: zod: ^3.25 || ^4 - zod@3.25.0: - resolution: {integrity: sha512-ficnZKUW0mlNivqeJkosTEkGbJ6NKCtSaOHGx5aXbtfeWMdRyzXLbAIn19my4C/KB7WPY/p9vlGPt+qpOp6c4Q==} + zod@3.24.0: + resolution: {integrity: sha512-Hz+wiY8yD0VLA2k/+nsg2Abez674dDGTai33SwNvMPuf9uIrBC9eFgIMQxBBbHFxVXi8W+5nX9DcAh9YNSQm/w==} zustand@4.5.6: resolution: {integrity: sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==} @@ -4434,8 +4434,8 @@ snapshots: express-rate-limit: 7.5.1(express@5.2.1) pkce-challenge: 5.0.1 raw-body: 3.0.2 - zod: 3.25.0 - zod-to-json-schema: 3.25.1(zod@3.25.0) + zod: 3.24.0 + zod-to-json-schema: 3.25.1(zod@3.24.0) transitivePeerDependencies: - supports-color @@ -7841,11 +7841,11 @@ snapshots: yocto-queue@0.1.0: {} - zod-to-json-schema@3.25.1(zod@3.25.0): + zod-to-json-schema@3.25.1(zod@3.24.0): dependencies: - zod: 3.25.0 + zod: 3.24.0 - zod@3.25.0: {} + zod@3.24.0: {} zustand@4.5.6(@types/react@18.3.20)(immer@10.1.1)(react@18.3.1): dependencies: From 80530c5b68d191a79728333131fce29febbd3b24 Mon Sep 17 00:00:00 2001 From: Jean-Luc Deziel Date: Thu, 19 Feb 2026 12:36:16 -0500 Subject: [PATCH 06/12] changelog --- ...in-map-layers-2eb91816-915b-4597-b434-ae4c0b597ea9.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@itwin-map-layers-2eb91816-915b-4597-b434-ae4c0b597ea9.json diff --git a/change/@itwin-map-layers-2eb91816-915b-4597-b434-ae4c0b597ea9.json b/change/@itwin-map-layers-2eb91816-915b-4597-b434-ae4c0b597ea9.json new file mode 100644 index 000000000..d8b0a9300 --- /dev/null +++ b/change/@itwin-map-layers-2eb91816-915b-4597-b434-ae4c0b597ea9.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Added MCP (Model Context Protocol) server exposing map-layer operations as MCP tools over stdio transport.", + "packageName": "@itwin/map-layers", + "email": "jean-luc.deziel@bentley.com", + "dependentChangeType": "patch" +} From 0d1dd12b2bf4c9d23d261c2def91f34faad010a6 Mon Sep 17 00:00:00 2001 From: Jean-Luc Deziel Date: Thu, 19 Feb 2026 12:58:01 -0500 Subject: [PATCH 07/12] fix tests --- packages/itwin/map-layers/src/mcp/cli.ts | 3 ++- packages/itwin/map-layers/src/mcp/server.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/itwin/map-layers/src/mcp/cli.ts b/packages/itwin/map-layers/src/mcp/cli.ts index ced42d389..907e1bba2 100644 --- a/packages/itwin/map-layers/src/mcp/cli.ts +++ b/packages/itwin/map-layers/src/mcp/cli.ts @@ -19,7 +19,8 @@ import { startServer } from "./server"; startServer().catch((err: unknown) => { + // eslint-disable-next-line no-console console.error("Fatal error starting MCP server:", err); - + throw err; }); diff --git a/packages/itwin/map-layers/src/mcp/server.ts b/packages/itwin/map-layers/src/mcp/server.ts index ac6e18f8b..fdb79191e 100644 --- a/packages/itwin/map-layers/src/mcp/server.ts +++ b/packages/itwin/map-layers/src/mcp/server.ts @@ -216,5 +216,6 @@ server.tool( export async function startServer(): Promise { const transport = new StdioServerTransport(); await server.connect(transport); + // eslint-disable-next-line no-console console.error("Map Layers MCP server running on stdio"); } From b45efee530c0a6354150fb051aa4de679a55cdef Mon Sep 17 00:00:00 2001 From: Jean-Luc Deziel Date: Thu, 19 Feb 2026 14:03:31 -0500 Subject: [PATCH 08/12] remove start server function --- packages/itwin/map-layers/package.json | 3 --- packages/itwin/map-layers/src/mcp/cli.ts | 26 --------------------- packages/itwin/map-layers/src/mcp/index.ts | 4 ++-- packages/itwin/map-layers/src/mcp/server.ts | 17 +------------- 4 files changed, 3 insertions(+), 47 deletions(-) delete mode 100644 packages/itwin/map-layers/src/mcp/cli.ts diff --git a/packages/itwin/map-layers/package.json b/packages/itwin/map-layers/package.json index 8e6d3e547..2ef69f59f 100644 --- a/packages/itwin/map-layers/package.json +++ b/packages/itwin/map-layers/package.json @@ -17,9 +17,6 @@ "types": "./lib/cjs/mcp/index.d.ts" } }, - "bin": { - "map-layers-mcp": "lib/cjs/mcp/cli.js" - }, "license": "MIT", "scripts": { "build": "npm run -s copy:locale && npm run -s build:cjs && npm run -s build:esm", diff --git a/packages/itwin/map-layers/src/mcp/cli.ts b/packages/itwin/map-layers/src/mcp/cli.ts deleted file mode 100644 index 907e1bba2..000000000 --- a/packages/itwin/map-layers/src/mcp/cli.ts +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env node -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Bentley Systems, Incorporated. All rights reserved. - * See LICENSE.md in the project root for license terms and full copyright notice. - *--------------------------------------------------------------------------------------------*/ - -/** - * Map Layers MCP Server — CLI entry point - * - * Starts the MCP server with a stdio transport. This file is the entry point - * for standalone (subprocess) usage. It is NOT imported by the library barrel, - * so importing `@itwin/map-layers` or `@itwin/map-layers/mcp` will not - * trigger a server. - * - * Usage: - * node lib/cjs/mcp/cli.js - */ - -import { startServer } from "./server"; - -startServer().catch((err: unknown) => { - // eslint-disable-next-line no-console - console.error("Fatal error starting MCP server:", err); - - throw err; -}); diff --git a/packages/itwin/map-layers/src/mcp/index.ts b/packages/itwin/map-layers/src/mcp/index.ts index 33106d9b9..f153d0799 100644 --- a/packages/itwin/map-layers/src/mcp/index.ts +++ b/packages/itwin/map-layers/src/mcp/index.ts @@ -7,7 +7,7 @@ * @itwin/map-layers/mcp — public API surface. * * Import from `@itwin/map-layers/mcp` to get the MCP tool functions, - * viewport accessor, and startServer for in-process usage. + * viewport accessor, and MCP server instance. */ export { @@ -25,4 +25,4 @@ export { export type { MapLayerInfo } from "./tools"; export { setViewportAccessor } from "./viewport"; -export { startServer } from "./server"; +export { server } from "./server"; diff --git a/packages/itwin/map-layers/src/mcp/server.ts b/packages/itwin/map-layers/src/mcp/server.ts index fdb79191e..d1f3ea24a 100644 --- a/packages/itwin/map-layers/src/mcp/server.ts +++ b/packages/itwin/map-layers/src/mcp/server.ts @@ -9,12 +9,9 @@ * Exposes iTwin.js map-layer operations as MCP tools over stdio transport. * * This module only **defines** the server and its tool registrations. - * It does NOT auto-start. Call `startServer()` or use the CLI entry point - * to start the stdio transport. */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { getViewportAccessor } from "./viewport"; import { @@ -33,7 +30,7 @@ import { // Server // --------------------------------------------------------------------------- -const server = new McpServer({ +export const server = new McpServer({ name: "map-layers-mcp", version: "1.0.0", }); @@ -207,15 +204,3 @@ server.tool( } }, ); - -// --------------------------------------------------------------------------- -// Start -// --------------------------------------------------------------------------- - -/** Start the MCP server on a stdio transport. */ -export async function startServer(): Promise { - const transport = new StdioServerTransport(); - await server.connect(transport); - // eslint-disable-next-line no-console - console.error("Map Layers MCP server running on stdio"); -} From 209638e90a08c363e74b008c1ca77b81d72f8860 Mon Sep 17 00:00:00 2001 From: jean-luc-deziel Date: Thu, 19 Mar 2026 13:14:26 -0400 Subject: [PATCH 09/12] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- packages/itwin/map-layers/src/mcp/server.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/itwin/map-layers/src/mcp/server.ts b/packages/itwin/map-layers/src/mcp/server.ts index d1f3ea24a..1ddaeffd0 100644 --- a/packages/itwin/map-layers/src/mcp/server.ts +++ b/packages/itwin/map-layers/src/mcp/server.ts @@ -30,9 +30,14 @@ import { // Server // --------------------------------------------------------------------------- +const SERVER_VERSION = + process.env.MAP_LAYERS_MCP_VERSION ?? + process.env.npm_package_version ?? + "0.0.0"; + export const server = new McpServer({ name: "map-layers-mcp", - version: "1.0.0", + version: SERVER_VERSION, }); // ── 1. open_map_layers_widget ──────────────────────────────────────────────── From 097fbfc57f3e7e43aff17bdb2d678eaa3514e288 Mon Sep 17 00:00:00 2001 From: Jean-Luc Deziel Date: Thu, 19 Mar 2026 15:15:46 -0400 Subject: [PATCH 10/12] fix? --- packages/itwin/map-layers/src/mcp/server.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/itwin/map-layers/src/mcp/server.ts b/packages/itwin/map-layers/src/mcp/server.ts index 1ddaeffd0..5dbe8d377 100644 --- a/packages/itwin/map-layers/src/mcp/server.ts +++ b/packages/itwin/map-layers/src/mcp/server.ts @@ -3,6 +3,8 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ +declare const process: { env: Record }; + /** * Map Layers MCP Server * From c7c8f93234202af7291e5f11c4c1f2a8176e7da5 Mon Sep 17 00:00:00 2001 From: Jean-Luc Deziel Date: Fri, 20 Mar 2026 09:20:03 -0400 Subject: [PATCH 11/12] Remove stdio transport mentions --- .../@itwin-map-layers-2eb91816-915b-4597-b434-ae4c0b597ea9.json | 2 +- packages/itwin/map-layers/src/mcp/server.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/change/@itwin-map-layers-2eb91816-915b-4597-b434-ae4c0b597ea9.json b/change/@itwin-map-layers-2eb91816-915b-4597-b434-ae4c0b597ea9.json index d8b0a9300..e07ea575e 100644 --- a/change/@itwin-map-layers-2eb91816-915b-4597-b434-ae4c0b597ea9.json +++ b/change/@itwin-map-layers-2eb91816-915b-4597-b434-ae4c0b597ea9.json @@ -1,6 +1,6 @@ { "type": "minor", - "comment": "Added MCP (Model Context Protocol) server exposing map-layer operations as MCP tools over stdio transport.", + "comment": "Added MCP (Model Context Protocol) server exposing map-layer operations as MCP tools.", "packageName": "@itwin/map-layers", "email": "jean-luc.deziel@bentley.com", "dependentChangeType": "patch" diff --git a/packages/itwin/map-layers/src/mcp/server.ts b/packages/itwin/map-layers/src/mcp/server.ts index 5dbe8d377..433fb6a35 100644 --- a/packages/itwin/map-layers/src/mcp/server.ts +++ b/packages/itwin/map-layers/src/mcp/server.ts @@ -8,7 +8,7 @@ declare const process: { env: Record }; /** * Map Layers MCP Server * - * Exposes iTwin.js map-layer operations as MCP tools over stdio transport. + * Exposes iTwin.js map-layer operations as MCP tools. * * This module only **defines** the server and its tool registrations. */ From ade01978dfdfe801d8e14a45c11d2f1a850e8b4e Mon Sep 17 00:00:00 2001 From: Jean-Luc Deziel Date: Fri, 20 Mar 2026 10:29:03 -0400 Subject: [PATCH 12/12] remove attach map layer tool for security concern --- packages/itwin/map-layers/src/mcp/index.ts | 2 +- packages/itwin/map-layers/src/mcp/server.ts | 55 +++++++++++---------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/packages/itwin/map-layers/src/mcp/index.ts b/packages/itwin/map-layers/src/mcp/index.ts index f153d0799..bbb7478eb 100644 --- a/packages/itwin/map-layers/src/mcp/index.ts +++ b/packages/itwin/map-layers/src/mcp/index.ts @@ -17,7 +17,7 @@ export { setMapTransparency, toggleTerrain, getMapLayerInfo, - attachMapLayer, + // attachMapLayer, // DISABLED — SSRF & credential-leakage risk (see server.ts) detachMapLayer, setMapLayerVisibility, } from "./tools"; diff --git a/packages/itwin/map-layers/src/mcp/server.ts b/packages/itwin/map-layers/src/mcp/server.ts index 433fb6a35..0c286dd58 100644 --- a/packages/itwin/map-layers/src/mcp/server.ts +++ b/packages/itwin/map-layers/src/mcp/server.ts @@ -17,7 +17,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { getViewportAccessor } from "./viewport"; import { - attachMapLayer, + // attachMapLayer, // DISABLED — see attach_map_layer tool comment below detachMapLayer, getMapLayerInfo, openMapLayersWidget, @@ -148,30 +148,35 @@ server.tool( ); // ── 7. attach_map_layer ────────────────────────────────────────────────────── - -server.tool( - "attach_map_layer", - "Attaches a new map layer to the viewport by URL. Supports WMS, WMTS, ArcGIS, ArcGISFeature, and TileURL formats.", - { - url: z.string().describe("The map layer service URL."), - name: z.string().describe("Display name for the layer."), - formatId: z - .enum(["WMS", "WMTS", "ArcGIS", "ArcGISFeature", "TileURL"]) - .optional() - .describe("The map service format. Defaults to 'WMS'."), - isOverlay: z.boolean().optional().describe("If true, attach as overlay; otherwise as background layer. Default: false."), - userName: z.string().optional().describe("Optional username for authenticated layers."), - password: z.string().optional().describe("Optional password for authenticated layers."), - }, - async ({ url, name, formatId, isOverlay, userName, password }) => { - try { - const result = attachMapLayer(getViewportAccessor(), url, name, formatId, isOverlay, userName, password); - return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; - } catch (e: any) { - return { content: [{ type: "text" as const, text: JSON.stringify({ action: "attach_map_layer", error: e.message }) }], isError: true }; - } - }, -); +// DISABLED: This tool accepts an arbitrary URL and plaintext credentials, which +// poses SSRF and credential-leakage risks when the MCP server is exposed remotely. +// Before re-enabling, add URL allowlisting/validation (block private IPs, file:// +// schemes, etc.), sanitize error messages so they never echo back the raw URL or +// credentials, and consider a credential-store reference instead of inline secrets. +// +// server.tool( +// "attach_map_layer", +// "Attaches a new map layer to the viewport by URL. Supports WMS, WMTS, ArcGIS, ArcGISFeature, and TileURL formats.", +// { +// url: z.string().describe("The map layer service URL."), +// name: z.string().describe("Display name for the layer."), +// formatId: z +// .enum(["WMS", "WMTS", "ArcGIS", "ArcGISFeature", "TileURL"]) +// .optional() +// .describe("The map service format. Defaults to 'WMS'."), +// isOverlay: z.boolean().optional().describe("If true, attach as overlay; otherwise as background layer. Default: false."), +// userName: z.string().optional().describe("Optional username for authenticated layers."), +// password: z.string().optional().describe("Optional password for authenticated layers."), +// }, +// async ({ url, name, formatId, isOverlay, userName, password }) => { +// try { +// const result = attachMapLayer(getViewportAccessor(), url, name, formatId, isOverlay, userName, password); +// return { content: [{ type: "text" as const, text: JSON.stringify(result) }] }; +// } catch (e: any) { +// return { content: [{ type: "text" as const, text: JSON.stringify({ action: "attach_map_layer", error: e.message }) }], isError: true }; +// } +// }, +// ); // ── 8. detach_map_layer ──────────────────────────────────────────────────────