Диагностика отсутствующего интеропа и документация к его поиску
ci / build (push) Successful in 34s

Продолжение 4d4e114 (сборки КОМПАС убраны из поставки).

- kompas_status проверяет интероп до обращения к сессии и отдаёт причину текстом:
  иначе MCP SDK показывает агенту только «An error occurred invoking …»
- провайдер сносит негодный остаток кеша перед Move: повтор после оборванной
  распаковки падал IOException вместо внятного сообщения
- README/plugin/README/doctor/CLAUDE.md: требование компонента SDK,
  KOMPAS_INTEROP_DIR, кеш в %LOCALAPPDATA%, счётчики тестов 381 (250 unit)
This commit is contained in:
2026-07-31 09:29:01 +03:00
parent 4d4e11425e
commit 37dc0fed36
7 changed files with 67 additions and 17 deletions
+6 -4
View File
@@ -27,7 +27,7 @@ Updating the base when a new КОМПАС version ships (in `kompas-sdk-docs`, o
## Current state
**Status:** v1 + v2 + STEP/assembly + direct B-rep edit + structural inspection + 2D drawings + Claude Code plugin packaging — implemented and working (full sketch→feature→inspect→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, 357 .NET tests green (226 unit + 131 integration) + 41 Pester tests** for the plugin's PowerShell scripts (not counted in the .NET total).
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).
Where to look (single source of truth — do **not** duplicate these lists here):
- **Full tool catalog** (by group, all 84) → [`README.md`](README.md) §«Инструменты».
@@ -77,7 +77,7 @@ Releases (`.gitea/workflows/release.yml`, tag `vX.Y.Z`) `dotnet publish -r win-x
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`). Vendored 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.
- **`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.
@@ -86,8 +86,9 @@ Extra CI gotchas:
- **`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` (vendored КОМПАС interop
assemblies from SDK `Samples/Common`, referenced via `Directory.Build.props``KompasInteropDir`).
`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
@@ -98,6 +99,7 @@ dotnet run --project src/Kompas.Mcp.Host # start the MCP server (stdio)
## 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`.
+13 -7
View File
@@ -18,7 +18,10 @@ LLM набор инструментов для создания документ
## Требования
- **Windows x64**, установленный **КОМПАС-3D** (разрабатывалось на v24 Home).
- **Windows x64**, установленный **КОМПАС-3D** (разрабатывалось на v24 Home) **вместе с компонентом
SDK**: interop-сборки в поставку сервера не входят и берутся из установки —
`SDK\Samples\CSharp.zip` (каталог `Common`). Если SDK не установлен, положите сборки рядом с
`kompas-mcp.exe` или укажите каталог с ними в `KOMPAS_INTEROP_DIR`.
- **.NET SDK 8+** (собирается и на SDK 10; целевой фреймворк `net8.0-windows`).
## Установка как плагин Claude Code
@@ -84,8 +87,8 @@ src/Kompas.Mcp.Host/bin/x64/Release/net8.0-windows/kompas-mcp.exe
```
src/Kompas.Mcp.Core/ COM-слой: STA-диспетчер, подключение, документы, эскизы/операции, конвертация, снимок, инспекция модели
src/Kompas.Mcp.Host/ MCP-сервер (stdio) + определения инструментов
tests/Kompas.Mcp.Tests/ 357 тестов: unit + integration (integration требуют КОМПАС)
libs/kompas-interop/ вендорские interop-сборки КОМПАС (из SDK Samples/Common)
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`: манифест, лаунчер, лок версии сервера, навыки (источник истины)
@@ -108,8 +111,8 @@ dotnet test --filter "Category=Unit" # только unit (без COM)
dotnet test --filter "Category=Integration" # только integration
```
357 тестов: 226 unit + 131 integration. CI (Gitea Actions, `.gitea/workflows/ci.yml`) собирает и
гоняет `Category=Unit&Requires!=Windows` на Linux-раннере (221 тест) — 5 тестов диспетчера STA
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`)
@@ -119,6 +122,7 @@ integration-тесты в CI не запускаются никогда (нуж
- **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.
@@ -126,5 +130,7 @@ integration-тесты в CI не запускаются никогда (нуж
## Лицензирование
`libs/kompas-interop/*.dll` — interop-сборки АСКОН из состава SDK КОМПАС-3D; распространяются
согласно условиям АСКОН. Для работы требуется установленный КОМПАС-3D.
`libs/kompas-interop/*.dll` — interop-сборки АСКОН из состава SDK КОМПАС-3D. Они нужны **только для
компиляции** и в релизный архив сервера не попадают (`Private=false` в `Kompas.Mcp.Core.csproj`):
сервер подставляет их в рантайме из установленного у пользователя КОМПАС-3D. Для работы требуется
установленный КОМПАС-3D с компонентом SDK.
+4
View File
@@ -8,6 +8,10 @@
- Windows x64;
- установленный **и запущенный** КОМПАС-3D (проверено на v24 Home) — сервер является COM-клиентом,
а не автономным CAD-движком, и работает только на той же машине;
- КОМПАС установлен **вместе с компонентом SDK**: interop-сборки принадлежат АСКОН, в поставку
сервера не входят и берутся из установки (`SDK\Samples\CSharp.zip`, каталог `Common`). При первом
запуске они распаковываются в `%LOCALAPPDATA%\kompas-mcp\interop\<ключ>`; если SDK не установлен,
укажите каталог с этими сборками в переменной окружения `KOMPAS_INTEROP_DIR`;
- чекаут репозитория и рабочие файлы — вне синхронизируемых папок OneDrive.
.NET Runtime ставить не нужно: сервер публикуется self-contained.
+13 -2
View File
@@ -45,7 +45,16 @@ description: Диагностика установки плагина КОМПА
присоединится к нему; не запущен, но зарегистрирован — попробует запустить новый экземпляр сам;
не зарегистрирован — это и есть причина сбоя: попроси пользователя установить КОМПАС-3D.
6. **Проверка соединения инструментами MCP.** Вызови инструмент `kompas_connect` — он либо
6. **Interop-сборки КОМПАС.** Сервер их не поставляет (они принадлежат АСКОН) и берёт из установки
КОМПАС: `<каталог установки>\SDK\Samples\CSharp.zip`, каталог `Common` внутри архива,
распаковка — в `%LOCALAPPDATA%\kompas-mcp\interop\<ключ>`. Проверь наличие архива:
`Test-Path ((Get-ItemProperty "HKLM:\SOFTWARE\Classes\CLSID\$((Get-ItemProperty 'HKLM:\SOFTWARE\Classes\KOMPAS.Application.5\CLSID').'(default)')\LocalServer32").'(default)'.Trim('"') | Split-Path | Split-Path | Join-Path -ChildPath 'SDK\Samples\CSharp.zip')`.
Архива нет — КОМПАС установлен без компонента SDK: попроси пользователя доустановить SDK либо
задать переменную окружения `KOMPAS_INTEROP_DIR` с каталогом, где лежат `KompasAPI7.dll`,
`Kompas6API5.dll`, `Kompas6Constants.dll`, `Kompas6Constants3D.dll`. Если переменная
`KOMPAS_INTEROP_DIR` уже задана — проверь, что эти четыре файла в указанном каталоге есть.
7. **Проверка соединения инструментами MCP.** Вызови инструмент `kompas_connect` — он либо
присоединится к уже запущенному КОМПАС, либо запустит новый и покажет окно, и вернёт версию и
редакцию. Если вызов упал с ошибкой — приложи её текст к отчёту дословно (обычно это означает,
что КОМПАС не установлен, лицензия не активирована, или предыдущий процесс КОМПАС завис —
@@ -53,7 +62,9 @@ description: Диагностика установки плагина КОМПА
Диспетчер задач). Затем вызови `kompas_status`, чтобы явно зафиксировать итоговое состояние
подключения в отчёте — учти, что сам по себе `kompas_status` не пытается подключиться, а
только читает текущее состояние сессии, поэтому он осмыслен только после `kompas_connect`.
Отдельный случай: если `kompas_status` вернул сообщение про interop-сборки — это диагноз
пункта 6, а не проблема соединения.
7. **Итог.** Собери отчёт: ожидаемая версия сервера, источник бинаря (релизный кеш или
8. **Итог.** Собери отчёт: ожидаемая версия сервера, источник бинаря (релизный кеш или
`KOMPAS_MCP_EXE`), путь до него, результат `kompas_connect`/`kompas_status`, и — если что-то не
сошлось — какое из действий выше нужно предпринять пользователю.
@@ -187,6 +187,13 @@ public sealed class KompasInteropProvider
}
}
// Негодный остаток предыдущей попытки (оборванная распаковка, архив без Common)
// сносим: иначе Move ниже упал бы IOException вместо внятного сообщения.
if (Directory.Exists(targetDirectory) && !HasRequiredFiles(targetDirectory))
{
try { Directory.Delete(targetDirectory, recursive: true); } catch { }
}
try
{
Directory.Move(staging, targetDirectory);
+17 -1
View File
@@ -1,5 +1,6 @@
using System.ComponentModel;
using Kompas.Mcp.Core;
using Kompas.Mcp.Core.Interop;
using ModelContextProtocol.Server;
namespace Kompas.Mcp.Host.Tools;
@@ -19,9 +20,24 @@ public sealed class SystemTools(KompasSession session)
[McpServerTool(Name = "kompas_status")]
[Description("Текущее состояние подключения к КОМПАС.")]
public string Status()
=> session.IsConnected && session.LastInfo is { } i
{
// Проверка интеропа идёт до любого обращения к сессии: без сборок КОМПАС первое же
// касание её свойств бросит FileLoadException, а MCP SDK покажет агенту только
// «An error occurred invoking …» без причины. Здесь причина попадает прямо в ответ —
// на этот инструмент опирается /kompas:doctor.
try
{
KompasInteropLoader.ResolveDirectory();
}
catch (KompasInteropException ex)
{
return "Сервер не может обратиться к КОМПАС. " + ex.Message;
}
return session.IsConnected && session.LastInfo is { } i
? $"Подключено: КОМПАС {i.Version} ({i.Edition})."
: "Не подключено. Вызовите kompas_connect.";
}
[McpServerTool(Name = "kompas_set_visible")]
[Description("Показать или скрыть главное окно КОМПАС.")]
@@ -98,10 +98,14 @@ public sealed class KompasInteropProviderTests : IDisposable
{
var install = CreateInstallation("KOMPAS-3D v24 Home", entries: new[] { "Automation/Interop.KGAXLib.dll" });
var provider = new KompasInteropProvider(null, MissingDirectory, Cache, () => install);
var error = Assert.Throws<KompasInteropException>(() => provider.ResolveDirectory());
var error = Assert.Throws<KompasInteropException>(
() => new KompasInteropProvider(null, MissingDirectory, Cache, () => install).ResolveDirectory());
Assert.Contains("Common/", error.Message, StringComparison.Ordinal);
// Повтор в новом процессе не должен спотыкаться об оставшийся негодный каталог кеша.
var repeated = Assert.Throws<KompasInteropException>(
() => new KompasInteropProvider(null, MissingDirectory, Cache, () => install).ResolveDirectory());
Assert.Equal(error.Message, repeated.Message);
}
[Fact]