refactor: name the view tool show_in_gods_eye_view

The tool and its panel now say "Show in God's Eye View" rather than
"show on the globe", in tool names, descriptions, server instructions and
docs.
This commit is contained in:
Sameh Khamis committed 2026-10-02 15:21:36 -07:00
1 parent 216a75ac71
commit 47ef168815
12 files changed
+48 -44

No files matched your search

+4 -4
View File
@@ -1,11 +1,11 @@
# God's Eye View Current State # God's Eye View Current State
## The globe in conversations — October 2, 2026 ## God's Eye View in conversations — October 2, 2026
Tool answers that can be seen on the globe include a view: camera, layers, Tool answers that can be shown in God's Eye View include a view: camera, layers,
style, map, marks and an aircraft or satellite to follow, written in the style, map, marks and an aircraft or satellite to follow, written in the
share-link format (`gods-eye-view/view`). `show_on_globe` shows a view as the share-link format (`gods-eye-view/view`). `show_in_gods_eye_view` shows a view as live
live globe inside clients that display MCP Apps, and as a link everywhere. God's Eye View inside clients that display MCP Apps, and as a link everywhere.
`?embed=1` shows the app as the globe alone and takes new views from the page `?embed=1` shows the app as the globe alone and takes new views from the page
that frames it. MCP leads with the tools that find what to show. See that frames it. MCP leads with the tools that find what to show. See
[tools and the MCP server](TOOLS.md). [tools and the MCP server](TOOLS.md).
+3 -3
View File
@@ -104,9 +104,9 @@ tilted camera over an area looks at its center from behind; camera fields
given with an area override its framing. `resolveViewArguments` turns them given with an area override its framing. `resolveViewArguments` turns them
into a view. into a view.
### The globe panel ### The God's Eye View panel
`show_on_globe` names an MCP Apps view (`io.modelcontextprotocol/ui`): `show_in_gods_eye_view` names an MCP Apps view (`io.modelcontextprotocol/ui`):
`_meta.ui.resourceUri` is `ui://gods-eye-view/globe`, a `text/html;profile=mcp-app` `_meta.ui.resourceUri` is `ui://gods-eye-view/globe`, a `text/html;profile=mcp-app`
resource from `createGlobePanelResource({ appUrl })` in resource from `createGlobePanelResource({ appUrl })` in
`src/tools/globePanel.js`. Clients that display apps render it inside the `src/tools/globePanel.js`. Clients that display apps render it inside the
@@ -250,4 +250,4 @@ public feeds the sources already use.
| `situation_brief` | `weather` | Weather, earthquakes, fires, aircraft, ships and cyclones for an area, by section | | `situation_brief` | `weather` | Weather, earthquakes, fires, aircraft, ships and cyclones for an area, by section |
| `military_awareness` | `military` | Military and other aircraft, ships and military installations within 250 km of a point, by section | | `military_awareness` | `military` | Military and other aircraft, ships and military installations within 250 km of a point, by section |
| `get_hud_caption` | `weather`, `summary` | The app's heads-up display caption for an area | | `get_hud_caption` | `weather`, `summary` | The app's heads-up display caption for an area |
| `show_on_globe` | `app` | A view on the globe: the live globe panel in clients with MCP Apps, and a link everywhere; takes another answer's view or an area or camera, layers, style, map, marks and something to follow | | `show_in_gods_eye_view` | `app` | A view in God's Eye View: the live panel in clients with MCP Apps, and a link everywhere; takes another answer's view or an area or camera, layers, style, map, marks and something to follow |
+3 -3
View File
@@ -14,9 +14,9 @@ const INSTRUCTIONS =
"Tools answer questions from God's Eye View's live public data. Location " + "Tools answer questions from God's Eye View's live public data. Location " +
'tools take an area: a place name, a bbox, or lat/lon with radius_km. ' + 'tools take an area: a place name, a bbox, or lat/lon with radius_km. ' +
'Results are capped; check truncated and total before concluding there is nothing more. ' + 'Results are capped; check truncated and total before concluding there is nothing more. ' +
'Answers that can be seen on the globe include data.view; to show one, call ' + "Answers that can be shown in God's Eye View include data.view; to show one, call " +
'show_on_globe with that view, adding layers, style, a camera or marks as ' + 'show_in_gods_eye_view with that view, adding layers, style, a camera or marks as ' +
'needed. It shows the live globe where the client displays apps and ' + "needed. It shows live God's Eye View where the client displays apps and " +
'returns a link everywhere.'; 'returns a link everywhere.';
/** Construct the local MCP server for Core's tools. */ /** Construct the local MCP server for Core's tools. */
+1 -1
View File
@@ -220,7 +220,7 @@ test('Realtime sessions carry supplied tools, and the standalone voice adds the
); );
assert.ok(names.includes('get_weather')); assert.ok(names.includes('get_weather'));
assert.ok(names.includes('military_awareness')); assert.ok(names.includes('military_awareness'));
assert.ok(!names.includes('show_on_globe')); assert.ok(!names.includes('show_in_gods_eye_view'));
assert.ok(!names.includes('aircraft_in_area')); assert.ok(!names.includes('aircraft_in_area'));
assert.ok(!names.includes('get_weather_map')); assert.ok(!names.includes('get_weather_map'));
// The action of the same name answers satellite passes. // The action of the same name answers satellite passes.
+2 -2
View File
@@ -4,10 +4,10 @@ import { coreTools, toFunctionOutput, toFunctionTools } from './index.js';
test('catalog tools become independent function records', () => { test('catalog tools become independent function records', () => {
const records = toFunctionTools(coreTools, { const records = toFunctionTools(coreTools, {
exclude: ['show_on_globe'], exclude: ['show_in_gods_eye_view'],
}); });
assert.equal(records.length, coreTools.length - 1); assert.equal(records.length, coreTools.length - 1);
assert.ok(!records.some((tool) => tool.name === 'show_on_globe')); assert.ok(!records.some((tool) => tool.name === 'show_in_gods_eye_view'));
const weather = records.find((tool) => tool.name === 'get_weather'); const weather = records.find((tool) => tool.name === 'get_weather');
const source = coreTools.find((tool) => tool.name === 'get_weather'); const source = coreTools.find((tool) => tool.name === 'get_weather');
assert.deepEqual(Object.keys(weather), [ assert.deepEqual(Object.keys(weather), [
+5 -5
View File
@@ -1,7 +1,7 @@
/** /**
* The globe panel: an MCP Apps view (`io.modelcontextprotocol/ui`) that shows * The God's Eye View panel: an MCP Apps view (`io.modelcontextprotocol/ui`) that shows
* God's Eye View inside a conversation. The panel is a small page that loads * God's Eye View inside a conversation. The panel is a small page that loads
* the app in embed mode and sends it each view the show_on_globe tool * the app in embed mode and sends it each view the show_in_gods_eye_view tool
* returns; see docs/TOOLS.md. * returns; see docs/TOOLS.md.
*/ */
@@ -33,7 +33,7 @@ function panelHtml(appOrigin) {
</style> </style>
</head> </head>
<body> <body>
<div id="status">Waiting for a view of the globe…</div> <div id="status">Waiting for a view…</div>
<button id="open" type="button" hidden>Open in God's Eye View</button> <button id="open" type="button" hidden>Open in God's Eye View</button>
<script> <script>
(() => { (() => {
@@ -82,7 +82,7 @@ function panelHtml(appOrigin) {
frame.allow = 'fullscreen'; frame.allow = 'fullscreen';
frame.src = embedUrl(url); frame.src = embedUrl(url);
document.body.appendChild(frame); document.body.appendChild(frame);
status.textContent = 'Loading the globe…'; status.textContent = "Loading God's Eye View…";
return; return;
} }
if (frameReady) postView(view); if (frameReady) postView(view);
@@ -148,7 +148,7 @@ export function createGlobePanelResource({ appUrl }) {
uri: GLOBE_PANEL_URI, uri: GLOBE_PANEL_URI,
name: 'globe', name: 'globe',
title: "God's Eye View globe", title: "God's Eye View globe",
description: 'The live globe, showing the view a tool returns.', description: "Live God's Eye View, showing the view a tool returns.",
mimeType: MCP_APP_MIME_TYPE, mimeType: MCP_APP_MIME_TYPE,
text: panelHtml(appOrigin), text: panelHtml(appOrigin),
_meta: { _meta: {
+4 -4
View File
@@ -23,12 +23,12 @@ test('the globe panel is an MCP Apps resource that may frame only the app', () =
assert.match(resource.text, /protocolVersion: "2026-01-26"/); assert.match(resource.text, /protocolVersion: "2026-01-26"/);
}); });
test('show_on_globe names the panel and shows a view another answer returned', async () => { test('show_in_gods_eye_view names the panel and shows a view another answer returned', async () => {
const catalog = composeCatalog({ const catalog = composeCatalog({
tools: coreTools, tools: coreTools,
services: { app: { baseUrl: 'http://localhost:5173/' } }, services: { app: { baseUrl: 'http://localhost:5173/' } },
}); });
assert.deepEqual(catalog.get('show_on_globe').ui, { assert.deepEqual(catalog.get('show_in_gods_eye_view').ui, {
resourceUri: GLOBE_PANEL_URI, resourceUri: GLOBE_PANEL_URI,
}); });
const earlier = { const earlier = {
@@ -46,7 +46,7 @@ test('show_on_globe names the panel and shows a view another answer returned', a
annotations: [], annotations: [],
url: 'http://localhost:5173/#v=2', url: 'http://localhost:5173/#v=2',
}; };
const shown = await catalog.call('show_on_globe', { const shown = await catalog.call('show_in_gods_eye_view', {
view: earlier, view: earlier,
layers: ['ais-live-vessels', 'military'], layers: ['ais-live-vessels', 'military'],
style: 'thermal', style: 'thermal',
@@ -55,7 +55,7 @@ test('show_on_globe names the panel and shows a view another answer returned', a
assert.deepEqual(shown.data.view.layers, ['ais-live-vessels', 'military']); assert.deepEqual(shown.data.view.layers, ['ais-live-vessels', 'military']);
assert.equal(shown.data.view.style, 'thermal'); assert.equal(shown.data.view.style, 'thermal');
await assert.rejects( await assert.rejects(
catalog.call('show_on_globe', { view: { camera: { lat: 'x' } } }), catalog.call('show_in_gods_eye_view', { view: { camera: { lat: 'x' } } }),
(error) => error.code === 'invalid_arguments', (error) => error.code === 'invalid_arguments',
); );
}); });
+2 -2
View File
@@ -40,7 +40,7 @@ import {
getTransitVehicles, getTransitVehicles,
} from './queries/mobility.js'; } from './queries/mobility.js';
import { placesNearby, planRoute, searchPlaces } from './queries/places.js'; import { placesNearby, planRoute, searchPlaces } from './queries/places.js';
import { showOnGlobe } from './queries/share.js'; import { showInGodsEyeView } from './queries/share.js';
import { findAlprCameras } from './queries/surveillance.js'; import { findAlprCameras } from './queries/surveillance.js';
import { import {
findCctvCameras, findCctvCameras,
@@ -121,5 +121,5 @@ export const coreTools = Object.freeze([
situationBrief, situationBrief,
militaryAwareness, militaryAwareness,
getHudCaption, getHudCaption,
showOnGlobe, showInGodsEyeView,
]); ]);
+9 -9
View File
@@ -77,16 +77,16 @@ function appBase(services) {
} }
} }
export const showOnGlobe = defineTool({ export const showInGodsEyeView = defineTool({
name: 'show_on_globe', name: 'show_in_gods_eye_view',
title: 'Show on the globe', title: "Show in God's Eye View",
description: description:
"Shows a view of God's Eye View's globe: in clients that display apps, " + "Shows a view in God's Eye View: in clients that display apps, the live " +
'the live globe in the conversation; everywhere, a link that opens it. ' + "God's Eye View globe in the conversation; everywhere, a link that opens " +
'Pass the view another answer returned, or describe one: an area framed from above ' + 'it. Pass the view another answer returned, or describe one: an area ' +
'or a camera position, with chosen data layers, visual style and map, ' + 'framed from above or a camera position, with chosen data layers, visual ' +
'optionally following an aircraft or satellite and with marks drawn on ' + 'style and map, optionally following an aircraft or satellite and with ' +
'the globe.', 'marks drawn on the map.',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: VIEW_ARGUMENTS, properties: VIEW_ARGUMENTS,
+9 -9
View File
@@ -7,7 +7,7 @@ test('links open the app looking straight down on the area', async () => {
tools: coreTools, tools: coreTools,
services: { app: { baseUrl: 'https://maps.example/view?x=1' } }, services: { app: { baseUrl: 'https://maps.example/view?x=1' } },
}); });
const result = await catalog.call('show_on_globe', { const result = await catalog.call('show_in_gods_eye_view', {
area: { lat: 30.2672, lon: -97.7431, radius_km: 5 }, area: { lat: 30.2672, lon: -97.7431, radius_km: 5 },
}); });
const url = new URL(result.data.url); const url = new URL(result.data.url);
@@ -35,11 +35,11 @@ test('altitude is bounded for tiny and planet-sized areas', async () => {
tools: coreTools, tools: coreTools,
services: { app: { baseUrl: 'http://localhost:4173/' } }, services: { app: { baseUrl: 'http://localhost:4173/' } },
}); });
const tiny = await catalog.call('show_on_globe', { const tiny = await catalog.call('show_in_gods_eye_view', {
area: { lat: 0, lon: 0, radius_km: 0.1 }, area: { lat: 0, lon: 0, radius_km: 0.1 },
}); });
assert.equal(tiny.data.view.camera.altitude_m, 500); assert.equal(tiny.data.view.camera.altitude_m, 500);
const world = await catalog.call('show_on_globe', { const world = await catalog.call('show_in_gods_eye_view', {
area: { bbox: [-180, -85, 180, 85] }, area: { bbox: [-180, -85, 180, 85] },
}); });
assert.equal(world.data.view.camera.altitude_m, 15000000); assert.equal(world.data.view.camera.altitude_m, 15000000);
@@ -48,7 +48,7 @@ test('altitude is bounded for tiny and planet-sized areas', async () => {
services: { app: { baseUrl: 'not a url' } }, services: { app: { baseUrl: 'not a url' } },
}); });
await assert.rejects( await assert.rejects(
broken.call('show_on_globe', { broken.call('show_in_gods_eye_view', {
area: { lat: 0, lon: 0, radius_km: 1 }, area: { lat: 0, lon: 0, radius_km: 1 },
}), }),
(error) => error.code === 'unavailable', (error) => error.code === 'unavailable',
@@ -61,7 +61,7 @@ test('links turn on the requested layers with the app share-link codec', async (
tools: coreTools, tools: coreTools,
services: { app: { baseUrl: 'http://localhost:4173/' } }, services: { app: { baseUrl: 'http://localhost:4173/' } },
}); });
const result = await catalog.call('show_on_globe', { const result = await catalog.call('show_in_gods_eye_view', {
area: { lat: 30.27, lon: -97.74, radius_km: 10 }, area: { lat: 30.27, lon: -97.74, radius_km: 10 },
layers: ['earthquakes', 'flights', 'flights'], layers: ['earthquakes', 'flights', 'flights'],
}); });
@@ -72,7 +72,7 @@ test('links turn on the requested layers with the app share-link codec', async (
'flights', 'flights',
]); ]);
await assert.rejects( await assert.rejects(
catalog.call('show_on_globe', { catalog.call('show_in_gods_eye_view', {
area: { lat: 0, lon: 0, radius_km: 1 }, area: { lat: 0, lon: 0, radius_km: 1 },
layers: ['not-a-layer'], layers: ['not-a-layer'],
}), }),
@@ -86,7 +86,7 @@ test('links carry a camera, style, map and an entity to follow', async () => {
tools: coreTools, tools: coreTools,
services: { app: { baseUrl: 'http://localhost:4173/' } }, services: { app: { baseUrl: 'http://localhost:4173/' } },
}); });
const result = await catalog.call('show_on_globe', { const result = await catalog.call('show_in_gods_eye_view', {
area: { lat: 25, lon: 121, radius_km: 200 }, area: { lat: 25, lon: 121, radius_km: 200 },
camera: { pitch_deg: -40, heading_deg: 350 }, camera: { pitch_deg: -40, heading_deg: 350 },
layers: ['ais-live-vessels'], layers: ['ais-live-vessels'],
@@ -103,13 +103,13 @@ test('links carry a camera, style, map and an entity to follow', async () => {
id: 'ae1234', id: 'ae1234',
}); });
assert.equal(result.data.view.camera.pitch_deg, -40); assert.equal(result.data.view.camera.pitch_deg, -40);
const camera = await catalog.call('show_on_globe', { const camera = await catalog.call('show_in_gods_eye_view', {
camera: { lat: 48.8584, lon: 2.2945, altitude_m: 1200, pitch_deg: -30 }, camera: { lat: 48.8584, lon: 2.2945, altitude_m: 1200, pitch_deg: -30 },
}); });
assert.equal(camera.data.view.camera.altitude_m, 1200); assert.equal(camera.data.view.camera.altitude_m, 1200);
assert.match(camera.summary, /^Open 48\.858, 2\.295 in God's Eye View: /); assert.match(camera.summary, /^Open 48\.858, 2\.295 in God's Eye View: /);
await assert.rejects( await assert.rejects(
catalog.call('show_on_globe', { layers: ['flights'] }), catalog.call('show_in_gods_eye_view', { layers: ['flights'] }),
/Give a view, an area, or a camera with lat and lon/, /Give a view, an area, or a camera with lat and lon/,
); );
}); });
+1 -1
View File
@@ -15,7 +15,7 @@ const mcpOff = Object.freeze({ mcp: false });
export const TOOL_SURFACES = Object.freeze({ export const TOOL_SURFACES = Object.freeze({
// Voice runs inside the app, so a link to it adds nothing. // Voice runs inside the app, so a link to it adds nothing.
show_on_globe: voiceOff, show_in_gods_eye_view: voiceOff,
// Voice has an app action of the same name. // Voice has an app action of the same name.
next_satellite_pass: voiceOff, next_satellite_pass: voiceOff,
// Voice receives text only, so tools that answer with an image are off. // Voice receives text only, so tools that answer with an image are off.
+5 -1
View File
@@ -20,7 +20,11 @@ test('each surface offers the tools the table does not turn off', () => {
assert.deepEqual(offered, expected, surface); assert.deepEqual(offered, expected, surface);
} }
const mcp = names(toolsForSurface(coreTools, 'mcp')); const mcp = names(toolsForSurface(coreTools, 'mcp'));
for (const name of ['show_on_globe', 'situation_brief', 'aircraft_in_area']) for (const name of [
'show_in_gods_eye_view',
'situation_brief',
'aircraft_in_area',
])
assert.ok(mcp.includes(name), name); assert.ok(mcp.includes(name), name);
for (const name of [ for (const name of [
'search_places', 'search_places',