CAKE MCP
by luckyman9487
README.md
# CAKE MCP
**CAKE = CAD Automation Knowledge Engine.**
把工程圖說讀成結構化資料的 MCP server,給 Claude 用來「看圖」。
`.dxf` 與 `.dwg` 都能直接給路徑(DWG 會借本機 CAD 引擎轉檔快取),
**沒有 CAD 檔、只有 `.pdf` 圖說時也讀得到**(`read_pdf`/`render_pdf`)。
工具只給客觀的幾何與文字;領域判讀(鋼筋、梁配筋…)建在它上面、分開呈現。
## 能拿它做什麼
不是把 DXF 轉成另一種檔案格式,是讓模型能對著圖回答問題、把圖上的東西整理成
你要的形式。你用自然語言問,模型自己決定調哪個工具。
**查圖上的某個資訊。**「B5-10 這根梁在哪、斷面多少?」「3F 平面圖在這個檔的
哪個位置?」——`find_text` 定出座標,`render_view` 只畫那一小塊回來看上下文。
先定位再截圖是主要用法,比整張畫快一個量級。
**做文書作業。** 門窗表、梁配筋登錄表、數量統計這種原本要人對著圖一格一格抄的
東西:
| 資料在哪 | 用什麼撈 |
|---|---|
| 圖框標題欄、門窗編號(藏在 INSERT 屬性裡,搜文字撈不到) | `get_block_attributes` |
| 圖上貼的 Excel、AutoCAD 原生表格、純線條畫的表 | `read_ole_tables`/`read_acad_tables` |
| 尺寸、跨長 | `get_dimensions` |
| 編號、標籤、任何文字 | `find_text` |
資料量大時(上千筆要分群、配對、交叉比對)直接 `import tools` 寫腳本一次輸出
xlsx,比串一堆工具呼叫快也準——README 後面有寫什麼時候該這樣做。
**摸清一個沒看過的檔。** 一個 modelspace 常平鋪幾十張圖。
`get_document_info(what="frames")` 列出所有圖框連同圖名圖號,等於一份圖紙目錄;
`what="layers"` 給每層的物件數、型別分布與文字樣本——圖層名多是 `A_1`、`G-1`
這種無意義代號,靠樣本內容認用途。
**交付。** `render_view` 出 PNG、`export_pdf` 出 PDF,都能只框指定範圍或圖層。
**只有 PDF、沒有 CAD 檔時。** 工程圖說常常只以 PDF 流通——實測某案的鋼筋施工圖
資料夾 180 份 PDF 對 58 份 dwg,其中筏基、地下層四個資料夾一個 dwg 都沒有。
`read_pdf(what="pages")` 先回逐頁地圖(哪些頁有文字層、哪些只是掃描影像),
有文字層的頁 `what="text"` 抽出來**無損、與解析度無關**;掃描頁用 `render_pdf`
以原生解析度裁一塊來看(整頁縮圖必糊,裁一小塊則一字不差)。
拿得到 `.dwg`/`.dxf` 時仍請優先走 CAD 那條路——PDF 沒有圖層、沒有 DIMENSION。
**它不替你判讀。** 哪個圖層是料單、哪個顏色代表修訂、加鐵標註怎麼算——每張圖、
每家事務所都不一樣。工具只給客觀的幾何與文字,語意一律從這張圖自己的內容推,
不把別張圖的慣例套過來。判讀邏輯建在它上面,跟原始資料分開呈現。
## 安裝
**最省事的方式:把這個 repo 的網址丟給 Claude Code 或 Codex,叫它照 README
裝好並掛上 MCP。** 它會自己 clone、建虛擬環境、裝相依、算出絕對路徑寫進設定
檔。下面「接上 client」那關最容易出錯的就是路徑,交給它比人手打穩。
```
https://github.com/luckyman9487/CAKE
照這個 repo 的 README 把它裝起來,然後掛進我的 MCP 設定
```
自己動手也可以:
```powershell
git clone https://github.com/luckyman9487/CAKE.git
cd CAKE
python -m venv .venv
.venv\Scripts\pip install -r requirements.txt
```
沒有打包成套件,也沒發 PyPI——就是 clone 下來直接跑。
| 需求 | |
|---|---|
| Python | 3.13 |
| `.dxf` | 任何作業系統 |
| `.dwg` 直傳 | **只有 Windows 原生 + 本機裝了 AutoCAD/相容 CAD 才能用**。偵測不到引擎時會回一則說明該怎麼辦的錯誤,不是當掉 |
| WSL | 跑得起來,但只剩 DXF——DWG 轉檔要呼叫本機 CAD 引擎(`tools/_core/_dwg.py`) |
## 接上 client
先自己跑一次確認裝好了:
```bash
.venv/Scripts/python services/server.py # stdio 模式,不會有輸出,Ctrl-C 結束
npx @modelcontextprotocol/inspector .venv/Scripts/python services/server.py
```
底下三種寫法**路徑一律用絕對路徑**——server 靠 `__file__` 推專案根,但 client
啟動它時的工作目錄不保證在專案根。
掛進 Claude Code:
```bash
claude mcp add cake -- <.venv 的 python 絕對路徑> <server.py 絕對路徑>
```
掛進 Claude Desktop(`claude_desktop_config.json`):
```json
{
"mcpServers": {
"cake": {
"command": "C:\\...\\CAKE\\.venv\\Scripts\\python.exe",
"args": ["C:\\...\\CAKE\\services\\server.py"]
}
}
}
```
掛進 Codex(`~/.codex/config.toml`):
```toml
[mcp_servers.cake]
command = "<.venv 的 python 絕對路徑>"
args = ["<server.py 絕對路徑>"]
startup_timeout_sec = 30 # 預設 10s,冷開機時不夠
tool_timeout_sec = 600 # 預設約 60s,render/大圖一定會超過
```
`tool_timeout_sec` 非調不可:整張 `render_view`/`export_pdf` 約 2 分鐘,
大圖載入也要 3~68 秒。(實際用的時候別畫整張:先 `find_text` 定位,再帶
`region=`/`layers=` 只畫那一小塊,畫前就濾掉範圍外實體,快一個量級。)
重開 client 就會看到 14 個工具。**不用另外裝 skill 或抄 SOP**——
`services/workflow.md`(四步讀圖流程 + 兩條鐵律)走 MCP 協議的 server
instructions,初始化握手時自動送給每一個連上的 client,接上即得。
## 結構
```
services/server.py MCP 協議層——14 個 @mcp.tool(),只轉接,不寫解析邏輯
services/workflow.md 讀圖流程(四步 + 兩鐵律),server 當 instructions 送出
tools/ 業務邏輯層(純 Python,不知道 MCP 存在)
_core/ 共用底層:載入/快取(_io)、DWG 轉檔(_dwg)、
實體→JSON(_describe)、bbox/near(_geom)、
便宜外接框(_bbox,空間過濾的先篩)、
分群與相對座標(_cluster)、版面推斷(_layout)、
inline 回傳量控(_payload)、取值例外(_errors)
document/ 文件總覽:版本、圖層、配置、範圍、系統變數、外參、圖框
entity/ 幾何物件查詢:線、圓、弧、聚合線…
semantic/ 帶語意的內容:文字、引線註解、標註量測值
block/ 區塊定義、內容與插入屬性(圖框標題欄在這)
embedded/ 內嵌物:OLE Excel 表格、ACAD 原生表格、純線條畫的表
render/ PNG / PDF 輸出(唯一需要 matplotlib 的分類)
pdf/ 讀 PDF 圖說:逐頁地圖、文字+座標、原生解析度裁圖
(只用 pypdf + Pillow,不需要任何外部光柵器)
scripts/ 輔助腳本(AGENTS.md 由 _WORKFLOW 產生,不手抄)
test/ 測試套件與測試圖(未進版控,見「測試」)
docs/regulations/ 法規參考 PDF(未進版控)
```
相依方向固定由上往下:各分類 → `_core`。分類之間只有一條例外——
`document` 轉接 `embedded.tables.list_xrefs`(原因見 `tools/embedded/__init__.py`)。
對外 14 個工具的清單在 `tools/__init__.py` 的 `__all__`;
被合併掉的 9 個舊函式仍保留在原分類內(`_SUPERSEDED`),供回退與測試比對。
**同一件事只能有一份實作。** 從實體身上取值的邏輯集中在 `_core/_describe.py`
的共用取值器(`text_anchor` / `dimension_points` / `mleader_text`),專用工具與
`describe_entity` 都呼叫它,不要各自再寫一份——否則會出現「同一個實體、換個
工具問就換個答案」,而 golden 抓不到(各自都跟自己的基準一致)。
**取值失敗收 `ATTR_ERRORS`,不要收 `Exception`。** DXF 的欄位是選用的,取不到很
正常,所以到處都是「try 取值、失敗退回預設」。這種地方一律收
`tools/_core/_errors.py` 的 `ATTR_ERRORS`——收 `Exception` 會連 `NameError`
(名字打錯)、`ImportError`、`MemoryError` 一起吞掉,症狀是**欄位安靜消失、回傳
看起來完全正常**,正是這個專案最怕的錯法,而且 golden 抓不到(基準是跟著錯的
一起錄的)。真的需要放寬的只有第三方/外部程序的邊界(CAD 引擎、pypdf、
matplotlib),名單與理由寫在 `pyproject.toml` 的 `per-file-ignores`,
`pip install ruff && ruff check .` 會擋下新的漏網。
`render/` 的 matplotlib 是延後到第一次渲染才 import 的(`_load_backend()`),
不要改回模組層 import:它佔 server 啟動時間的四分之三,而 14 個工具只有 2 個
用得到,冷開機會撞上某些 client 的 10s 啟動逾時。
(`pdf/` 沒有這個問題:pypdf 與 Pillow 都是輕量 import。)
## 使用指引放哪裡
**放在工具的 docstring,不要另外寫 SOP 文件。**
docstring 是掛上 MCP 後唯一會自動進入模型 context 的說明;另寫的 skill/
指南要靠被觸發,而且是工具行為的副本——工具一改就默默過期,golden 測試也
守不到 markdown。實際踩過:一份剛寫好的 SOP 在一小時內就有兩處變成假的
(「find_text 不支援 layer/bbox」、「世界座標 = 插入點 + 局部座標」,後者
沒算 scale,實測差 7996 個單位)。
反過來說,**領域判讀知識**(梁怎麼讀、斷筋點怎麼算、加鐵標註的意思)該寫成
skill——那不是工具行為的副本,工具再改也不會讓它過期。
**docstring 的正本在 `tools/`,`server.py` 只用 `@_doc_from(dxf.x)` 借過去**,
不要在 wrapper 手寫第二份——兩份會各改各的漂掉。`@_doc_from` **必須放在
`@mcp.tool()` 下面**(先執行),FastMCP 在裝飾當下就抓 `__doc__` 當工具描述。
跨工具的**讀圖流程**放 `services/workflow.md`(server.py 讀進來當 MCP 握手的
server instructions),Claude Code 與 Codex 都收得到,專案裡不必再放一份
AGENTS.md。哪天遇到不吃 instructions 的 client,用 `scripts/gen_agents_md.py`
產一份,**不要手抄**(`--check` 可驗有沒有漂)。
它是散文不是程式碼,所以獨立成一個 `.md`——夾在 `server.py` 中間會佔掉那個檔案
三分之一,改一句流程就得動 Python 檔。
## 什麼時候該繞過 MCP 直接寫腳本
MCP 工具是「一問一答」用的。這些情況直接用 `.venv/Scripts/python` import
`tools`,比串一堆工具呼叫快也準:
- 對上萬筆資料做統計、分群、交叉比對(回傳量與 context 都吃不消)
- 要**兩兩配對**的邏輯:文字按 x 連續性切群、跟最近的標註配對、算相對位移
- 一次輸出整張表(存 xlsx,別用 markdown 直出)
```python
import sys
sys.path.insert(0, r"<專案根>")
import tools as R # 與 MCP 同一份程式碼、同一份快取
hits = R.find_text(DXF, layer="NBAR", include_blocks=True, output="file")
```
Windows 上用 Bash 跑要加 `PYTHONIOENCODING=utf-8`,否則中文印成亂碼——
那是主控台顯示問題,不是資料壞掉,別誤判。
## 測試
**測試套件不在這個 repo 裡。** 它跑在真實的客戶施工圖上(三張 .dxf 共 53MB),
斷言與基準檔寫著那些圖上的實際值——梁編號、斷面尺寸、鋼筋標註。那是客戶的
東西,不該因為「拿來當測試資料」就變成公開內容,所以整個 `test/` 留在本機。
實際跑的是四道底線,改任何讀取邏輯後都要綠:
- **golden `verify`** — 縱向:既有行為沒被改壞
- **golden `equiv`** — 縱向:合併後的新工具與被取代的舊工具輸出等價
- **`test_consistency`** — 橫向:同一個實體從不同工具問到的值一致
- **`test_boxes`** — 空間過濾的先篩(`_core/_bbox`)必須是**真超集**:
對三張圖逐一實體驗證 `outer_box(e) ⊇ extents(e, fast=True)`,並比對
「先篩後的命中數 = 逐一算 extents 的命中數」。這條一破,`bbox=`/`near=`
的結果會**悄悄變少**(該命中的實體被判在框外),而回傳看起來完全正常
**後兩者守的不是同一件事。** golden 守「同一個工具前後輸出一致」,consistency
守「同一個實體換個工具問還是同一個答案」。後者是踩過坑才加的——`find_text`
修好文字錨點但 `describe_entity` 沒跟上,同一個字兩個工具回不同座標,golden
完全抓不到,因為各自都跟自己的基準一致。
**重錄基準不是拿來讓紅燈變綠的。** verify 紅了先逐欄位查差在哪、確認每一項都
是故意改的,才重錄;否則只是把 bug 蓋章成新標準。
要自己建一套:拿任意幾張 DXF 放本機、對 `tools/` 的公開函式錄一份輸出當基準,
之後比對。核心不變量就是上面那三條,跟用哪張圖無關。
原始碼註解裡到處是 `test_S1`/`test_S2`/`test_S3` 加一個數字(「實測 test_S2
全部 5706 個 TEXT 都差半個字高」、「test_S1 有 1422 個小 block 插入會被擋下」)
——那就是這三張圖的代號。檔案不在 repo 裡,但那些數字是每個判斷與門檻值的來由,
留著比拿掉有用:至少你知道那個 20000 的上限不是隨手寫的。
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues