Версии: v0.1.0 → v0.2.0 → v0.3.0 → v0.3.1 → v0.4.0 → v0.4.1 → v0.5 → v0.5.1 → v0.5.2 → v0.5.3 Время: несколько сессий, май 2026.
Branch: v0.5.3-link-convert — off main (v0.5.2 смержен в 7524e90).
Commits: 1 feature (c) + docs. Build: :compileJava green.
linkSlotsconverterTypearg (опциональный,"module:Type") — при наличии: тип грузится и должен бытьjavax.baja.util.BConverter(иначе-32602/-32005); отбивка-32016отcheckLinkпропускается (вызывающий явно мостит несовместимость — invalid-reason пишется вlinkCheckNoteрезультата); добавляетсяBConversionLink( source, sourceSlot, sinkSlot, converter)вместо обычногоBLinkизmakeLink(). Result получаетlinkType("link"/"conversionLink") и (если конвертер задан)converter. Несовместимый named-конвертер → faulted link-слот илиadd()отказывает →-32603. По дизайн-решению Q6 — никакогоconvert: trueавто-подбора.
BConversionLink extends BLink— конструктор(BComponent source, Slot sourceSlot, Slot sinkSlot, BConverter converter);getConverter() /setConverter(). НикакогоmakeConversionLink()— используем конструктор напрямую +sink.add(name, link, cx).BConverter—abstract extends BStruct;convert(BObject, BObject, Context). Concrete-сабклассы:baja:NullConverter(no-op) + модульные. Получить инстанс:Sys.getType(spec).getInstance()→(BConverter).- Реестра конвертеров для пары типов в публичном baja нет — Workbench
использует agent-registry; поэтому авто-подбор отложен/не делается,
converterTypeуказывается явно (это и было решением Q6 ещё в v0.5.1). LinkCheck— финальный класс, толькоisValid()/getInvalidReason(); не перечисляет доступные конверсии. НетisValidWithConversion(). Поэтому checkLink при наличии converterType просто bypass'ится, валидность решаетсяBConversionLinkна add()/ активации.
v0.5.3-link-convert— unmerged, offmain(7524e90).main—7524e90(v0.5.2 merge).- Метки
v0.5-user-context/v0.5.1-write-tools/v0.5.2-write-tools-polish— оставлены.
Branch: v0.5.2-write-tools-polish — off post-merge main
(v0.5 + v0.5.1 смержены в 3189c1d; v0.5.2 — первая ветка от чистого
main, не стэк). Commits: 4 (a/b/d/e) + docs.
Build: per-commit :compileJava green.
BValueCoercer(item a) — вынес дублирующуюся JSON↔BSimple coercion изSetSlotTool/InvokeActionToolв один package-private хелпер (coerce/toJsonScalar/typeSpec/isSupported+UnsupportedTypeException). Без изменения поведения. Оба тула теперь делегируют; их приватные копии + bespoke-исключения удалены.clearSlot(item d) — новыйwrite-тул: ресет Property-слота вprop.getDefaultValue()под user-Context gateway. Работает для любого Property-типа (не только BSimple — coerce не нужен). Result:{ord, slotName, previousValue, defaultValue, type, changed}.tools/list45 → 46.unlinkSlotsформа{sinkOrd, linkName}(item e) — рядом с{linkOrd}. Валидация "linkOrd XOR (sinkOrd & linkName)" вcall(). Заодно фикс латентного бага:BLink— этоBRelation, неBComponent, поэтому у него нетgetSlotPath();linkSlots/unlinkSlotsтеперь строят link-ord из ord'а sink'а + имени слота (раньше падало в else-веткуgetSlotPathOrd().toString()→ относительныйslot:/..., тот же класс бага, что чинилcadf51c).addExtensionpre-check (item b) —parent.isChildLegal(ext)+ext.isParentLegal(parent)перед add; несовместимая пара →-32015ERR_EXTENSION_NOT_APPLICABLE сdata{parentOrd, parentType, extensionType, reason}вместо generic-32603. Активирует declared-but-dormant-32015. Дефолты возвращают true → unconstrained parents не затронуты.- Smoke: новый шаг 28
clearSlot(ресетитnoteиз шага 27, проверяетchanged); шаги перенумерованы 26-32; баннер обновлён. getFeatureDumperror-code таблица:-32015теперь active.
getSlotPathOrd()==BOrd.make(getSlotPath())(декомпиляция baja) — стрингуется в относительныйslot:/.... Поэтому канонический ord для результатов —"station:|" + getSlotPath()(Ords.stationOrd, введён в v0.5.1 post-smoke).BLink extends BRelation(неBComponent).BRelation—BComplex, но неBComponent→ нетgetSlotPath(). Source/sink компоненты у линка —BComponent(getSourceComponent()и т.д.), а сам линк — нет.BComponent.isChildLegal/isParentLegalдефолт =return true; constrained-сабклассы (BHistoryExtи т.п.) переопределяют. Это и есть то, чтоadd()'scheckAddконсультирует — годится для pre-check'а без recon'а agent-registry.Property.getDefaultValue()— есть, отдаёт declared default слота (для динамического свойства — значение, с которым его добавили).
v0.5.2-write-tools-polish— unmerged, offmain(665f06c).main— at3189c1d(v0.5+v0.5.1 merge) +665f06c(gitignore chore).- Метки
v0.5-user-context/v0.5.1-write-tools— оставлены.
Branch: v0.5.1-write-tools — stacked на v0.5-user-context
(оба unmerged). Rebase plan на main после merge'а v0.5 — content
identical, hashes change.
Commits: 8 атомарных. LOC: +1 662 / −5 = +1 657 net.
Jar: ≈230 KiB (v0.5) → ≈238 KiB estimated.
- 6 новых write-tools (плюс v0.5 createComponent = M1 set
complete):
removeComponent(DESTRUCTIVE) —parent.remove(slot, cx), defaultdryRun=true, inbound-link safety check,force=trueoverride. Outbound (component-as-source) detection отложено до v0.6 (требует station walk).setSlot(MUTATION) —BComplex.set(prop, value, cx)с BSimple coercion (BString/BBoolean/BInteger/BLong/BFloat/BDouble). Complex types (BStatusValue/BFacets/...) — refuse с -32602 + hint.invokeAction(MUTATION) —BComponent.invoke(Action, BValue, cx), parameter coercion из default's class. Returns{returnValue, returnType, durationMs}.addExtension(MUTATION) —parent.add(name, extInstance, cx)для extension types. v0.5.1 без type-applicability pre-check (Niagara валидирует в add(), -32603); pre-check + dedicated -32015 — v0.5.2.linkSlots(MUTATION) —sink.makeLink(source, sourceSlot, sinkSlot, cx) + sink.add(linkName, link, cx). Pre-check черезsink.checkLink(...)→LinkCheck.isValid()→ -32016 ERR_LINK_TYPE_MISMATCH с reason. Auto-pick converter (convert: true) — отложено в v0.5.2.unlinkSlots(DESTRUCTIVE) —sink.remove(linkProperty, cx). Refuse если ord не BLink (предотвращает случайное удаление обычных компонент).commitStation(MUTATION) —BStation.doSave(Context)под user-Context. Использует Context-taking variant вместо no-argsave()чтобы permission-check работал через gateway.
- 4 новых error code (-32013..-32016):
ERR_COMPONENT_HAS_INBOUND_LINKS(removeComponent refused)ERR_ACTION_NOT_FOUND(invokeAction)ERR_EXTENSION_NOT_APPLICABLE(declared, активно используется с v0.5.2)ERR_LINK_TYPE_MISMATCH(linkSlots checkLink failure)
- Smoke client +6 шагов (26-31): createComponent fixture → setSlot → invokeAction error-path → commitStation → removeComponent dryRun preview → removeComponent actual cleanup. Total: 25 (v0.5) → 31 (v0.5.1). Reuses v0.5 pre-flight (test BUser, enableTestSetup).
BStationимеет иsave(), иdoSave(Context). No-arg variant — convenience, идёт под default cx (= service identity); Context-taking — propagates user identity. Используем второй для audit'а.BLinkхранится как child slot на sink-компоненте. Поэтомуsink.getLinks()возвращает inbound links — этого достаточно для removeComponent safety check без station walk.LinkCheckAPI чистый:BComponent.checkLink(...)отдаётLinkCheckсisValid()+getInvalidReason(). Niagara сама type-compatibility predicate, мы только surface причину в -32016 data.BComplex.getPropertyInParent()— каноничный slot resolver. Initial RemoveComponentTool draft использовалparent.getProperty(target.getSlot(target))который не composes; recon поправил до commit'а. Same fix применился в unlinkSlots.
- Auto-pick
BTypeConverterна link-mismatch (convert flag + named converterType). addExtensiontype-applicability pre-check + активация -32015.BValueCoercerhelper — dedupe SetSlot ↔ InvokeAction primitive coercion (cosmetic).unlinkSlotsergonomic args{sinkOrd, linkName}(alongside current{linkOrd}).- Smoke e2e fixtures для addExtension / linkSlots / unlinkSlots (требуют station-specific helper).
clearSlottool (отдельная семантика от setSlot — reset to type default).
v0.5.1-write-tools— unmerged (stacked).v0.5-user-context— unmerged (under review).main— at v0.4.1 merge.- Stack: main (v0.4.1) → v0.5-user-context (11 commits) → v0.5.1-write-tools (8 + 5 fix commits).
- M1-tail оказался mechanically rep — все tools follow createComponent reference shape (resolve / validate / OpDesc / gateway.run). Каждый tool ≈ 200-280 LOC, без новой архитектурной работы.
- 7-tool batch shipped в одном branch'е без блокеров — recon из v0.5 design pass покрыл API surface'ы заранее.
Прогон smoke против живой 4.15.3.28 — 5 fix-коммитов поверх
v0.5.1-write-tools, итог 33 passed / 0 failed.
mcp:tokenHashперсистится без cx —BUser.tags().set(Tag)без Context отрабатывает; user-Bearer auth заработала сразу после пересборки jar'а (первые 401 были из-за того, что smoke не перехватывалMcp-Session-Idиз ответаinitialize()на v0.5/v0.5.1 путях — fixeca9523/4c879b7). Диаг-поля вsetupTestUser(tagsSetReturned/readbackHash/readbackMatches) добавлены для этого расследования и оставлены.slot:/...ord на servlet-потоке резолвится от локального хоста.createComponentвозвращалslot:/Drivers/Foo(черезgetSlotPath()), иBOrd.make("slot:/...").get()в servlet-хендлере резолвил относительный путь от неявной базы потока — локального хоста →ord not resolvable: localhost. Декомпиляция baja:getSlotPathOrd()==BOrd.make(getSlotPath())— тоже относительный, так что первый fix (4612c06) не помог. Решение — хелперtools.Ords:resolve(s)=BOrd.make(s).get(Sys.getStation())(относительныйslot:/...от корня станции, абсолютные ords базу игнорируют) +stationOrd(c)="station:|" + c.getSlotPath()для результатов. Прошито во все write-тулзы (cadf51c).- Коды ошибок тулзов терялись. Tool, бросающий
RpcException(-32014 и т.д.), отдавался как{isError:true, content:[{text:"Error: ..."}]}— код не доезжал до клиента (определять -32013..-32016 было незачем).McpProtocol.callToolтеперь ловитRpcExceptionотдельно и кладёт код/data вresult.errorCode/result.errorData(271e8e6). - Smoke step 27 был невалиден —
setSlot("displayName")на голомbaja:Folder, у которого нет настраиваемого скалярного слота (тул правильно отвечалNo such slot). Теперь:createComponentдинамическогоbaja:String-свойстваnoteна фикстуре (тул принимает любойBValue, не только компоненты), потомsetSlot. enableTestSetup=trueна станции — это флаг для smoke/CI; снять обратно вfalseпосле прогонов.
Branch: v0.5-user-context (off main после v0.4.1 merge).
Commits: 10 атомарных. LOC: +1 867 / −19 = +1 848 net.
Jar: 220.4 KiB → ≈230–235 KiB (estimated, +20 class files; точное
число — после следующего clean assemble).
- User-Context auth pipeline: Bearer →
BUserчерез walkBUserService.getUsers()+ constant-time compare противmcp:tokenHashтэга. LegacyapiTokenостаётся read-only service identity для мониторинга / read-tools; write-tools подrequiresUserContext=trueотбивают apiToken через-32011 ERR_USER_NOT_FOUNDДО dispatch'а. UserContextGateway.run(BUser, OpDesc, args, sessionId, ContextAwareWork<T>)— единственная entry-point для write-tools. СтроитBasicContext(user), оборачиваетPermissionExceptionв-32010с richdata{user, ord, operation, tool, detail}, emit'ит один audit-record на вызов.- Audit pipeline: JSONL primary (всегда on, full record:
{ts, user, sessionId, tool, ord, action, args, resultOk, durationMs, errorCode, errorMessage}), best-effort secondary черезBAuditHistoryServiceAdapter— reflection-only наcom.tridium.history.audit.BAuditHistoryService, никакого compile-time dep наhistory-rt(lightweight JACE стартует безNoClassDefFoundError). Redactor с regex blacklist(password|secret|token|apikey|passcode|pwd|credential)+ 256-char string truncation. - Tool interface: 2 default-метода —
requiresUserContext()(defaultfalse) иannotations()(defaultToolAnnotations.READ_ONLY). Per MCP 2025-06-18 §6.1, каждая строкаtools/listтеперь несётannotations:{readOnlyHint, destructiveHint, idempotentHint, openWorldHint}плюс niagaramcp-extensionrequiresUserContext. Существующие 36 tools наследуют дефолты — zero touch. - Auto-promote JSON-string tool result в
result.structuredContent(per spec §5.4) — purely additive, legacy clients продолжают читатьcontent[0].text. Бэкфилл, обещанный в Q11, случился автоматически для всего каталога. createComponent(categorywrite, annotationsMUTATION, requiresUserContext) — reference write-tool: добавляет freshBComponentуказанного типа как child существующего parent'а под calling user permissions. Args:{parentOrd, type, name, nameStrategy?:"fail"|"suffix"}.setupTestUser(test-only, gated byenableTestSetupproperty; default false) — для smoke step 25: биндитmcp:tokenHashтэг к pre-created BUser'у через apiToken, чтобы smoke мог подключиться под user-Bearer без bog-fragment'а в pre-deployment runbook.- Smoke step 25 — e2e mutation: smoke генерит фреш-токен,
setupTestUser биндит, reconnect под user-Bearer, createComponent,
проверяет
structuredContent.ord. Покрывает реальный pipeline (BearerResolver → CallContext → Gateway → BasicContext(user) → permission-checkedparent.add→ audit). - 2 новых JSON-RPC error code:
-32010 ERR_PERMISSION_DENIED,-32011 ERR_USER_NOT_FOUND. - 2 новых property:
tokenSalt(READONLY, lazy-generated),enableTestSetup(bool, default false).
Sys.makeContext/BUserService.runAs/BLocalSession.makeContextне существуют. В Niagara нет thread-local context;Contextпередаётся explicit в каждый mutating-вызов. Gateway-сигнатура переразвернулась сrun(BUser, ThrowingSupplier<T>)наrun(BUser, OpDesc, args, sessionId, ContextAwareWork<T>)— work получает cx и threads его в каждыйparent.add(name, val, cx).BUser implements Context, Principal— BUser сам по себе Context.new BasicContext(user)нужен только для default facets, можно и без обёртки.BAuditServiceотсутствует в публичном API. Есть только interfacesAuditor.audit(AuditEvent)+SecurityAuditor. Concrete servicecom.tridium.history.audit.BAuditHistoryService— non-public, не на каждой станции. Перевернуло Q5 design: JSONL primary, BAuditHistoryService через reflection-adapter.PermissionExceptionбез rich payload — только String message. Wrapper собираетdata{user, ord, operation, tool}из call-siteOpDesc(поэтому OpDesc — отдельный параметр, не derivable из exception'а).- TagDictionary auto-bootstrap отскоплен.
BTagDictionaryprogrammatic-construction требует populatedBTagInfoList— это отдельный ~100-LOC feature. Commit 4 ship'нул schema-constants + reflection-check «registered already?»; programmatic register — v0.5.x. Tag write/read работает без registration; Workbench TagBrowser entry — опционально.
- Programmatic BTagDictionary auto-construction.
- JSONL audit log size-rotation.
- Operator-configurable
auditRedactPatternproperty. - Workbench action
generateUserToken(BString)+ MCP toolrotateMcpToken. writePointretrofit кrequiresUserContext=true.- M1 write-tools хвост (по шаблону
createComponent):removeComponent,setSlot,invokeAction,addExtension,linkSlots/unlinkSlots,commitStation— единый batch.
v0.5-user-context— unmerged (10 commits).main— at v0.4.1 merge.- Stack: main (v0.4.1 merged) → v0.5-user-context (10 commits, unmerged).
- Plan был на 9 commits; +1 на ходу —
SetupTestUserToolextracted из commit 9 в commit 10, чтобы production tool (createComponent) не смешивался в одном review-юните с test-only setupTestUser + enableTestSetup property. - Auto-promote JSON-string результата в
structuredContent— один-к-одному закрыло Q11 backfill «бесплатно» для всех 36 существующих tools. - Brief estimate "~100 LOC for gateway" не учитывал supporting packages (auth + audit + Tool interface + smoke). Финал ~1 800 LOC. Каждая часть осталась "small + focused"; собранный объём — цена за full pipeline в одном release'е.
Branch: v0.4.1-ux-polish (off main после v0.4 merge).
Commits: 5 атомарных. LOC: +702 / −9 = +693 net.
Jar: 216.6 KB → 220.4 KiB (+4 %, target ≤220 KB на грани, hard
ceiling ≤225 KB ✓).
- 4 read-only count properties на
BMcpPlatformService(flags=3 → SUMMARY+READONLY):toolCount(36),resourceCount(6),promptCount(7),sessionCount(live, refresh через notification из McpSessions create/remove/closeAll — без polling, без новых потоков). Видны на Workbench Property Sheet — оператор видит runtime-стат при открытии сервиса. - 36-й tool
getFeatureDump(categorydiagnostic) — статический feature-инвентарь сервера, два формата:text(банер для операторов) /json(для AI-клиентов). Включает версию, tools по категориям, resources split (static / templates), prompts, transport-флаги, knowledge-стат, sessions, health и 9 impl-defined JSON-RPC error-кодов с расшифровкой. - Smoke client +2 шага (steps 23–24, getFeatureDump text + json), всего 24 (брифу казалось 24 → 26, но baseline был 22).
NIAGARAMCP_VERSIONbump 0.4.0 → 0.4.1.
BFilePathдляknowledgeFilePath—BFilePathне существует в baja 4.15.3.28;FilePathесть (но не BSimple);BAbstractFileтолько для in-station file space;BFacetsне имеетFILE_BROWSEkey. Workbench file-picker UX отложен до появленияniagaramcp-wbsubproject. 19 LOC javadoc на property объясняет почему.
- Per-transport
sessionCountsplit (SSE vs Streamable) — нужно расширение McpSessions API. niagaramcp-wbsubproject: file-picker дляknowledgeFilePath, custom Property Sheet renderer дляsessionCount(live counter), PX-виджеты.
v0.4.1-ux-polish— unmerged.main— at v0.4 merge.- Stack теперь: main (v0.4 merged) → v0.4.1-ux-polish (5 commits, unmerged).
- Выбрал notification-pattern для
sessionCountвместоBService.tick()(бриф рекомендовал tick). Notifications дают exactness (без 1-сек лага), не требуют тик-handler-регистрации, и общий принцип "no new threads" сохраняется. - Brief assumed 24 baseline smoke steps — реально было 22. Новый total = 24, не 26. Зафиксировано в notes.
- Auto-install copy task снова падает на station-locked jar (как в v0.3.0/v0.3.1/v0.4) — разовая ручная замена при остановленной станции.
Branch: v0.4-operational (off v0.3.1-diagnostics tip — v0.3.1
не была затегана/смержена, поэтому v0.4 продолжает stack).
Commits: 8 атомарных. LOC: +805 / −2 423 = −1 618 net
(commit 3 убрал 12 неиспользуемых JSON utility-классов).
Jar: 249 KB → 211.6 KB (−13 %, target ≤220 KB ✓).
- 2 свойства:
sseEnabled/streamableEnabled— операторские toggles на транспорты. Disabled → HTTP 503 + JSON-RPC -32009./healthвсегда доступен. - Tool category tags — все 35 tools имеют
category(10 категорий);tools/listотдаёт field per tool для UI-группировки. - 35-й tool
getDiagnosticDump— combined snapshot (server + sessions- knowledge + health + auditLogTail) в один round-trip.
serverInfo.transportsмассив вinitialize-ответе.serverInfo.versionтеперь корректно отражает версию модуля ("0.4.0"вместо исторического"1.0.0").- ToolSchemaHelpers utility class.
- Smoke client +3 шага (steps 20-22), всего 22.
- 12 неиспользуемых JSON utility-классов
(XML/CDL/Cookie/HTTP/Property/Test/JSONML/etc.) из
com.niagaramcp.json. Сэкономлено ~36 KB jar. - 11 файлов JSON library: javadoc-комментарии переведены RU→EN.
- Runtime-mutable transport toggles (без restart).
- Per-transport session split в getDiagnosticDump (требует API расширения на McpSessions).
- i18n message bundle (Russian descriptions в tool API).
- Дальнейший json shading (rename в com.niagaramcp.embedded.json).
- BPasswordPassword, BAuditService, RBAC.
- Schedules read/write.
v0.4-operational— unmerged.main— at v0.3.0 merge + recon.- v0.3.1 tag не существует (v0.3.1-diagnostics тоже unmerged).
- Stack: main → v0.3.1-diagnostics (8 commits) → v0.4-operational (8 commits).
Backfilled in v0.4.1 commit 5: эта секция была пропущена при добавлении v0.4-секции в commit 8 предыдущей сессии.
Branch: v0.3.1-diagnostics (off main at tag v0.3.0).
Commits: 7 атомарных. LOC: +1 512 / −20 = +1 492 net.
Jar: 236.4 KB → 243.4 KB (+7 KB / +3 %).
- 4 диагностических tools (28 → 32):
getServerInfo— snapshot version/uptime/sessions/knowledge file/transports/registered tools/resources/prompts.probeOrd {ord}— resolve ord, отдаёт exists/type/displayName/ parentOrd/slotCount + isControlPoint/isWritable/isAlarmSource + hasHistoryExt + historyExtCount/historyExtIds. Garbage ords дают{exists:false}, не error.checkKnowledgeIntegrity— пройти каждый ord в knowledge model, репортить broken refs.getServiceHealth— alarm/history availability + knowledge file readability/writability + sample-resource read-back.
- Unauthenticated
/healthendpoint — единственное исключение из Bearer-on-everything. Для k8s/Prometheus/watchdog. Отдаёт200 + {status:"ok", version, uptimeSeconds, knowledgeFileSize, sessionCount, healthyServices}в норме /503 + status:"degraded"при проблемах. Не показывает station data, equipment, ords. - 3 новых operator-property:
mcpProtocolVersion(String) — override MCP protocol version в initialize (default empty → "2025-06-18").maxHistoryRecordsPerQuery(int, default 10000) — cap дляreadHistory.disabledTools(String, comma-separated) — оператор может подавить tool'ы при старте. Restart-required.
- 8 standardised JSON-RPC error codes (-32001..-32008): session/
tool/resource/knowledge/schema/ord/history/alarm. Error responses
с
error.dataJSONObject ({toolName: ...},{uri: ...}и т.д.). samples/папка (вне jar):mall-knowledge.yaml(синтетический shopping mall fixture, ~14 equipment / 12 spaces) +samples/README.md. Операторы импортят черезimportKnowledgeчтобы тестить queries без реального walkthrough.- Smoke client +6 шагов (14–19), всего 19.
BHistoryService.getHistoryDb()не существует — правильноgetDatabase().HistorySpaceConnection.timeQueryвозвращаетBITable, неHistoryCursor.BAlarmRecord.getSource()возвращаетBOrdListнапрямую, неBAlarmSource.
Все 3 находки исправлены в тех же commit'ах + задокументированы.
- Tool category tags в tools/list.
- Combined diagnostic snapshot (получит имя
getDiagnosticDumpв v0.4). - Transport toggles (sseEnabled/streamableEnabled).
v0.3.1-diagnostics— unmerged (left for human review per брифу).main— at v0.3.0 merge + recon.- v0.3.0 tag exists, v0.3.1 tag не создан.
Дата: 2026-05-09 Версии: v0.1.0 → v0.2.0 → v0.3.0 Общий объём: ~5 365 LOC + 2 115 LOC recon docs + ~840 LOC concept docs Время: одна продлённая сессия
Модуль niagaramcp на v0.1.0 — базовый MCP-сервер на Niagara Framework. Поддерживает только legacy SSE+messages транспорт (deprecated в MCP-спеке за 2025-03-26). Имеет 5 базовых tools (echo, listChildren, readPoint, writePoint, bqlQuery), Bearer auth, Streamable HTTP отсутствует, semantic layer отсутствует.
Цели сессии:
- Реализовать Streamable HTTP транспорт per MCP spec 2025-06-18 (v0.2.0)
- Добавить semantic enrichment layer для bootstrap'а AI-понимания станции (v0.3.0)
Recon-driven development: сначала static inspection кодовой базы для проверки предположений, потом implementation на их основе.
docs/recon-2026-05-09.md(1 411 строк) — read-only inventory существующего кода, validation предположенийdocs/agent-briefs/v0.2.0-streamable-http-prompt.md(590 строк) — brief для Claude Code на основе recon'а- 7 atomic commits на
v0.2.0-streamable-httpветке - Smoke test через ручной Python script (
clients/python/niagaramcp_smoke.py) - Manual verification на live station, потом merge + tag + GitHub release
- Новый класс
Sessioninterface — обобщение поверх старогоMcpSessionи новогоStreamableSession - Переименование
McpSession.java→SseSession.javaдля ясности StreamableSession.java(86 LOC) — реализация streamable транспорта с lazy idle eviction (без новых тредов)McpProtocol.handle()принимаетSessionвместоMcpSession— единственное изменение, остальное транспорт-агностично- 5 dispatch branches в
McpServlet.java: GET /mcp, POST /mcp, DELETE /mcp + сохранение SSE+messages flow mcpSessionIdleTimeoutSecproperty (default 1800)- Capability advertisement: protocolVersion bumped to 2025-06-18
- ADR-0001 — Streamable HTTP transport design
- 12 files changed, +897/-72 lines
- jar 117 550 → 120 523 bytes (+2 973, в пределах "v0.1.0 ±5 KB
- ~15 KB" envelope из брифа)
- 290 LOC новых Java vs 250 LOC оценки в recon (+16 %)
Все ключевые предсказания recon §13 подтвердились:
McpProtocol.handle()транспорт-агностичен- Auth выше транспорта (новые endpoints наследуют checkAuth для free)
- Tools unchanged
- Java 8 baseline preserved
- Никаких новых threads, deps, permissions
9 шагов в docs/v0.2.0-implementation-notes.md. Покрытие: deploy
- load + curl runbook + SSE backward compat + idle eviction
- DELETE idempotency + cross-transport isolation + concurrency + auth. Python smoke client автоматизирует 8 из 9 (idle eviction остаётся manual т.к. требует короткий timeout config).
4 концепт-документа в docs/concepts/:
01-concept.md— AI walkthrough как способ bootstrap'а semantic model. Решает chicken-and-egg: AI помогает построить модель, которой AI потом пользуется.02-format.md— schema knowledge.yaml: spaces, equipment_types, equipment, points, schedules. EquipmentType может extend другой тип (rooftop extends ahu).03-workflow.md— 6 фаз walkthrough'а. AI инициирует, оператор подтверждает. Применение patterns для serial naming. Time budget 30 мин - 2 часа на типовой объект.04-roadmap.md— v0.3.0 scope: knowledge layer + walkthrough tools + MCP Resources + History tools + Alarms tools.
Тот же recon-driven workflow, что и v0.2.0:
- Два recon-файла (split для избежания API timeout):
docs/recon-2026-05-13-niagara-apis.md(920 LOC) иdocs/recon-2026-05-13-mcp-and-plan.md(1 195 LOC) - 13 atomic commits на
v0.3.0-semantic-layerветке (commit 2 разбит на 2a/2b для изоляции YAML emitter'а) - Verification через extended Python smoke client (15 шагов)
- Manual verification на live station, потом merge + tag + GitHub release
Knowledge layer:
- Custom YAML emitter+reader (~330 LOC code, 649 LOC с javadoc) — без snake-yaml dependency, но dual-format read tolerant к JSON
KnowledgeStore— atomic file ops, lock(), backup rotation, schema validationKnowledgeModel+ 5 immutable POJOs (Space, EquipmentType, Equipment, Point) — Java 8 без recordsBMcpPlatformServicewired with 3 new properties:knowledgeFilePath,knowledgeAutoBackup,knowledgeBackupCount- knowledge.yaml авто-создаётся в
${niagaraUserHome}/niagaramcp/ - Sample
standard-types.yamlв jar (5 generic equipment types), доступен через resourceniagara://samples/standard-types
25 новых tools:
- 4 walkthrough read:
getOverview,inspectComponent,findComponentsByType,getSlots - 5 walkthrough write basic:
createSpace,updateSpace,createEquipmentType,updateEquipmentType,createEquipment - 5 walkthrough write advanced:
updateEquipment,bulkCreateEquipment,assignPointToEquipment,createStandalonePoint,validateKnowledge - 5 management:
getKnowledgeSummary,findUnmappedComponents,exportKnowledge,importKnowledge,reloadKnowledge - 3 search:
findEquipment,findInSpace,findPoints - 1 history:
readHistory(черезHistorySpaceConnection.timeQuery) - 2 alarms:
getActiveAlarms,getAlarmHistory(черезAlarmDbConnection.getOpenAlarms/timeQuery/bqlQuery)
MCP Resources + Prompts:
- 3 static resources:
niagara://overview,niagara://kinds/catalog,niagara://samples/standard-types - 3 templated resources:
niagara://equipment/{type}/{id},niagara://space/{path},niagara://standalone-point/{id} - 7 prompts: 4 walkthrough (
new_station,continue,verify_types,apply_pattern) + 3 query (equipment_state,zone_comfort,alarm_summary) - 5 new dispatch branches в
McpProtocol.handle():resources/list,resources/read,resources/templates/list,prompts/list,prompts/get - Capability advertisement extended:
tools+resources+prompts
ADR-0002 — Semantic enrichment layer design (Status, Context, Decision, Consequences, Alternatives).
- 62 files changed, +5 075/-8 lines
- 38 new Java files в 4 новых packages:
yaml/,knowledge/,resources/,prompts/ - jar 120 523 → 236 107 bytes (+115 584, +96 %)
- 30 tools всего (5 baseline + 25 new)
Recon errors caught на compile (зафикшены в тех же commit'ах):
HistorySpaceConnection.timeQuery()returnsBITable<BHistoryRecord>notHistoryCursor(recon §1.3 ошибся)BHistoryService.getHistoryDb()— actualgetDatabase()BAlarmRecord.getSource()returnsBOrdList, неBAlarmSource
Плюс 3 gradle deps оказались не transitive (:history-rt,
:alarm-rt, :bql-rt) — recon §6.7 надеялся на transitive, но
пришлось добавить explicit.
- ✓ Java 8 baseline (no var, lambdas-in-interfaces, switch expressions, records)
- ✓ No new threads (zero-thread baseline preserved per ADR-0001 precedent)
- ✓ No new third-party deps (YAML hand-written)
- ✓ No module-permissions.xml edits
- ✓ Backward compat: все v0.2.0 endpoints работают (15-step smoke test зелёный)
13 шагов в docs/v0.3.0-implementation-notes.md. Покрытие через
extended Python smoke client (15 шагов: 8 v0.2.0 + 7 v0.3.0,
включая resources/list, resources/read, prompts/list,
getKnowledgeSummary). Остаётся manual: audit log, backup rotation,
readHistory против реальной точки с history extension,
getActiveAlarms против реальной DB, importKnowledge sample,
reloadKnowledge round-trip.
Каждая major version — single iteration:
- Recon — read-only inventory + validation предположений.
Output:
docs/recon-YYYY-MM-DD.md. Не модифицирует код. - Brief archive — recon prompt архивируется в
docs/agent-briefs/. Implementation prompt тоже туда. - Implementation — на отдельной feature branch, atomic commits, каждый green-buildable. Branch не мерджится автоматом.
- Notes —
docs/vX.Y.Z-implementation-notes.mdпишется в последнем commit'е на ветке: surprises, recon errors, verification needed checklist. - Verification — manual + automated через Python smoke client.
- Merge + tag + release — после прохождения verification человеком.
Каждый коммит на feature branch собирается зелёно в isolation
через ./gradlew :niagaramcp-rt:assemble --configure-on-demand.
Если коммит ломает build — fix в том же commit'е, не "fix in next".
Каждая major version обязана сохранять предыдущую функциональность бит-в-бит. v0.2.0 не сломал v0.1.0 SSE flow. v0.3.0 не сломал v0.2.0 Streamable. Поэтому все 5 baseline tools всё ещё в списке, все predыдущие endpoints отвечают как раньше.
Каждый implementation brief начинается с раздела Hard Constraints (Java 8, no threads, no deps, no permissions, no merge). Это эффективнее post-hoc review — Claude Code не пытается их нарушить и не предлагает трюки которые их обходят.
Один Python script (clients/python/niagaramcp_smoke.py),
stdlib-only, растёт с каждой версией. v0.2.0 → 8 шагов,
v0.3.0 → 15 шагов. Запускается за 5 секунд. Каждый push на main
должен пройти его без regression — это гарантия backward compat.
mcpProtocolVersionproperty вместо hardcodedmaxHistoryRecordsPerQueryproperty (default 10000)defaultHistoryAggregationpropertyauditLogPathнезависимый от knowledgeFilePathauditLogMaxSizeBytes+ rotationtools.disabledlist — selective disableresources.cacheTtlSeclogLevelenum vsshowLogboolean
getServerInfotool — quick capabilities/version без initialize- Health check endpoint без auth (
/niagaramcp/health) - Spy view — live session counts, audit log tail
- Tool category tags в
tools/listresponse - Better error messages (стандартизация JSON-RPC error codes)
- Web admin UI (read-only dashboard)
- 236 KB > 200 KB roadmap target. Trim в v0.4:
- shading + trimming
com.niagaramcp.json(~30 KB savings) - dedup javadoc per-tool (~10-15 KB)
- JSON-only knowledge format (drops yaml/, ~22 KB) — если выяснится что operators редко правят файл вручную
- shading + trimming
- Bajadoc encoding fix (UTF-8 для русских javadoc) — 5 LOC
- Self-contained gradle build для open source contributors —
свой gradlew/wrapper/settings в
niagaramcp/, чтобы не требовать TridiumEMEA workspace
- Walkthrough UX iteration после первого реального объекта
- Performance profile на станции с 5000+ equipment
- Resource pagination/lazy loading стратегии
- Project Haystack import/export (если на проектах есть Haystack tags)
docs/concepts/01-concept.md— semantic enrichment концепцияdocs/concepts/02-format.md— knowledge.yaml schemadocs/concepts/03-workflow.md— walkthrough flowdocs/concepts/04-roadmap.md— v0.3.0 scope (now released)docs/adr/0001-streamable-http-transport.md— v0.2.0 designdocs/adr/0002-semantic-layer.md— v0.3.0 designdocs/recon-2026-05-09.md— v0.2.0 baseline recondocs/recon-2026-05-13-niagara-apis.md— v0.3.0 Niagara API surfacesdocs/recon-2026-05-13-mcp-and-plan.md— v0.3.0 MCP + impl plandocs/v0.2.0-implementation-notes.md— v0.2.0 deliverable notesdocs/v0.3.0-implementation-notes.md— v0.3.0 deliverable notesdocs/agent-briefs/v0.2.0-streamable-http-prompt.md— Claude Code briefdocs/agent-briefs/v0.3.0-implementation-prompt.md— Claude Code brief
niagaramcp-rt/src/com/niagaramcp/server/— module sourceclients/python/niagaramcp_smoke.py— regression test clientclients/python/README.md— client docs
v0.1.0— pre-session baselinev0.2.0— Streamable HTTP transportv0.3.0— Semantic Enrichment Layer
GitHub: github.com/mefodiytr/niagaramcp Owner: mefodiytr Email: mefodiytr@gmail.com License: Apache 2.0
Документ накопительный. Каждая следующая major version добавляет свой раздел сверху. Старые разделы не редактируются.