NODEDC_DESIGN_GUIDELINE/docs/MODULE_FOUNDRY_MCP.md

304 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# NDC Module Foundry — базовый MCP-контур
## Назначение
`NDC Module Foundry` — модульный контур поверх канонического `NODE.DC Design Guideline`. Он создаёт и редактирует **экземпляры** готовых страниц в `Applications`; сам `Page Library` и его шаблоны остаются неизменяемым каноном.
MCP не вводит новую оркестрацию. Доступ выдаётся уже существующему AI Workspace Assistant через его generic entitlement adapter. Поэтому ассистент может продолжать работать с Engine, Ops, Ontology и другими подключёнными модулями по действующим правилам платформы, а Foundry определяет только границы действий **внутри Foundry**.
## Границы v0.1
| Разрешено | Не разрешено |
| --- | --- |
| Просматривать Page Library и экземпляры Applications | Менять канонический Page Library или дизайн-компоненты |
| Создавать application instance | Удалять application instance через MCP |
| Изменять название, slug и описание instance | Создавать свободный canvas или произвольный React-интерфейс |
| Добавлять повторные instances зарегистрированной страницы | Вызывать Engine, provider API или произвольный внешний endpoint из Foundry MCP |
| Создавать/обновлять provider-neutral map pin bindings | Хранить provider tokens, transport payload или credentials в manifest |
| Создавать/обновлять versioned provider-neutral `map.style_profile` | Хардкодить provider-specific statuses, цвета или размеры в renderer |
| Задавать text label/order binding и сохранять полный Map view/window layout по `bindingId` | Использовать редактируемое имя как machine identity или превращать empty selection в all |
| Связывать versioned data product с approved Map entity-stream slot | Хранить provider ID, tenant/connection, endpoint или credential в data binding |
| Управлять server-owned consumer только для persisted approved binding | Передавать reader capability, EDP URL или raw provider payload через MCP |
Каждая запись требует `idempotencyKey`. Операция сохраняется в persistent runtime volume и повторный вызов с тем же ключом и тем же входом вернёт исходный результат без дублирования. Повтор ключа с иным входом завершается конфликтом.
## MCP endpoint
`POST /api/mcp`
Поддерживается MCP protocol `2025-06-18`. Endpoint принимает JSON-RPC `initialize`, `tools/list`, `tools/call` и `ping`.
Обязательные HTTP-заголовки каждого вызова:
```http
Authorization: Bearer <Foundry capability or Foundry Agent credential>
MCP-Protocol-Version: 2025-06-18
```
Endpoint поддерживает два существующих контура входа, не смешивая их:
- AI Workspace получает короткоживущую Foundry capability после штатной
entitlement-проверки. Она подписана существующим внутренним service
credential, привязана к `actorId` и `ownerKey`, живёт не более 10 минут и не
раскрывает platform credential worker-у;
- внешний Codex получает отдельный durable Foundry Agent credential через
настройки текущего пользователя Foundry. Credential действует до явного
`revoke` и не является AI Workspace capability.
Заголовки с actor/owner от клиента не принимаются: пользовательский контекст
извлекается только из проверенной capability или server-side записи Agent.
Доступные инструменты:
- `foundry_status`
- `foundry_list_applications`
- `foundry_get_application`
- `foundry_create_application`
- `foundry_update_application_metadata`
- `foundry_add_page_instance`
- `foundry_upsert_map_pin_binding`
- `foundry_remove_map_pin_binding`
- `foundry_upsert_map_presentation_profile`
- `foundry_update_map_page_settings`
- `foundry_save_map_page_view_state`
- `foundry_upsert_map_data_product_binding`
- `foundry_plan_map_data_product_consumer`
- `foundry_apply_map_data_product_consumer`
- `foundry_get_map_data_product_consumer_status`
- `foundry_accept_map_data_product_consumer`
- `foundry_rollback_map_data_product_consumer`
`foundry_upsert_map_pin_binding` хранит только визуальную, provider-neutral привязку `elevated-spike`: стабильный id, subject, координаты, semantic status и ссылку на источник сущности. Поток живых данных не передаётся в MCP по одной позиции.
`foundry_remove_map_pin_binding` удаляет одну устаревшую визуальную привязку по
стабильному `bindingId`. Операция не затрагивает data-product consumer, его
последний безопасный snapshot или канонический шаблон `Page Library`.
`foundry_upsert_map_presentation_profile` управляет отдельным версионированным
`map.style_profile` страницы. Профиль содержит renderer-neutral геометрию
таргета, label contract, semantic styles, правила классов, facet filters,
counters и sort order. Условия классов могут ссылаться только на объявленные
provider-neutral facet fields. Data Product binding выбирает профиль через
`presentationProfileId`; binding также может задавать пользовательский
`displayName` и порядок строки в dropdown `Объекты`. Provider raw status,
endpoint и credential в профиль не допускаются. Полная machine-readable JSON Schema профиля публикуется прямо
в `tools/list`: label использует `mode`, `fields`, плашку, `fontWeight` и
`sizePx`, а target,
facets, styles, classes и sort имеют закрытые наборы полей. Поэтому новый MCP
клиент может создать профиль для другого spatial domain без чтения исходников
Foundry и без неописанного generic JSON.
`foundry_update_map_page_settings` меняет подложку, terrain, здания, атмосферу,
освещение и сетку только у указанного экземпляра Map внутри `Application`.
Операция не пишет в `Page Library`, принимает закрытый typed patch и поэтому
может безопасно воспроизводить визуальное окружение через MCP.
`foundry_save_map_page_view_state` является MCP-эквивалентом одной Application
кнопки `Сохранить`. Операция атомарно сохраняет camera/map height, typed patch
base settings, exact visibility/facet selections и все canonical binding
windows (`open`, `rect`, `maximized`, `zIndex`). Состояние keyed только по
`bindingId`: editable `displayName` не участвует в identity. Missing facet не
ограничивает выборку, явно пустой список остаётся empty после reopen и не
мигрирует обратно в `Все`.
`foundry_upsert_map_data_product_binding` сохраняет только декларацию
`data product → Map entity-stream slot`: versioned data product id, semantic
types, допустимую field projection и необязательную ссылку на существующий
presentation profile. Endpoint, provider, tenant, connection,
token, credential и raw payload валидатор отклоняет. Page runtime получает
scoped snapshot/patch поток через Platform, но не через Foundry MCP.
Пять consumer-инструментов управляют тем же runtime, который обслуживает Map
Page. `plan` проверяет persisted binding, Data Product version/delivery,
versioned policy и безопасно показывает `readerGrantAction=ensure|reuse`.
Если grant отсутствует, EDP сам разрешает единственный active writer scope по
Data Product id; provider/tenant/connection в Foundry не возвращаются. `apply`
принимает exact `planId`, подписанно передаёт EDP только SHA-256 digest нового
target-scoped token, коммитит snapshot и включает consumer; `status` возвращает только safe cursor,
счётчики subject/status/reconnect и количество viewers/upstream streams;
`accept` берёт bounded diagnostic lease; `rollback` останавливает consumer, но
сохраняет последний safe snapshot. Capability value, grant path, internal URL и
fact attributes в MCP diagnostics не возвращаются.
Reader grant версионируется поколениями. При замене Data Product Foundry не
перезаписывает immutable request generation и не ослабляет EDP conflict fence:
он выпускает отдельный successor token/generation, проверяет им новый catalog и
коммитит новый snapshot. Только после успешного snapshot predecessor generation
отзывается exact revoke. Если bootstrap не удался, persisted predecessor
snapshot и его grant остаются рабочими. Если временно не удался только revoke,
следующий exact plan получает action `finalize-reader-grant-rotation` и завершает
отзыв без повторного bootstrap.
## Runtime data-product boundary
Для Map Page Foundry предоставляет только same-origin runtime routes:
```text
GET /api/applications/:applicationId/pages/:pageId/data-bindings/:bindingId/snapshot
GET /api/applications/:applicationId/pages/:pageId/data-bindings/:bindingId/history?from=:iso&to=:iso&resolutionMs=:ms&sourceIds=:ids&limit=:n&cursor=:opaque
GET /api/applications/:applicationId/pages/:pageId/data-bindings/:bindingId/stream?after=:cursor
```
Маршрут разрешает persisted binding, а затем server-owned consumer находит
opaque EDP reader grant по `sha256(applicationId/pageId/bindingId)` и persisted
active generation. Generation 1 сохраняет совместимый legacy filename, а
successor capabilities хранятся отдельными root-owned immutable files.
Нормальный grant создаётся внутри persistent private runtime Foundry; runner монтирует
отдельный Ed25519 private key только для подписи digest-only provisioner
request, а EDP получает только public trust. Старый root-owned read-only grant
directory остаётся fallback для уже выданных grant. Сам token передаётся
только как `Authorization` во внутренний External Data Plane и никогда не
попадает в browser, manifest, MCP или лог. В browser отдаётся только safe Foundry projection канонического data-product envelope:
snapshot, bounded `nodedc.data-product.history/v1`, safe
`nodedc.data-product.patch/v1` upserts и cursor. History query проходит через
тот же exact binding/read grant и не создаёт L2 execution на каждого viewer.
`Last-Event-ID`
авторитетнее старого `after` при автоматическом SSE reconnect.
Consumer state хранится отдельно от application manifest в persistent runtime
volume. Snapshot atomically заменяет subject projection, включая явный empty
snapshot. Patch применяется только от exact previous cursor; новый cursor и
semantic state сначала сохраняются, затем fan-out отправляется browsers.
Повторный cursor идемпотентно игнорируется, gap вызывает snapshot rebase.
Несколько viewers одного binding делят один upstream EDP stream; закрытие
последнего viewer закрывает subscription, но не влияет на независимый producer
в Engine L2.
Freshness и remove принадлежат versioned provider-neutral policy из
`registry/data-product-consumer-policies.json`. Stale вычисляется по
`observedAt`; terminal statuses сохраняются. Временная transport/credential
ошибка не удаляет subject. Удаление допустимо только при отсутствии в
авторитетном snapshot rebase или по canonical `tombstone`/`revoked` operation.
Renderer получает стабильный `sourceId + semanticType`, persisted coordinates
и Foundry-owned `presentationStatus`. Операционные visual classes, фильтры,
счётчики и сортировка разрешаются отдельно из orthogonal state facets и
page-owned `map.style_profile`; provider identity на стиль и lifecycle не
влияет.
## Внешний Codex: Foundry + отдельная Ontology MCP
В профиле Foundry раздел `Настройки → Codex Agent API` создаёт Agent текущего
пользователя и выдаёт одноразовую setup-команду. Команда устанавливает в Codex
две независимые MCP-конфигурации:
```text
nodedc_module_foundry → POST /api/mcp
nodedc_ontology → POST /api/ontology-mcp
```
Это одна операция установки, но не одна MCP. Foundry credential принимается
только `/api/mcp`, Ontology credential — только `/api/ontology-mcp`; их
перекрёстное использование отклоняется. Ontology route после своей проверки
проксирует MCP JSON-RPC во внутренний `Ontology Core /mcp` с уже существующим
server-only `NODEDC_INTERNAL_ACCESS_TOKEN`. Foundry не импортирует ontology
tools в свой список и не становится маршрутизатором платформенных MCP.
Agent credentials не имеют календарного срока жизни: они валидны до явного
отзыва Agent. Setup code одноразовый и живёт 15 минут. Raw credentials
возвращаются только в момент redeem; в Foundry runtime сохраняются только
SHA-256 digests, device metadata и bounded audit. Один `revoke` закрывает обе
credentials конкретного Agent.
Первый срез намеренно ограничен текущим аутентифицированным пользователем:
Codex может создавать и настраивать его Applications средствами Foundry MCP и
читать общую Ontology через отдельную read-only MCP. Sharing, роли,
межпользовательская видимость, release/publication и новые product ACL в этот
срез не входят.
Установщик идемпотентно обновляет только блоки `nodedc_module_foundry` и
`nodedc_ontology` в `~/.codex/config.toml`, сохраняет остальные MCP (включая
Engine и Ops), делает backup конфигурации и устанавливает skill
`foundry-context`. Перед успешным завершением он выполняет `initialize` и
`tools/list` для обеих MCP. После установки Codex Desktop нужно полностью
перезапустить.
## Entitlement для AI Workspace
`POST /api/ai-workspace/entitlements`
Endpoint предназначен для уже существующего AI Workspace generic adapter. Он принимает его штатный request-контракт и выдаёт динамический MCP grant только если совпала одна из политик:
- `FOUNDRY_ALLOW_ALL_AUTHENTICATED=true` — временно разрешает всем уже аутентифицированным пользователям;
- `FOUNDRY_ALLOWED_OWNER_IDS` — список дополнительных разрешённых user id / owner key;
- `FOUNDRY_ALLOWED_OWNER_GROUPS` — список дополнительных разрешённых групп.
По умолчанию доступ получает `user_root` (`dcctouch@gmail.com`) и техническая
группа Authentik `nodedc:module-foundry:access`. В Launcher эта группа выдаёт
роль сервиса `member`; все остальные пользователи получают «нет доступа» до
явной выдачи права администратором Hub. Многопользовательское владение
Applications в v0.1 не вводится: это общий внутренний контур с аудитом actor id.
Для production предпочтительны явно заданные users/groups; режим `ALLOW_ALL_AUTHENTICATED` допустим только для внутренней песочницы.
Штатный запрос adapter выглядит так:
```json
{
"schemaVersion": "ai-workspace.entitlement-request.v1",
"appId": "module-foundry",
"owner": {
"userId": "authenticated-user-id",
"key": "owner-or-workspace-key",
"groups": ["optional-group"]
},
"activeContext": {},
"runContext": {},
"requestedAt": "2026-07-13T00:00:00.000Z"
}
```
## Server-side configuration
Все значения находятся только в server environment. В browser bundle не передаются tokens, entitlement keys и runtime volume paths.
```dotenv
FOUNDRY_PUBLIC_URL=https://<future-foundry-domain>
FOUNDRY_MCP_URL=https://<future-foundry-domain>/api/mcp
# Existing NODE.DC server-to-server credential; never send it to a browser or worker.
NODEDC_INTERNAL_ACCESS_TOKEN=<existing-platform-service-value>
# Internal-only Ontology Core address; never expose the Core directly.
NODEDC_ONTOLOGY_CORE_URL=http://ontology-core:8080
FOUNDRY_MCP_CAPABILITY_TTL_MS=600000
# Optional exceptional identities, not the default dcctouch superadmin grant.
FOUNDRY_ALLOWED_OWNER_IDS=<comma-separated-extra-ids>
FOUNDRY_ALLOWED_OWNER_GROUPS=<comma-separated-extra-groups>
FOUNDRY_ALLOW_ALL_AUTHENTICATED=false
FOUNDRY_MCP_ALLOWED_ORIGINS=https://<ai-workspace-domain>
```
Foundry не требует у пользователя вводить platform secret.
`NODEDC_INTERNAL_ACCESS_TOKEN` — уже существующий server-to-server credential
платформы: он находится только в server environment и используется для
Launcher/Authentiк handoff, внутренней entitlement-проверки и server-side
Ontology Core proxy. AI Workspace worker получает лишь короткоживущую
capability; внешний Codex — отдельные Agent credentials, но не internal token.
После появления домена в deployment AI Workspace добавляется только штатная настройка adapter; новый worker, отдельная оркестрация или специальный bridge не нужны:
```text
AI_WORKSPACE_ENTITLEMENT_ADAPTERS_JSON='{"module-foundry":{"url":"https://<future-foundry-domain>/api/ai-workspace/entitlements","required":false}}'
```
Если в переменной уже есть другие adapters, `module-foundry` добавляется в тот же JSON-объект — существующие `ops`, `engine`, `launcher` и другие grants не заменяются.
## Deployment package
Foundry подготовлен к отдельному deployment как stateless server + один named persistent volume. Runtime volume содержит Applications, Design Profiles, idempotency audit, data-product consumer state/cursors, media uploads, Agent/device records, setup-code digests и Agent audit. Он является единственным источником изменяемого Foundry state; Git не используется для runtime-данных и tile cache. Raw Foundry/Ontology Agent credentials и EDP reader capabilities в volume не записываются.
```bash
cp .env.example .env
# использовать уже имеющийся NODEDC_INTERNAL_ACCESS_TOKEN из server environment
docker compose --env-file .env -f infra/docker-compose.module-foundry.yml up -d --build
curl http://127.0.0.1:9920/healthz
```
Внешний маршрут подготовлен: `https://foundry.nodedc.ru``172.22.0.222:9920` → container `:3333`. Compose на NAS должен слушать именно `172.22.0.222:9920`, а не все интерфейсы. До запуска Foundry proxy закономерно возвращает `502`; это проверяемый признак, что DNS/TLS/proxy уже доходят до внутреннего destination. Публикация допустима только после проверки Launcher's handoff, server-side session revalidation, синхронизации Authentik-группы и выдачи Hub access. Только после этого добавляется Foundry entitlement adapter в platform AI Workspace deployment.
## Следующие слои
1. Онтологический contract приложения, страницы, page instance, Map Page и visual entities.
2. Расширение runtime adapter с points на targets, zones, routes и tracks.
3. Управляемая публикация и повторное использование presentation profiles между Applications.
4. Access/Hub registration и публикация module route.
5. Отдельная пустая программируемая страница — только после того, как готовые page templates и их MCP-контуры отработаны.