Skip to main content
Glama
khs0927

Power CAD MCP

by khs0927
README.md
# Power CAD MCP

AI 어시스턴트(Claude Desktop, Claude Code 등 MCP 클라이언트)가 **AutoCAD 2027 도면을 읽고, 수정하고, 검증**하도록 해 주는
[Model Context Protocol](https://modelcontextprotocol.io) 서버 모음입니다.

| 구성 | 위치 | 용도 |
| --- | --- | --- |
| **power-cad-server + AutoCAD 2027 플러그인 (C#/.NET 10, 주력)** | [`dotnet/`](dotnet) | 실도면 수정. AutoCAD 내부에서 트랜잭션으로 실행하고, 수정 직전 대상 확인·수정 직후 자동 검증·실패 시 롤백 |
| power-cad-mcp (Python) | [`src/power_cad_mcp`](src/power_cad_mcp) | COM 폴백 작도 및 파일 인벤토리(42개 도구)와 AutoCAD 없는 DXF/PNG/PDF 작도·미리보기, Ontology 건물 데이터 조회·도면 대조·블록 대조·인벤토리 대조·Graph RAG 질의응답(11개 도구) |
| best-cad-mcp (외부, 선택) | `uvx --from best-cad-mcp cad-mcp` | 도면 의미·객체 관계 분석 — 별도 MCP 서버로 함께 연결 |

- 설계 문서: [프레임워크](docs/framework/AutoCAD2027_Framework.md) · [운영 지침(ASTRA)](docs/framework/ASTRA_CAD_Operating_Playbook.md) ·
  [시각 안내](docs/framework/AutoCAD2027_Framework.html) · [오픈소스 고정 목록](docs/framework/framework.sources.json)

## 주력: power-cad-server (C#)

```
Claude ─stdio─▶ power-cad-server ─Named Pipe(토큰)─▶ PowerCad.Plugin.A27 (AutoCAD 2027 내부) ─▶ 도면 DB
```

도구 65개 (C# 0.5.0 preview):

| 분류 | 도구 |
| --- | --- |
| 프레임워크 | `cad_get_document_identity`, `cad_bind_document`, `cad_extract_snapshot`, `cad_query_page`, `cad_review_snapshot`, `cad_plan_create`, `cad_plan_get`, `cad_plan_execute` |
| 의미 컨텍스트 | `cad_context_query`, `cad_context_select`, `cad_context_actions`, `cad_context_action_select` |
| Ontology (읽기 전용) | `ontology_catalog`, `ontology_find_elements`, `ontology_blocks`, `ontology_drawings`, `ontology_element_context`, `ontology_search`, `ontology_ask`, **`ontology_auto_context`**, `ontology_locate`, `ontology_block_candidates` — Python 서버와 같은 이름·인자·결과 형태(AutoCAD에 도면이 열려 있으면 `in_open_drawing` 표시, `ontology_locate`는 `cad_*` 편집 도구용 `fingerprint`도 함께 반환). `POWERCAD_ONTOLOGY_URL`을 설정해야 동작하며, 없으면 `[ONTOLOGY_NOT_CONFIGURED]` 오류를 돌려줍니다 |
| 연결 | `cad_status`, `cad_list_targets`, `cad_select_target` |
| 조회 | `cad_query`(필터·`group_by` 집계·`compact`·`within_mode`·`xdata` 태그 필터), `cad_get`(XData 포함), `cad_xdata_get`(핸들별 XData 원문과 key→value `tags`), `cad_inspect`(단위·현재값·문자/치수 스타일·선종류·블록·범위), `cad_layers` |
| 인벤토리 | `cad_inventory` — 이름을 몰라도 모든 레이아웃(탭 순서·플롯 장치/용지·뷰포트/객체 수), 블록 정의(동적 블록 실제 이름·익명/레이아웃/XREF 여부·속성 정의·객체 종류별 개수·중첩 블록·레이아웃별 삽입 수), 레이아웃별 블록 참조(핸들·위치·회전·축척·레이어·속성값, 중첩 참조는 `max_depth`(기본 2)까지 경로 표시), XREF(경로·찾음/미해결 상태·부착/오버레이·중첩 그래프)를 읽기 전용으로 나열. XREF 파일은 열지 않으며 `max_blocks`·`max_references`·전송 크기 한도를 넘으면 `*_truncated`로 표시 |
| 작성 | `cad_create` — line, polyline, circle, arc, text(`justify`·`style`·`width_factor`), mtext, insert, point, **dimension**(rotated/aligned, 스타일·문자 재지정), **hatch**(SOLID·ANSI31 등), 공통 `color`/`linetype`/`lineweight` · `cad_create_many` — 작도 계획 전체(최대 5000개)를 한 트랜잭션으로: 없는 레이어·문자 스타일·치수 스타일만 생성(기존 것은 변경하지 않음), `insert.attributes`, 객체별 `hs` 태그를 XData로 저장(기본 앱 `HS-STEEL`), `offset`, `skip_missing_blocks`, 생성 핸들 전부 재검증·실패 시 전체 롤백, `dry_run` 지원 |
| 수정 | `cad_replace_text`, `cad_move`, `cad_modify_opening`, `cad_set_properties`(레이어·색·선종류·선가중치·문자 높이/회전/스타일/정렬), `cad_copy`(배열 복사), `cad_transform`(회전·축척·대칭, 문자는 읽히는 방향 유지), `cad_offset`(선·직선 폴리선·원·호 간격띄우기, `count`로 여러 줄), `cad_delete` |
| 레이어 | `cad_set_layer`(생성·색·선종류·선가중치·켜기/동결/플롯·현재 레이어, 잠금 해제는 사용자 확인 필요) |
| 블록·패턴 자산 | `cad_block_library`, `cad_export_block`, `cad_import_block`, `cad_export_hatch_pattern`(해치 패턴을 .pat로 내보내기) |
| 측정 | `cad_measure`(길이·둘레·면적(호 구간 포함)·경로 거리, mm 도면은 m² 병기 — 실면적표용) |
| 화면 | `cad_zoom`, `cad_snapshot`(모델 뷰를 PNG로 반환해 결과를 눈으로 확인) |
| 묶음 | `cad_batch`(최대 20단계를 한 트랜잭션으로, 위 수정 명령 모두 사용 가능) |
| **HS-STEEL 자산** | `cad_hs_registry_status`, `cad_hs_assets`, `cad_hs_asset_get`, `cad_hs_section_info`(읽기 전용) · `cad_hs_styles_apply`, `cad_hs_block_place`, `cad_hs_section_draw`, `cad_hs_grid`, `cad_hs_members_place`(작도, 바인딩·검증·롤백 동일) · `cad_hs_member_list`(XData 물량, 읽기 전용) · `cad_hs_steel_prepare`, `cad_hs_steel_catalog_prepare`. 설정: `POWER_CAD_HS_REGISTRY` 또는 `POWER_CAD_HS_STEEL_ROOT`(hs-steel-cad 레지스트리), `HS_STEEL_ASSET_ROOT`(PC별 원본 자산 루트, 예 `C:\cad\HSSTEEL`·`C:\HS-STEEL`), `POWERCAD_ASSET_DB`(공유 자산 DB 폴더, `powercad.asset.card/v1`). 없으면 내장 HS 표준(HS-* 레이어 15개, HS-KOR)으로 동작. 사용 지침: [HS_STEEL_DRAWING_PLAYBOOK](docs/playbooks/HS_STEEL_DRAWING_PLAYBOOK.md) |
| 저장 | `cad_save` — 기본 `copy`: 열린 도면은 그대로 두고 DWG/DXF 사본 저장(기존 파일은 `overwrite` 필요) · `save`: 열린 도면 자체 저장(`user_confirmed` 필요) |
| 시각 검증 (보조, 읽기 전용) | `cad_visual_inspect`, `cad_visual_verify` — 현재 뷰를 NVIDIA Cosmos Reason2로 확인. `NVIDIA_COSMOS_ENDPOINT`를 설정해야 동작하며, 결과는 참고용이라 수정을 승인하지 않습니다 ([워크플로](docs/framework/NVIDIA_COSMOS_VISUAL_WORKFLOW.md)) |
| HS 강재 핸드오프 (읽기 전용) | `cad_hs_steel_catalog_prepare`(`hs-steel-section-catalog/1` 검증), `cad_hs_steel_prepare`(`hs-steel-draw-plan/1` → `cad_plan_create` 단계로 변환, CAD는 수정하지 않음) |

서버 옵션: `power-cad-server [--simulate] [--target <autocad-2027-PID|PID>] [--read-only] [--home <dir>]`.
같은 값을 환경 변수 `POWER_CAD_SIMULATE`, `POWER_CAD_TARGET`, `POWER_CAD_READ_ONLY`, `POWER_CAD_HOME`(검색 폴더, 기본 `%LOCALAPPDATA%\PowerCad`)으로도 줄 수 있고 CLI 플래그가 우선합니다.
블록 라이브러리 폴더는 기본 `Documents\PowerCad\blocks`이며 `POWER_CAD_BLOCK_LIBRARY`로 바꿉니다.

AutoCAD가 명령·대화상자로 바쁘면 20초 안에 `CAD_BUSY`로 답합니다(`POWER_CAD_BUSY_TIMEOUT`, 초 단위로 변경).

모든 수정은 같은 절차를 거칩니다: **지문으로 대상 확인 → 한 트랜잭션에서 변경 → 변경된 대상만 재검증 → 실패 시 전체 롤백 → before/after 보고**.
`dry_run: true`로 실제와 같은 조건의 미리보기를 받을 수 있습니다.

### 설치 (Windows, AutoCAD 2027)

```powershell
git clone https://github.com/khs0927/power-cad-mcp
cd power-cad-mcp
powershell -ExecutionPolicy Bypass -File scripts\install_autocad_plugin.ps1
```

.NET 10 SDK가 없으면 winget으로 설치한 뒤 플러그인과 서버를 빌드하고, AutoCAD 번들을
`%APPDATA%\Autodesk\ApplicationPlugins\PowerCad.bundle`에 설치하고, Claude Desktop에 `power-cad`를 등록합니다.
AutoCAD를 재시작한 뒤 명령줄에서 `POWERCAD_STATUS`로 확인하세요.
빌드 없이 쓰려면 [Releases](https://github.com/khs0927/power-cad-mcp/releases)에서 `PowerCad-<버전>-win-x64.zip`을 받아 압축을 풀고, 그 폴더에서 `powershell -ExecutionPolicy Bypass -File scripts\install_autocad_plugin.ps1 -SkipBuild`를 실행합니다.

AutoCAD 없이 먼저 써 보기: `power-cad-server --simulate` (샘플 평면도: 벽, 실명, 동적 문, 창, 잠긴 레이어).

### 개발

```bash
./scripts/build_dotnet.sh          # restore → build(플러그인 포함) → 전체 테스트 → dist/PowerCad.bundle, dist/server/win-x64
dotnet test dotnet/PowerCad.Tests
```

---

## Codex 연결

`OpenAI Docs`의 [MCP 등록 안내](https://learn.chatgpt.com/docs/extend/mcp?surface=cli)에 따라 빌드된 서버를 등록합니다.

```powershell
powershell -ExecutionPolicy Bypass -File scripts\register_codex.ps1
```

개발용 시뮬레이터는 `-Name power-cad-dev -Simulate`로 별도 등록할 수 있습니다.
등록 전에 새 플러그인도 설치해야 실제 도면에서 새 계약을 사용할 수 있습니다.

새 서버에서는 `cad_get_document_identity`로 현재 도면 ID를 읽고 `cad_bind_document`로 고정한 뒤 수정합니다.
`document_id`는 도면 DB의 `FingerprintGuid`에 묶여 있어 같은 세션에서 다시 조회해도 바뀌지 않습니다(`database_fingerprint`로도 확인 가능).
자세한 개발 상태·사용 순서·인수 조건은 [프레임워크 운영 문서](docs/framework/FRAMEWORK_IMPLEMENTATION.md)를 참조하세요.

## 보조: power-cad-mcp (Python)

- **AutoCAD 백엔드 (Windows)** — 실행 중인 AutoCAD에 COM으로 붙어 실시간으로 그립니다.
- **Headless DXF 백엔드 (모든 OS)** — AutoCAD 없이 [ezdxf](https://ezdxf.mozman.at/)로 도면을 만들고 DXF/PNG/PDF/SVG로 저장합니다.

두 백엔드는 같은 42개 도구를 제공합니다.

![demo](docs/floor_plan.png)

### 도구 목록 (Python)

| 분류 | 도구 |
| --- | --- |
| 세션/파일 | `drawing_census`, `cad_status`, `new_drawing`, `open_drawing`, `save_drawing`, `get_drawing_info`, `export_drawing`, `render_preview` |
| 레이어 | `list_layers`, `create_layer`, `update_layer`, `set_current_layer`, `delete_layer` |
| 작도 | `draw_line`, `draw_polyline`, `draw_rectangle`, `draw_polygon`, `draw_circle`, `draw_arc`, `draw_ellipse`, `draw_point`, `add_text`, `add_mtext`, `add_dimension`, `add_hatch`, **`draw_batch`** |
| 블록 | `list_blocks`, `create_block`, `insert_block` |
| 조회/편집 | `list_entities`, `get_entity`, `delete_entities`, `move_entities`, `copy_entities`, `rotate_entities`, `scale_entities`, `mirror_entities`, `offset_entity`, `set_entity_properties` |
| 화면/기타 | `zoom_extents`, `zoom_window`, `run_command` |
| Ontology (읽기 전용) | `ontology_catalog`, `ontology_find_elements`, `ontology_blocks`, `ontology_drawings`, `ontology_element_context`, `ontology_search`, `ontology_ask`, **`ontology_auto_context`**, `ontology_locate`, `ontology_block_candidates`, `ontology_census_check` |

> `save_drawing(path=...)`·`export_drawing`은 **열려 있는 도면 자신이 아닌 기존 파일을 덮어쓰지 않습니다**. 교체하려면 `overwrite=true`를 명시하세요 (자동 승인되는 WRITE 도구가 도면 텍스트의 프롬프트 주입으로 사용자 파일을 덮어쓰는 것을 방지).

규칙:
- 좌표는 `[x, y]` 또는 `[x, y, z]`(도면 단위), 각도는 **도(degree)**, +X 기준 반시계 방향입니다.
- 생성된 모든 객체는 **handle**(16진 문자열)과 함께 반환되며, 이후 편집은 handle로 합니다.
- 색상은 ACI 번호(0–256) 또는 이름(`red`, `yellow`, `green`, `cyan`, `blue`, `magenta`, `white`, `bylayer` …).
- 많은 객체를 그릴 때는 `draw_batch` 한 번으로 처리하면 훨씬 빠릅니다 (AutoCAD 왕복 횟수 감소).
- 상대 경로는 `POWER_CAD_WORKSPACE`(기본: 현재 폴더) 기준으로 해석되며, 확장자가 없으면 자동으로 붙습니다.

### 설치 (Python, Windows + AutoCAD 2027)

사전 조건: Windows 10/11, AutoCAD 2027 실행 중, Python 3.10+ (없으면 스크립트가 `uv`로 설치).

```powershell
git clone https://github.com/khs0927/power-cad-mcp
cd power-cad-mcp
powershell -ExecutionPolicy Bypass -File scripts\setup_windows.ps1
```

스크립트가 하는 일: `.venv` 생성 → 패키지 설치 → AutoCAD 연결 확인(`--check`) →
`%APPDATA%\Claude\claude_desktop_config.json`에 `power-cad-py` 서버 등록(기존 파일은 `.bak`으로 백업).
주력 C# 서버(`power-cad`, `install_autocad_plugin.ps1`이 등록)와 이름이 겹치지 않아 둘을 함께 쓸 수 있습니다. 다른 이름은 `-Name`으로 지정합니다.
끝나면 Claude Desktop을 재시작하세요.

### 수동 등록

Claude Desktop (`%APPDATA%\Claude\claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "power-cad-py": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/khs0927/power-cad-mcp", "power-cad-mcp"],
      "env": { "POWER_CAD_BACKEND": "autocad" }
    }
  }
}
```

Claude Code:

```bash
claude mcp add power-cad-py -e POWER_CAD_BACKEND=autocad -- uvx --from git+https://github.com/khs0927/power-cad-mcp power-cad-mcp
```

### 연결 확인

```powershell
.venv\Scripts\power-cad-mcp.exe --backend autocad --check   # 상태 JSON 출력, 연결 실패 시 exit 1
python scripts\smoke_test_autocad.py                         # 새 도면에 테스트 도형을 그림
```

## 설정 (환경 변수)

| 변수 | 기본값 | 설명 |
| --- | --- | --- |
| `POWER_CAD_BACKEND` | `auto` | `autocad` / `dxf` / `auto`(Windows면 AutoCAD, 아니면 DXF) |
| `POWER_CAD_PROGID` | – | COM ProgID 지정(쉼표 구분). 기본 순서: `AutoCAD.Application`, `.26`(2027), `.25`, `.24` |
| `POWER_CAD_LAUNCH` | `0` | `1`이면 AutoCAD가 꺼져 있을 때 직접 실행 |
| `POWER_CAD_WORKSPACE` | 현재 폴더 | 상대 경로 기준 폴더 |
| `POWER_CAD_DXF_PATH` | – | DXF 백엔드에서 시작 시 열/저장할 파일 |
| `POWER_CAD_ALLOW_COMMANDS` | `1` | `0`이면 `run_command` 비활성화(차단 명령·보호 시스템 변수는 `1`이어도 항상 거부) |
| `POWER_CAD_ALLOW_LISP` | `0` | `1`이면 `run_command`에서 AutoLISP 식 허용(위험 함수는 계속 차단) |
| `POWER_CAD_ONTOLOGY_ROOT` | – | `khs0927/Ontology` 로컬 checkout 경로. 설정 시 C# 주력 서버에서 CAIR context 도구 활성화 |
| `POWER_CAD_ONTOLOGY_COMMAND` | `aec-mcp` | Ontology MCP 실행 명령. ROOT가 설정된 경우 기본값 사용 |
| `POWER_CAD_ONTOLOGY_TIMEOUT` | `20` | **.NET 서버 전용**: `aec-mcp` stdio 호출 제한시간(초, 1–120). Python REST 도구에는 `POWERCAD_ONTOLOGY_TIMEOUT`을 씁니다 |
| `POWER_CAD_BUSY_TIMEOUT` | `20` | **.NET AutoCAD 플러그인**: AutoCAD가 명령·대화상자로 바쁠 때 `CAD_BUSY`로 답하기까지 기다리는 시간(초, 3–300) |
| `POWER_CAD_SION_URL` | – | Sion Ontology Platform의 HTTP base URL. 설정 시 Sion AEC federation을 우선 사용 |
| `POWER_CAD_SION_TIMEOUT` | `20` | Sion AEC HTTP 호출 제한시간(초, 1–120) |
| `POWER_CAD_SION_TOKEN` | – | 원격 Sion 호출용 Bearer token. loopback이 아닌 Sion URL에는 필수 |
| `POWERCAD_ONTOLOGY_URL` | Python: `http://127.0.0.1:58000` · C#: – | `ontology_*` 도구가 읽는 Ontology REST API 주소 (`POWER_CAD_ONTOLOGY_URL`도 인식). C# 서버는 설정하지 않으면 `ontology_*` 도구가 `[ONTOLOGY_NOT_CONFIGURED]`로 답합니다 |
| `POWERCAD_ONTOLOGY_TIMEOUT` | `10` | Python·C# 서버: Ontology REST 호출 제한시간(초, 1–120). .NET `aec-mcp`용 `POWER_CAD_ONTOLOGY_TIMEOUT`과는 별개입니다 |
| `POWERCAD_ONTOLOGY_ASK_TIMEOUT` | `120` | Python·C# 서버: `ontology_ask`(`POST /v1/ask`, 로컬 LLM 답변 생성) 전용 제한시간(초, 5–600). 일반 REST 제한시간보다 짧게 적용하지 않습니다 |
| `POWERCAD_ONTOLOGY_TOKEN` | – | Python·C# 서버가 Ontology REST에 보내는 Bearer token. **Ontology 서버의 `AEC_API_TOKEN`과 같은 값**. Ontology의 `scripts/ops/init-env.ps1 -SetUserEnv`가 생성·설정 ([보안 메모](docs/guides/SECURITY.md)) |
| `POWERCAD_ONTOLOGY_AUTO_CONTEXT` | `0` | Python 서버: `1`이면 `draw_batch`에 `task`가 있을 때 `ontology_auto_context`를 먼저 실행 |
| `POWERCAD_ONTOLOGY_LIVE_URL` | – | 테스트 전용: 설정하면 `tests/test_ontology_live.py`가 이 주소의 실제 Ontology API로 계약 검사를 실행(없으면 skip) |

CLI 옵션: `power-cad-mcp [--backend auto|autocad|dxf] [--workspace DIR] [--dxf-path FILE] [--launch]
[--transport stdio|streamable-http|sse --host 127.0.0.1 --port 8765] [--check] [--version]`

## Sion / Ontology / CAIR 컨텍스트 연결

C#/.NET 10 주력 서버는 두 개의 읽기 전용 의미 컨텍스트 경로를 지원합니다.

- `POWER_CAD_SION_URL`이 설정되어 있으면 **Sion Ontology Platform의 `/api/v1/aec/query`를 우선 사용**합니다.
- Sion URL이 없으면 기존 `POWER_CAD_ONTOLOGY_ROOT` + `aec-mcp` 직접 연결을 사용합니다.

Sion 경로에서는 응답이 반드시 `canonical=false`, `read_only=true`여야 합니다. 이 계약을 만족하지 않으면 Power CAD가 컨텍스트를 거부합니다.

1. `cad_context_query`가 Sion 또는 Ontology에서 CAIR global memory를 검색합니다.
2. CAD 형식의 `geometry_ref`에서 handle 후보만 추출합니다.
3. 후보마다 현재 AutoCAD에 `cad_get`을 호출해 실제 존재와 fingerprint를 검증합니다.
4. 결과는 1, 2, 3… 번호가 붙은 `context_id` candidate space로 반환됩니다.
5. `cad_context_select(context_id, choice)`가 선택 시점에 fingerprint를 **다시 검증**합니다.
6. `cad_context_actions(context_id, candidate_choice)`가 현재 live entity에 허용되는 작업만 번호형 action-space로 반환합니다.
7. `cad_context_action_select(..., action_choice)`가 번호를 실제 `cad_get` / `cad_move` / `cad_replace_text` / `cad_modify_opening` 중 하나로 해석하면서 fingerprint를 다시 검증합니다.
8. 후보 선택과 action 선택 모두 `may_execute_mutation=false`입니다. 실제 수정은 기존 edit tool의 `expect_fingerprint`와 transaction/rollback 경계를 그대로 거쳐야 합니다.

골든 경로는 `Ontology → Sion AEC federation → Power CAD live verification → AutoCAD transaction/rollback`입니다. Ontology/GraphRAG/Sion 결과가 직접 AutoCAD를 수정할 수 없고, 오래된 지식이나 잘못된 매핑은 live CAD 재검증 단계에서 차단됩니다.

## Ontology 건물 데이터 자동 조회 (Python·C# 서버)

Google Drive의 모든 DWG/DXF를 파싱해 둔 Ontology(`aec_intelligence`, Postgres + AGE + pgvector)를 **읽기 전용 REST API**로 조회합니다.
자동화를 시작할 때 문·창·벽·실, 상세도, CAD 블록을 사용자가 일일이 나열하지 않아도 관련 항목을 스스로 모아 옵니다.

설정:

1. Ontology 쪽에서 REST API를 띄웁니다(Ontology `docker compose up api` 기본 `http://127.0.0.1:58000`).
2. 다른 주소라면 `POWERCAD_ONTOLOGY_URL`을 지정합니다. 예: `claude mcp add power-cad -e POWERCAD_ONTOLOGY_URL=http://127.0.0.1:58000 -- power-cad-mcp`
   C# 주력 서버(`power-cad-server`)는 기본 주소가 없으므로 항상 지정합니다. 예: `claude mcp add power-cad -e POWERCAD_ONTOLOGY_URL=http://127.0.0.1:58000 -- power-cad-server`
   (C# 서버의 `ontology_*` 도구는 아래 표와 같은 이름·인자·결과를 돌려주며, 오류 문구 앞에 `[ONTOLOGY_UNAVAILABLE]`/`[ONTOLOGY_FAILED]` 같은 코드가 붙습니다.
   `draw_batch` 연결과 `open_drawing` 표시는 Python 서버에만 있습니다.)
3. 서비스가 꺼져 있으면 각 도구는 `Ontology service is not reachable at …` 오류를 돌려주고, 작도 도구는 영향을 받지 않습니다.

| 도구 | REST | 용도 |
| --- | --- | --- |
| `ontology_catalog` | `GET /v1/catalog` | 종류별 객체 수(한글 별칭 포함), 도면 분류, 레이어, 블록, 층, 프로젝트 |
| `ontology_find_elements` | `GET /v1/elements?kind=` | `Door`/`Window`/`Wall`/`Space`/`Column`/`Beam`… 또는 `문`/`창호`/`벽체`; `drawing_category`·`layer`·`block_name`(와일드카드)·`text`·`bbox` 필터, `next_cursor` 페이지. 층·시트는 클라이언트에서 거릅니다 (`2F`=`2층`). Python 서버는 층을 `storey=2층` 파라미터로도 보내 두어, 서버 측 층 필터를 지원하는 API에서는 그대로 쓰입니다(현재 API는 무시) |
| `ontology_blocks` | `GET /v1/blocks?name_like=` | 블록 정의, 사용 횟수, 속성 태그, 레이어, 예시 파일. `category`(Door, 창호…)는 인스턴스 분류로 거릅니다 |
| `ontology_drawings` | `GET /v1/drawings?category=` | `plan`/`detail`/`section`/`elevation`/`structural`/`schedule` 또는 `평면도`/`상세도`/`창호도`…; 시트별 도면번호·도면명·축척 |
| `ontology_element_context` | `GET /v1/elements/{id}/context?hops=` | 객체와 1–2 hop 그래프 이웃(층·실·호스트 벽·시트·블록 정의) |
| `ontology_search` | `POST /v1/search` | 어휘 + 벡터 + 그래프 하이브리드 검색 (`top_k`, `kind`, `storey`) |
| `ontology_ask` (Python·C#) | `POST /v1/ask` | Graph RAG 질의응답: 서버가 질문을 키워드/벡터/지식그래프 탐색/프로젝트 요약으로 라우팅하고, 로컬 LLM(Ollama)이 그 근거만으로 한국어 답을 만듭니다. 모든 답은 `[C#]` 인용(문서·레이아웃/시트·객체 id·bbox)을 달고, 근거가 없으면 `refused: true`. `question`, `project_id?`, `top_k=12`(1–30), `generate=true`를 두 서버에서 동일하게 사용합니다. `generate=false`면 LLM 없이 검색+발췌 답만. 인용된 `object_ids`는 `ontology_locate`로 열린 도면에서 찾습니다. 제한시간 `POWERCAD_ONTOLOGY_ASK_TIMEOUT`(초, 5–600, 기본 120) |
| `ontology_auto_context` | 위 조합 | 작업 문장 하나로 필요한 자료 묶음을 한 번에 반환 (`project_id`, 검색 결과 수 `k` 지정 가능) |
| `ontology_locate` | `GET /v1/elements/{id}/context` + 열린 도면 | Ontology 객체 id → 현재 열린 도면의 실제 handle 확인(읽기 전용) |
| `ontology_block_candidates` (Python·C#) | `GET /v1/blocks` + `list_blocks` | Ontology 블록 중 열린 도면에 정의가 있어 `insert_block`에 바로 쓸 수 있는 것 / 다른 파일에만 있는 것 구분(읽기 전용) |
| `ontology_census_check` (Python) | `GET /v1/drawings` + `GET /v1/elements?document_id=` + `drawing_census` | 도면 파일의 census와 Ontology가 가진 같은 파일의 행 대조: 수집 여부, 핸들 차이, 레이어별 객체 수, 클래스별 수(ZIUM 레이어 포함). 맞대어 볼 수 없는 것은 `not_comparable`(읽기 전용, 파일을 쓰지 않음) |

`ontology_auto_context(task, drawing?, project_id?, k?)`는 작업 문장(한국어/영어)에서 키워드로 대상을 추론합니다.
`project_id`를 주면 객체·도면·블록·검색 호출 모두 그 프로젝트로 한정합니다.

- 문/도어/door → `Door`, 창/창문/window → `Window`, 창호 → `Window`+`Door`, 벽/벽체/wall → `Wall`, 실/방/room → `Space`
- 기둥 → `Column`, 보 → `Beam`, 철골/H-형강 → `SteelSection`+구조도, 계단·슬래브
- 평면/상세/단면/입면/구조/리스트·일람표 → 도면 분류, 블록/심볼 → 블록 목록
- `2층`/`2F`/`지하1층`/`B1F`/`옥상` → 층, `AW-02` 같은 부호 → 검색어

예: `"2층 평면도 문 리스트 갱신"` → 2F의 Door 객체, 평면도·일람표 시트, Door 블록, 검색 결과를 한 번에 받습니다.
층/시트/부호 필터로 결과가 없으면 해당 클래스 전체로 넓히고 `warnings`에 이유를 남깁니다.
검색은 추론한 클래스(하나일 때)와 층을 `kind`/`storey` 필터로 함께 보냅니다. API는 층을 정확히 일치로 비교하고 층 정보가 없는 도면도 많으므로,
층 필터로 결과가 없으면 층 없이 다시 찾습니다. API가 질의 전체를 한 구절로 비교하기 때문에 작업 문장으로 결과가 없으면
부호·도면·클래스 단어로 다시 검색하는데, 클래스 단어는 그 클래스를 `kind`로 묶어 보내므로 `창`이 `창고`, `실`이 `화장실`에 걸리지 않습니다.
이때 `warnings`에는 실제로 보낸 키워드만 남깁니다.
도면이 열려 있으면 묶음의 검색 결과·객체마다 `in_open_drawing`(원본 파일이 지금 열린 도면인지)이 붙고, `open_drawing`과
`counts.in_open_drawing`이 함께 옵니다. 열린 도면을 알 수 없으면(예: AutoCAD 미실행) 이 표시는 조용히 생략됩니다.

### Ontology 객체 → 도면 handle (`ontology_locate`)

Ontology 객체에는 `handle`, `document_id`, `source_file`이 있지만 **handle은 도면 하나 안에서만 고유**합니다.
다른 도면의 handle을 그대로 `move_entities`에 넘기면 엉뚱한 객체가 바뀔 수 있으므로, 편집 전에 `ontology_locate(element_ids)`로 확인합니다.

- 객체마다 `/v1/elements/{id}/context`로 행을 받아 `source_file`과 열린 도면(`get_drawing_info`의 name/path)을 비교합니다.
  양쪽 모두 폴더가 있으면 **전체 경로**(구분자 `\`=`/`, 겹친 `/` 정리, 대소문자 무시, `.dwg`/`.dxf`/`.dwt`/`.dws` 확장자 무시)를 비교하고,
  한쪽이 파일 이름뿐이면 파일 이름만 비교합니다(`A-201.dwg` = `a-201.DXF`). 이름이 같아도 열린 도면이 **저장되지 않았거나**(경로 없음)
  기본 이름(`Drawing1` 등)이면 같은 파일이라고 증명할 수 없으므로 `other_drawing`("open drawing is unsaved/default-named; cannot prove it is the source file")입니다.
- 같은 도면이면 **모델 공간에서만** handle을 찾고(레이어 레코드·블록 정의 안·종이 공간 객체는 "없음"), 엔티티 종류·레이어·블록 이름이 객체 클래스에 맞는지 봅니다
  (예: 블록 `DOOR_SINGLE`인 문인데 LINE이면 불일치, 문인데 DIMENSION/TEXT면 불일치, 실(Space)은 TEXT 허용).
  `matched`에는 긍정 근거가 하나 이상 필요합니다: 알려진 클래스(문·창·벽·기둥·실·주석 등), 같은 레이어, 또는 같은 블록 이름(익명 `*U…` 참조 포함).
- 결과 `status`: `matched`(편집 가능) · `unverified`(반대 근거도 긍정 근거도 없음 — 편집에 쓰지 않음) · `mismatch`(handle은 있으나 대상이 달라 보임, `reasons`) · `handle_missing`(같은 도면 모델 공간에 없음, 다른 배치 탭 등)
  · `other_drawing`(다른 파일, 열린 도면 없음, 미저장/기본 이름 — handle을 조회조차 하지 않음) · `not_found`(Ontology에 없는 id, 또는 API가 다른 객체를 돌려줌 — `error: "API returned element …"`). `.`/`..` 같은 id는 오류로 거부합니다.
- 각 결과에 `element_id`, `handle`, `source_file`, `open_drawing`, `entity`(종류·레이어·블록 이름·위치 요약)가 있고, `matched_handles`에 편집 가능한 handle만 모읍니다.
- 도면을 절대 수정하지 않습니다.

권장 흐름:

1. `ontology_auto_context(task)` — 대상 후보를 모으고 `in_open_drawing`으로 지금 도면의 것인지 확인
2. 필요하면 해당 도면을 `open_drawing`으로 연 뒤 `ontology_locate(element_ids)` — `status="matched"`인 handle만 고름
3. `get_entity` / `move_entities` / `delete_entities` / `set_entity_properties` 등 편집 도구에 그 handle을 넘김

자동화 진입점 연결: `draw_batch(operations, task="…", auto_context=true)`로 호출하거나 `POWERCAD_ONTOLOGY_AUTO_CONTEXT=1`을 켜면
작도 전에 `ontology_auto_context(task)`가 실행되어 결과의 `ontology_context`에 붙습니다. 서비스가 꺼져 있어도 작도는 그대로 진행되고
`ontology_context.available=false`와 오류 문구만 남습니다. 조회가 성공하면 `ontology_targets`도 붙습니다: 묶음 안의 객체를 추가 Ontology 호출 없이
`ontology_locate`와 같은 규칙으로 열린 도면에 대조해, `matched`인 객체의 `element_id`·`class`·`name`·`handle`·엔티티 종류와 상태별 개수(`counts`)를 요약합니다.
작도 작업 자체는 바뀌지 않습니다. Ontology 결과는 참고 정보이므로, 실제 수정에는 `matched` handle만 쓰세요.

### Ontology 블록 → `insert_block` (`ontology_block_candidates`)

`insert_block`은 열린 도면에 정의된 블록 이름만 받습니다. `ontology_block_candidates(name_or_task, project_id?, limit?)`는
Ontology 블록 카탈로그(`/v1/blocks`)와 열린 도면의 블록 정의(`list_blocks`)를 대조합니다. 도면을 수정하지 않고, 다른 파일에서 정의를 가져오지도 않습니다.

- `name_or_task`: 블록 이름/와일드카드(`DOOR*` → `name_like`) 또는 작업 문장(`"문 블록 배치"` → 인스턴스가 `Door`로 분류된 블록).
  공백 없는 단어(`문`, `door`)는 이름과 분류 두 가지로 찾습니다.
- 이름 비교는 대소문자를 무시하고 블록의 `effective_names`도 봅니다(동적 블록은 `*U12` 같은 익명 이름으로 저장되기 때문).
- `insertable`: 열린 도면에 정의가 있음 → `insert_name`을 그대로 `insert_block(name=…)`에 넘깁니다(`base_point`, `entity_count` 포함).
  이름 질의에 맞지만 Ontology에 없는 도면 내 블록도 `note`와 함께 여기에 나옵니다.
- `other_files`: 다른 도면에만 있음 → `example_files`에 있는 파일을 열어 작업하거나, 사용자가 블록을 직접 가져와야 합니다.
  Ontology가 이 도면에서 읽었다고 하는데 지금 정의가 없으면(purge/이름 변경) `note`로 알려 줍니다.
- `not_insertable`: XREF(Ontology 쪽이든 열린 도면의 같은 이름 정의든), effective name 없는 익명 블록. 블록 테이블을 읽지 못하면 `unverified`에 두고 `warnings`에 이유를 남깁니다.

### 도면 인벤토리 ↔ Ontology (`ontology_census_check`, Python 서버)

`drawing_census`(로컬, 파일의 모든 객체)와 Ontology(수집된 건물 데이터)는 따로 만든 인벤토리입니다.
`ontology_census_check(path, census_json?, project_id?, standard?)`는 같은 파일을 두고 둘을 대조합니다. 파일도 도면도 바꾸지 않습니다.

- 문서 찾기: `/v1/drawings`의 `name`/`source_key`를 파일명으로 비교(대소문자 무시, `.dwg` = `.dxf`). 여러 개면 `project_id` → 높은 리비전 → 최근 수집 순으로 고르고 나머지는 `other_documents`.
- `handles`: handle이 있는 Ontology 요소를 census의 `entity_index`에서 찾습니다. `only_in_ontology`는 수집 후 파일이 바뀐 것(또는 다른 리비전),
  `only_in_census_*`는 Ontology가 요소로 만들지 않은 레이아웃 객체(치수·일반 선 등 — 결함 아님). 블록 정의 내부와 ATTRIB은 제외합니다.
- `layers`: Ontology `Layer` 행의 `entity_count`/`block_entity_count`와 census의 레이아웃/블록 정의 객체 수 → `equal`/`differs`/`not_in_ontology`.
- `classes`: 클래스별 Ontology 요소 수 옆에 같은 레이어의 census 객체 수와 ZIUM 표준 레이어(문 `DOOR`, 창 `WIN`·`WINBAR`, 벽 `WAL1`~`3`, 계단 `STAIR`, 실 `실명`)의 객체 수.
  객체 수 ≠ 요소 수이므로 일치 판정은 하지 않습니다. 정확한 대조는 `handles`입니다.
- `not_comparable`: handle 없는 Ontology 행(문서·레이어·뷰), 블록 내부, 기둥·구조벽이 섞인 ZIUM `COL`, 표준 없이 만든 census 등 — 이유와 함께.
- 파일이 수집 시각 이후 수정됐거나 `census_json`이 다른 파일의 것이면 `warnings`에 남깁니다. `entity_index`가 없는 예전 census.json은 `drawing_census`를 다시 돌리세요.

### COM 편집 플랜에서 Ontology id 쓰기 (`scripts/com_plan_runner.py`)

`com_plan_runner.py`의 플랜(`docs/playbooks/*.plan.json`)은 대상을 handle + 예상 형상으로 고정합니다. op마다 `elements`를 두면
handle 대신(또는 함께) Ontology 객체 id로 대상을 지정할 수 있습니다.

```json
{"id": "D1", "op": "delete", "elements": {"@door": "obs_18a…", "AA4": "obs_33b…"},
 "expect": {"@door": "AcDbBlockReference", "AA4": "AcDbPolyline"}}
```

- `@`로 시작하는 키는 자리표시자로, 그 op의 `expect` 키·`handles`·`handle`에서 확인된 handle로 바뀝니다.
- 그 밖의 키는 플랜에 이미 적힌 handle이며, 객체가 정확히 그 handle로 확인되어야 합니다.
- 검증 전에 Ontology REST API(`--ontology-url`, 없으면 `POWERCAD_ONTOLOGY_URL`, 기본 `http://127.0.0.1:58000`)에서 객체를 받아
  `ontology_locate`와 같은 규칙(원본 파일 = 열린 도면, handle 존재, 엔티티 타당성)으로 대조합니다. 하나라도 `matched`가 아니거나
  쓰이지 않는 키·같은 handle로 겹치는 대상이 있으면 `{"ok": false, "stage": "resolve"}`로 플랜 전체를 거부합니다(`unverified`도 거부, 해석 중 예외도 traceback 없이 `stage:"resolve"`).
- `elements`가 없어도 한 op 안에서 같은 handle을 두 번 적으면(`aa4`와 `AA4`처럼 대소문자만 달라도) 검증 단계에서 거부합니다.
- 그 뒤 기존 예상 형상 검사가 확인된 handle에 그대로 실행되고, 결과에 `resolved`(키 → id → handle)가 붙습니다.
  `elements`가 없는 플랜은 Ontology에 접속하지 않습니다.

## 보안

`run_command`는 AutoCAD 명령줄에 그대로 입력을 보냅니다. 도면 속 텍스트가 프롬프트에 섞여 들어올 수 있으므로
AutoCAD 밖으로 나갈 수 있는 명령(`SHELL`, `START`, `SCRIPT`, `APPLOAD`, `NETLOAD`, `VBARUN`, `QUIT` 등)은 항상 차단되고,
보안 관련 시스템 변수(`SECURELOAD`, `TRUSTEDPATHS`, `MTEXTED` 등 `PROTECTED_SYSVARS`)는 어떤 형태로도 바꿀 수 없고,
AutoLISP는 기본적으로 꺼져 있습니다. 필요 없다면 `POWER_CAD_ALLOW_COMMANDS=0`으로 완전히 끌 수 있습니다.

`POWER_CAD_ALLOW_COMMANDS` 기본값을 `1`로 유지하는 이유: `run_command`는 MCP `destructive` 주석이 붙어 있어 클라이언트가
실행 전 사용자 승인을 받고, OS·코드 로딩으로 나가는 경로(차단 명령, 보호 시스템 변수, LISP)는 기본값에서도 막혀 있습니다.
남는 위험은 열린 도면 안의 편집(되돌리기 가능)뿐인데, 끄면 FILLET/OFFSET 옵션처럼 전용 도구가 없는 작업을 전혀 할 수 없습니다.
도면을 무인으로 일괄 처리하거나 신뢰할 수 없는 도면 텍스트를 다룰 때는 `0`으로 두세요.

## 구조

```
src/power_cad_mcp/
  server.py            MCP 도구 정의 (MCPServer, mcp SDK 2.x)
  backends/base.py     백엔드 인터페이스 (handle 기반, 각도=도)
  backends/com_backend.py   AutoCAD COM: 전용 STA 스레드, busy 재시도(IMessageFilter), 오류 변환
  backends/dxf_backend.py   ezdxf headless 백엔드 + 렌더링
  safety.py            run_command 필터
  ontology.py          Ontology REST 클라이언트(urllib), 작업 문장 키워드 추론, auto_context 묶음, 객체→도면 handle 대조(locate)
  census.py            drawing_census: 파일의 모든 객체 인벤토리(census.json)
  census_check.py      census ↔ Ontology 대조(ontology_census_check)
  source_binding_acceptance.py  Ontology 핸드오프 → 실행 전 준비 상태 검증(실행 권한은 부여하지 않음)
  config.py · errors.py · geometry.py · colors.py   설정, 오류 변환, 기하 계산, 색 이름
tests/
  fake_acad.py         AutoCAD COM 객체 모델 모사 → COM 백엔드를 Linux CI에서도 검증
  test_live_autocad.py 실제 AutoCAD 대상 테스트 (옵트인)
```

COM 객체는 스레드(아파트먼트)에 묶여 있고 MCP 런타임은 동기 도구를 임의의 워커 스레드에서 실행하므로,
모든 AutoCAD 호출은 하나의 전용 STA 스레드로 모아서 실행합니다. AutoCAD가 바쁠 때(`RPC_E_CALL_REJECTED`)는
COM 메시지 필터가 자동으로 재시도하며, 도형을 만드는 호출은 중복 생성을 막기 위해 통째로 재실행하지 않습니다.

## 개발

```bash
uv venv && uv pip install -e ".[dev]"
pytest                     # DXF end-to-end, 가짜 AutoCAD COM, stdio 프로세스, Ontology 계약, 유닛 (실기 AutoCAD·실제 Ontology API 테스트는 옵트인)
ruff check . && ruff format --check .
python -m build            # dist/*.whl, dist/*.tar.gz
python examples/demo_floor_plan.py            # headless 데모 → examples/out/
python examples/demo_floor_plan.py --backend autocad   # 실행 중인 AutoCAD에 그리기
```

실제 AutoCAD 테스트 (Windows):

```powershell
$env:POWER_CAD_LIVE_TESTS = "1"; pytest -m autocad -v
```

실제 Ontology API와의 계약 테스트 (도면이 하나 이상 적재된 API가 필요, 읽기 전용):

```bash
POWERCAD_ONTOLOGY_LIVE_URL=http://127.0.0.1:58000 pytest tests/test_ontology_live.py -v
```

## 문제 해결

- **`Could not attach to a running AutoCAD`** — AutoCAD가 실행 중이고 도면 하나가 열려 있는지 확인하세요.
  AutoCAD와 MCP 서버는 **같은 사용자, 같은 권한 수준**이어야 합니다(한쪽만 "관리자 권한으로 실행"이면 COM 연결 실패).
- **`AutoCAD stayed busy`** — 명령 실행 중이거나 대화상자가 열려 있습니다. AutoCAD에서 `Esc`를 누르고 다시 시도하세요.
- **PDF 내보내기** — `DWG To PDF.pc3` 플로터를 사용해 도면 범위를 용지에 맞춰 출력합니다.
- **DXF로 내보내기(AutoCAD)** — AutoCAD의 SaveAs를 사용하므로, 원래 DWG가 있다면 다시 원래 파일로 저장해 활성 문서를 되돌립니다.

## License

MIT

TDQS

B3.1/5.0

Scored across 41 tools

Disambiguation4/5

Each tool targets a distinct action+resource: drawing primitives, entity edits (move/copy/rotate/scale/mirror), and layer operations are cleanly separated. Minor overlap between create_layer (which also updates) and update_layer, and draw_batch vs individual draw_* tools, but boundaries remain readable.

Naming Consistency4/5

Overwhelmingly consistent verb_noun pattern (draw_line, list_entities, create_layer, zoom_extents). Only minor deviations like cad_status (noun-first) and get_drawing_info break the otherwise predictable scheme.

Tool Count3/5

41 tools is heavy for a single server, though CAD genuinely spans drawing, layers, blocks, entity editing, views and export so most tools earn their place. Still, the surface could be trimmed or further consolidated (e.g. more batch variants).

Completeness4/5

Strong lifecycle coverage: drawing setup, primitives, layer management, blocks, entity CRUD and transforms, viewing and export. Gaps like trim/extend/fillet and undo are notable but not blocking for core workflows.

Maintenance

ActivityMaintained
ResponsivenessNo issues