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.)
|
# Enable dangerous AutoCAD commands (ERASE ALL, QUIT, etc.)
|
||||||
# Set to true only if you trust all MCP clients
|
# Set to true only if you trust all MCP clients
|
||||||
DANGEROUS_COMMANDS_ENABLED=false
|
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/
|
out/
|
||||||
benchmarks/results/*
|
benchmarks/results/*
|
||||||
!benchmarks/results/.gitkeep
|
!benchmarks/results/.gitkeep
|
||||||
|
!benchmarks/results/published
|
||||||
|
!benchmarks/results/published/**
|
||||||
|
benchmarks/.competitors/
|
||||||
*.dxf
|
*.dxf
|
||||||
*.dwg
|
*.dwg
|
||||||
*.bak
|
*.bak
|
||||||
|
|
||||||
|
# Local editor/agent state
|
||||||
|
.cursor/
|
||||||
|
|||||||
+4
-3
@@ -4,9 +4,10 @@ WORKDIR /app
|
|||||||
|
|
||||||
RUN useradd --create-home --shell /bin/bash mcp
|
RUN useradd --create-home --shell /bin/bash mcp
|
||||||
|
|
||||||
COPY pyproject.toml ./
|
COPY pyproject.toml README.md LICENSE ./
|
||||||
COPY server.py config.py security.py ./
|
COPY server.py config.py security.py version.py ./
|
||||||
COPY backends/ ./backends/
|
COPY backends/ ./backends/
|
||||||
|
COPY engineering/ ./engineering/
|
||||||
|
|
||||||
RUN pip install --no-cache-dir -e .
|
RUN pip install --no-cache-dir -e .
|
||||||
|
|
||||||
@@ -15,6 +16,6 @@ USER mcp
|
|||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
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"]
|
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]
|
[project]
|
||||||
name = "autocad-mcp-pro"
|
name = "autocad-mcp-pro"
|
||||||
version = "1.3.0"
|
version = "1.4.0"
|
||||||
description = "Production-grade AutoCAD MCP server — COM API + ezdxf dual engine"
|
description = "Production-grade AutoCAD MCP server — COM API + ezdxf dual engine"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
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 = [
|
dependencies = [
|
||||||
"fastmcp>=3.0",
|
"fastmcp>=3.0",
|
||||||
"ezdxf>=1.3",
|
"ezdxf>=1.3",
|
||||||
"pydantic>=2.0",
|
"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]
|
[project.optional-dependencies]
|
||||||
com = [
|
com = [
|
||||||
"pywin32>=306",
|
"pywin32>=306",
|
||||||
@@ -26,6 +59,9 @@ full = [
|
|||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
autocad-mcp = "server:main"
|
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]
|
[tool.uv]
|
||||||
dev-dependencies = [
|
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