diff --git a/src/Kompas.Mcp.Core/KompasSession.cs b/src/Kompas.Mcp.Core/KompasSession.cs
index 4988da9..1859048 100644
--- a/src/Kompas.Mcp.Core/KompasSession.cs
+++ b/src/Kompas.Mcp.Core/KompasSession.cs
@@ -43,6 +43,13 @@ public sealed class KompasSession : IDisposable
public bool IsConnected => _app is not null;
+ /// Сведения о последнем успешном подключении (null, пока не подключались).
+ public KompasConnectionInfo? LastInfo { get; private set; }
+
+ /// Показать/скрыть окно КОМПАС.
+ public Task SetVisibleAsync(bool visible, CancellationToken ct = default)
+ => _dispatcher.InvokeAsync(() => Application.Visible = visible, ct);
+
/// Корневой объект API5. Доступен только на STA-потоке после подключения.
public KompasObject Kompas =>
_kompas ?? throw new InvalidOperationException("Нет подключения к КОМПАС. Сначала вызовите ConnectAsync.");
@@ -58,7 +65,7 @@ public sealed class KompasSession : IDisposable
private KompasConnectionInfo ConnectCore(bool visible)
{
if (_app is not null)
- return BuildInfo(LastProgId);
+ return LastInfo ??= BuildInfo(LastProgId);
// 1) attach к уже запущенному экземпляру
foreach (var id in ProgIds)
@@ -107,7 +114,7 @@ public sealed class KompasSession : IDisposable
_app.Visible = visible;
- return BuildInfo(LastProgId);
+ return LastInfo = BuildInfo(LastProgId);
}
private string LastProgId { get; set; } = string.Empty;
diff --git a/src/Kompas.Mcp.Host/Program.cs b/src/Kompas.Mcp.Host/Program.cs
index 3751555..3ed0fee 100644
--- a/src/Kompas.Mcp.Host/Program.cs
+++ b/src/Kompas.Mcp.Host/Program.cs
@@ -1,2 +1,29 @@
-// See https://aka.ms/new-console-template for more information
-Console.WriteLine("Hello, World!");
+using Kompas.Mcp.Core;
+using Kompas.Mcp.Core.Documents;
+using Kompas.Mcp.Core.Modeling;
+using Kompas.Mcp.Core.Threading;
+using Kompas.Mcp.Core.Vision;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+var builder = Host.CreateApplicationBuilder(args);
+
+// ВАЖНО: stdout занят протоколом MCP (stdio). Все логи — только в stderr.
+builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace);
+builder.Logging.SetMinimumLevel(LogLevel.Information);
+
+// COM-слой: один STA-поток, одно подключение, общие сервисы (синглтоны).
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+
+// MCP-сервер поверх stdio; инструменты находятся по атрибутам в этой сборке.
+builder.Services
+ .AddMcpServer()
+ .WithStdioServerTransport()
+ .WithToolsFromAssembly();
+
+await builder.Build().RunAsync();
diff --git a/src/Kompas.Mcp.Host/Tools/DocumentTools.cs b/src/Kompas.Mcp.Host/Tools/DocumentTools.cs
new file mode 100644
index 0000000..39f1bf7
--- /dev/null
+++ b/src/Kompas.Mcp.Host/Tools/DocumentTools.cs
@@ -0,0 +1,69 @@
+using System.ComponentModel;
+using Kompas.Mcp.Core;
+using Kompas.Mcp.Core.Documents;
+using ModelContextProtocol.Server;
+
+namespace Kompas.Mcp.Host.Tools;
+
+/// Инструменты управления документами.
+[McpServerToolType]
+public sealed class DocumentTools(KompasSession session, DocumentService docs)
+{
+ [McpServerTool(Name = "document_create")]
+ [Description("Создать новый документ и сделать его активным. type: part | assembly | drawing | fragment | specification | text.")]
+ public async Task Create([Description("Тип документа")] string type)
+ {
+ await session.ConnectAsync();
+ var info = await docs.CreateAsync(DocumentTypes.Parse(type));
+ return $"Создан документ типа {info.Type}.";
+ }
+
+ [McpServerTool(Name = "document_open")]
+ [Description("Открыть документ КОМПАС с диска по полному пути.")]
+ public async Task Open(
+ [Description("Полный путь к файлу")] string path,
+ [Description("Открыть только для чтения")] bool readOnly = false)
+ {
+ await session.ConnectAsync();
+ var info = await docs.OpenAsync(path, readOnly);
+ return $"Открыт документ: {info.Name} ({info.Type}).";
+ }
+
+ [McpServerTool(Name = "document_save_as")]
+ [Description("Сохранить активный документ по указанному пути. Безопасно для новых документов. Расширения: .m3d (деталь), .a3d (сборка), .cdw (чертёж), .frw (фрагмент).")]
+ public async Task SaveAs([Description("Полный путь файла с расширением")] string path)
+ {
+ await session.ConnectAsync();
+ await docs.SaveAsAsync(path);
+ return $"Сохранено: {path}";
+ }
+
+ [McpServerTool(Name = "document_save")]
+ [Description("Сохранить активный документ (для уже сохранённого ранее).")]
+ public async Task Save()
+ {
+ await session.ConnectAsync();
+ await docs.SaveAsync();
+ return "Документ сохранён.";
+ }
+
+ [McpServerTool(Name = "document_close")]
+ [Description("Закрыть активный документ. save — сохранять ли изменения при закрытии.")]
+ public async Task Close([Description("Сохранять изменения")] bool save = false)
+ {
+ await session.ConnectAsync();
+ var ok = await docs.CloseAsync(save);
+ return ok ? "Документ закрыт." : "Не удалось закрыть документ.";
+ }
+
+ [McpServerTool(Name = "document_active")]
+ [Description("Сведения об активном документе.")]
+ public async Task Active()
+ {
+ await session.ConnectAsync();
+ var i = await docs.GetActiveAsync();
+ if (i is null) return "Нет активного документа.";
+ var where = string.IsNullOrEmpty(i.PathName) ? "не сохранён" : i.PathName;
+ return $"{i.Type}: {i.Name} ({where}).";
+ }
+}
diff --git a/src/Kompas.Mcp.Host/Tools/FeatureTools.cs b/src/Kompas.Mcp.Host/Tools/FeatureTools.cs
new file mode 100644
index 0000000..fa372f0
--- /dev/null
+++ b/src/Kompas.Mcp.Host/Tools/FeatureTools.cs
@@ -0,0 +1,42 @@
+using System.ComponentModel;
+using Kompas.Mcp.Core;
+using Kompas.Mcp.Core.Modeling;
+using ModelContextProtocol.Server;
+
+namespace Kompas.Mcp.Host.Tools;
+
+/// Инструменты формообразующих операций.
+[McpServerToolType]
+public sealed class FeatureTools(KompasSession session, PartModeler modeler)
+{
+ [McpServerTool(Name = "extrude_boss")]
+ [Description("Выдавить эскиз, добавив материал (на глубину depth, мм). Возвращает id операции.")]
+ public async Task ExtrudeBoss(int sketchId, double depth, [Description("Прямое направление")] bool forward = true)
+ {
+ await session.ConnectAsync();
+ var id = await modeler.ExtrudeAsync(sketchId, depth, cut: false, forward: forward);
+ return $"Выдавливание (boss) создано, id={id}.";
+ }
+
+ [McpServerTool(Name = "extrude_cut")]
+ [Description("Вырезать выдавливанием по эскизу. throughAll=true — насквозь (для отверстий, depth игнорируется); иначе на глубину depth.")]
+ public async Task ExtrudeCut(
+ int sketchId,
+ [Description("Глубина, мм (если не throughAll)")] double depth = 0,
+ [Description("Вырезать насквозь")] bool throughAll = true,
+ [Description("Прямое направление")] bool forward = true)
+ {
+ await session.ConnectAsync();
+ var id = await modeler.ExtrudeAsync(sketchId, depth <= 0 ? 1 : depth, cut: true, forward: forward, throughAll: throughAll);
+ return $"Вырез (cut) создан, id={id}.";
+ }
+
+ [McpServerTool(Name = "rebuild")]
+ [Description("Перестроить активный документ.")]
+ public async Task Rebuild()
+ {
+ await session.ConnectAsync();
+ await modeler.RebuildAsync();
+ return "Документ перестроен.";
+ }
+}
diff --git a/src/Kompas.Mcp.Host/Tools/SketchTools.cs b/src/Kompas.Mcp.Host/Tools/SketchTools.cs
new file mode 100644
index 0000000..1f0b97b
--- /dev/null
+++ b/src/Kompas.Mcp.Host/Tools/SketchTools.cs
@@ -0,0 +1,56 @@
+using System.ComponentModel;
+using Kompas.Mcp.Core;
+using Kompas.Mcp.Core.Modeling;
+using ModelContextProtocol.Server;
+
+namespace Kompas.Mcp.Host.Tools;
+
+/// Инструменты построения эскизов в активной детали.
+[McpServerToolType]
+public sealed class SketchTools(KompasSession session, PartModeler modeler)
+{
+ [McpServerTool(Name = "sketch_create")]
+ [Description("Создать эскиз на базовой плоскости (XOY, XOZ или YOZ) активной детали и открыть его на редактирование. Возвращает id эскиза для последующих sketch_add_* и операций.")]
+ public async Task Create([Description("Плоскость: XOY, XOZ или YOZ")] string plane)
+ {
+ await session.ConnectAsync();
+ var id = await modeler.OpenSketchAsync(BasePlanes.Parse(plane));
+ return $"Эскиз создан, id={id}. Добавьте геометрию (sketch_add_*) и завершите sketch_close.";
+ }
+
+ [McpServerTool(Name = "sketch_add_circle")]
+ [Description("Добавить окружность в открытый эскиз. Координаты — в плоскости эскиза, мм.")]
+ public async Task AddCircle(int sketchId, double centerX, double centerY, double radius)
+ {
+ await session.ConnectAsync();
+ await modeler.AddCircleAsync(sketchId, centerX, centerY, radius);
+ return "Окружность добавлена.";
+ }
+
+ [McpServerTool(Name = "sketch_add_line")]
+ [Description("Добавить отрезок в открытый эскиз (две точки, мм).")]
+ public async Task AddLine(int sketchId, double x1, double y1, double x2, double y2)
+ {
+ await session.ConnectAsync();
+ await modeler.AddLineAsync(sketchId, x1, y1, x2, y2);
+ return "Отрезок добавлен.";
+ }
+
+ [McpServerTool(Name = "sketch_add_rectangle")]
+ [Description("Добавить прямоугольник по двум противоположным углам в открытый эскиз (мм).")]
+ public async Task AddRectangle(int sketchId, double x1, double y1, double x2, double y2)
+ {
+ await session.ConnectAsync();
+ await modeler.AddRectangleAsync(sketchId, x1, y1, x2, y2);
+ return "Прямоугольник добавлен.";
+ }
+
+ [McpServerTool(Name = "sketch_close")]
+ [Description("Завершить редактирование эскиза (перед операцией выдавливания).")]
+ public async Task Close(int sketchId)
+ {
+ await session.ConnectAsync();
+ await modeler.CloseSketchAsync(sketchId);
+ return "Эскиз закрыт.";
+ }
+}
diff --git a/src/Kompas.Mcp.Host/Tools/SystemTools.cs b/src/Kompas.Mcp.Host/Tools/SystemTools.cs
new file mode 100644
index 0000000..db069e4
--- /dev/null
+++ b/src/Kompas.Mcp.Host/Tools/SystemTools.cs
@@ -0,0 +1,34 @@
+using System.ComponentModel;
+using Kompas.Mcp.Core;
+using ModelContextProtocol.Server;
+
+namespace Kompas.Mcp.Host.Tools;
+
+/// Системные инструменты: подключение, статус, видимость окна.
+[McpServerToolType]
+public sealed class SystemTools(KompasSession session)
+{
+ [McpServerTool(Name = "kompas_connect")]
+ [Description("Подключиться к КОМПАС-3D: присоединиться к запущенному экземпляру или запустить новый, показать окно. Возвращает версию и редакцию.")]
+ public async Task Connect()
+ {
+ var info = await session.ConnectAsync();
+ return $"Подключено к КОМПАС {info.Version} (build {info.BuildNumber}, {info.Edition}), ProgID {info.ProgId}.";
+ }
+
+ [McpServerTool(Name = "kompas_status")]
+ [Description("Текущее состояние подключения к КОМПАС.")]
+ public string Status()
+ => session.IsConnected && session.LastInfo is { } i
+ ? $"Подключено: КОМПАС {i.Version} ({i.Edition})."
+ : "Не подключено. Вызовите kompas_connect.";
+
+ [McpServerTool(Name = "kompas_set_visible")]
+ [Description("Показать или скрыть главное окно КОМПАС.")]
+ public async Task SetVisible([Description("true — показать, false — скрыть")] bool visible)
+ {
+ await session.ConnectAsync();
+ await session.SetVisibleAsync(visible);
+ return visible ? "Окно КОМПАС показано." : "Окно КОМПАС скрыто.";
+ }
+}
diff --git a/src/Kompas.Mcp.Host/Tools/VisionTools.cs b/src/Kompas.Mcp.Host/Tools/VisionTools.cs
new file mode 100644
index 0000000..eb00409
--- /dev/null
+++ b/src/Kompas.Mcp.Host/Tools/VisionTools.cs
@@ -0,0 +1,26 @@
+using System.ComponentModel;
+using Kompas.Mcp.Core;
+using Kompas.Mcp.Core.Vision;
+using ModelContextProtocol.Protocol;
+using ModelContextProtocol.Server;
+
+namespace Kompas.Mcp.Host.Tools;
+
+/// «Зрение агента»: снимок 3D-модели.
+[McpServerToolType]
+public sealed class VisionTools(KompasSession session, SnapshotService snapshot)
+{
+ [McpServerTool(Name = "model_snapshot")]
+ [Description("Снять текущий вид активной 3D-модели в PNG и вернуть как изображение — чтобы увидеть результат построения и решить следующий шаг.")]
+ public async Task Snapshot(
+ [Description("Разрешение в DPI; 0 — экранное")] int resolution = 0)
+ {
+ await session.ConnectAsync();
+ var snap = await snapshot.CaptureActiveModelAsync(resolution: resolution);
+ return new ImageContentBlock
+ {
+ Data = snap.Bytes,
+ MimeType = snap.MimeType,
+ };
+ }
+}