feat(foundry): close the operational map data loop
This commit is contained in:
+358
-3
@@ -122,6 +122,261 @@ function normalizeMcpError(error) {
|
||||
return { code: "foundry_operation_failed", status: 500 };
|
||||
}
|
||||
|
||||
const mapPresentationProfileInputSchema = {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: [
|
||||
"id", "version", "title", "semanticTypes", "label", "target", "facets",
|
||||
"styles", "classes", "defaultClassId", "sort",
|
||||
],
|
||||
properties: {
|
||||
id: { type: "string", description: "Stable provider-neutral map.style_profile id." },
|
||||
version: { type: "string", pattern: "^\\d+\\.\\d+\\.\\d+$" },
|
||||
title: { type: "string" },
|
||||
semanticTypes: {
|
||||
type: "array",
|
||||
minItems: 1,
|
||||
maxItems: 8,
|
||||
uniqueItems: true,
|
||||
items: { type: "string" },
|
||||
},
|
||||
label: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: [
|
||||
"mode", "fields", "fontWeight", "sizePx", "color", "outlineColor",
|
||||
"outlineWidthPx", "backgroundColor", "backgroundOpacity", "paddingX", "paddingY",
|
||||
"maxLength", "offsetX", "offsetY", "hideCameraHeightMeters",
|
||||
],
|
||||
properties: {
|
||||
mode: { type: "string", enum: ["subject_id", "attributes", "none"] },
|
||||
fields: {
|
||||
type: "array",
|
||||
minItems: 1,
|
||||
maxItems: 16,
|
||||
uniqueItems: true,
|
||||
description: "Provider-neutral fact attributes in fallback order, for example display_name, label, name, title.",
|
||||
items: { type: "string" },
|
||||
},
|
||||
fontWeight: { type: "integer", minimum: 400, maximum: 700 },
|
||||
sizePx: { type: "number", minimum: 8, maximum: 32 },
|
||||
color: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
|
||||
outlineColor: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
|
||||
outlineWidthPx: { type: "number", minimum: 0, maximum: 6 },
|
||||
backgroundColor: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
|
||||
backgroundOpacity: { type: "number", minimum: 0, maximum: 1 },
|
||||
paddingX: { type: "number", minimum: 0, maximum: 40 },
|
||||
paddingY: { type: "number", minimum: 0, maximum: 40 },
|
||||
maxLength: { type: "integer", minimum: 8, maximum: 240 },
|
||||
offsetX: { type: "number", minimum: -100, maximum: 100 },
|
||||
offsetY: { type: "number", minimum: -100, maximum: 100 },
|
||||
hideCameraHeightMeters: { type: "number", minimum: 1, maximum: 100000000 },
|
||||
},
|
||||
},
|
||||
target: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: [
|
||||
"variant", "stemHeightMeters", "headSizePx", "stemWidthPx", "outlineColor",
|
||||
"outlineOpacity", "outlineWidthPx", "hideCameraHeightMeters",
|
||||
],
|
||||
properties: {
|
||||
variant: {
|
||||
type: "string",
|
||||
enum: ["elevated-spike"],
|
||||
description: "Renderer-neutral presentation preset interpreted by the active Map adapter.",
|
||||
},
|
||||
stemHeightMeters: { type: "number", minimum: 1, maximum: 100000 },
|
||||
headSizePx: { type: "number", minimum: 1, maximum: 64 },
|
||||
stemWidthPx: { type: "number", minimum: 0.25, maximum: 16 },
|
||||
outlineColor: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
|
||||
outlineOpacity: { type: "number", minimum: 0, maximum: 1 },
|
||||
outlineWidthPx: { type: "number", minimum: 0, maximum: 8 },
|
||||
hideCameraHeightMeters: { type: "number", minimum: 1, maximum: 100000000 },
|
||||
},
|
||||
},
|
||||
facets: {
|
||||
type: "array",
|
||||
minItems: 1,
|
||||
maxItems: 16,
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["id", "field", "label", "filterable", "counter", "values"],
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
field: { type: "string", description: "Normalized provider-neutral Data Product field." },
|
||||
label: { type: "string" },
|
||||
filterable: { type: "boolean" },
|
||||
counter: { type: "boolean" },
|
||||
values: {
|
||||
type: "array",
|
||||
minItems: 1,
|
||||
maxItems: 32,
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["value", "label", "order"],
|
||||
properties: {
|
||||
value: { type: "string" },
|
||||
label: { type: "string" },
|
||||
order: { type: "integer", minimum: 0, maximum: 1000 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
styles: {
|
||||
type: "array",
|
||||
minItems: 1,
|
||||
maxItems: 32,
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["id", "color", "opacity"],
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
color: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
|
||||
opacity: { type: "number", minimum: 0, maximum: 1 },
|
||||
},
|
||||
},
|
||||
},
|
||||
classes: {
|
||||
type: "array",
|
||||
minItems: 1,
|
||||
maxItems: 64,
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["id", "label", "priority", "match", "styleId", "renderable"],
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
label: { type: "string" },
|
||||
priority: { type: "integer", minimum: -10000, maximum: 10000 },
|
||||
match: {
|
||||
type: "array",
|
||||
maxItems: 8,
|
||||
description: "All conditions use declared normalized facet fields; an empty list is the fallback class.",
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["field", "equals"],
|
||||
properties: { field: { type: "string" }, equals: { type: "string" } },
|
||||
},
|
||||
},
|
||||
styleId: { type: "string" },
|
||||
renderable: { type: "boolean" },
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultClassId: { type: "string" },
|
||||
sort: {
|
||||
type: "array",
|
||||
maxItems: 16,
|
||||
items: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["field", "order"],
|
||||
properties: {
|
||||
field: { type: "string" },
|
||||
order: { type: "array", minItems: 1, uniqueItems: true, items: { type: "string" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mapPageSettingsPatchInputSchema = {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
minProperties: 1,
|
||||
properties: {
|
||||
imagerySource: { type: "string" },
|
||||
imageryVisible: { type: "boolean" },
|
||||
cacheEnabled: { type: "boolean" },
|
||||
cacheNoOverwrite: { type: "boolean" },
|
||||
terrainEnabled: { type: "boolean" },
|
||||
terrainExaggeration: { type: "number" },
|
||||
monochrome: { type: "boolean" },
|
||||
monochromeColor: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
|
||||
imageryGamma: { type: "number" },
|
||||
imageryHue: { type: "number" },
|
||||
imageryAlpha: { type: "number" },
|
||||
globeColor: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
|
||||
backgroundColor: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
|
||||
atmosphereEnabled: { type: "boolean" },
|
||||
atmosphereHue: { type: "number" },
|
||||
atmosphereSaturation: { type: "number" },
|
||||
atmosphereBrightness: { type: "number" },
|
||||
fogEnabled: { type: "boolean" },
|
||||
fogDensity: { type: "number" },
|
||||
sunEnabled: { type: "boolean" },
|
||||
sunHour: { type: "number" },
|
||||
sunIntensity: { type: "number" },
|
||||
shadowsEnabled: { type: "boolean" },
|
||||
buildingsVisible: { type: "boolean" },
|
||||
buildingsColor: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
|
||||
buildingsOpacity: { type: "number" },
|
||||
buildingsDetail: { type: "number" },
|
||||
imageryBrightness: { type: "number" },
|
||||
imageryContrast: { type: "number" },
|
||||
imagerySaturation: { type: "number" },
|
||||
gridVisible: { type: "boolean" },
|
||||
gridLodEnabled: { type: "boolean" },
|
||||
gridHeightMeters: { type: "number" },
|
||||
gridLod1MaxHeightKm: { type: "number" },
|
||||
gridLod1StepKm: { type: "number" },
|
||||
gridLod2MaxHeightKm: { type: "number" },
|
||||
gridLod2StepKm: { type: "number" },
|
||||
gridLod3StepKm: { type: "number" },
|
||||
gridRadiusKm: { type: "number" },
|
||||
gridLineWidth: { type: "number" },
|
||||
gridColor: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
|
||||
gridOpacity: { type: "number" },
|
||||
gridDotsEnabled: { type: "boolean" },
|
||||
gridDotsSize: { type: "number" },
|
||||
gridDotsColor: { type: "string", pattern: "^#[0-9A-Fa-f]{6}$" },
|
||||
gridDotsOpacity: { type: "number" },
|
||||
},
|
||||
};
|
||||
|
||||
const mapSubjectStateInputSchema = {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["bindingId", "visible", "filters", "window"],
|
||||
properties: {
|
||||
bindingId: { type: "string", description: "Stable Map Data Product binding id. Editable labels are never state keys." },
|
||||
visible: { type: "boolean", description: "False is an explicit empty map state and must not be normalized to all." },
|
||||
filters: {
|
||||
type: "object",
|
||||
description: "Facet selections keyed by normalized field. Missing field is unconstrained; an empty array matches nothing.",
|
||||
additionalProperties: { type: "array", uniqueItems: true, items: { type: "string" } },
|
||||
},
|
||||
window: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["open", "rect", "maximized", "zIndex"],
|
||||
properties: {
|
||||
open: { type: "boolean" },
|
||||
rect: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["x", "y", "width", "height"],
|
||||
properties: {
|
||||
x: { type: "number" },
|
||||
y: { type: "number" },
|
||||
width: { type: "number", minimum: 200 },
|
||||
height: { type: "number", minimum: 160 },
|
||||
},
|
||||
},
|
||||
maximized: { type: "boolean" },
|
||||
zIndex: { type: "integer", minimum: 1 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const tools = [
|
||||
{
|
||||
name: "foundry_status",
|
||||
@@ -138,7 +393,7 @@ const tools = [
|
||||
{
|
||||
name: "foundry_get_application",
|
||||
title: "Get application instance",
|
||||
description: "Read one editable application instance, its page instances, features and map pin bindings.",
|
||||
description: "Read one editable application instance, its page instances, features, map presentation profiles and bindings.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
@@ -245,6 +500,95 @@ const tools = [
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "foundry_remove_map_pin_binding",
|
||||
title: "Remove map pin binding",
|
||||
description: "Remove one obsolete visual map-pin binding from a Map Page instance. The operation does not affect data-product consumers or the canonical Page Library.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["applicationId", "pageId", "bindingId", "idempotencyKey"],
|
||||
properties: {
|
||||
applicationId: { type: "string" },
|
||||
pageId: { type: "string", description: "Map Page instance id inside the application." },
|
||||
bindingId: { type: "string", description: "Stable id of the obsolete visual map-pin binding." },
|
||||
idempotencyKey: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "foundry_upsert_map_presentation_profile",
|
||||
title: "Upsert Map presentation profile",
|
||||
description: "Create or update a versioned provider-neutral Map presentation profile. The profile owns target geometry, state classes, filters, counters, sort order and labels; it contains no provider transport or credentials.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["applicationId", "pageId", "idempotencyKey", "profile"],
|
||||
properties: {
|
||||
applicationId: { type: "string" },
|
||||
pageId: { type: "string", description: "Map Page instance id inside the application." },
|
||||
idempotencyKey: { type: "string" },
|
||||
profile: {
|
||||
...mapPresentationProfileInputSchema,
|
||||
description: "Versioned provider-neutral map.style_profile. Classification uses declared normalized facets, never provider-specific raw status values.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "foundry_update_map_page_settings",
|
||||
title: "Update Application Map settings",
|
||||
description: "Update visual environment settings on one Map page instance inside one Application. This operation never modifies the canonical Page Library template.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["applicationId", "pageId", "idempotencyKey", "settings"],
|
||||
properties: {
|
||||
applicationId: { type: "string" },
|
||||
pageId: { type: "string", description: "Map page instance id inside the editable Application." },
|
||||
idempotencyKey: { type: "string" },
|
||||
settings: mapPageSettingsPatchInputSchema,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "foundry_save_map_page_view_state",
|
||||
title: "Save Application Map view state",
|
||||
description: "Persist one complete current Map view through the Application Save path: camera, map geometry, base settings, exact subject visibility/facets and every binding window geometry/z-order. Stable identity is bindingId.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["applicationId", "pageId", "idempotencyKey", "viewState"],
|
||||
properties: {
|
||||
applicationId: { type: "string" },
|
||||
pageId: { type: "string", description: "Map Page instance id inside the editable Application." },
|
||||
idempotencyKey: { type: "string" },
|
||||
viewState: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["mapHeight", "camera", "subjectStates"],
|
||||
properties: {
|
||||
settings: mapPageSettingsPatchInputSchema,
|
||||
mapHeight: { type: "integer", minimum: 360, maximum: 5000 },
|
||||
camera: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
required: ["longitude", "latitude", "height", "heading", "pitch", "roll"],
|
||||
properties: {
|
||||
longitude: { type: "number" },
|
||||
latitude: { type: "number" },
|
||||
height: { type: "number" },
|
||||
heading: { type: "number" },
|
||||
pitch: { type: "number" },
|
||||
roll: { type: "number" },
|
||||
},
|
||||
},
|
||||
subjectStates: { type: "array", maxItems: 64, items: mapSubjectStateInputSchema },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "foundry_upsert_map_data_product_binding",
|
||||
title: "Upsert Map data product binding",
|
||||
@@ -262,10 +606,13 @@ const tools = [
|
||||
required: ["id", "dataProductId", "slotId", "semanticTypes"],
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
displayName: { type: "string", minLength: 1, maxLength: 120, description: "User-defined text label shown in the Objects menu and window header." },
|
||||
order: { type: "integer", minimum: 0, maximum: 10000, description: "Application composition order inside the Objects menu." },
|
||||
dataProductId: { type: "string", description: "Versioned provider-neutral product, for example fleet.positions.current.v1." },
|
||||
slotId: { type: "string", description: "Approved Map Page entity-stream slot." },
|
||||
semanticTypes: { type: "array", items: { type: "string" } },
|
||||
fieldProjection: { type: "array", items: { type: "string" } },
|
||||
presentationProfileId: { type: "string", description: "Existing page-owned provider-neutral Map presentation profile id." },
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -363,6 +710,10 @@ function toolMap(operations) {
|
||||
foundry_update_application_metadata: (input, actor) => operations.updateApplicationMetadata(input, actor),
|
||||
foundry_add_page_instance: (input, actor) => operations.addPageInstance(input, actor),
|
||||
foundry_upsert_map_pin_binding: (input, actor) => operations.upsertMapPinBinding(input, actor),
|
||||
foundry_remove_map_pin_binding: (input, actor) => operations.removeMapPinBinding(input, actor),
|
||||
foundry_upsert_map_presentation_profile: (input, actor) => operations.upsertMapPresentationProfile(input, actor),
|
||||
foundry_update_map_page_settings: (input, actor) => operations.updateMapPageSettings(input, actor),
|
||||
foundry_save_map_page_view_state: (input, actor) => operations.saveMapPageViewState(input, actor),
|
||||
foundry_upsert_map_data_product_binding: (input, actor) => operations.upsertMapDataProductBinding(input, actor),
|
||||
foundry_plan_map_data_product_consumer: (input) => operations.planMapDataProductConsumer(input),
|
||||
foundry_apply_map_data_product_consumer: (input, actor) => operations.applyMapDataProductConsumer(input, actor),
|
||||
@@ -405,8 +756,8 @@ export async function handleFoundryMcpRequest(request, response, options) {
|
||||
return sendJson(response, 200, mcpResult(id, {
|
||||
protocolVersion: MCP_PROTOCOL_VERSION,
|
||||
capabilities: { tools: { listChanged: false } },
|
||||
serverInfo: { name: "nodedc_module_foundry", version: "0.2.0" },
|
||||
instructions: "NDC Module Foundry edits application instances and controls approved server-owned data-product consumers. Page Library is read-only. Application deletion is unavailable. Provider endpoints and credentials are never MCP inputs or outputs.",
|
||||
serverInfo: { name: "nodedc_module_foundry", version: "0.5.0" },
|
||||
instructions: "NDC Module Foundry edits application instances, provider-neutral map presentation profiles and approved server-owned data-product consumers. Page Library is read-only. Application deletion is unavailable. Provider endpoints and credentials are never MCP inputs or outputs.",
|
||||
}));
|
||||
}
|
||||
if (message.method === "notifications/initialized") return sendJson(response, 202, {});
|
||||
@@ -477,6 +828,10 @@ export async function handleFoundryEntitlementRequest(request, response, options
|
||||
"foundry.application.update",
|
||||
"foundry.page-instance.create",
|
||||
"foundry.map-pin.upsert",
|
||||
"foundry.map-pin.remove",
|
||||
"foundry.map-presentation-profile.upsert",
|
||||
"foundry.map-page-settings.update",
|
||||
"foundry.map-page-view-state.save",
|
||||
"foundry.map-data-product.upsert",
|
||||
"foundry.map-data-product-consumer.read",
|
||||
"foundry.map-data-product-consumer.lifecycle",
|
||||
|
||||
Reference in New Issue
Block a user