feat(release): CI matrix, PyPI trusted-publishing pipeline, registry manifest, Docker fix

- ci.yml: ruff lint/format, Linux 3.11/3.12 tests + coverage, Windows mocked-COM leg, package build + clean-venv wheel smoke, Docker smoke, server.json schema validation
- release.yml: tag -> test gate -> tag/version check -> build -> PyPI trusted publishing (OIDC) -> GitHub Release
- pyproject: SPDX license, authors, keywords, classifiers, project URLs, autocad-mcp-pro console alias; version 1.4.0
- Dockerfile: ship engineering/ + version.py (image previously missed both -> engineering tools failed in containers); healthcheck imports engineering
- server.json: MCP registry manifest (io.github.u-c4n/autocad-mcp, pypi package)
- docs/RELEASE-DISTRIBUTION.md: one-time owner actions (PyPI trusted publisher, mcp-publisher login) + directory submissions runbook

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Umutcan Edizsalan
2026-07-23 15:15:07 +03:00
co-authored by Cursor
parent 9ec6c4a989
commit 2f56a73093
8 changed files with 348 additions and 4 deletions
+8
View File
@@ -17,3 +17,11 @@ LOG_LEVEL=INFO
# Enable dangerous AutoCAD commands (ERASE ALL, QUIT, etc.)
# Set to true only if you trust all MCP clients
DANGEROUS_COMMANDS_ENABLED=false
# Exposed tool profile: full (default) | core | lean
# core hides raw escape hatches and long-tail tools; lean is a curated
# ~46-tool drafting/inspection core for clients with tight tool caps.
TOOL_PROFILE=full
# Opt-in 3D solids (COM backend only; ezdxf reports them as unsupported)
ENABLE_3D=false
+110
View File
@@ -0,0 +1,110 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install ruff
- run: ruff check .
- run: ruff format --check .
test-linux:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install -e ".[pdf]"
pip install pytest pytest-asyncio pytest-cov
- name: Run tests
run: pytest --cov=. --cov-report=xml --cov-report=term-missing -v
- name: Upload coverage
if: matrix.python-version == '3.11'
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.xml
test-windows:
# Windows leg exercises the mocked-COM suite on the platform the COM
# backend actually targets (pywin32 installs; no live AutoCAD needed).
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: |
pip install -e ".[full]"
pip install pytest pytest-asyncio pytest-cov
- name: Run tests
run: pytest -v
package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Build sdist and wheel
run: |
pip install build twine
python -m build
twine check dist/*
- name: Install wheel in a clean venv and smoke test
run: |
python -m venv /tmp/smoke
/tmp/smoke/bin/pip install dist/*.whl
/tmp/smoke/bin/autocad-mcp --help
/tmp/smoke/bin/python -c "import server, config, security, version, backends, engineering; print('wheel ok', version.__version__)"
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t autocad-mcp:ci .
- name: Smoke test image
run: docker run --rm autocad-mcp:ci python -c "import server, engineering.gear, version; print('docker ok', version.__version__)"
registry-validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Validate server.json against MCP registry schema
if: ${{ hashFiles('server.json') != '' }}
run: |
pip install check-jsonschema
check-jsonschema --schemafile https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json server.json
+88
View File
@@ -0,0 +1,88 @@
name: Release
# Tag-driven release: run the test gate, build, publish to PyPI via trusted
# publishing (OIDC — no API token stored in the repo), then cut the GitHub
# Release with the built artifacts attached.
#
# One-time setup on PyPI (manual): project "autocad-mcp-pro" -> Publishing ->
# add GitHub publisher repo=U-C4N/Autocad-MCP workflow=release.yml
# environment=pypi.
on:
push:
tags: ["v*"]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: |
pip install -e ".[pdf]"
pip install pytest pytest-asyncio ruff
- run: ruff check .
- run: pytest -q
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Verify tag matches package version
run: |
pip install tomli-w 2>/dev/null || true
PKG_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
TAG_VERSION="${GITHUB_REF_NAME#v}"
if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
echo "Tag $GITHUB_REF_NAME does not match pyproject version $PKG_VERSION" >&2
exit 1
fi
- name: Build distributions
run: |
pip install build twine
python -m build
twine check dist/*
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
publish-pypi:
needs: build
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Publish to PyPI (trusted publishing)
uses: pypa/gh-action-pypi-publish@release/v1
github-release:
needs: publish-pypi
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "$GITHUB_REF_NAME" dist/* \
--title "$GITHUB_REF_NAME" \
--generate-notes
+6
View File
@@ -43,6 +43,12 @@ desktop.ini
out/
benchmarks/results/*
!benchmarks/results/.gitkeep
!benchmarks/results/published
!benchmarks/results/published/**
benchmarks/.competitors/
*.dxf
*.dwg
*.bak
# Local editor/agent state
.cursor/
+4 -3
View File
@@ -4,9 +4,10 @@ WORKDIR /app
RUN useradd --create-home --shell /bin/bash mcp
COPY pyproject.toml ./
COPY server.py config.py security.py ./
COPY pyproject.toml README.md LICENSE ./
COPY server.py config.py security.py version.py ./
COPY backends/ ./backends/
COPY engineering/ ./engineering/
RUN pip install --no-cache-dir -e .
@@ -15,6 +16,6 @@ USER mcp
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import server; print('ok')" || exit 1
CMD python -c "import server, engineering.gear; print('ok')" || exit 1
CMD ["python", "server.py"]
+51
View File
@@ -0,0 +1,51 @@
# Release & Distribution Runbook (v1.4.0+)
The code side of a release is fully automated; three account-bound steps need
a human once, plus the directory submissions. Track them here.
## One-time setup (owner action)
- [ ] **PyPI trusted publisher** — on <https://pypi.org/manage/account/publishing/>
add a *pending publisher* for project `autocad-mcp-pro`:
repository `U-C4N/Autocad-MCP`, workflow `release.yml`, environment `pypi`.
(No API token is stored anywhere; `release.yml` uses OIDC.)
- [ ] **GitHub environment** — create the `pypi` environment in the repo
settings (Settings → Environments) so the publish job can bind to it.
- [ ] **MCP registry login** — install `mcp-publisher`, then
`mcp-publisher login github` (authenticates the `io.github.u-c4n` namespace).
## Every release
1. Bump `pyproject.toml` + `server.json` versions, add the CHANGELOG section,
update the README snapshot line — `tests/test_release_consistency.py`
enforces all of it.
2. Push `main`, wait for CI to go green (lint, linux, windows, package,
docker, registry-validate).
3. Tag and push: `git tag v1.4.0 && git push origin v1.4.0`.
`release.yml` runs the test gate, verifies tag == package version, builds,
publishes to PyPI via trusted publishing and cuts the GitHub Release.
4. Verify: `pip install autocad-mcp-pro==<version>` in a clean venv;
`uvx autocad-mcp-pro --help`.
5. Publish the registry manifest: `mcp-publisher publish` (validates
`server.json`, checks the `mcp-name` marker in the PyPI README).
## Directory listings (submit once, then keep fresh)
- [ ] Glama — <https://glama.ai/mcp/servers> (auto-indexes GitHub; claim the
server page and verify metadata).
- [ ] mcpservers.org — submit via their GitHub repo PR flow.
- [ ] awesome-mcp-servers — PR adding the server under CAD/engineering.
- [ ] Smithery — optional; requires a `smithery.yaml` (evaluate demand first).
## Benchmark refresh (per release)
```bash
python -m benchmarks.run_competitors --server autocad-mcp-pro --backend ezdxf
python -m benchmarks.run_competitors --server puran-water-autocad-mcp --backend ezdxf
python -m benchmarks.run_competitors --server beiming183-autocad-mcp --backend ezdxf
# sanitize + copy reports into benchmarks/results/published/, then:
python -m benchmarks.render_live_chart
```
Bump the competitor pins in `benchmarks/competitors.yaml` deliberately (new
SHA = re-read their tool contracts before trusting the mappings).
+37 -1
View File
@@ -1,15 +1,48 @@
[project]
name = "autocad-mcp-pro"
version = "1.3.0"
version = "1.4.0"
description = "Production-grade AutoCAD MCP server — COM API + ezdxf dual engine"
readme = "README.md"
requires-python = ">=3.11"
license = "MIT"
license-files = ["LICENSE"]
authors = [
{ name = "Umutcan Edizsalan" },
]
keywords = [
"autocad",
"mcp",
"model-context-protocol",
"cad",
"dxf",
"ezdxf",
"drafting",
"engineering-drawing",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Manufacturing",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"fastmcp>=3.0",
"ezdxf>=1.3",
"pydantic>=2.0",
]
[project.urls]
Homepage = "https://github.com/U-C4N/Autocad-MCP"
Repository = "https://github.com/U-C4N/Autocad-MCP"
Issues = "https://github.com/U-C4N/Autocad-MCP/issues"
Changelog = "https://github.com/U-C4N/Autocad-MCP/blob/main/CHANGELOG.md"
[project.optional-dependencies]
com = [
"pywin32>=306",
@@ -26,6 +59,9 @@ full = [
[project.scripts]
autocad-mcp = "server:main"
# Alias matching the PyPI distribution name so `uvx autocad-mcp-pro` works
# without a --from indirection (MCP registry / uvx convention).
autocad-mcp-pro = "server:main"
[tool.uv]
dev-dependencies = [
+44
View File
@@ -0,0 +1,44 @@
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.u-c4n/autocad-mcp",
"title": "AutoCAD MCP Pro",
"description": "AutoCAD automation for AI agents: live COM or headless ezdxf, ISO dims, GD&T, quality gate.",
"repository": {
"url": "https://github.com/U-C4N/Autocad-MCP",
"source": "github"
},
"version": "1.4.0",
"packages": [
{
"registryType": "pypi",
"identifier": "autocad-mcp-pro",
"version": "1.4.0",
"transport": {
"type": "stdio"
},
"environmentVariables": [
{
"name": "AUTOCAD_MCP_BACKEND",
"description": "Backend selection: auto (default), com (live AutoCAD, Windows) or ezdxf (headless DXF).",
"isRequired": false,
"format": "string",
"isSecret": false
},
{
"name": "ALLOWED_PATHS",
"description": "Comma-separated absolute paths the server may read and write. Empty blocks file access outside temp.",
"isRequired": false,
"format": "string",
"isSecret": false
},
{
"name": "TOOL_PROFILE",
"description": "Exposed tool profile: full (default), core or lean.",
"isRequired": false,
"format": "string",
"isSecret": false
}
]
}
]
}