diff --git a/src/Kompas.Mcp.Core/Documents/DocumentInfo.cs b/src/Kompas.Mcp.Core/Documents/DocumentInfo.cs
new file mode 100644
index 0000000..b2bb660
--- /dev/null
+++ b/src/Kompas.Mcp.Core/Documents/DocumentInfo.cs
@@ -0,0 +1,14 @@
+namespace Kompas.Mcp.Core.Documents;
+
+/// Сводка по документу КОМПАС.
+public sealed record DocumentInfo
+{
+ /// Имя файла без пути (или имя по умолчанию для несохранённого).
+ public required string Name { get; init; }
+
+ /// Полный путь файла; пусто, если документ ещё не сохранён.
+ public required string PathName { get; init; }
+
+ /// Тип документа.
+ public required KompasDocumentType Type { get; init; }
+}
diff --git a/src/Kompas.Mcp.Core/Documents/DocumentService.cs b/src/Kompas.Mcp.Core/Documents/DocumentService.cs
new file mode 100644
index 0000000..b3311bf
--- /dev/null
+++ b/src/Kompas.Mcp.Core/Documents/DocumentService.cs
@@ -0,0 +1,70 @@
+using System.Runtime.Versioning;
+using Kompas.Mcp.Core.Threading;
+using Kompas6Constants;
+using KompasAPI7;
+
+namespace Kompas.Mcp.Core.Documents;
+
+/// Жизненный цикл документов КОМПАС через API7 ().
+[SupportedOSPlatform("windows")]
+public sealed class DocumentService
+{
+ private readonly KompasSession _session;
+ private readonly KompasDispatcher _dispatcher;
+
+ public DocumentService(KompasSession session, KompasDispatcher dispatcher)
+ {
+ _session = session ?? throw new ArgumentNullException(nameof(session));
+ _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
+ }
+
+ /// Создать новый документ заданного типа и сделать его активным.
+ public Task CreateAsync(KompasDocumentType type, bool visible = true, CancellationToken ct = default)
+ => _dispatcher.InvokeAsync(() =>
+ {
+ var doc = (IKompasDocument)_session.Application.Documents.Add(DocumentTypes.ToEnum(type), visible);
+ return ToInfo(doc);
+ }, ct);
+
+ /// Открыть документ с диска.
+ public Task OpenAsync(string path, bool readOnly = false, bool visible = true, CancellationToken ct = default)
+ => _dispatcher.InvokeAsync(() =>
+ {
+ var doc = (IKompasDocument)_session.Application.Documents.Open(path, visible, readOnly);
+ if (doc is null)
+ throw new InvalidOperationException($"Не удалось открыть документ: {path}");
+ return ToInfo(doc);
+ }, ct);
+
+ /// Сводка по активному документу или null, если документов нет.
+ public Task GetActiveAsync(CancellationToken ct = default)
+ => _dispatcher.InvokeAsync(() =>
+ {
+ var doc = _session.Application.ActiveDocument;
+ return doc is null ? null : ToInfo(doc);
+ }, ct);
+
+ /// Сохранить активный документ (для уже сохранённого — без диалога).
+ public Task SaveAsync(CancellationToken ct = default)
+ => _dispatcher.InvokeAsync(() => RequireActive().Save(), ct);
+
+ /// Сохранить активный документ по указанному пути (безопасно для новых документов).
+ public Task SaveAsAsync(string path, CancellationToken ct = default)
+ => _dispatcher.InvokeAsync(() => RequireActive().SaveAs(path), ct);
+
+ /// Закрыть активный документ. — сохранять ли изменения.
+ public Task CloseAsync(bool save = false, CancellationToken ct = default)
+ => _dispatcher.InvokeAsync(() =>
+ RequireActive().Close(save ? DocumentCloseOptions.kdSaveChanges : DocumentCloseOptions.kdDoNotSaveChanges), ct);
+
+ private IKompasDocument RequireActive()
+ => _session.Application.ActiveDocument
+ ?? throw new InvalidOperationException("Нет активного документа.");
+
+ private static DocumentInfo ToInfo(IKompasDocument doc) => new()
+ {
+ Name = doc.Name ?? string.Empty,
+ PathName = doc.PathName ?? string.Empty,
+ Type = DocumentTypes.FromEnum(doc.DocumentType),
+ };
+}
diff --git a/src/Kompas.Mcp.Core/Documents/KompasDocumentType.cs b/src/Kompas.Mcp.Core/Documents/KompasDocumentType.cs
new file mode 100644
index 0000000..1da7bdb
--- /dev/null
+++ b/src/Kompas.Mcp.Core/Documents/KompasDocumentType.cs
@@ -0,0 +1,58 @@
+using Kompas6Constants;
+
+namespace Kompas.Mcp.Core.Documents;
+
+/// Дружелюбный тип документа КОМПАС (обёртка над ).
+public enum KompasDocumentType
+{
+ Part,
+ Assembly,
+ Drawing,
+ Fragment,
+ Specification,
+ Text,
+}
+
+/// Маппинг дружелюбного типа документа ↔ перечисление API7 ↔ строка.
+public static class DocumentTypes
+{
+ public static DocumentTypeEnum ToEnum(KompasDocumentType type) => type switch
+ {
+ KompasDocumentType.Part => DocumentTypeEnum.ksDocumentPart,
+ KompasDocumentType.Assembly => DocumentTypeEnum.ksDocumentAssembly,
+ KompasDocumentType.Drawing => DocumentTypeEnum.ksDocumentDrawing,
+ KompasDocumentType.Fragment => DocumentTypeEnum.ksDocumentFragment,
+ KompasDocumentType.Specification => DocumentTypeEnum.ksDocumentSpecification,
+ KompasDocumentType.Text => DocumentTypeEnum.ksDocumentTextual,
+ _ => throw new ArgumentOutOfRangeException(nameof(type), type, "Неизвестный тип документа."),
+ };
+
+ public static KompasDocumentType FromEnum(DocumentTypeEnum type) => type switch
+ {
+ DocumentTypeEnum.ksDocumentPart => KompasDocumentType.Part,
+ DocumentTypeEnum.ksDocumentAssembly => KompasDocumentType.Assembly,
+ DocumentTypeEnum.ksDocumentDrawing => KompasDocumentType.Drawing,
+ DocumentTypeEnum.ksDocumentFragment => KompasDocumentType.Fragment,
+ DocumentTypeEnum.ksDocumentSpecification => KompasDocumentType.Specification,
+ DocumentTypeEnum.ksDocumentTextual => KompasDocumentType.Text,
+ _ => KompasDocumentType.Part,
+ };
+
+ /// Разобрать строку ("part", "assembly", "drawing", "fragment", ...) в тип.
+ public static KompasDocumentType Parse(string value)
+ {
+ ArgumentNullException.ThrowIfNull(value);
+ return value.Trim().ToLowerInvariant() switch
+ {
+ "part" or "деталь" => KompasDocumentType.Part,
+ "assembly" or "сборка" => KompasDocumentType.Assembly,
+ "drawing" or "чертеж" or "чертёж" => KompasDocumentType.Drawing,
+ "fragment" or "фрагмент" => KompasDocumentType.Fragment,
+ "specification" or "спецификация" => KompasDocumentType.Specification,
+ "text" or "txt" or "текст" => KompasDocumentType.Text,
+ _ => throw new ArgumentException(
+ $"Неизвестный тип документа '{value}'. Допустимо: part, assembly, drawing, fragment, specification, text.",
+ nameof(value)),
+ };
+ }
+}
diff --git a/tests/Kompas.Mcp.Tests/Integration/DocumentTests.cs b/tests/Kompas.Mcp.Tests/Integration/DocumentTests.cs
new file mode 100644
index 0000000..dc803ab
--- /dev/null
+++ b/tests/Kompas.Mcp.Tests/Integration/DocumentTests.cs
@@ -0,0 +1,42 @@
+using Kompas.Mcp.Core.Documents;
+
+namespace Kompas.Mcp.Tests.Integration;
+
+/// Интеграция: жизненный цикл документов.
+[Trait("Category", "Integration")]
+[Collection(KompasCollection.Name)]
+public sealed class DocumentTests
+{
+ private readonly DocumentService _docs;
+
+ public DocumentTests(KompasFixture fx)
+ => _docs = new DocumentService(fx.Session, fx.Dispatcher);
+
+ [Fact]
+ public async Task Create_part_saveas_close_roundtrip()
+ {
+ var created = await _docs.CreateAsync(KompasDocumentType.Part);
+ Assert.Equal(KompasDocumentType.Part, created.Type);
+
+ var path = TestPaths.NewFile(".m3d");
+ await _docs.SaveAsAsync(path);
+
+ var active = await _docs.GetActiveAsync();
+ Assert.NotNull(active);
+ Assert.Equal(path, active!.PathName, ignoreCase: true);
+
+ var closed = await _docs.CloseAsync(save: false);
+ Assert.True(closed);
+ Assert.True(File.Exists(path));
+ }
+
+ [Fact]
+ public async Task Create_fragment_reports_type()
+ {
+ await _docs.CreateAsync(KompasDocumentType.Fragment);
+ var active = await _docs.GetActiveAsync();
+ Assert.NotNull(active);
+ Assert.Equal(KompasDocumentType.Fragment, active!.Type);
+ await _docs.CloseAsync(save: false);
+ }
+}
diff --git a/tests/Kompas.Mcp.Tests/TestPaths.cs b/tests/Kompas.Mcp.Tests/TestPaths.cs
new file mode 100644
index 0000000..bcdab34
--- /dev/null
+++ b/tests/Kompas.Mcp.Tests/TestPaths.cs
@@ -0,0 +1,19 @@
+namespace Kompas.Mcp.Tests;
+
+/// Пути для тестовых артефактов: корень репозитория и gitignore-папка .scratch.
+internal static class TestPaths
+{
+ public static string RepoRoot()
+ {
+ var dir = new DirectoryInfo(AppContext.BaseDirectory);
+ while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "KompasMcp.slnx")))
+ dir = dir.Parent;
+ return dir?.FullName ?? AppContext.BaseDirectory;
+ }
+
+ public static string Scratch
+ => Directory.CreateDirectory(Path.Combine(RepoRoot(), ".scratch")).FullName;
+
+ public static string NewFile(string extension)
+ => Path.Combine(Scratch, $"test_{DateTime.Now:HHmmss}_{Guid.NewGuid():N}{extension}");
+}