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:
co-authored by
Cursor
parent
9ec6c4a989
commit
2f56a73093
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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"]
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user