diff --git a/src/Kompas.Mcp.Core/Modeling/SketchGeometry.cs b/src/Kompas.Mcp.Core/Modeling/SketchGeometry.cs
new file mode 100644
index 0000000..8fd736b
--- /dev/null
+++ b/src/Kompas.Mcp.Core/Modeling/SketchGeometry.cs
@@ -0,0 +1,36 @@
+namespace Kompas.Mcp.Core.Modeling;
+
+/// Чистые помощники геометрии эскиза: маппинги контракта в параметры COM и валидация.
+public static class SketchGeometry
+{
+ /// Направление дуги для ksArcByAngle: против часовой → 1, по часовой → -1.
+ public static short ArcDirection(bool counterClockwise) => (short)(counterClockwise ? 1 : -1);
+
+ /// describe для ksRegularPolygon: вписанный (вершины на окружности) → false; описанный → true.
+ public static bool PolygonDescribe(bool inscribed) => !inscribed;
+
+ /// Требовать строго положительное значение (радиус, полуось).
+ public static void RequirePositive(double value, string paramName)
+ {
+ if (!(value > 0))
+ throw new ArgumentOutOfRangeException(paramName, value, "Значение должно быть > 0.");
+ }
+
+ /// Требовать >= 3 вершин для правильного многоугольника.
+ public static void RequireVertexCount(int count)
+ {
+ if (count < 3)
+ throw new ArgumentOutOfRangeException(nameof(count), count, "Число вершин должно быть >= 3.");
+ }
+
+ /// Валидировать список точек: не null, минимум , все координаты конечны.
+ public static void RequirePoints(IReadOnlyList<(double x, double y)> points, int min, string paramName)
+ {
+ ArgumentNullException.ThrowIfNull(points, paramName);
+ if (points.Count < min)
+ throw new ArgumentException($"Нужно минимум {min} точек, передано {points.Count}.", paramName);
+ for (int i = 0; i < points.Count; i++)
+ if (!double.IsFinite(points[i].x) || !double.IsFinite(points[i].y))
+ throw new ArgumentException($"Точка [{i}] имеет неконечную координату.", paramName);
+ }
+}
diff --git a/src/Kompas.Mcp.Host/Tools/SketchPoint.cs b/src/Kompas.Mcp.Host/Tools/SketchPoint.cs
new file mode 100644
index 0000000..40c78b6
--- /dev/null
+++ b/src/Kompas.Mcp.Host/Tools/SketchPoint.cs
@@ -0,0 +1,10 @@
+using System.ComponentModel;
+using System.Text.Json.Serialization;
+
+namespace Kompas.Mcp.Host.Tools;
+
+/// Точка эскиза в его плоскости (мм). Элемент списка для ломаной/сплайна.
+/// JSON-имена зафиксированы lowercase (x/y), чтобы схема инструмента совпадала с контрактом.
+public sealed record SketchPoint(
+ [property: JsonPropertyName("x")][property: Description("Координата X в плоскости эскиза, мм")] double X,
+ [property: JsonPropertyName("y")][property: Description("Координата Y в плоскости эскиза, мм")] double Y);
diff --git a/tests/Kompas.Mcp.Tests/SketchGeometryTests.cs b/tests/Kompas.Mcp.Tests/SketchGeometryTests.cs
new file mode 100644
index 0000000..7921ee7
--- /dev/null
+++ b/tests/Kompas.Mcp.Tests/SketchGeometryTests.cs
@@ -0,0 +1,54 @@
+using Kompas.Mcp.Core.Modeling;
+
+namespace Kompas.Mcp.Tests;
+
+[Trait("Category", "Unit")]
+public sealed class SketchGeometryTests
+{
+ [Theory]
+ [InlineData(true, 1)]
+ [InlineData(false, -1)]
+ public void ArcDirection_maps_orientation(bool ccw, int expected)
+ => Assert.Equal(expected, (int)SketchGeometry.ArcDirection(ccw));
+
+ [Theory]
+ [InlineData(true, false)] // вписанный → describe=false
+ [InlineData(false, true)] // описанный → describe=true
+ public void PolygonDescribe_inverts_inscribed(bool inscribed, bool expected)
+ => Assert.Equal(expected, SketchGeometry.PolygonDescribe(inscribed));
+
+ [Fact]
+ public void RequirePositive_throws_on_zero_or_negative()
+ {
+ Assert.Throws(() => SketchGeometry.RequirePositive(0, "radius"));
+ Assert.Throws(() => SketchGeometry.RequirePositive(-1, "radius"));
+ Assert.Null(Record.Exception(() => SketchGeometry.RequirePositive(0.1, "radius")));
+ }
+
+ [Fact]
+ public void RequireVertexCount_throws_below_three()
+ {
+ Assert.Throws(() => SketchGeometry.RequireVertexCount(2));
+ Assert.Null(Record.Exception(() => SketchGeometry.RequireVertexCount(3)));
+ }
+
+ [Fact]
+ public void RequirePoints_throws_on_null()
+ => Assert.Throws(
+ () => SketchGeometry.RequirePoints(null!, 2, "points"));
+
+ [Fact]
+ public void RequirePoints_throws_when_too_few()
+ => Assert.Throws(
+ () => SketchGeometry.RequirePoints(new[] { (0.0, 0.0) }, 2, "points"));
+
+ [Fact]
+ public void RequirePoints_throws_on_non_finite()
+ => Assert.Throws(
+ () => SketchGeometry.RequirePoints(new[] { (0.0, 0.0), (double.NaN, 1.0) }, 2, "points"));
+
+ [Fact]
+ public void RequirePoints_passes_on_valid()
+ => Assert.Null(Record.Exception(
+ () => SketchGeometry.RequirePoints(new[] { (0.0, 0.0), (1.0, 1.0) }, 2, "points")));
+}