@
ci / build (push) Successful in 29s

Документация переписана под состояние без каталога docs/

Каталог docs/ удалён в b022a91, но CLAUDE.md и README ссылались на
ARCHITECTURE.md, TODO.md, OPEN_QUESTIONS.md, IMPLEMENTATION_PLAN.md и
presentation.html — все ссылки были битыми.

- CLAUDE.md: явно зафиксировано, что прозаическая документация — только он и
  README, docs/ воссоздавать не нужно; бэклог (чертёж: базы/допуски/привязка,
  сопряжения сверх coincidence и distance) и известный подводный камень с
  направлением по нормали грани перенесены из удалённых TODO/OPEN_QUESTIONS
- убран субагент docs-maintainer: он удалён вместе с docs/, доки правятся руками
- ссылка на спеку по параметрическим эскизам заменена на память
- README: структура репозитория приведена к реальной, добавлены --version,
  run-ps-tests.ps1 и оговорка про .slnx на SDK 8
- kompas-mcp-dev: справка SDK описана как RAG-база kompas-sdk вместо
  несуществующего docs/Kompas3D_SDK/, добавлен порядок работ по новому
  инструменту, команды проверки и границы CI

Счётчики сверены по коду: 84 инструмента, 250 unit + 131 integration, 41 Pester.
@
This commit is contained in:
2026-07-31 09:38:19 +03:00
parent 37dc0fed36
commit d78ba750a8
3 changed files with 520 additions and 186 deletions
+52 -17
View File
@@ -1,22 +1,22 @@
---
name: kompas-mcp-dev
description: Внутренняя методика доработки самого MCP-сервера КОМПАС-3D в этом репозитории — полигон usecases/, продуктизация приёма в общий инструмент, субагенты kompas-sdk-research и docs-maintainer. Используй, когда задача — доработать сервер, а не построить деталь. Для построения деталей — навык kompas-3d.
description: Внутренняя методика доработки самого MCP-сервера КОМПАС-3D в этом репозитории — два слоя (инструмент/методика), полигон usecases/, продуктизация приёма в общий инструмент, делегирование поиска по справке SDK субагенту kompas-sdk-research. Используй, когда задача — доработать сервер (новый инструмент, тесты, плагин, CI), а не построить деталь. Для построения деталей — навык kompas-3d.
---
# kompas-mcp-dev — доработка MCP-сервера КОМПАС-3D
Заготовка: сюда перенесён материал из навыка `kompas-3d`, относящийся к разработке сервера,
а не к построению деталей. Доводка — вместе с проработкой рабочего цикла репозитория.
Область: код сервера, тесты, упаковка в плагин. Построение деталей — навык `kompas-3d`.
Архитектура, проверенные COM-цепочки и подводные камни — в `CLAUDE.md` (единственный
архитектурный документ: каталога `docs/` в репозитории нет и заводить его не нужно).
## Два слоя (важно не смешивать)
- **MCP-сервер** (`src/Kompas.Mcp.Host`) — *инструмент*. Он **транслирует возможности COM API/SDK
КОМПАС** в MCP-инструменты. Инструменты делаются **общими** (по возможностям SDK), а не под
конкретную задачу. Расширяя MCP, добавляй универсальную операцию (например `import_step`,
`list_components`, `export_step`, «переместить грань», «рассечь тело»), а не «сделать вот эту деталь».
конкретную задачу. Расширяя MCP, добавляй универсальную операцию (`import_step`, `list_components`,
«переместить грань», «рассечь тело»), а не «сделать вот эту деталь».
- **Навык `kompas-3d`** — *методика*. Как из общих инструментов собрать решение: порядок шагов,
выбор геометрии, проверка результата, обход подводных камней. Подходы вырабатываются в
**`usecases/`** и поднимаются туда.
выбор геометрии, проверка результата, обход подводных камней.
Итог: **MCP = чем делать, навык = как делать.** Новый приём сначала обкатывается кейсом, потом
обобщается: инструмент → в MCP, метод → в навык `kompas-3d`.
@@ -24,19 +24,54 @@ description: Внутренняя методика доработки самог
## Продуктизация приёма
> Если нужного инструмента ещё нет — не хардкодь обход под кейс. Заведи кейс в `usecases/`,
> докажи механику (тест или минимальный прогон в usecases/), затем продуктизируй как
> **общий** инструмент MCP (с тестами, TDD) и опиши приём в навыке `kompas-3d`.
> докажи механику (спайк или минимальный прогон), затем продуктизируй как **общий** инструмент MCP
> (с тестами, TDD) и опиши приём в навыке `kompas-3d`.
Порядок для нового инструмента:
1. **Сигнатуры и константы** — через субагента `kompas-sdk-research` (см. ниже), затем перепроверка
рефлексией по `libs/kompas-interop/*.dll`: справка врёт (напр. `IRadialDimension.NominalValue`
отдаёт радиус, а не диаметр; `direction` у `ksInclineDefinition` инвертирован).
2. **Спайк вживую** — минимальный прогон на запущенном КОМПАС; COM-цепочка считается верной только
после этого.
3. **Сервис в `src/Kompas.Mcp.Core/<домен>/`** + валидация параметров отдельным статическим классом
(`*Validation`, `SketchGeometry`) — она покрывается unit-тестами без COM.
4. **Инструмент в `src/Kompas.Mcp.Host/Tools/<Группа>Tools.cs`** — по файлу на группу.
5. **Тесты**: unit на валидацию/маппинг перечислений, integration на реальную геометрию
(класс наследует `IntegrationTestBase`, артефакты — в `.scratch/`).
6. **Документация**: каталог инструментов в `README.md` (+ счётчик в заголовке), проверенная
COM-цепочка и подводные камни — в `CLAUDE.md` §«Key implementation facts» и в память
(`kompas-*-api*.md`). Правь эти файлы сам — субагента для документации больше нет.
## Проверка
```powershell
dotnet build -c Release
dotnet test --filter "Category=Unit" # без COM
dotnet test --filter "Category=Integration" # нужен запущенный КОМПАС
pwsh -NoProfile -File tools/tests/run-ps-tests.ps1 # PowerShell плагина (Pester)
```
CI гоняет только `Category=Unit&Requires!=Windows` на Linux-раннере — integration там не запустится
никогда. Формат решения `.slnx` понимает лишь SDK 9+, поэтому в CI проекты указаны путями.
## Делегирование
- **Поиск по справке SDK** (сигнатуры, константы, перечисления, цепочки COM-вызовов) — субагент
**`kompas-sdk-research`** (Haiku, read-only, по MD-базе `docs/Kompas3D_SDK/`). Не грепай справку сам.
- **Правка документации проекта** (`README.md`, `CLAUDE.md`, `docs/*`) — субагент **`docs-maintainer`**
(Sonnet). Передавай ему сводку изменений, не правь доки вручную.
**Поиск по справке SDK** (сигнатуры, константы, перечисления, цепочки COM-вызовов) — субагент
**`kompas-sdk-research`** (Haiku, read-only). Справка живёт **не в репозитории**, а в RAG-базе
`kompas-sdk` (MCP-сервер, репозиторий `kompas-sdk-docs`) — грепать по проекту бесполезно, доступ к
базе есть только у субагента. Ошибка стоит дёшево: ответ всё равно перепроверяется рефлексией по
interop-сборкам.
## Ориентиры в репозитории
- Инструменты и их реализация: `src/Kompas.Mcp.Host/Tools/*`, `src/Kompas.Mcp.Core/*`.
- Кейсы (полигон подходов): `usecases/` (см. `usecases/README.md`) — в `.gitignore`, в плагин не входит.
- Публикуемые навыки плагина: `plugin/skills/` (`kompas-3d`, `kompas-fdm-design`).
- Память: `kompas-step-and-assembly-api` (проверенные COM-приёмы импорта/экспорта/обхода сборки).
- Инструменты и реализация: `src/Kompas.Mcp.Host/Tools/*`, `src/Kompas.Mcp.Core/*`.
- Кейсы (полигон подходов): `usecases/` — локальный, в `.gitignore`, в плагин не входит.
- Публикуемые навыки плагина: `plugin/skills/` (`kompas-3d`, `kompas-fdm-design`) — источник истины;
`.claude/skills/<навык>` это junction'ы, раскладку делает `tools/sync-agent-assets.ps1`.
Перед `git checkout` на ревизию без `plugin/skills` — сначала `-Remove`, иначе checkout ломается
на полпути (подробности в `CLAUDE.md`).
- Плагин и доставка сервера: `plugin/` (манифест, `server.lock.json`, лаунчер) — версия в
`plugin.json` и `server.lock.json` должна совпадать, это проверяет CI.
- Память: `kompas-step-and-assembly-api`, `kompas-drawing-api7`, `kompas-hole-api7`,
`kompas-formative-features-api5`, `kompas-pattern-mirror-api5`, `kompas-variables-api5`.
+401 -114
View File
@@ -4,183 +4,470 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project goal
Build an **MCP (Model Context Protocol) server** that lets an LLM drive the CAD system **КОМПАС-3D** (ASCON). The server exposes КОМПАС operations (create/open documents, build 2D geometry, 3D parts and assemblies, read/write parameters, run macros) as MCP tools.
An **MCP (Model Context Protocol) server** that lets an LLM drive the CAD system **КОМПАС-3D** (ASCON).
The server is a COM client of an installed КОМПАС: it exposes CAD operations (documents, 2D sketches,
3D parts, assemblies, drawings, STEP round-trip, model inspection) as MCP tools over stdio.
Authoritative references:
- **SDK knowledge base in RAG: MCP server `kompas-sdk`** — 2465 articles (one per COM interface/topic) served from the `kompas-sdk-docs` repo, indexed on CT 127 (rag-node). **Not in this repo anymore** — Grep/Read won't find it. For any lookup, dispatch the **`kompas-sdk-research`** subagent (Haiku), which owns the MCP tools; see below.
**There is no `docs/` directory** — it was deleted deliberately (commit `b022a91`). This file and
[`README.md`](README.md) are the only prose documentation in the repo; verified COM call-chains live
in «Key implementation facts» below plus the memory files (`kompas-*-api*.md`). Do not re-create
`docs/ARCHITECTURE.md`, `docs/TODO.md`, `docs/OPEN_QUESTIONS.md` — anything worth keeping goes here.
Authoritative API references:
- **SDK knowledge base in RAG: MCP server `kompas-sdk`** — 2465 articles (one per COM interface/topic),
served from the separate `kompas-sdk-docs` repo, indexed on CT 127 (rag-node). **Not in this repo**
Grep/Read won't find it. For any lookup dispatch the **`kompas-sdk-research`** subagent (Haiku), which
owns the MCP tools.
- Online SDK docs: https://help.ascon.ru/KOMPAS_SDK/22/ru-RU/index.html
- Local SDK install: `C:\Program Files\ASCON\KOMPAS-3D v24 Home\SDK` (headers `Include\ksConstants*.h` are the last word on numeric constant values)
- Local SDK install: `C:\Program Files\ASCON\KOMPAS-3D v24 Home\SDK` (headers `Include\ksConstants*.h`
are the last word on numeric constant values)
- `libs/kompas-interop/*.dll` — reflect over these to verify any signature a research answer gives you.
## Navigating the SDK docs (MCP `kompas-sdk`)
### Navigating the SDK base (MCP `kompas-sdk`, via the subagent)
The base is distilled from the 26 000-page Help&Manual WebHelp export: one article per COM interface/topic under `sdk/interfaces|enums|structures|guides` (+ `resources/` images), each with frontmatter (`title`, `type`, `api`, `domain`, `tags`, `sdk_version`); methods/properties are `## ` headings inside the interface article. Search order:
The base is distilled from the 26 000-page Help&Manual WebHelp export: one article per COM
interface/topic under `sdk/interfaces|enums|structures|guides` (+ `resources/` images), each with
frontmatter (`title`, `type`, `api`, `domain`, `tags`, `sdk_version`); methods/properties are `## `
headings inside the interface article. Search order:
- **`grep_knowledge`** for an exact name — method, interface, enum, constant (`SetSideParam`, `ksHoleTypeEnum`, `o3d_incline`). Method names are `## SetSideParam - …` headings, so the name lands you in the right article. Regex works.
- **`search_knowledge`** for task-shaped questions («чем построить массив по сетке»), with filters `type` (`interface`/`enum`/`struct`/`guide`), `tags` (`api7`, `api5`, `3d`, `sketch`, …), `path_prefix` (`sdk/enums/`).
- **`get_chunk_context`** to read around a hit; `get_document` only for small articles — `sdk/interfaces/геометрия.md` is 261 KB and will blow the context.
- **`grep_knowledge`** for an exact name — method, interface, enum, constant (`SetSideParam`,
`ksHoleTypeEnum`, `o3d_incline`). Method names are `## SetSideParam - …` headings. Regex works.
- **`search_knowledge`** for task-shaped questions («чем построить массив по сетке»), with filters
`type` (`interface`/`enum`/`struct`/`guide`), `tags` (`api7`, `api5`, `3d`, `sketch`, …),
`path_prefix` (`sdk/enums/`).
- **`get_chunk_context`** to read around a hit; `get_document` only for small articles —
`sdk/interfaces/геометрия.md` is 261 KB and will blow the context.
- **`knowledge_status`** — indexed commit, document and point counts, when a search looks suspiciously empty.
It covers the **COM API7/API5** reference this project uses. The cross-platform Qt/C++ **KsAPI** flavour (`ksapi_*`, non-COM binding) is intentionally **excluded** — ignore unless explicitly working with KsAPI.
Covers the **COM API7/API5** reference this project uses. The cross-platform Qt/C++ **KsAPI** flavour
(`ksapi_*`, non-COM) is intentionally **excluded** — ignore unless explicitly working with KsAPI.
Updating the base when a new КОМПАС version ships (in `kompas-sdk-docs`, on the machine with КОМПАС installed): `pwsh tools/update_sdk_docs.ps1 -SdkVersion 25` regenerates `sdk/` from `SDK\Help\KOMPAS_SDK_ru-RU.zip`; review the diff (deleted articles = broken parser, not a shrunken API), commit, tag `sdk-vNN`, push — Gitea Actions on `rag-node` revalidates and swaps the `kompas_sdk` alias. Indexing is incremental by checksum.
Updating the base when a new КОМПАС ships (in `kompas-sdk-docs`, on the machine with КОМПАС installed):
`pwsh tools/update_sdk_docs.ps1 -SdkVersion 25` regenerates `sdk/` from `SDK\Help\KOMPAS_SDK_ru-RU.zip`;
review the diff (deleted articles = broken parser, not a shrunken API), commit, tag `sdk-vNN`, push —
Gitea Actions on `rag-node` revalidates and swaps the `kompas_sdk` alias. Indexing is incremental by checksum.
## Current state
**Status:** v1 + v2 + STEP/assembly + direct B-rep edit + structural inspection + 2D drawings + Claude Code plugin packaging — implemented and working (full sketchfeatureinspect→STEP round-trip + `move_face` + `describe_model` validated end-to-end; server also ships as the `kompas` plugin, see below).
Stack: **.NET 8 (`net8.0-windows`, x64), C#**, MCP via the official `ModelContextProtocol` SDK over **stdio**. **84 MCP tools, 381 .NET tests green (250 unit + 131 integration) + 41 Pester tests** for the plugin's PowerShell scripts (not counted in the .NET total).
**Status:** working end-to-end. Parts (sketchfeatureinspect), forming ops, patterns/mirror, holes,
direct B-rep edit, STEP import/export, assemblies, 2D drawings, structural inspection — implemented and
validated live. Also packaged as the Claude Code plugin `kompas` (no release published yet).
Where to look (single source of truth — do **not** duplicate these lists here):
- **Full tool catalog** (by group, all 84) → [`README.md`](README.md) §«Инструменты».
- **Roadmap / what's already done** → [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) §10.
- **Backlog / what's left** → [`docs/TODO.md`](docs/TODO.md) (canonical; top of «2D-ЧЕРТЁЖ» = next priority. Associative diametral/radial→circle binding (`associate` flag), sheet format (`drawing_set_sheet_format`), leaders (`drawing_add_leader`) done — next: bases, tolerances, arc/angular/rough binding; plus extra mate types, package E continuation).
- **Design decisions / caveats** → [`docs/OPEN_QUESTIONS.md`](docs/OPEN_QUESTIONS.md). Known caveat («Ревью v2»): boss/cut direction on a selected face depends on face-normal orientation — `forward` may need flipping; the agent picks via snapshot or `describe_face` normal.
- **Verified COM call-chains & gotchas** → «Key implementation facts» below + memory files (`kompas-*-api*.md`).
Stack: **.NET 8 (`net8.0-windows`, x64), C#**, MCP via the official `ModelContextProtocol` SDK over **stdio**.
**84 MCP tools; 381 .NET tests green (250 unit + 131 integration) + 41 Pester tests** for the plugin's
PowerShell (not counted in the .NET total).
Principle: **MCP = translating SDK capabilities into general tools** (not task-specific). On top of MCP — skill **`.claude/skills/kompas-3d/`** with a methodology (playbooks, heuristics, validated in `usecases/`). Layout: `usecases/` (in .gitignore) is the proving ground; proven techniques are promoted to the skill. A separate skill **`.claude/skills/kompas-fdm-design/`** is a DFM methodology layer for FDM/FFF 3D-printing (overhang rules, wall thickness, teardrop holes, fits/clearances, orientation for strength, elephant foot, bosses/inserts/snap-fits) with a lightweight geometry audit using existing inspection tools; it works on top of `kompas-3d` (the build layer) and exports via `export_step` — no slicer integration.
The full tool catalog (by group, all 84) lives in [`README.md`](README.md) §«Инструменты» — the single
source of truth; do not duplicate it here.
**Skill layout / sync** (`tools/sync-agent-assets.ps1`, module `tools/AgentAssets.psm1`): source of truth is `plugin/skills/{kompas-3d,kompas-fdm-design}`; `.claude/skills/<name>` and `.agents/skills/<name>` are **junctions** onto it created by the script (their paths are pinpointed in `.gitignore` — git sees a junction as a plain directory). Modes are mutually exclusive parameter sets (`-Remove -Check` together is a parameter-binding error):
```
src/Kompas.Mcp.Core/ COM layer: STA dispatcher, session, documents, modeling, drawings, conversion, inspection
src/Kompas.Mcp.Host/ MCP stdio server; Tools/*.cs = one file per tool group
tests/Kompas.Mcp.Tests/ unit (root) + Integration/ (needs a running КОМПАС)
libs/kompas-interop/ ASCON interop DLLs — compile-time only, see «Interop assemblies are not shipped»
plugin/ Claude Code plugin `kompas`: manifest, launcher, server lock, skills (source of truth)
tools/ skill-junction script + Pester tests
.claude/agents/ kompas-sdk-research (Haiku) — the only subagent left in this repo
.claude/skills/ junctions onto plugin/skills + kompas-mcp-dev (internal, non-junctioned)
```
```powershell
dotnet build -c Release # build
dotnet test --filter "Category=Unit" # 250 unit tests, no COM
dotnet test --filter "Category=Integration" # 131 integration tests, needs running КОМПАС
dotnet run --project src/Kompas.Mcp.Host # start the MCP server (stdio)
pwsh -NoProfile -File tools/tests/run-ps-tests.ps1 # 41 Pester tests (plugin PowerShell)
```
Principle: **MCP = translating SDK capabilities into general tools**, never task-specific ones. If a
technique is missing, don't hardcode a workaround for the case at hand — prove the mechanism, then add
a *general* tool with tests, and describe the method in the `kompas-3d` skill.
### Backlog (canonical since `docs/TODO.md` is gone)
- **2D drawing** (top priority): bases (`drawing_add_base`), tolerances, associative binding for
arc/angular/rough dimensions (needs its own spike — `ILineDimension` has no `BaseObject`).
- **Assembly**: mate types beyond `coincidence`/`distance``parallel`, `perpendicular`, `concentric`,
`angle`, `tangency` (enum values exist in `MateType.cs`, not verified live).
- **Known caveat:** boss/cut direction on a *selected face* depends on the face-normal orientation —
`forward` may need flipping. Pick the direction from `describe_face` normal or a snapshot before building.
## Skills and their layout
- **`kompas-3d`** — methodology for *building* things through the MCP tools (playbooks, heuristics).
- **`kompas-fdm-design`** — DFM layer for FDM/FFF printing (overhang rules, wall thickness, teardrop
holes, fits/clearances, orientation for strength, elephant foot, bosses/inserts/snap-fits) plus a
light geometry audit via the inspection tools. Builds through `kompas-3d`, exports via `export_step`,
no slicer integration.
- **`kompas-mcp-dev`** — internal: developing the server itself (the two layers, the `usecases/` proving
ground, the checklist for productizing a technique into a general tool, delegation). Lives at
`.claude/skills/kompas-mcp-dev/SKILL.md` as a real directory (not a junction) because the published
skills must not reference things a plugin user doesn't have.
**Sync** (`tools/sync-agent-assets.ps1`, module `tools/AgentAssets.psm1`): the source of truth is
`plugin/skills/{kompas-3d,kompas-fdm-design}`; `.claude/skills/<name>` and `.agents/skills/<name>` are
**junctions** onto it created by the script (their paths are pinpointed in `.gitignore` — git sees a
junction as a plain directory). Modes are mutually exclusive parameter sets (`-Remove -Check` together
is a parameter-binding error):
- no flags — link (`Created`/`AlreadyLinked`)
- `-Check` — verify, prints `БИТО` and exits 1 on drift
- `-AllowCopy` — fall back to a real copy (`Copied`) if a junction can't be created
- `-Remove` — unlink (`Removed`/`Missing`/`Skipped`)
**Gotcha:** switching branch/commit to one predating `plugin/skills` leaves the junctions dangling and `git checkout` fails mid-way (`unable to create file .claude/skills/kompas-fdm-design/SKILL.md: No such file or directory`) — part of the worktree is already deleted while HEAD stays on the old branch. Recover with `git ls-files --deleted -z | xargs -0 git restore --`.
**Gotcha:** switching to a branch/commit predating `plugin/skills` leaves the junctions dangling and
`git checkout` fails mid-way (`unable to create file .claude/skills/kompas-fdm-design/SKILL.md: No such
file or directory`) — part of the worktree is already deleted while HEAD stays on the old branch.
Recover with `git ls-files --deleted -z | xargs -0 git restore --`. Correct order:
Correct order:
1. `pwsh -NoProfile -File tools/sync-agent-assets.ps1 -Remove`
2. `git checkout <ref>`
3. `pwsh -NoProfile -File tools/sync-agent-assets.ps1` (re-link, if `plugin/skills` exists on the new ref)
3. `pwsh -NoProfile -File tools/sync-agent-assets.ps1` (re-link, if `plugin/skills` exists on that ref)
`-Remove` is safe because it deletes the reparse point itself via `[System.IO.Directory]::Delete` (no `-Recurse`) — never walks into the target, so `plugin/skills` content is untouched, and a real (non-junction) directory is left alone (`Skipped`); it detects a junction by the `ReparsePoint` attribute, not by resolving the target, which is exactly why it still works once the target is gone. No `post-checkout` hook on purpose — it fires after checkout already broke, too late to help; step 3 covers the re-link anyway. Tests: `Describe 'Remove-AgentSkillLink'` (5 cases) in `tools/tests/AgentAssets.Tests.ps1`, run via `pwsh -NoProfile -File tools/tests/run-ps-tests.ps1` — part of 41 Pester tests total across the plugin's PowerShell (`AgentAssets.Tests.ps1` 12, `KompasMcpBootstrap.Tests.ps1` 23, `Launcher.Tests.ps1` 6).
`-Remove` is safe: it deletes the reparse point itself via `[System.IO.Directory]::Delete` (no `-Recurse`),
never walks into the target, and leaves a real (non-junction) directory alone (`Skipped`); it detects a
junction by the `ReparsePoint` attribute rather than by resolving the target, which is exactly why it
still works once the target is gone. No `post-checkout` hook on purpose — it fires after checkout already
broke, too late to help.
## Delegation subagent
**`kompas-sdk-research`** (`.claude/agents/`, model **Haiku**, read-only — MCP `kompas-sdk` tools only):
finds an interface/method/enum/constant signature in the SDK knowledge base and returns a compressed
summary. Dispatch it for **any** SDK lookup — it holds the only access to the base, which no longer lives
in this repo. Its system prompt is in the agent definition, so pass only the question as the prompt.
A wrong answer is cheap: re-verify against `libs/kompas-interop/*.dll` by reflection.
(The former `docs-maintainer` subagent was removed together with `docs/` — edit `README.md`/`CLAUDE.md`
directly.)
## Claude Code plugin (`plugin/`)
The server and both skills ship as the Claude Code plugin **`kompas`** — `plugin/` is a self-contained tree, distributed independently of the rest of the repo (see «Distribution channel» below).
The server and both published skills ship as the plugin **`kompas`** — `plugin/` is a self-contained tree,
distributed independently of the rest of the repo.
- `plugin/.claude-plugin/plugin.json` — manifest: `name: "kompas"`, `version` (currently `0.0.0`). Version is Claude Code's cache key — `/plugin update`/auto-update skip a plugin whose version matches what's already installed, so every release must bump it.
- `plugin/server.lock.json` — pins exactly one server version: `{version, sourceSha, url, sha256}`. `0.0.0` with empty fields = no release published yet.
- `plugin/skills/{kompas-3d,kompas-fdm-design}/`**the** source of truth for both skills (see skill-layout section above); `.claude/skills/kompas-mcp-dev/SKILL.md` is a real (non-junctioned) internal skill covering server *development* itself (usecases proving ground, productizing playbooks, subagents) — split out because the published skill edition must not reference things a plugin user doesn't have; marked as a draft pending a repo workflow pass.
- `plugin/commands/doctor.md``/kompas:doctor`: compares expected version (from the lock) against `KOMPAS_MCP_EXE` override / `install.json` marker / `kompas-mcp.exe --version`, checks whether КОМПАС is installed/running, then `kompas_connect``kompas_status`.
- `plugin/.mcp.json` — declares the `kompas` MCP server as `powershell -File ${CLAUDE_PLUGIN_ROOT}/scripts/launch-kompas-mcp.ps1`.
- `plugin/scripts/KompasMcpBootstrap.psm1` — delivery: `Read-KompasMcpLock`/`Test-KompasMcpInstall`/`Install-KompasMcpServer`/`Resolve-KompasMcpExecutable`. Downloads the release asset, verifies SHA256, unpacks under `%LOCALAPPDATA%\kompas-mcp\.tmp` and atomically moves it to `%LOCALAPPDATA%\kompas-mcp\<version>`, writes an `install.json` marker. An install is only trusted when the marker's `version`/`sha256` match the lock.
- `plugin/scripts/launch-kompas-mcp.ps1` — entry point: `KOMPAS_MCP_EXE` env var wins (dev mode), else deliver per the lock; runs the server and forwards its exit code. Invariant: **not a single byte reaches stdout before the server starts** (stdout is the JSON-RPC channel) — all diagnostics go to stderr prefixed `kompas-mcp launcher:`.
- `plugin/README.md` — prerequisites and install; `plugin/adapters/{codex,opencode}/` — snippets (not separate distributions) wiring the same launcher into Codex and opencode.
- CLI flag `--version` (`Kompas.Mcp.Core/Startup/CliArguments.IsVersionRequest`, early-exit in `Program.cs` before the host/КОМПАС start) — prints the 3-part assembly version and exits; `/kompas:doctor` uses it to cross-check the installed binary against the lock.
- `plugin/.claude-plugin/plugin.json` — manifest: `name: "kompas"`, `version` (currently `0.0.0`).
Version is Claude Code's cache key — `/plugin update` and auto-update skip a plugin whose version
matches what's installed, so **every release must bump it**.
- `plugin/server.lock.json` — pins exactly one server version: `{version, sourceSha, url, sha256}`.
`0.0.0` with empty fields = no release published yet.
- `plugin/skills/{kompas-3d,kompas-fdm-design}/` — the source of truth for both published skills.
- `plugin/commands/doctor.md``/kompas:doctor`: compares the expected version (from the lock) against
the `KOMPAS_MCP_EXE` override / `install.json` marker / `kompas-mcp.exe --version`, checks whether
КОМПАС is installed and running, then `kompas_connect``kompas_status`.
- `plugin/.mcp.json` — declares the `kompas` MCP server as
`powershell -File ${CLAUDE_PLUGIN_ROOT}/scripts/launch-kompas-mcp.ps1`.
- `plugin/scripts/KompasMcpBootstrap.psm1` — delivery: `Read-KompasMcpLock` / `Test-KompasMcpInstall` /
`Install-KompasMcpServer` / `Resolve-KompasMcpExecutable`. Downloads the release asset, verifies SHA256,
unpacks under `%LOCALAPPDATA%\kompas-mcp\.tmp` and atomically moves it to
`%LOCALAPPDATA%\kompas-mcp\<version>`, writes an `install.json` marker. An install is trusted only when
the marker's `version`/`sha256` match the lock.
- `plugin/scripts/launch-kompas-mcp.ps1` — entry point: the `KOMPAS_MCP_EXE` env var wins (dev mode),
otherwise deliver per the lock; runs the server and forwards its exit code. Invariant: **not a single
byte reaches stdout before the server starts** (stdout is the JSON-RPC channel) — all diagnostics go to
stderr prefixed `kompas-mcp launcher:`.
- `plugin/README.md` — prerequisites and install; `plugin/adapters/{codex,opencode}/` — config snippets
(not separate distributions) wiring the same launcher into Codex and opencode.
- CLI flag `--version` (`Core/Startup/CliArguments.IsVersionRequest`, early-exit in `Program.cs` before
the host and any КОМПАС contact) — prints the 3-part assembly version; `/kompas:doctor` uses it to
cross-check the installed binary against the lock.
**Gotchas:**
- **Plugin PowerShell files must be UTF-8 **with BOM**.** The launcher runs under `powershell` (Windows PowerShell 5.1), which reads BOM-less files in the system ANSI codepage — Cyrillic string literals corrupt parsing of the whole script. `pwsh` 7 doesn't reproduce this (BOM auto-detected either way).
- Switching branch/commit across a `plugin/skills` boundary can dangle the skill junctions mid-checkout — same failure mode and fix as described in the skill-layout section above.
- **Plugin PowerShell files must be UTF-8 *with BOM*.** The launcher runs under `powershell` (Windows
PowerShell 5.1), which reads BOM-less files in the system ANSI codepage — Cyrillic string literals then
corrupt parsing of the whole script. `pwsh` 7 doesn't reproduce this (BOM auto-detected either way).
- Switching branch/commit across a `plugin/skills` boundary can dangle the skill junctions mid-checkout —
same failure mode and fix as in the skill-layout section above.
### Distribution channel — the `dist` branch, not `main`
Releases (`.gitea/workflows/release.yml`, tag `vX.Y.Z`) `dotnet publish -r win-x64 --self-contained` zip → idempotent upsert of a Gitea release + asset via the Gitea API → re-verify the downloaded asset's SHA256 → commit updated `server.lock.json`/`plugin.json` to branch **`dist`**, on top of the tagged commit. A private plugin marketplace (`home-repo-cc`) will reference the plugin via a `git-subdir` source pinned to `ref: dist`**not yet added**, and there is **no release yet**. Rationale: a release is built from the tagged commit; if the marketplace pointed at `main`, a user could get a newer `plugin/` tree under a version whose binary was built from a different (older) commit. `.gitea/workflows/ci.yml` runs on push/PR to `main`: build + unit tests (`Category=Unit&Requires!=Windows`, explicit project paths — see gotcha below) + consistency check between `plugin.json` and `server.lock.json`. Runner: `gitea-runner` (docker executor, label `ubuntu-latest`).
Releases (`.gitea/workflows/release.yml`, tag `vX.Y.Z`): `dotnet publish -r win-x64 --self-contained`
zip → idempotent upsert of a Gitea release + asset via the Gitea API → re-verify the downloaded asset's
SHA256 → commit updated `server.lock.json`/`plugin.json` to branch **`dist`**, on top of the tagged commit.
A private plugin marketplace (`home-repo-cc`) will reference the plugin via a `git-subdir` source pinned to
`ref: dist`**not added yet**, and there is **no release yet**. Rationale: a release is built from the
tagged commit; if the marketplace pointed at `main`, a user could get a newer `plugin/` tree under a
version whose binary was built from an older commit.
Extra CI gotchas:
- **SDK 8 doesn't understand the `.slnx` solution format** (`MSBUILD : error MSB1003`) — CI builds/tests explicit project paths (`dotnet build src/Kompas.Mcp.Host/Kompas.Mcp.Host.csproj`, `dotnet test tests/Kompas.Mcp.Tests/Kompas.Mcp.Tests.csproj`) instead of the solution. Not reproducible locally with SDK 9+.
- **`net8.0-windows` builds and runs unit tests fine on the Linux runner** with `-p:EnableWindowsTargeting=true` — confirmed by a green CI run (`Passed! Failed: 0, Passed: 221`; 245 after the interop-resolver tests). ASCON interop DLLs link as ordinary references; `ole32`/`oleaut32` are only needed at COM runtime, not build time. 5 `DispatcherTests` are tagged `[Trait("Requires", "Windows")]` and excluded — `Thread.SetApartmentState(STA)` throws `PlatformNotSupportedException` on Linux.
- **`.gitignore`'s `.mcp.json` rule is anchored to the repo root** (`/.mcp.json`) — without the leading slash the glob also matched the tracked, portable `plugin/.mcp.json`.
- Integration tests never run in CI (require a running КОМПАС with GUI and a license) — this is a hard boundary, not a future task.
`.gitea/workflows/ci.yml` runs on push/PR to `main`: `plugin.json``server.lock.json` consistency check
(jq), build, then unit tests filtered `Category=Unit&Requires!=Windows` (245 of the 250). Runner:
`gitea-runner` (docker executor, label `ubuntu-latest`).
**Delegation subagents** (`.claude/agents/`, dispatched via the `Agent` tool to keep heavy work off Opus's context — system prompt lives in the agent definition, so pass only the variable part as the prompt):
- **`kompas-sdk-research`** (model: **Haiku**, read-only — MCP `kompas-sdk` tools only): finds an interface/method/enum/constant signature in the SDK knowledge base and returns a compressed summary. Dispatch it for **any** SDK lookup — it holds the only access to the base, which no longer lives in this repo; a wrong answer is cheap — Opus re-verifies against `libs/kompas-interop/*.dll` by reflection.
- **`docs-maintainer`** (model: **Sonnet**, `Read`/`Edit`/`Write`/`Glob`/`Grep`): syncs `README.md`/`CLAUDE.md`/`docs/ARCHITECTURE.md`/`docs/OPEN_QUESTIONS.md`/`docs/presentation.html` with code changes from a change summary you provide. Dispatch it for doc updates instead of editing docs by hand. (Both were skills before — converted to native subagents; the model split reflects task type: bounded retrieval → Haiku, judgement-heavy editing → Sonnet.)
Layout: `src/Kompas.Mcp.Core` (COM layer), `src/Kompas.Mcp.Host` (MCP stdio server + tools),
`tests/Kompas.Mcp.Tests` (unit + integration), `libs/kompas-interop/*.dll` (КОМПАС interop
assemblies from SDK `Samples/Common`, referenced via `Directory.Build.props``KompasInteropDir`
**compile-time only**, see «Interop assemblies are not shipped» below).
```powershell
dotnet build -c Release # build
dotnet test --filter "Category=Unit" # unit tests (no COM)
dotnet test --filter "Category=Integration" # integration (requires running КОМПАС)
dotnet run --project src/Kompas.Mcp.Host # start the MCP server (stdio)
```
CI gotchas:
- **SDK 8 doesn't understand the `.slnx` solution format** (`MSBUILD : error MSB1003`) — CI builds and
tests explicit project paths (`src/Kompas.Mcp.Host/Kompas.Mcp.Host.csproj`,
`tests/Kompas.Mcp.Tests/Kompas.Mcp.Tests.csproj`) instead of the solution. Not reproducible locally with SDK 9+.
- **`net8.0-windows` builds and runs unit tests fine on the Linux runner** with
`-p:EnableWindowsTargeting=true`. ASCON interop DLLs link as ordinary references; `ole32`/`oleaut32` are
only needed at COM runtime, not build time. The 5 `DispatcherTests` are tagged
`[Trait("Requires", "Windows")]` and excluded — `Thread.SetApartmentState(STA)` throws
`PlatformNotSupportedException` on Linux.
- **`.gitignore`'s `.mcp.json` rule is anchored to the repo root** (`/.mcp.json`) — without the leading
slash the glob also matched the tracked, portable `plugin/.mcp.json`.
- Integration tests never run in CI (they need a running КОМПАС with GUI and a license) — a hard boundary,
not a future task.
## Key implementation facts (don't relearn)
- **Interop assemblies are not shipped** (`KompasAPI7`, `Kompas6API5`, `Kompas6Constants`, `Kompas6Constants3D` belong to ASCON). `libs/kompas-interop` is referenced with `<Private>false</Private>` — compile-time only, absent from `bin/` **and** from the published archive/`deps.json`. At runtime `KompasInteropLoader.Install()` (first line of `Program.Main`) hooks `AssemblyLoadContext.Default.Resolving`; lookup order: `KOMPAS_INTEROP_DIR` → next to `kompas-mcp.exe` → cache → the installed КОМПАС. **The install has no loose interop DLLs** — they live only inside `<install>\SDK\Samples\CSharp.zip` under `Common/`, so the provider unzips them into `%LOCALAPPDATA%\kompas-mcp\interop\<key>` (key = hash of the zip's path+size+mtime, so a КОМПАС upgrade re-extracts; staging dir + `Directory.Move` for atomicity). Install dir comes from `HKCR\<ProgID>\CLSID``HKCR\CLSID\{…}\LocalServer32` (`"…\Bin\kHome.Exe"` on v24 Home) — `HKLM\SOFTWARE\ASCON` holds settings, not the path. All interop identities are `Version=1.0.0.0` with **no strong name**, so resolving by simple name is version-agnostic across КОМПАС releases. Failure throws `KompasInteropException` with an actionable message; `Program` logs it at startup and `kompas_status` returns it (it checks interop **before** touching `KompasSession` — otherwise the MCP SDK shows only «An error occurred invoking …»). **The test project copies the DLLs itself** (`<None Include="$(KompasInteropDir)\*.dll">`) — the Linux CI runner has no КОМПАС to resolve from.
- **All COM calls run on one dedicated STA thread** (`KompasDispatcher.InvokeAsync`); КОМПАС is single-instance STA. Services never touch COM off that thread.
- **Connection**: API5 (`KOMPAS.Application.5``KompasObject`) then `ksGetApplication7()``IApplication`; the API5 root is needed for `ksPart` 3D-building and `ksDocument3D` snapshots. API7 is used for app/documents, holes, assemblies, drawings, direct B-rep edit.
- **3D via API5 `ksPart`**: `NewEntity(o3d_*)` + matching definition; always check `entity.Create()` return. Entity codes: `o3d_sketch`/`o3d_bossExtrusion`/`o3d_cutExtrusion`/`o3d_bossRotated`/`o3d_fillet`/`o3d_chamfer`, `o3d_shellOperation=43`, `o3d_ribOperation=44`, `o3d_baseEvolution=45` (sweep), `o3d_baseLoft=30`, `o3d_planeOffset=14`, `o3d_meshCopy=35` (linear pattern), `o3d_circularCopy=36`, `o3d_mirrorOperation=48`, `o3d_mirrorAllOperation=49`, `o3d_incline=42` (draft), axes `o3d_axisOX/OY/OZ=71/72/73`. Definitions: `ksBossExtrusionDefinition`/`ksCutExtrusionDefinition`/`ksBossRotatedDefinition`/`ksFilletDefinition`/`ksChamferDefinition`/`ksShellDefinition`/`ksRibDefinition`/`ksBaseEvolutionDefinition`/`ksBaseLoftDefinition`/`ksPlaneOffsetDefinition`/`ksMeshCopyDefinition`/`ksCircularCopyDefinition`/`ksMirrorCopyDefinition`/`ksMirrorCopyAllDefinition`/`ksInclineDefinition`. Cut-through holes use `dtBoth` + `etThroughAll`.
- **Face/edge selection**: `ksPart.EntityCollection(o3d_face|o3d_edge)``SelectByPoint(x,y,z)` (world mm) → `GetByIndex(0)`; or by stable index from `list_faces`/`list_edges` (helpers `SelectFaceByIndex`/`SelectEdgeByIndex`, range-checked before mutation; by-index more reliable than by-point). Classify: `ksFaceDefinition.IsPlanar/IsCylinder/…` + `GetArea(ST_MIX_MM)`; `ksEdgeDefinition.IsLineSeg/IsCircle/IsArc/…` + `GetLength()`. Sketch axis = line with system style **3** (осевая).
- **МЦХ / mass props**: use **API5 `ksPart.CalcMassInertiaProperties(ST_MIX_MM|ST_MIX_KG)`**`ksMassInertiaParam` (`v`/`m`/`F`/`xc`/`yc`/`zc`), computed on demand. **Do NOT** use API7 `IMassInertiaParam7.Calculate()` — its `Actual` flag sticks after the first calc and won't refresh on API5 geometry changes (stale volume). Bounding box: `ksPart.GetGabarit(full:false, …)`.
- **Snapshot**: `ksDocument3D.SaveAsToRasterFormat(file, ksRasterFormatParam)` renders to a temp file (the in-memory `resultArrayBytes` stays empty when a filename is given); read bytes back. Return to MCP via `ImageContentBlock.FromBytes(bytes, mime)`**not** `Data = bytes` (Data holds base64 bytes). Use `model_snapshot` only for visually-spatial questions; **prefer `describe_model` first** (structural «passport» — bbox + МЦХ + bodies + topology + feature tree + variables, no image token cost).
- Logs go to **stderr** (stdout is the MCP channel). Integration tests reuse one КОМПАС via `KompasFixture`; artifacts land in gitignored `.scratch/`. All integration test classes inherit `IntegrationTestBase` (`IAsyncLifetime`) → `DocumentService.CloseAllAsync` after each test (prevents tab accumulation).
- **Feature tree API**: read via API5 `ksPart.GetFeature()``(ksFeature).SubFeatureCollection(true,false)``ksFeatureCollection`. Cast `(ksFeature)part` does NOT work (RCW QI returns null) — must use `GetFeature()`. `ksFeature.type` returns only coarse `o3d_entity` and does NOT distinguish operations; precise type and params come from `ksFeature.GetObject()``ksEntity.GetDefinition()` + the matching definition (`ksBossExtrusionDefinition.GetSideParam`, `ksFilletDefinition.radius`, `ksChamferDefinition.GetChamferParam`, …). Node names ("Элемент выдавливания:1") are localized by КОМПАС. Features registered by id in `_features` support patterns/mirror; helpers `RequireSketch`/`RequireFeature`.
- **Variables (API5)**: create/read via `ksPart.GetFeature().VariableCollection` (property on root ksFeature) — **NOT** `ksPart.VariableCollection()` (returns external-only). Expression is the leading field; value is derived. Apply via `ksPart.RebuildModel()` (check return); after rebuild the RCW variable is stale → re-read via a fresh collection (`ReadValueFresh`). Dependents recalculate automatically; deletion fails if dependents exist (`RemoveVariable` returns FALSE). **Geometry limitation:** variables drive geometry only in a parametric model (sketch dims linked to variable names); our sketches use literal coords → `set_variable` stores/computes the value but does NOT move geometry. Parametric sketches **unavailable via COM** (`ksCDimWithVariable` not constructible from external automation — see `docs/superpowers/specs/2026-05-27-parametric-sketch-findings.md`; do not retry without new info).
- **Topology / measure API**: `ksFaceDefinition.EdgeCollection` / `GetCylinderParam` / `GetSurface().GetNormal` + `normalOrientation`; `ksEdgeDefinition.GetAdjacentFace(bool)` / `GetVertex(bool)``ksVertexDefinition.GetPoint`; bodies via `ksPart.BodyCollection()``ksBody` (`IsSolid` / `FaceCollection`); measurements via `ksPart.GetMeasurer()``ksMeasurer` (`SetObject1/2`, `unit=ST_MIX_MM`, `Calc`, `distance`/`MinDistance`/`angle` in degrees / `IsAngleValid`).
- **"STEP import without history"** detected structurally: `bodyCount > 0 && no formative features && features.Count <= bodyCount+1` (only origin + body). STEP with edits ("Смещённая плоскость" / "Разрезать" / "Переместить грани" / "Булева операция") already has history.
- **STEP import/export COM pattern** (verified): `IApplication.get_Converter((object)(int)ksConverterFromSTEP=-3)` (pass format code, not DLL path) → `IConverter.ConverterParameters(cmd)``IAdditionConvertParameters.Format=ksConverterFromSTEP``IKompasDocument3D1.ConvertFromAdditionFormat(path, prm)`. Export: `ConvertToAdditionFormat` (format codes AP203/AP214/AP242). Param co-classes (AdditionConvertParameters etc.) are NOT CoCreatable — only via converter factories. Assembly traversal (`list_components`): `IKompasDocument3D.TopPart → IPart7.Parts (IParts7)`. Extract component: `prm.NeedCreateComponentsFiles=true` (writes .m3d files next to STEP) + `IPart7.OpenSourceDocument`.
- **Direct B-rep face editing (`move_face`)** (verified): API7 face via `IPart7.FindObjectsByPoint(x,y,z,true)` → cast to `KompasAPI7.IFace`; containers via runtime COM-QI `(ISurfaceContainer)part`, `(IModelContainer)part`; `FaceMover` (SetFaces + Offset + Direction + Update) moves the face. distance>0 = outward (add material), <0 = inward. Works on parametric and imported B-rep. Full split-reposition workflow (SplitSolids/BodyRepositions) is driveable; only partly exposed as tools (`split_solid_by_plane`, `move_body`, `boolean_union`).
- **Interop assemblies are not shipped** (`KompasAPI7`, `Kompas6API5`, `Kompas6Constants`,
`Kompas6Constants3D` belong to ASCON). `libs/kompas-interop` is referenced with `<Private>false</Private>`
— compile-time only, absent from `bin/` **and** from the published archive/`deps.json`. At runtime
`KompasInteropLoader.Install()` (first line of `Program.cs`) hooks `AssemblyLoadContext.Default.Resolving`;
lookup order: `KOMPAS_INTEROP_DIR` → next to `kompas-mcp.exe` → cache → the installed КОМПАС.
**The install has no loose interop DLLs** — they live only inside `<install>\SDK\Samples\CSharp.zip`
under `Common/`, so the provider unzips them into `%LOCALAPPDATA%\kompas-mcp\interop\<key>` (key = hash
of the zip's path+size+mtime, so a КОМПАС upgrade re-extracts; staging dir + `Directory.Move` for
atomicity). The install dir comes from `HKCR\<ProgID>\CLSID``HKCR\CLSID\{…}\LocalServer32`
(`"…\Bin\kHome.Exe"` on v24 Home) — `HKLM\SOFTWARE\ASCON` holds settings, not the path. All interop
identities are `Version=1.0.0.0` with **no strong name**, so resolving by simple name is version-agnostic
across КОМПАС releases. Failure throws `KompasInteropException` with an actionable message; `Program`
logs it at startup (non-fatal — `--version` must work without КОМПАС) and `kompas_status` returns it
(it checks interop **before** touching `KompasSession`, otherwise the MCP SDK shows only «An error
occurred invoking …»). **The test project copies the DLLs itself**
(`<None Include="$(KompasInteropDir)\*.dll">`) — the Linux CI runner has no КОМПАС to resolve from.
- **All COM calls run on one dedicated STA thread** (`KompasDispatcher.InvokeAsync`); КОМПАС is
single-instance STA. Services never touch COM off that thread.
- **Connection**: API5 (`KOMPAS.Application.5``KompasObject`) then `ksGetApplication7()``IApplication`;
the API5 root is needed for `ksPart` 3D building and `ksDocument3D` snapshots. API7 drives app/documents,
holes, assemblies, drawings and direct B-rep edit.
- **3D via API5 `ksPart`**: `NewEntity(o3d_*)` + matching definition; always check `entity.Create()` return.
Entity codes: `o3d_sketch`/`o3d_bossExtrusion`/`o3d_cutExtrusion`/`o3d_bossRotated`/`o3d_fillet`/`o3d_chamfer`,
`o3d_shellOperation=43`, `o3d_ribOperation=44`, `o3d_baseEvolution=45` (sweep), `o3d_baseLoft=30`,
`o3d_planeOffset=14`, `o3d_meshCopy=35` (linear pattern), `o3d_circularCopy=36`, `o3d_mirrorOperation=48`,
`o3d_mirrorAllOperation=49`, `o3d_incline=42` (draft), axes `o3d_axisOX/OY/OZ=71/72/73`. Definitions:
`ksBossExtrusionDefinition`/`ksCutExtrusionDefinition`/`ksBossRotatedDefinition`/`ksFilletDefinition`/
`ksChamferDefinition`/`ksShellDefinition`/`ksRibDefinition`/`ksBaseEvolutionDefinition`/
`ksBaseLoftDefinition`/`ksPlaneOffsetDefinition`/`ksMeshCopyDefinition`/`ksCircularCopyDefinition`/
`ksMirrorCopyDefinition`/`ksMirrorCopyAllDefinition`/`ksInclineDefinition`. Cut-through holes use
`dtBoth` + `etThroughAll`.
- **Face/edge selection**: `ksPart.EntityCollection(o3d_face|o3d_edge)``SelectByPoint(x,y,z)` (world mm)
`GetByIndex(0)`; or by stable index from `list_faces`/`list_edges` (helpers `SelectFaceByIndex`/
`SelectEdgeByIndex`, range-checked before mutation; by-index is more reliable than by-point). Classify:
`ksFaceDefinition.IsPlanar/IsCylinder/…` + `GetArea(ST_MIX_MM)`; `ksEdgeDefinition.IsLineSeg/IsCircle/IsArc/…`
+ `GetLength()`. A sketch axis is a line with system style **3** (осевая).
- **МЦХ / mass props**: use **API5 `ksPart.CalcMassInertiaProperties(ST_MIX_MM|ST_MIX_KG)`**
`ksMassInertiaParam` (`v`/`m`/`F`/`xc`/`yc`/`zc`), computed on demand. **Do NOT** use API7
`IMassInertiaParam7.Calculate()` — its `Actual` flag sticks after the first calc and won't refresh on
API5 geometry changes (stale volume). Bounding box: `ksPart.GetGabarit(full:false, …)`.
- **Snapshot**: `ksDocument3D.SaveAsToRasterFormat(file, ksRasterFormatParam)` renders to a temp file (the
in-memory `resultArrayBytes` stays empty when a filename is given); read the bytes back. Return to MCP via
`ImageContentBlock.FromBytes(bytes, mime)`**not** `Data = bytes` (Data holds base64). Use
`model_snapshot` only for visually-spatial questions; **prefer `describe_model` first** (structural
«passport» — bbox + МЦХ + bodies + topology + feature tree + variables, no image token cost).
- Logs go to **stderr** (stdout is the MCP channel). Integration tests reuse one КОМПАС via `KompasFixture`;
artifacts land in gitignored `.scratch/`. All integration test classes inherit `IntegrationTestBase`
(`IAsyncLifetime`) → `DocumentService.CloseAllAsync` after each test (prevents tab accumulation).
- **Feature tree API**: read via API5 `ksPart.GetFeature()``(ksFeature).SubFeatureCollection(true,false)`
`ksFeatureCollection`. Casting `(ksFeature)part` does NOT work (RCW QI returns null) — must use
`GetFeature()`. `ksFeature.type` returns only a coarse `o3d_entity` and does NOT distinguish operations;
the precise type and params come from `ksFeature.GetObject()``ksEntity.GetDefinition()` + the matching
definition (`ksBossExtrusionDefinition.GetSideParam`, `ksFilletDefinition.radius`,
`ksChamferDefinition.GetChamferParam`, …). Node names («Элемент выдавливания:1») are localized by КОМПАС.
Features registered by id in `_features` support patterns/mirror; helpers `RequireSketch`/`RequireFeature`.
- **Variables (API5)**: create/read via `ksPart.GetFeature().VariableCollection` (property on the root
ksFeature) — **NOT** `ksPart.VariableCollection()` (returns external-only). Expression is the leading
field; value is derived. Apply via `ksPart.RebuildModel()` (check the return); after a rebuild the RCW
variable is stale → re-read via a fresh collection (`ReadValueFresh`). Dependents recalculate
automatically; deletion fails while dependents exist (`RemoveVariable` returns FALSE).
**Geometry limitation:** variables drive geometry only in a parametric model (sketch dims linked to
variable names); our sketches use literal coordinates → `set_variable` stores/computes the value but does
NOT move geometry. Parametric sketches are **unavailable via COM** (`ksCDimWithVariable` is not
constructible from external automation — see memory `kompas-parametric-sketch-findings`; do not retry
without new information).
- **Topology / measure API**: `ksFaceDefinition.EdgeCollection` / `GetCylinderParam` /
`GetSurface().GetNormal` + `normalOrientation`; `ksEdgeDefinition.GetAdjacentFace(bool)` /
`GetVertex(bool)``ksVertexDefinition.GetPoint`; bodies via `ksPart.BodyCollection()``ksBody`
(`IsSolid` / `FaceCollection`); measurements via `ksPart.GetMeasurer()``ksMeasurer` (`SetObject1/2`,
`unit=ST_MIX_MM`, `Calc`, `distance`/`MinDistance`/`angle` in degrees / `IsAngleValid`).
- **«STEP import without history»** is detected structurally: `bodyCount > 0 && no formative features &&
features.Count <= bodyCount+1` (only origin + body). A STEP that has been edited («Смещённая плоскость» /
«Разрезать» / «Переместить грани» / «Булева операция») already has history.
- **STEP import/export COM pattern** (verified): `IApplication.get_Converter((object)(int)ksConverterFromSTEP=-3)`
(pass the format code, not a DLL path) → `IConverter.ConverterParameters(cmd)` →
`IAdditionConvertParameters.Format=ksConverterFromSTEP` → `IKompasDocument3D1.ConvertFromAdditionFormat(path, prm)`.
Export: `ConvertToAdditionFormat` (format codes AP203/AP214/AP242). Param co-classes
(`AdditionConvertParameters` etc.) are NOT CoCreatable — only via the converter factories. Assembly
traversal (`list_components`): `IKompasDocument3D.TopPart → IPart7.Parts (IParts7)`. Extract a component:
`prm.NeedCreateComponentsFiles=true` (writes .m3d files next to the STEP) + `IPart7.OpenSourceDocument`.
- **Direct B-rep face editing (`move_face`)** (verified): API7 face via
`IPart7.FindObjectsByPoint(x,y,z,true)` → cast to `KompasAPI7.IFace`; containers via runtime COM-QI
`(ISurfaceContainer)part`, `(IModelContainer)part`; `FaceMover` (SetFaces + Offset + Direction + Update)
moves the face. distance>0 = outward (adds material), <0 = inward. Works on parametric and imported B-rep.
The full split-reposition workflow (SplitSolids/BodyRepositions) is driveable; only partly exposed as tools
(`split_solid_by_plane`, `move_body`, `boolean_union`).
- **Forming ops (API5 definitions)**:
- `shell` (`ksShellDefinition`, o3d_shellOperation=43): `thickness` + `thinType` (bool, `= !outward`) + `FaceArray()` (open faces ≥1 by index). Transient RCWs intentionally not released (consistent with Fillet/Chamfer).
- `rib` (`ksRibDefinition`, =44): `SetSketch(sketch)``index=0`, `angle`, `side` (`left|right|up|down` → 0/1/2/3 via `SketchGeometry.RibSide()`); `SetThinParam(dtBoth, t/2, t/2)` if symmetric, else `(dtNormal, t, 0)`. Empirical: the rib contour must float in the gap (not touch the body) — КОМПАС extends the web automatically.
- `sweep` (`ksBaseEvolutionDefinition`, =45): `SetSketch(profile)``PathPartArray().Add(path)``sketchShiftType=0``SetThinParam(false)``Create()`. Profile & path on different (usually ⟂) planes; `profileSketchId != pathSketchId`.
- `loft` (`ksBaseLoftDefinition`, =30): `Sketchs().Add(each)``SetLoftParam(false,false,true)``SetThinParam(false)`. ≥2 closed sections on parallel planes — use `sketch_create_on_offset_plane` for non-base sections.
- offset plane (`sketch_create_on_offset_plane`, `ksPlaneOffsetDefinition`, o3d_planeOffset=14): `SetPlane(base)` + `offset` mm + `direction``Create()` → sketch on it (`OpenSketchOnOffsetPlaneAsync`).
- `draft` (`ksInclineDefinition`, o3d_incline=42 — API5 calls the op **Incline**, NOT Draft): `FaceArray()` (by index) + `SetPlane(neutral base plane)` + `angle` (degrees) + `direction`. **Empirical, opposite to SDK docs:** `direction=false`=expanding/outward (adds material), `true`=tapering/inward → code maps `def.direction = !outward`. Validate `0<angle<90`. (`o3d_DraftFromEdges=644` / `IDraftFromEdges` is a different op — not used.)
- **Patterns & mirror (API5)**: `linear_pattern` (`ksMeshCopyDefinition`, =35): `SetAxis1(axis)` + `SetCopyParamAlongAxis(true,0,count,step,false)` + `count2=1` (disables 2nd direction → 1D); features via `OperationArray()`. `circular_pattern` (`ksCircularCopyDefinition`, =36): `SetAxis(axis)`; `count1=1` (radial off), `count2=count` (ring), `step2=angle°` between neighbours, `factor2=false`, `inverce=reverse`; `GetOperationArray().Add(feature)`. `mirror_operation` (`ksMirrorCopyDefinition`, =48): `SetPlane(base)` + `GetOperationArray().Add(feature)`. `mirror_body` (`ksMirrorCopyAllDefinition`, =49): `SetPlane(base)`; `ChooseBodies()` casts to null in interop and is NOT needed — mirrors all bodies keeping the original (volume doubles). Empirical: `count` includes the original instance (count=3 → 3 instances). Axes: `CoordinateAxis{X,Y,Z}``o3d_axisOX/OY/OZ` (`Core/Modeling/CoordinateAxis.cs`, analogous to `BasePlane.cs`).
- **Hole ops (API7 only — `ksHoleDefinition` absent in API5 interop; `HoleService`)**: `HoleCore(ksHoleTypeEnum holeType, Action<IHole3D> configure)``configure` sets `HoleParameters` **after** `HoleType` is assigned (the type-specific sub-interface is inaccessible until the type is fixed). Placement shared: `IModelContainer`/`IHole3D`/`IHoleDisposal` + `Points3D`, direction chosen by volume delta, orphan rollback. None registered in `_features` → no id returned. `hole`=`ksHTBase`, configure=null. `hole_counterbore`=`ksHTCounterbore` + `(ISpotfacingHoleParameters)` (`SpotfacingDiameter`/`SpotfacingDepth`). `hole_countersink`=`ksHTCountersinking` + `(ICountersinkHoleParameters)` (`CountersinkType=ksCTDiameterAngle`, `CountersinkDiameter`/`Angle`). `hole_conic`=`ksHTConic` + `(IConicHoleParameters)` (`ConicType=ksCNAngle`, `ConicAngle`).
- **Assembly (API7, `AssemblyService`, namespace `Kompas.Mcp.Core.Assemblies`)**: `assembly_add_component``IParts7.AddFromFile(path, ExternalFile=true, Redraw=true)``IPart7.Placement` (`IPlacement3D`) → `SetOrigin(x,y,z)` (mm, assembly world CS) → `UpdatePlacement(true)``top.RebuildModel(true)`. **Empirical:** `UpdatePlacement` returns FALSE for a manually-positioned component (no mates) — NOT an error. Validate `DocumentType==ksDocumentAssembly` before touching `TopPart`; rollback via `IFeature7.Delete` + `RebuildModel`. `assembly_add_mate``top.MateConstraints` (`IMateConstraints3D`) → `Add(MateConstraintType)``BaseObject1/2` (faces via `top.FindObjectsByPoint(x,y,z,FirstLevel=false)`) → `ParamValue` (distance) → `Update()``RebuildModel(true)`; check `mate.Valid` (false → rollback `mate.Owner.Delete`). Types `coincidence`(mc_Coincidence=0) / `distance`(mc_Distance=5, value>0) verified live; `parallel`/`perpendicular`/`concentric`/`angle`/`tangency` — future.
- **Drawing (API7, `DrawingService`, namespace `Kompas.Mcp.Core.Drawings`)** — entry via `IKompasDocument2D`; requires an active drawing doc (`DocumentType==ksDocumentDrawing`). All coords are **view-local** (mm) unless noted. Model file must be saved before placing views.
- `drawing_create_standard_views`: `doc2d.ViewsAndLayersManager.Views.AddStandartViews(path, "#Спереди", projTypes, x, y, scale, dx=20, dy=20)` where `projTypes=object[]{1,3,5}` (Front/Up/Left as SAFEARRAY VT_I4). Success: `created==3`; content check: sum of new views' `IView.ObjectCount` > 0 else rollback `IView.Delete`. Returns `ViewNumbers` (the `IView.Number` values — address views by these when placing annotations).
- `drawing_fill_title_block`: `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) → `stamp.Text[columnId].Str = text` (indexed property; `IText.Str` overwrites, no Clear needed) → `stamp.Update()`. Fields→columns (`ksStampEnum`): Name=1, Designation=2, Material=3. Non-transactional (all cells → single `Update`).
- **Dimensions go into `(ISymbols2DContainer)view`, NOT the sheet, and are NOT counted in `IView.ObjectCount`** (that counts geometry in `IDrawingContainer`) — verify placement via the container's `*.Count`. Common flow: `Add()` → set coords → `AutoNominalValue=true``Update()` → check `Valid` → read `((IDimensionText)dim).NominalValue`; rollback `dim.Delete()` on FALSE/invalid/zero. Leader/angle in **radians** (`DimensionAngles.ToRadians`). Free placement = set coords directly; **associative** (diametral/radial only) = set `BaseObject` to a projected circle → value read from geometry (see below).
- linear (`ILineDimension`): `X1,Y1,X2,Y2` (extension pts) + `X3,Y3` (dim line) + `Orientation` (`DimensionOrientation{Horizontal,Vertical,Parallel}``ksLinDParallel=0`/`Horizontal=1`/`Vertical=2`; parallel needs no explicit Angle).
- `shell` (`ksShellDefinition`, o3d_shellOperation=43): `thickness` + `thinType` (bool, `= !outward`) +
`FaceArray()` (≥1 open face by index). Transient RCWs intentionally not released (consistent with
Fillet/Chamfer).
- `rib` (`ksRibDefinition`, =44): `SetSketch(sketch)` → `index=0`, `angle`, `side`
(`left|right|up|down` → 0/1/2/3 via `SketchGeometry.RibSide()`); `SetThinParam(dtBoth, t/2, t/2)` if
symmetric, else `(dtNormal, t, 0)`. Empirical: the rib contour must float in the gap (not touch the body)
— КОМПАС extends the web automatically.
- `sweep` (`ksBaseEvolutionDefinition`, =45): `SetSketch(profile)` → `PathPartArray().Add(path)` →
`sketchShiftType=0` → `SetThinParam(false)` → `Create()`. Profile and path on different (usually ⟂)
planes; `profileSketchId != pathSketchId`.
- `loft` (`ksBaseLoftDefinition`, =30): `Sketchs().Add(each)` → `SetLoftParam(false,false,true)` →
`SetThinParam(false)`. ≥2 closed sections on parallel planes — use `sketch_create_on_offset_plane` for
non-base sections.
- offset plane (`sketch_create_on_offset_plane`, `ksPlaneOffsetDefinition`, o3d_planeOffset=14):
`SetPlane(base)` + `offset` mm + `direction` → `Create()` → sketch on it (`OpenSketchOnOffsetPlaneAsync`).
- `draft` (`ksInclineDefinition`, o3d_incline=42 — API5 calls the op **Incline**, NOT Draft): `FaceArray()`
(by index) + `SetPlane(neutral base plane)` + `angle` (degrees) + `direction`. **Empirical, opposite to
the SDK docs:** `direction=false` = expanding/outward (adds material), `true` = tapering/inward → the code
maps `def.direction = !outward`. Validate `0<angle<90`. (`o3d_DraftFromEdges=644` / `IDraftFromEdges` is
a different op — not used.)
- **Patterns & mirror (API5)**: `linear_pattern` (`ksMeshCopyDefinition`, =35): `SetAxis1(axis)` +
`SetCopyParamAlongAxis(true,0,count,step,false)` + `count2=1` (disables the 2nd direction → 1D); features
via `OperationArray()`. `circular_pattern` (`ksCircularCopyDefinition`, =36): `SetAxis(axis)`; `count1=1`
(radial off), `count2=count` (ring), `step2=angle°` between neighbours, `factor2=false`, `inverce=reverse`;
`GetOperationArray().Add(feature)`. `mirror_operation` (`ksMirrorCopyDefinition`, =48): `SetPlane(base)` +
`GetOperationArray().Add(feature)`. `mirror_body` (`ksMirrorCopyAllDefinition`, =49): `SetPlane(base)`;
`ChooseBodies()` casts to null in the interop and is NOT needed — it mirrors all bodies keeping the original
(volume doubles). Empirical: `count` includes the original instance (count=3 → 3 instances). Axes:
`CoordinateAxis{X,Y,Z}` → `o3d_axisOX/OY/OZ` (`Core/Modeling/CoordinateAxis.cs`, analogous to `BasePlane.cs`).
- **Hole ops (API7 only — `ksHoleDefinition` is absent from the API5 interop; `HoleService`)**:
`HoleCore(ksHoleTypeEnum holeType, Action<IHole3D> configure)` — `configure` sets `HoleParameters` **after**
`HoleType` is assigned (the type-specific sub-interface is inaccessible until the type is fixed). Placement
is shared: `IModelContainer`/`IHole3D`/`IHoleDisposal` + `Points3D`, direction chosen by volume delta,
orphan rollback. None are registered in `_features` → no id returned. `hole`=`ksHTBase`, configure=null.
`hole_counterbore`=`ksHTCounterbore` + `(ISpotfacingHoleParameters)` (`SpotfacingDiameter`/`SpotfacingDepth`).
`hole_countersink`=`ksHTCountersinking` + `(ICountersinkHoleParameters)` (`CountersinkType=ksCTDiameterAngle`,
`CountersinkDiameter`/`Angle`). `hole_conic`=`ksHTConic` + `(IConicHoleParameters)` (`ConicType=ksCNAngle`,
`ConicAngle`).
- **Assembly (API7, `AssemblyService`, namespace `Kompas.Mcp.Core.Assemblies`)**: `assembly_add_component` —
`IParts7.AddFromFile(path, ExternalFile=true, Redraw=true)` → `IPart7.Placement` (`IPlacement3D`) →
`SetOrigin(x,y,z)` (mm, assembly world CS) → `UpdatePlacement(true)` → `top.RebuildModel(true)`.
**Empirical:** `UpdatePlacement` returns FALSE for a manually-positioned component (no mates) — NOT an error.
Validate `DocumentType==ksDocumentAssembly` before touching `TopPart`; rollback via `IFeature7.Delete` +
`RebuildModel`. `assembly_add_mate` — `top.MateConstraints` (`IMateConstraints3D`) →
`Add(MateConstraintType)` → `BaseObject1/2` (faces via `top.FindObjectsByPoint(x,y,z,FirstLevel=false)`) →
`ParamValue` (distance) → `Update()` → `RebuildModel(true)`; check `mate.Valid` (false → rollback
`mate.Owner.Delete`). Types `coincidence` (mc_Coincidence=0) and `distance` (mc_Distance=5, value>0) are
verified live; the rest are backlog.
- **Drawing (API7, `DrawingService`, namespace `Kompas.Mcp.Core.Drawings`)** — entry via `IKompasDocument2D`;
requires an active drawing document (`DocumentType==ksDocumentDrawing`). All coordinates are **view-local**
(mm) unless noted. The model file must be saved before placing views.
- `drawing_create_standard_views`: `doc2d.ViewsAndLayersManager.Views.AddStandartViews(path, "#Спереди",
projTypes, x, y, scale, dx=20, dy=20)` where `projTypes=object[]{1,3,5}` (Front/Up/Left as SAFEARRAY VT_I4).
Success: `created==3`; content check: sum of the new views' `IView.ObjectCount` > 0 else rollback
`IView.Delete`. Returns `ViewNumbers` (the `IView.Number` values — address views by these when placing
annotations).
- `drawing_fill_title_block`: `doc2d.LayoutSheets.ItemByNumber[1].Stamp` (`IStamp`) →
`stamp.Text[columnId].Str = text` (indexed property; `IText.Str` overwrites, no Clear needed) →
`stamp.Update()`. Fields→columns (`ksStampEnum`): Name=1, Designation=2, Material=3. Non-transactional
(all cells → a single `Update`).
- **Dimensions go into `(ISymbols2DContainer)view`, NOT the sheet, and are NOT counted in `IView.ObjectCount`**
(that counts geometry in `IDrawingContainer`) — verify placement via the container's `*.Count`. Common flow:
`Add()` → set coords → `AutoNominalValue=true` → `Update()` → check `Valid` → read
`((IDimensionText)dim).NominalValue`; rollback `dim.Delete()` on FALSE/invalid/zero. Leader and angle are in
**radians** (`DimensionAngles.ToRadians`). Free placement = set coordinates directly; **associative**
(diametral/radial only) = set `BaseObject` to a projected circle → the value is read from geometry.
- linear (`ILineDimension`): `X1,Y1,X2,Y2` (extension points) + `X3,Y3` (dimension line) + `Orientation`
(`DimensionOrientation{Horizontal,Vertical,Parallel}` → `ksLinDParallel=0`/`Horizontal=1`/`Vertical=2`;
parallel needs no explicit Angle).
- diametral (`IDiametralDimension`): `Xc,Yc,Radius` + `Angle`; `NominalValue` = 2·Radius.
- radial (`IRadialDimension`): `Xc,Yc,Radius` + `Angle`, `DimensionType=true`; **`NominalValue` = Radius, NOT diameter** — contrary to SDK docs, confirmed by spike.
- **associative** diametral/radial (`associate=true` flag): instead of coords, find a projected circle in `(IDrawingContainer)view.Circles` by center+radius key (`CircularObjectMatch.SelectMatchIndex`, tol 1 mm; throws on none/ambiguous; concentric disambiguated by radius), set `dim.BaseObject = circle` (`_Circle` implements `IDrawingObject`) → `NominalValue` read from geometry. `RequireViewContainers` gives both QIs; free path stays on `RequireSymbols2DContainer`. Arc/angular/rough binding — not yet (own spike needed; `ILineDimension` has no `BaseObject`).
- angular (`IAngleDimension`, `AngleDimensions.Add(ksDrADimension=10)`): vertex `Xc,Yc` + side pts `X1,Y1`/`X2,Y2` + arc-position `X3,Y3` (positions the arc only, does NOT pick the angle); measured angle chosen by `DimensionType` = `AngleDimensionType{Min,Max,More}` (on rays 0°/45° → min=45°/max=135° supplement/more=315° reflex). `NominalValue` in degrees.
- `drawing_add_rough` (`Roughs.Add()`/`IRough`: `BranchX0/Y0`, `Angle`) + `(IRoughParams)rough` QI: `SignType` = `RoughSignType{NoProcessing,DeleteMaterial,WithoutDeleteMaterial}`, value (Ra/Rz) = `RoughParamText.Str`.
- `drawing_add_text`**text lives in `IDrawingContainer`, NOT `ISymbols2DContainer`, and NOT in `IView.ObjectCount`**: `(IDrawingContainer)view.DrawingTexts.Add()`/`IDrawingText` (`X/Y/Angle`) + `(IText)dt.Str` = text; count via `DrawingTexts.Count`. Do NOT set `Height` (that's block height, not font).
- `drawing_set_technical_requirements`**document-level**: `(IDrawingDocument)doc.TechnicalDemand`/`ITechnicalDemand`: `td.Text.Str = text``td.Update()` (`IsCreated` False→True on first set; overwrites; `\n` multiline).
- `DrawingAnnotationResult{Value(read-back), ViewNumber}` is the shared result for rough/text.
- `drawing_add_leader``ISymbols2DContainer.Leaders.Add(ksDrLeader)``IBaseLeader`. **КРИТИЧЕСКИЙ ПОРЯДОК (спайк):** новая выноска имеет 0 ответвлений → `(IBranchs)bl.AddBranchByPoint(0,x,y)` (остриё) ДО `SetBranchTextPosition(textX,textY)`/`Update`, иначе КОМПАС падает `RPC_E_SERVERFAULT`. Текст — `(ILeader)bl.TextOnShelf.Str`; `ShelfDirection` (enum `ShelfDirection{Auto,Right,Left,Up,Down}`, Auto = не задавать). `AddBranchByPoint`/`SetBranchTextPosition` возвращают bool — проверять. Результат — общий `DrawingAnnotationResult`.
- `drawing_set_sheet_format``RequireLayoutSheet(n).Format` (`ISheetFormat`): `Format`=`ksDocumentFormatEnum` (`PaperFormat{A0..A5,User}`+`PaperFormats.Parse/ToKompas/FromKompas`), standard → set `VerticalOrientation=!landscape` (W/H auto-computed from enum+orientation, e.g. A3 landscape→420×297); **User → set `FormatWidth/Height` only, КОМПАС auto-derives `VerticalOrientation` from W/H and ignores the flag, never swaps W/H (spike)**`sheet.Update()`. New drawing defaults A4 portrait. `ValidateFormatDimensions`: User needs W/H>0, standard forbids W/H (must be 0).
- **Richer sketch primitives (API5 `ksDocument2D` wrappers)**: `ksArcBy3Points`; `ksArcByAngle` (centre/radius/start-end angles in degrees + counterClockwise flag); `ksEllipse` via `ksEllipseParam` (`KompasObject.GetParamStruct(ko_EllipseParam=22)`; **property names are `A`/`B` UPPERCASE in the interop**); `ksRegularPolygon` via `ksRegularPolygonParam` (`ko_RegularPolygonParam=92`; `describe = !inscribed`); NURBS spline via `ksNurbs(order=4)` + `ksNurbsPoint` loop + `ksEndObj`; `ksPoint`. Param structs released after use. `PartModeler` is partial: `.cs` (registry/helpers/`NewParam<T>`/reset), `.Sketch.cs` (2D primitives), `.Features.cs` (extrude/revolve/fillet/chamfer). Static `SketchGeometry` (`Core/Modeling`) holds enum maps + validators (`RequirePositive`, `RequireVertexCount`, `RequirePoints`, `RequireMin`). Point lists use `record SketchPoint(X, Y)` (JSON names `x`/`y`).
- radial (`IRadialDimension`): `Xc,Yc,Radius` + `Angle`, `DimensionType=true`; **`NominalValue` = Radius,
NOT diameter** — contrary to the SDK docs, confirmed by a spike.
- **associative** diametral/radial (`associate=true`): instead of coordinates, find a projected circle in
`(IDrawingContainer)view.Circles` by a center+radius key (`CircularObjectMatch.SelectMatchIndex`, tol 1 mm;
throws on none/ambiguous; concentric disambiguated by radius), set `dim.BaseObject = circle` (`_Circle`
implements `IDrawingObject`) → `NominalValue` read from geometry. `RequireViewContainers` gives both QIs;
the free path stays on `RequireSymbols2DContainer`. Arc/angular/rough binding — not yet (needs its own
spike; `ILineDimension` has no `BaseObject`).
- angular (`IAngleDimension`, `AngleDimensions.Add(ksDrADimension=10)`): vertex `Xc,Yc` + side points
`X1,Y1`/`X2,Y2` + arc-position `X3,Y3` (positions the arc only, does NOT pick the angle); the measured
angle is chosen by `DimensionType` = `AngleDimensionType{Min,Max,More}` (on rays 0°/45° → min=45° /
max=135° supplement / more=315° reflex). `NominalValue` in degrees.
- `drawing_add_rough` (`Roughs.Add()`/`IRough`: `BranchX0/Y0`, `Angle`) + `(IRoughParams)rough` QI:
`SignType` = `RoughSignType{NoProcessing,DeleteMaterial,WithoutDeleteMaterial}`, value (Ra/Rz) =
`RoughParamText.Str`.
- `drawing_add_text` — **text lives in `IDrawingContainer`, NOT `ISymbols2DContainer`, and NOT in
`IView.ObjectCount`**: `(IDrawingContainer)view.DrawingTexts.Add()`/`IDrawingText` (`X/Y/Angle`) +
`(IText)dt.Str` = text; count via `DrawingTexts.Count`. Do NOT set `Height` (that's block height, not font).
- `drawing_set_technical_requirements` — **document-level**: `(IDrawingDocument)doc.TechnicalDemand` /
`ITechnicalDemand`: `td.Text.Str = text` → `td.Update()` (`IsCreated` False→True on first set; overwrites;
`\n` multiline).
- `drawing_add_leader` — `ISymbols2DContainer.Leaders.Add(ksDrLeader)` → `IBaseLeader`. **Critical order
(spike):** a fresh leader has 0 branches → `(IBranchs)bl.AddBranchByPoint(0,x,y)` (the tip) BEFORE
`SetBranchTextPosition(textX,textY)`/`Update`, otherwise КОМПАС dies with `RPC_E_SERVERFAULT`. Text —
`(ILeader)bl.TextOnShelf.Str`; `ShelfDirection` (enum `ShelfDirection{Auto,Right,Left,Up,Down}`, Auto = leave
unset). `AddBranchByPoint`/`SetBranchTextPosition` return bool — check them.
- `drawing_set_sheet_format` — `RequireLayoutSheet(n).Format` (`ISheetFormat`): `Format`=`ksDocumentFormatEnum`
(`PaperFormat{A0..A5,User}` + `PaperFormats.Parse/ToKompas/FromKompas`); standard → set
`VerticalOrientation=!landscape` (W/H auto-computed from enum+orientation, e.g. A3 landscape → 420×297);
**User → set `FormatWidth/Height` only, КОМПАС derives `VerticalOrientation` from W/H, ignores the flag and
never swaps W/H (spike)** → `sheet.Update()`. A new drawing defaults to A4 portrait.
`ValidateFormatDimensions`: User needs W/H>0, standard forbids W/H (must be 0).
- `DrawingAnnotationResult{Value(read-back), ViewNumber}` is the shared result for rough/text/leader.
- **Richer sketch primitives (API5 `ksDocument2D` wrappers)**: `ksArcBy3Points`; `ksArcByAngle` (centre/radius/
start-end angles in degrees + counterClockwise flag); `ksEllipse` via `ksEllipseParam`
(`KompasObject.GetParamStruct(ko_EllipseParam=22)`; **the property names are `A`/`B` UPPERCASE in the interop**);
`ksRegularPolygon` via `ksRegularPolygonParam` (`ko_RegularPolygonParam=92`; `describe = !inscribed`); a NURBS
spline via `ksNurbs(order=4)` + `ksNurbsPoint` loop + `ksEndObj`; `ksPoint`. Param structs are released after
use. `PartModeler` is partial: `.cs` (registry/helpers/`NewParam<T>`/reset), `.Sketch.cs` (2D primitives),
`.Features.cs` (extrude/revolve/fillet/chamfer). Static `SketchGeometry` (`Core/Modeling`) holds the enum maps
+ validators (`RequirePositive`, `RequireVertexCount`, `RequirePoints`, `RequireMin`). Point lists use
`record SketchPoint(X, Y)` (JSON names `x`/`y`).
## КОМПАС-3D API architecture (the critical context)
КОМПАС-3D is automated via a **COM Automation API**, Windows-only. КОМПАС-3D **must be installed and running/launchable** on the same machine — the MCP server is a COM client, not a standalone CAD engine. There are **two coexisting API generations**:
КОМПАС-3D is automated via a **COM Automation API**, Windows-only. КОМПАС **must be installed and
running/launchable on the same machine** — the MCP server is a COM client, not a standalone CAD engine.
Two API generations coexist:
| | Entry point | Namespace (C#) | Style | Use when |
|---|---|---|---|---|
| **API5** (legacy) | `KompasObject` | `Kompas6API5` | procedural, flat | older features, 2D primitives, bootstrapping |
| **API7** (modern, preferred) | `IApplication` / `_Application` | `KompasAPI7` | OOP, interface-based | everything new — documents, 3D, parameters |
| **API5** (legacy) | `KompasObject` | `Kompas6API5` | procedural, flat | 3D `ksPart` building, 2D primitives, bootstrapping |
| **API7** (modern, preferred) | `IApplication` / `_Application` | `KompasAPI7` | OOP, interface-based | documents, holes, assemblies, drawings, B-rep edit |
Constants live in separate libraries: `Kompas6Constants`, `Kompas6Constants3D` (C#), or `ksConstants.h` / `ksConstants3D.h` (C++).
Constants live in separate libraries: `Kompas6Constants`, `Kompas6Constants3D` (C#), or `ksConstants.h` /
`ksConstants3D.h` (C++).
### Connection pattern (verified from `Samples/CSharp.zip`)
1. Get the КОМПАС root object by COM ProgID. **Verified on this machine (v24 Home):**
`KOMPAS.Application.7` and `KOMPAS.Application.5` are registered; `KOMPASLT.*` is **absent**
the Home edition uses the regular (non-LT) ProgIDs. Resolution order:
`KOMPAS.Application.7` and `KOMPAS.Application.5` are registered; `KOMPASLT.*` is **absent** — the Home
edition uses the regular (non-LT) ProgIDs. Resolution order:
- `KOMPAS.Application.7` — direct API7 `IApplication` (preferred)
- `KOMPAS.Application.5` — API5 `KompasObject`, then `ksGetApplication7()`
- `KOMPASLT.Application.5` — fallback for other installations
- New instance: `Activator.CreateInstance(Type.GetTypeFromProgID(progId))`
- Attach to a running instance: `Marshal.GetActiveObject(progId)`
2. If you entered via API5, obtain the modern application: `IApplication appl = (IApplication)kompas.ksGetApplication7();` (via `KOMPAS.Application.7` you already hold `IApplication`).
3. Set `appl.Visible = true` to show the КОМПАС window; drive documents via API7 interfaces (`IKompasDocument2D`, `IKompasDocument3D`, …).
2. If you entered via API5, obtain the modern application:
`IApplication appl = (IApplication)kompas.ksGetApplication7();`
3. Set `appl.Visible = true` to show the КОМПАС window; drive documents via API7 interfaces
(`IKompasDocument2D`, `IKompasDocument3D`, …).
> The SDK C# samples are mostly **КОМПАС plugin libraries** (DLLs loaded *into* КОМПАС as ActiveX, entry methods like `ExternalRunCommand`/`ExternalMenuItem`, registered via `[ComRegisterFunction]`). For an MCP server we want the **opposite direction**: a standalone *external automation client* that connects out-of-process via the ProgID pattern above. Read the samples for API usage, not for the plugin packaging/registration boilerplate.
> The SDK C# samples are mostly **КОМПАС plugin libraries** (DLLs loaded *into* КОМПАС as ActiveX, entry
> methods like `ExternalRunCommand`/`ExternalMenuItem`, registered via `[ComRegisterFunction]`). This server
> is the **opposite direction**: a standalone *external automation client* connecting out-of-process via the
> ProgID pattern above. Read the samples for API usage, not for the plugin packaging/registration boilerplate.
## SDK layout (read-only reference, not part of this repo)
Under `C:\Program Files\ASCON\KOMPAS-3D v24 Home\SDK`:
- `lib\*.tlb` — COM type libraries to reference / generate interop from: `kAPI5.tlb`, `kAPI7.tlb`, `ksConstants.tlb`, `ksConstants3D.tlb`, `kAPI2D5COM.tlb`, `kAPI3D5COM.tlb`.
- `lib\*.tlb` — COM type libraries: `kAPI5.tlb`, `kAPI7.tlb`, `ksConstants.tlb`, `ksConstants3D.tlb`,
`kAPI2D5COM.tlb`, `kAPI3D5COM.tlb`.
- `lib64\` — 64-bit import libs (`kAPI7.lib`, `kAPI5.lib`, …) and `.a` for C++/Builder.
- `Include\` — C++/Pascal headers (`Ks_TLB.h`, `kAPI2D5COM.h`, `LDefin2D.pas`, …).
- `KsAPI\` — the **KsAPI** (Qt/C++ cross-platform flavour): `Include\KsAPI.h`, `ksConstants*.h`, `Lib64\ksAPI.lib`, plus `Help\С чего начать.pdf` and a Linux/Debian 12 build guide.
- `Samples\` — zipped examples per language: `CSharp.zip`, `C++.zip`, `Pascal.zip`, `Basic.zip`. Inside `CSharp.zip`, the `Step1``Step12` and `Step2_API7_2D` / `Step2_API7_3D` projects are the progressive tutorial; `Gayka`, `SlideWrk`, `EventsAuto` are larger feature demos.
- `Help\KOMPAS_SDK_ru-RU.zip` and `KsAPI\Help\KsAPI_Help.zip` — offline copy of the docs.
- `KsAPI\` — the **KsAPI** (Qt/C++ cross-platform flavour): `Include\KsAPI.h`, `ksConstants*.h`,
`Lib64\ksAPI.lib`, plus a Linux/Debian 12 build guide. Not used by this project.
- `Samples\` — zipped examples per language: `CSharp.zip`, `C++.zip`, `Pascal.zip`, `Basic.zip`. Inside
`CSharp.zip` the `Step1``Step12` and `Step2_API7_2D`/`Step2_API7_3D` projects are the progressive tutorial;
`Gayka`, `SlideWrk`, `EventsAuto` are larger feature demos. `Common/` holds the interop assemblies the
server extracts at runtime.
- `Help\KOMPAS_SDK_ru-RU.zip` — the offline docs the RAG base is built from.
To inspect a sample without unpacking the whole archive: `unzip -p "<path>\Samples\CSharp.zip" "Automation/Step2_API7_3D/Step2_API7_3D.cs"`. Note the `.cs` sample sources are **Windows-1251 encoded** (Cyrillic comments appear garbled in UTF-8 tools).
To inspect a sample without unpacking the whole archive:
`unzip -p "<path>\Samples\CSharp.zip" "Automation/Step2_API7_3D/Step2_API7_3D.cs"`. The `.cs` sample sources
are **Windows-1251 encoded** (Cyrillic comments appear garbled in UTF-8 tools).
## Conventions / gotchas
- **Windows-only & 64-bit**: target x64 to match the installed КОМПАС; mixing bitness breaks COM activation. КОМПАС v24 Home is the installed edition — some full-edition API features may be unavailable.
- **Encoding**: SDK source samples are CP1251. New project files should be UTF-8.
- **COM lifetime**: release COM objects (`Marshal.ReleaseComObject`) / scope them; a leaked reference keeps the КОМПАС process alive.
- **API choice**: prefer API7 for new tool implementations; drop to API5 only for things API7 doesn't expose.
- **Windows-only & 64-bit**: target x64 to match the installed КОМПАС; mixing bitness breaks COM activation.
v24 Home is the installed edition — some full-edition API features may be unavailable.
- **Encoding**: SDK sources are CP1251; project files are UTF-8; plugin PowerShell is UTF-8 **with BOM**.
- **COM lifetime**: release COM objects (`Marshal.ReleaseComObject`) or scope them; a leaked reference keeps
the КОМПАС process alive.
- **API choice**: prefer API7 for new tools; drop to API5 only for what API7 doesn't expose.
- **Sequential tool calls**: the build session keeps sketches by `id` and the server serves requests
concurrently — dependent tool calls must be awaited one at a time.
+67 -55
View File
@@ -2,36 +2,43 @@
**MCP-сервер для управления CAD-системой КОМПАС-3D (АСКОН) языковой моделью.**
Сервер — это COM-клиент КОМПАС-3D: он подключается к установленному КОМПАС и предоставляет
LLM набор инструментов для создания документов, построения 3D-деталей (эскиз → выдавливание),
импорта/экспорта STEP, работы со сборками, структурного осмотра модели (`describe_model` — дерево
операций, тела, переменные, топология одним вызовом) и снимка модели (агент «видит» результат).
Сервер — COM-клиент КОМПАС-3D: подключается к установленному КОМПАС и даёт LLM 84 инструмента для
создания документов, построения 3D-деталей (эскиз → формообразующая операция), работы со сборками и
чертежами, импорта/экспорта STEP, прямого редактирования импортированной B-rep, а также структурного
осмотра модели (`describe_model` — дерево операций, тела, топология, переменные, МЦХ одним вызовом)
и снимка модели, по которому агент «видит» результат.
Принцип: **MCP транслирует возможности SDK КОМПАС в общие инструменты** (не под конкретную задачу).
Поверх MCP — навык **`.claude/skills/kompas-3d/`** с методикой работы (playbook'и, эвристики,
обкатанные в `usecases/`). Отдельный навык **`.claude/skills/kompas-fdm-design/`** — методика
проектирования под FDM/FFF 3D-печать (правила DFM + лёгкий гео-аудит инструментами осмотра);
работает поверх `kompas-3d`, экспорт через `export_step`, слайсер не вызывает.
Принцип: **MCP транслирует возможности SDK КОМПАС в общие инструменты**, а не под конкретную задачу.
Поверх MCP — навык **`kompas-3d`** с методикой работы (playbook'и, эвристики). Отдельный навык
**`kompas-fdm-design`** — методика проектирования под FDM/FFF 3D-печать (правила DFM + лёгкий
гео-аудит инструментами осмотра): работает поверх `kompas-3d`, экспортирует через `export_step`,
слайсер не вызывает.
> Подробности: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) · план — [docs/IMPLEMENTATION_PLAN.md](docs/IMPLEMENTATION_PLAN.md)
> · спорные вопросы — [docs/OPEN_QUESTIONS.md](docs/OPEN_QUESTIONS.md) · обзор — [docs/presentation.html](docs/presentation.html)
Проектной документации в отдельном каталоге нет: этот README — обзор и каталог инструментов,
[CLAUDE.md](CLAUDE.md) — архитектура, проверенные COM-приёмы и подводные камни.
## Требования
- **Windows x64**, установленный **КОМПАС-3D** (разрабатывалось на v24 Home) **вместе с компонентом
SDK**: interop-сборки в поставку сервера не входят и берутся из установки —
`SDK\Samples\CSharp.zip` (каталог `Common`). Если SDK не установлен, положите сборки рядом с
SDK**: interop-сборки принадлежат АСКОН, в поставку сервера не входят и берутся из установки —
`SDK\Samples\CSharp.zip` (каталог `Common`), при первом запуске распаковываются в
`%LOCALAPPDATA%\kompas-mcp\interop\<ключ>`. Если SDK не установлен, положите сборки рядом с
`kompas-mcp.exe` или укажите каталог с ними в `KOMPAS_INTEROP_DIR`.
- **.NET SDK 8+** (собирается и на SDK 10; целевой фреймворк `net8.0-windows`).
- **.NET SDK 8+** для сборки из исходников (целевой фреймворк `net8.0-windows`; формат решения
`.slnx` понимает только SDK 9+ — на SDK 8 собирайте проекты по путям). Для готового релиза рантайм
не нужен: сервер публикуется self-contained.
## Установка как плагин Claude Code
Сервер и оба навыка (`kompas-3d`, `kompas-fdm-design`) упакованы в плагин `kompas` (каталог
[`plugin/`](plugin/)) — это самый простой способ подключить проект к Claude Code без ручной сборки:
установка через `/plugin marketplace add` + `/plugin install`, лаунчер сам скачивает и проверяет
self-contained сборку сервера по `plugin/server.lock.json`. Подробности, режим разработки
(`KOMPAS_MCP_EXE`) и сниппеты для Codex/opencode — в [`plugin/README.md`](plugin/README.md).
Публичного релиза пока нет — используйте режим разработки или сборку ниже.
Сервер и оба публикуемых навыка упакованы в плагин `kompas` (каталог [`plugin/`](plugin/)) — самый
простой способ подключить проект к Claude Code без ручной сборки: `/plugin marketplace add` +
`/plugin install`, дальше лаунчер сам скачивает self-contained сборку сервера по
`plugin/server.lock.json` и сверяет её SHA256. Проверка установки — команда `/kompas:doctor`.
Подробности, режим разработки (`KOMPAS_MCP_EXE`) и сниппеты для Codex/opencode — в
[`plugin/README.md`](plugin/README.md).
> Публичного релиза пока нет (`server.lock.json` в состоянии `0.0.0`, запись в каталоге плагинов не
> заведена) — используйте режим разработки или сборку ниже.
## Сборка и запуск
@@ -41,6 +48,7 @@ dotnet build -c Release
dotnet run --project src/Kompas.Mcp.Host -c Release
# или собранный exe (платформа x64 — из Directory.Build.props):
src/Kompas.Mcp.Host/bin/x64/Release/net8.0-windows/kompas-mcp.exe
kompas-mcp.exe --version # версия бинаря, КОМПАС для этого не нужен
```
### Подключение к MCP-клиенту (stdio)
@@ -48,12 +56,12 @@ src/Kompas.Mcp.Host/bin/x64/Release/net8.0-windows/kompas-mcp.exe
```json
{
"mcpServers": {
"kompas3d": { "command": "C:\\путь\\к\\kompas-mcp.exe", "args": [] }
"kompas": { "command": "C:\\путь\\к\\kompas-mcp.exe", "args": [] }
}
}
```
## Инструменты (84 инструмента)
## Инструменты (84)
| Группа | Инструменты |
|---|---|
@@ -63,12 +71,12 @@ src/Kompas.Mcp.Host/bin/x64/Release/net8.0-windows/kompas-mcp.exe
| Features | `extrude_boss`, `extrude_cut`, `revolve_boss`, `revolve_cut`, `fillet_edge`, `chamfer_edge`, `fillet_edge_index`, `chamfer_edge_index`, `shell`, `rib`, `sweep`, `loft`, `linear_pattern`, `circular_pattern`, `mirror_operation`, `mirror_body`, `hole`, `hole_counterbore`, `hole_countersink`, `hole_conic`, `draft`, `rebuild` |
| Edit | `move_face`, `split_solid_by_plane`, `move_body`, `boolean_union` |
| Inspection | `describe_model`, `list_features`, `list_bodies`, `list_variables`, `describe_face`, `describe_edge`, `measure` |
| Vision | `model_snapshot` (PNG-снимок; fallback — сначала `describe_model`) |
| Vision | `model_snapshot` (PNG-снимок; сначала `describe_model`) |
| Query | `get_part_info`, `get_bounding_box`, `list_faces`, `list_edges`, `list_components` |
| Variables | `create_variable`, `set_variable`, `set_variable_note`, `delete_variable` |
| Conversion | `import_step`, `export_step` |
| Assembly | `assembly_add_component`, `assembly_add_mate` |
| Drawing | `drawing_create_standard_views`, `drawing_fill_title_block`, `drawing_add_linear_dimension`, `drawing_add_diametral_dimension`, `drawing_add_radial_dimension`, `drawing_add_angular_dimension`, `drawing_add_rough`, `drawing_add_text`, `drawing_set_technical_requirements`, `drawing_set_sheet_format`, `drawing_add_leader` |
| Drawing | `drawing_create_standard_views`, `drawing_fill_title_block`, `drawing_add_linear_dimension`, `drawing_add_diametral_dimension`, `drawing_add_radial_dimension`, `drawing_add_angular_dimension`, `drawing_add_leader`, `drawing_add_rough`, `drawing_add_text`, `drawing_set_technical_requirements`, `drawing_set_sheet_format` |
| Validation | `validate_part` |
### Типовой сценарий
@@ -79,29 +87,29 @@ src/Kompas.Mcp.Host/bin/x64/Release/net8.0-windows/kompas-mcp.exe
> `describe_model` предпочтителен перед `model_snapshot`: отдаёт структурный «паспорт» детали
> (дерево операций, тела, переменные, топологию, МЦХ) без расхода токенов на изображение.
> ⚠️ Вызовы зависимых инструментов выполняйте **последовательно** (дождавшись ответа):
> сессия построения хранит эскизы по `id`, а сервер обрабатывает запросы конкурентно.
> ⚠️ Вызовы зависимых инструментов выполняйте **последовательно**, дождавшись ответа: сессия
> построения хранит эскизы по `id`, а сервер обрабатывает запросы конкурентно.
## Структура
```
src/Kompas.Mcp.Core/ COM-слой: STA-диспетчер, подключение, документы, эскизы/операции, конвертация, снимок, инспекция модели
src/Kompas.Mcp.Host/ MCP-сервер (stdio) + определения инструментов
tests/Kompas.Mcp.Tests/ 381 тест: unit + integration (integration требуют КОМПАС)
libs/kompas-interop/ interop-сборки КОМПАС (из SDK Samples/Common) — только для компиляции, в поставку не входят
docs/ архитектура, план, презентация, спеки и планы (superpowers/)
usecases/ полигон обкатки подходов (в .gitignore); приёмы поднимаются в навык kompas-3d
plugin/ плагин Claude Code `kompas`: манифест, лаунчер, лок версии сервера, навыки (источник истины)
tools/ скрипты раскладки навыков (junction) и их Pester-тесты
.claude/skills/ junction на plugin/skills/{kompas-3d,kompas-fdm-design} + kompas-mcp-dev (внутренний навык)
.claude/agents/ субагенты-делегаты: docs-maintainer (Sonnet), kompas-sdk-research (Haiku)
src/Kompas.Mcp.Core/ COM-слой: STA-диспетчер, подключение, документы, эскизы/операции,
сборки, чертежи, конвертация, снимок, инспекция модели
src/Kompas.Mcp.Host/ MCP-сервер (stdio) + определения инструментов (Tools/ — по группе на файл)
tests/Kompas.Mcp.Tests/ 381 тест: unit (корень) + Integration/ (нужен запущенный КОМПАС)
libs/kompas-interop/ interop-сборки КОМПАС (из SDK Samples/Common) — только для компиляции
plugin/ плагин Claude Code `kompas`: манифест, лаунчер, лок версии сервера,
навыки kompas-3d и kompas-fdm-design (источник истины)
tools/ раскладка навыков junction'ами + Pester-тесты PowerShell плагина
.gitea/workflows/ CI (сборка + unit-тесты) и релиз (публикация в ветку dist)
.claude/skills/ junction'ы на plugin/skills + kompas-mcp-dev (внутренний навык разработки)
.claude/agents/ субагент kompas-sdk-research (Haiku) — поиск по справке SDK
```
Справка по COM API КОМПАС живёт **вне репозитория** — в приватном
[`kompas-sdk-docs`](https://git.shahovalov.ru/mikhail/kompas-sdk-docs) (2465 статей,
собраны из HTML-справки SDK) и проиндексирована в RAG на CT 127. Доступ — через MCP-сервер
`kompas-sdk` (порт 8092, bearer в `KOMPAS_SDK_RAG_TOKEN`); ищет по ней субагент
`kompas-sdk-research`.
[`kompas-sdk-docs`](https://git.shahovalov.ru/mikhail/kompas-sdk-docs) (2465 статей, собраны из
HTML-справки SDK) и проиндексирована в RAG. Доступ — через MCP-сервер `kompas-sdk` (bearer в
`KOMPAS_SDK_RAG_TOKEN`); ищет по ней субагент `kompas-sdk-research`.
## Тесты
@@ -109,28 +117,32 @@ tools/ скрипты раскладки навыков (junctio
dotnet test # все (integration требуют КОМПАС)
dotnet test --filter "Category=Unit" # только unit (без COM)
dotnet test --filter "Category=Integration" # только integration
pwsh -NoProfile -File tools/tests/run-ps-tests.ps1 # Pester-тесты скриптов плагина
```
381 тест: 250 unit + 131 integration. CI (Gitea Actions, `.gitea/workflows/ci.yml`) собирает и
гоняет `Category=Unit&Requires!=Windows` на Linux-раннере (245 тестов) — 5 тестов диспетчера STA
помечены `Requires=Windows` (`Thread.SetApartmentState` не работает на Linux) и в CI не идут;
integration-тесты в CI не запускаются никогда (нужен запущенный КОМПАС с GUI и лицензией).
Плюс 41 Pester-тест PowerShell-скриптов плагина и раскладки навыков (`tools/tests/run-ps-tests.ps1`)
— в счёт .NET-тестов не входят.
381 тест: 250 unit + 131 integration. CI (Gitea Actions, `.gitea/workflows/ci.yml`) собирает проект и
гоняет `Category=Unit&Requires!=Windows` на Linux-раннере (245 тестов) — 5 тестов STA-диспетчера
помечены `Requires=Windows` (`Thread.SetApartmentState` не работает на Linux). Integration-тесты в CI
не запускаются никогда: нужен запущенный КОМПАС с GUI и лицензией. Плюс 41 Pester-тест
PowerShell-скриптов плагина и раскладки навыков — в счёт .NET-тестов не входят.
## Архитектура (кратко)
- **STA-поток** (`KompasDispatcher`) владеет всеми COM-вызовами — КОМПАС однопоточный STA.
- Подключение через API5 `KompasObject` (`KOMPAS.Application.5`) → API7 `IApplication` (`ksGetApplication7`).
- **Interop-сборки подставляются в рантайме** (`KompasInteropLoader`) из установленного КОМПАС, а не из поставки сервера: установка находится по `HKCR\CLSID\{...}\LocalServer32`, сборки распаковываются из `SDK\Samples\CSharp.zip` в `%LOCALAPPDATA%\kompas-mcp\interop\<ключ>`.
- 3D строится через API5 `ksPart`, снимок — через `ksDocument3D`; прямое редактирование импортированной B-rep — через API7. Структурный осмотр, переменные, сборки и чертежи инкапсулированы в сервисах COM-слоя.
- STEP импорт/экспорт — через встроенный конвертер КОМПАС.
- MCP — официальный C# SDK `ModelContextProtocol`, транспорт stdio, логи в stderr.
- Детали реализации, обоснования и проверенные COM-приёмы — в [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) и [CLAUDE.md](CLAUDE.md).
- Подключение через API5 `KompasObject` (`KOMPAS.Application.5`) → API7 `IApplication`
(`ksGetApplication7`). 3D строится через API5 `ksPart`, снимок — через `ksDocument3D`; отверстия,
сборки, чертежи и прямое редактирование B-rep — через API7.
- **Interop-сборки подставляются в рантайме** (`KompasInteropLoader`) из установленного КОМПАС, а не
из поставки сервера: установка находится по `HKCR\CLSID\{...}\LocalServer32`, сборки распаковываются
из `SDK\Samples\CSharp.zip` в `%LOCALAPPDATA%\kompas-mcp\interop\<ключ>`.
- STEP импорт/экспорт — через встроенный конвертер КОМПАС (AP203/AP214/AP242).
- MCP — официальный C# SDK `ModelContextProtocol`, транспорт stdio, логи в stderr (stdout занят
протоколом).
- Детали реализации, обоснования и проверенные COM-приёмы — в [CLAUDE.md](CLAUDE.md).
## Лицензирование
`libs/kompas-interop/*.dll` — interop-сборки АСКОН из состава SDK КОМПАС-3D. Они нужны **только для
компиляции** и в релизный архив сервера не попадают (`Private=false` в `Kompas.Mcp.Core.csproj`):
сервер подставляет их в рантайме из установленного у пользователя КОМПАС-3D. Для работы требуется
установленный КОМПАС-3D с компонентом SDK.
компиляции** и в релизный архив сервера не попадают (`Private=false`): сервер подставляет их в
рантайме из установленного у пользователя КОМПАС-3D. Для работы требуется установленный КОМПАС-3D
с компонентом SDK.