← 返回作品集

infra-mcp

給 AI Agent 的基礎設施資源層——分配機器、port 和名字,讓 AI 不只會寫程式,還能真的把東西部署出去。

Python MCP Cloudflare SQLite systemd 42 tools Open Source

核心問題

AI coding agent 現在寫程式跟測試都做得不錯。但要部署的時候會撞牆:哪個 port 是空的?hostname 該叫什麼?tunnel 怎麼註冊?這些問題的答案不在程式碼裡,在基礎設施的狀態裡——而 AI 看不到那個狀態。

常見的作法是讓 AI 直接 ssh 進去 netstat 一下、翻 Caddy config、猜一個沒人用的 port。這能動,但每次都是重新推理一遍,而且沒有任何東西記得它做過什麼。下次要收回資源時,沒人知道 8043 是誰的。

infra-mcp 是一台 MCP server,把這些基礎設施原語變成有狀態、可查詢、可回收的工具:port 分配、DNS 管理、Cloudflare Tunnel 註冊、服務部署、Gitea repo 管理。可以想成 AI agent 跟基礎設施之間的資源排程層

AI Agent(Claude Code / Cursor / 任何 MCP client)
│ MCP tools
infra-mcp server ── SQLite registry(誰佔了什麼)
┌────┴──────────┬──────────────┬──────────────┐
▼ ▼ ▼ ▼
Port 分配 Cloudflare VPS 部署 Gitea
allocate DNS / Tunnel systemd/Caddy repo 管理
release Access health check
│ │ │ │
└───────────────┴──────────────┴──────────────┘
全部進 registry,可稽核、可回收

42 個工具

分類工具
Portsallocate_port release_port reconcile_ports check_listening_ports
Servicesregister_service record_service update_service deploy_service restart_service stop_service purge_service upgrade_service get_service_info get_service_logs check_service_health get_caddy_config
Securityaudit_all_services validate_service_security check_firewall
Tunnelsregister_main_tunnel list_main_tunnels create_cloudflare_tunnel delete_cloudflare_tunnel list_cloudflare_tunnels get_tunnel_config get_tunnel_token list_public_hostnames add_public_hostname remove_public_hostname
DNScreate_dns_record update_dns_record delete_dns_record list_dns_records
Accesscreate_access_application delete_access_application list_access_applications list_access_policies
Gitcreate_gitea_repo list_gitea_repos get_gitea_repo delete_gitea_repo
Inventorylist_resources

兩次改版,修的是同一件事

v1.0.0 在 2026 年 5 月上線,38 個工具,功能表看起來很完整。接下來兩個月它一直在跑,我也一直在用。

然後 7 月做了兩次改版,一天一次。事後回頭看,這兩次修的其實是同一類問題,而且不是「功能不夠」——是工具說的話不能信,但它看起來完全正常在運作。這比壞掉危險得多:壞掉會停,說謊會繼續。

0.0
稽核工具給每一個服務的安全分數
3 個月
六個服務暴露在公網 IP 上沒被發現
32 / 34
路徑記錄指向不存在的目錄

v1.1.0 沒人信的稽核工具,保護不了任何東西

安全稽核工具從 v1.0.0 就在了。我沒在用它,而且它值得被忽略:每一個服務都評 0.0 分。

原因有兩個,都很蠢。它推導 Caddy config 檔名的規則是 {project}-{service}.caddy,但真實檔名是照 hostname 命名的,所以永遠找不到檔案;然後 check_listening_ports0.0.0.0 判定得比 Tailscale 位址還嚴重。

代價

六個服務在公網 IP 上開著大約三個月沒人發現。它們不在那兩個「有通過篩選」的服務裡——因為舊版只稽核標記為 deployed 的記錄,其他的根本沒掃。

一個滿口 0.0 分的工具,跟一個關掉的工具,防護效果一模一樣。而且前者更糟,因為它讓人以為有在看。

v1.1.0 把稽核重寫成可信的:

同一版還修了 purge_service——它從來沒有一次成功回傳過。標記記錄為已清除之後,它用一個「排除已清除列」的查詢再讀一次,拿到 None,然後拋錯,對一件已經做完的工作回報 PURGE_FAILED。看到這個訊息最直覺的兩個反應——重試、或手動去清——都是錯的。

反過來它也可能在網站還在服務的時候回報成功:Caddy 移除和檔案刪除的失敗被丟掉不看。現在任何一步失敗都回 PURGE_INCOMPLETE 並指名留下了什麼。

另外新增了 check_firewall,它不相信防火牆對自己的說法。一個處於 rc 狀態的 ufw 套件,會讓 /etc/ufw/ufw.conf 寫著 ENABLED=yessystemctl is-enabled ufw 回答 enabled,而實際上一條過濾規則都沒有。所以它去看活的 iptables/ip6tables 規則、套件狀態和持久化設定——IPv6 分開檢查,因為清掉 ufw 可能讓 v6 鏈停在 ACCEPT 而 v4 看起來一切正常。

v2.0.0 猜出來的路徑,被當成事實記了兩個月

v1.1.0 上線隔天,我做了一次全機隊普查,把 registry 裡每一筆路徑拿去跟真實檔案系統對。結果是 34 筆資料/設定路徑裡有 32 筆指向不存在的目錄,6 筆 static 路徑全錯。

根因是結構性的,不是 bug:register_service 會替每一個服務推導出五個子路徑(app、static、data、config、log)。但我的機隊裡多數服務根本不是這台 server 部署的——它們是我以前手動架的,只是被登記進來當作清單。對這些服務,推導出來的路徑就是虛構

虛構為什麼危險

purge_service 會拿這些路徑來問我「要不要刪這些目錄」。稽核報告會把它們當成真實資源列出來。整個系統對這些路徑的態度,跟對真實路徑一模一樣——因為 schema 裡它們就是同一個欄位。

問題不在於某一筆記錯了,而在於 schema 讓「猜測」跟「事實」無法區分。

所以 v2.0.0 不是修那 32 筆,是消滅這一類記錄存在的可能性

資源模型:從猜路徑到 layer + roots + overrides

五個路徑欄位被砍掉,換成:

子路徑改由單一 resolver 依慣例推導。關鍵在最後那一段判斷:

# main/utils.py — 所有需要具體子路徑的消費者都走這裡
if layer == STANDARD and project_root:
app = f"{root}/app/" # static 型別除外
data = f"{root}/data/"
config = f"{root}/config/"
log = f"/var/log/{project}/"
# NONSTANDARD 什麼都不推導。
# 一個不是我分配的路徑是「觀察」,
# 觀察只能住在 path_overrides,或者哪裡都不住。

這樣一來,虛構路徑不是「比較少見」,是寫不出來。要有子路徑,得先有一個我確實分配過的根。

分配 vs 記錄:新工具 record_service

把兩件事拆開之後,職責也跟著清楚了:

register_servicerecord_service(新)
用途分配資源給新服務登記已存在、非我部署的服務
layerstandardnonstandard
路徑配發根,子路徑依慣例推導零預設值,不知道的就留 null
deploy消費這些根去部署拒絕執行
purge可刪它分配的目錄永不刪除任何目錄

「不知道」現在是一個可以被表達的狀態。以前不行——schema 逼你填一個值,所以填進去的是猜的。

其他一併修掉的

技術選擇

我從這個專案學到的

做 AI agent 用的工具,最大的風險不是功能不夠,是它在錯的時候還很有說服力

人類用錯工具會起疑:分數全都是 0.0,我會覺得怪。但 agent 不會——它拿到什麼就信什麼,然後基於那個往下做決定。一個推導出來的路徑,跟一個真的路徑,在 JSON response 裡長得完全一樣。

所以這兩版的修法有個共同形狀,而且我覺得它比任何單一功能都重要:讓系統有能力說「我不知道」。稽核多了 UNVERIFIED;註冊多了 nonstandard 這一層;purge 多了 PURGE_INCOMPLETE。三個都不是新功能,是把原本被迫二選一的地方開出第三個誠實的選項。

下一步是把這個模型推到 deploy_service 的完整生命週期,還有讓 registry 能主動偵測漂移——不是等我想到要做普查才發現 32 筆是假的。

相關專案

GitHub →
← 返回作品集