Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 26 additions & 30 deletions examples/map-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,44 +153,40 @@ export function createServer(): McpServer {
description:
"Display an interactive world map zoomed to a specific bounding box. Use the GeoCode tool to find the bounding box of a location. The widget is interactive and exposes tools for navigation (fly to locations) and querying the current view.",
inputSchema: z.object({
west: z
.number()
.optional()
.default(-0.5)
.describe("Western longitude (-180 to 180)"),
south: z
.number()
.optional()
.default(51.3)
.describe("Southern latitude (-90 to 90)"),
east: z
.number()
.optional()
.default(0.3)
.describe("Eastern longitude (-180 to 180)"),
north: z
.number()
.optional()
.default(51.7)
.describe("Northern latitude (-90 to 90)"),
west: z.number().optional().describe("Western longitude (-180 to 180)"),
south: z.number().optional().describe("Southern latitude (-90 to 90)"),
east: z.number().optional().describe("Eastern longitude (-180 to 180)"),
north: z.number().optional().describe("Northern latitude (-90 to 90)"),
label: z
.string()
.optional()
.describe("Optional label to display on the map"),
}),
_meta: { ui: { resourceUri } },
},
async ({ west, south, east, north, label }): Promise<CallToolResult> => ({
content: [
{
type: "text",
text: `Displaying globe at: W:${west.toFixed(4)}, S:${south.toFixed(4)}, E:${east.toFixed(4)}, N:${north.toFixed(4)}${label ? ` (${label})` : ""}`,
async ({ west, south, east, north, label }): Promise<CallToolResult> => {
// Determine what to log based on whether coordinates were provided
const hasCoords =
west !== undefined &&
south !== undefined &&
east !== undefined &&
north !== undefined;
const text = hasCoords
? `Displaying globe at: W:${west.toFixed(4)}, S:${south.toFixed(4)}, E:${east.toFixed(4)}, N:${north.toFixed(4)}${label ? ` (${label})` : ""}`
: `Displaying globe at default location${label ? ` (${label})` : ""}`;

return {
content: [
{
type: "text",
text,
},
],
_meta: {
viewUUID: randomUUID(),
},
],
_meta: {
viewUUID: randomUUID(),
},
}),
};
},
);

// geocode tool - searches for places using Nominatim (no UI)
Expand Down
87 changes: 71 additions & 16 deletions examples/map-server/src/mcp-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,38 @@ async function loadCesium(): Promise<void> {
script.onload = () => {
// Set CESIUM_BASE_URL for asset loading
(window as any).CESIUM_BASE_URL = CESIUM_BASE_URL;

const CGlobal = (window as any).Cesium;
if (CGlobal?.TaskProcessor) {
// Run terrain geometry tasks synchronously on the main thread for sandboxed iframes
const origSchedule = CGlobal.TaskProcessor.prototype.scheduleTask;
CGlobal.TaskProcessor.prototype.scheduleTask = function (params: any) {
if (this._workerPath?.includes("transferTypedArrayTest")) {
return Promise.resolve(new ArrayBuffer(1));
}
if (this._workerPath?.includes("createVerticesFromHeightmap")) {
try {
const D = { ...params };
if (D.ellipsoid)
D.ellipsoid = CGlobal.Ellipsoid.clone(D.ellipsoid);
if (D.rectangle)
D.rectangle = CGlobal.Rectangle.clone(D.rectangle);
const r = CGlobal.HeightmapTessellator.computeVertices(D);
return Promise.resolve({
...r,
vertices: r.vertices.buffer,
numberOfAttributes: r.encoding.stride,
gridWidth: D.width,
gridHeight: D.height,
});
} catch (err) {
log.error("Sync computeVertices error:", err);
}
}
return origSchedule.call(this, params);
};
}

resolve();
};
script.onerror = () =>
Expand Down Expand Up @@ -373,6 +405,9 @@ async function initCesium(): Promise<any> {
sceneModePicker: false,
navigationHelpButton: false,
fullscreenButton: false,
// Disable Knockout.js UI widgets to avoid EvalError under strict CSP without 'unsafe-eval'
infoBox: false,
selectionIndicator: false,
// Disable terrain (requires Ion)
terrainProvider: undefined,
// WebGL context options for sandboxed iframe rendering
Expand Down Expand Up @@ -831,11 +866,18 @@ app.ontoolinput = async (params) => {
| undefined;

if (args && viewer) {
// Handle both nested boundingBox and flat format
const DEFAULT_BBOX: BoundingBox = {
west: -0.5,
south: 51.3,
east: 0.3,
north: 51.7,
}; // London
let bbox: BoundingBox | null = null;

let isExplicitInput = false;
// 1. Check if model provided explicit coordinates
if (args.boundingBox) {
bbox = args.boundingBox;
isExplicitInput = true;
} else if (
args.west !== undefined &&
args.south !== undefined &&
Expand All @@ -848,27 +890,40 @@ app.ontoolinput = async (params) => {
east: args.east,
north: args.north,
};
isExplicitInput = true;
}

if (bbox) {
// Mark that we received explicit tool input (overrides persisted state)
if (isExplicitInput && bbox) {
// Model told us exactly where to look
hasReceivedToolInput = true;
log.info("Positioning camera to bbox:", bbox);

// Position camera instantly (no animation)
log.info("Positioning camera to explicit bbox:", bbox);
setViewToBoundingBox(viewer, bbox);
} else {
// Model didn't provide coordinates (just requested to open the map).
// The View determines its own default state.

// First try to restore persisted user state
if (restorePersistedView(viewer)) {
hasReceivedToolInput = true; // Count restoration as having an initial position
log.info("Restored persisted view on startup");
} else {
// Fallback to the app's default home location (London)
hasReceivedToolInput = true; // We've positioned the camera
log.info("Using default app home location (London)");
setViewToBoundingBox(viewer, DEFAULT_BBOX);
}
}

// Wait for tiles to load at this location
await waitForTilesLoaded(viewer);
// Wait for tiles to load at the new location
await waitForTilesLoaded(viewer);

// Now hide loading indicator
hideLoading();
// Now hide loading indicator
hideLoading();

log.info(
"Camera positioned, tiles loaded. Height:",
viewer.camera.positionCartographic.height,
);
}
log.info(
"Camera positioned, tiles loaded. Height:",
viewer.camera.positionCartographic.height,
);
}
};

Expand Down