副標:用 Oracle VM、Cloudflare、nginx 與 FastMCP,把一個本來只會在 terminal 裡跑的 MCP server,變成 ChatGPT 真能連上的公開 /mcp 端點。
上一篇我談的是觀念邊界:MCP 真正改變的是責任分工,不只是工具接線方式。
這一篇就不再停在觀念了。
我們直接講施工。
因為只要你真的開始自己架 remote MCP server,很快就會發現最煩的地方通常不是 @mcp.tool,而是下面這些很土、但又絕對逃不掉的問題:
- VM 到底有沒有真的對外
- public IP、VCN、subnet、Internet Gateway、security rules 哪裡少一塊
- 為什麼你在機器裡
curl localhost成功,外面卻還是打不到 - 為什麼 nginx 看起來正常,ChatGPT 還是連不上
/mcp - 為什麼 TLS、DNS、Cloudflare、origin config,會把一個原本像 Python 題目的東西變成網路題
我自己的做法是:先把 FastMCP server 當成很薄的一層,再讓它去吃 GitHub 上的 skill files,最後在後面呼叫既有 Make execution flows。
整條路線長這樣:
ChatGPT
→ Cloudflare
→ nginx
→ FastMCP
→ skill loader / adapter
→ Make execution flows
這篇會把這條路完整拆開。
先講結論:如果你只是想驗證概念,先不要上 Oracle VM
我先給一個不那麼浪漫的建議。
如果你現在只是:
- 想先驗證一個 skill 能不能被 host 正常看到
- 還不確定 tool surface 該長什麼樣
- 只是想測本機或內部 demo
- 還不想碰 DNS、TLS、反向代理、systemd、機器維運
那你其實不一定要一開始就選 Oracle VM。
因為一旦你選了 VM,你同時也選了:
- SSH
- 網路拓樸
- 防火牆規則
- OS patching
- process management
- certificate handling
- 外部可用性
也就是說,你選的不是「部署地點」,而是一整包運維責任。
我自己會選 Oracle VM,是因為這套 server 後面真的要當長駐公開入口,而且我不想把 capability surface 交給第三方平台的預設行為。但如果你現在只是驗證 MVP,先用 PaaS 或 tunnel 也很合理。
這篇要完成的 end state
我這篇採用的 reference implementation 很明確:
- Oracle VM 跑 Ubuntu
- FastMCP app 不直接暴露給公網
- nginx 負責 public HTTPS 入口
- Cloudflare 管 DNS 與外層代理
- 對外真正給 host 的入口是:
https://mcp.example.com/mcp
這樣做的目的不是把架構弄複雜,而是讓責任切得比較乾淨:
- FastMCP 負責 MCP capability surface
- nginx 負責 reverse proxy 與 HTTPS entrypoint
- Cloudflare 負責 DNS 與 edge-facing traffic
- Oracle VM 提供你可控的運行環境
我會怎麼分四個施工階段
我現在最推薦的順序不是「先寫 server」,而是這樣:
- 先把 Oracle VM 網路層打通
- 再讓 FastMCP 在內部 port 跑起來
- 再掛 nginx 與 HTTPS
- 最後才把 endpoint 接進 ChatGPT Developer Mode
這個順序有一個非常實際的好處:
每一層壞掉時,你比較知道自己是在 debug 哪一層。
第 1 階段:把 Oracle VM 變成真的 public host
Oracle 官方文件講得很清楚。
一台 compute instance 要從 internet 可達,至少要同時滿足幾件事:
- instance 有 public IP
- subnet / route table 真的能通到 Internet Gateway
- security rules 允許對應 ingress / egress
- 你能用正確 private key SSH 進去
這裡最常見的錯覺是:
「我看到 public IP 了,應該就能連了吧?」
不一定。
因為 Oracle networking 真正決定生死的,是:
- VCN / subnet
- route table
- Internet Gateway
- security list 或 NSG
一個我很推薦的起手式
如果你現在是從零開始建 Oracle VM,我反而會建議先用 Oracle 的 VCN with Internet Connectivity wizard 做起始版本。
因為這個 wizard 會幫你先把一部分 public subnet、gateway、route table、security list 的骨架搭起來,對第一版比較不容易漏東漏西。
之後你再慢慢改成你自己的 network layout。
SSH 先過,再談別的
Oracle 官方的 Linux instance 連線文件也很直接:
你需要的是正確的 public IP、正確的 使用者名稱,以及正確的 private key。如果你拿的是 .pub,那不是登入鑰匙,那只是公鑰。
這個坑很土,但很常見。
最小檢查清單可以先用這組:
ssh -i /path/to/private-key ubuntu@<PUBLIC_IP>
如果你連 SSH 都還沒通,不要急著去懷疑 FastMCP。
十之八九是 VM 網路層還沒真的通。
第 2 階段:先讓 FastMCP 在內部 port 活著
我非常不建議一開始就直接把 FastMCP 裸奔到公網 port。
比較穩的做法是:
- FastMCP 先只 listen 在內部
- 例如
127.0.0.1:8000 - 外面再由 nginx 把
/mcp反代進來
這樣你排錯時比較有層次,也比較容易保證 app 不會直接暴露。
在 Ubuntu 上的最小安裝步驟
這是一組很典型的起手式:
sudo apt update
sudo apt install -y python3 python3-venv python3-pip git nginx
接著把 repo 拉下來:
sudo mkdir -p /opt/job-mcp
sudo chown "$USER":"$USER" /opt/job-mcp
cd /opt/job-mcp
git clone https://github.com/huaihsuanbusiness/job-skills-gateway app
cd app
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -r requirements.txt
一個夠薄的 FastMCP app
對這類 skill gateway,我現在的偏好很明確:
- server 只暴露高層 skills
- skill files 從 repo 讀
- adapter 去叫 backend execution layer
- 不要把 raw Make flow names 直接暴露給 host
如果你要用 ASGI app 接 nginx / uvicorn,FastMCP 現在的 Python SDK 可以直接建立 streamable HTTP app。
一個很小的骨架長這樣:
from fastmcp import FastMCP
from fastmcp.server.http import create_streamable_http_app
mcp = FastMCP("Job Skills Gateway")
@mcp.tool
def healthcheck() -> dict:
"""Return basic liveness for deployment checks."""
return {"ok": True}
app = create_streamable_http_app(
server=mcp,
streamable_http_path="/mcp",
)
如果你只是要快速本機驗證,也可以直接用 FastMCP 的 HTTP transport 跑起來。
但對我要的部署形狀來說,ASGI app + uvicorn + nginx 比較好控。
本機先驗證,不要急著外放
先在機器裡測:
source /opt/job-mcp/app/.venv/bin/activate
uvicorn mcp_server.app.server:app --host 127.0.0.1 --port 8000
再開另一個 shell 測:
curl -i http://127.0.0.1:8000/mcp
這裡我要刻意提醒一個容易把人嚇到的點:
在某些 MCP / ASGI 配置下,普通
curl打/mcp回 406 或其他看起來不友善的狀態,不一定代表 endpoint 壞掉。
因為 MCP 不是一般的 HTML 首頁。
在我自己的部署裡,406 Not Acceptable 反而是個訊號,表示請求真的打到 MCP endpoint 了,只是你不是用對 transport 方式在跟它講話。
第 3 階段:把它變成真正會活下去的 service
如果你還在靠一個 terminal 視窗手動跑 uvicorn,那不叫部署完成。
那叫「剛好現在有跑」。
這一步我建議很務實地交給 systemd。
參考 service unit
[Unit]
Description=Job MCP Server
After=network.target
[Service]
User=ubuntu
Group=ubuntu
WorkingDirectory=/opt/job-mcp/app
EnvironmentFile=/opt/job-mcp/app/.env
ExecStart=/opt/job-mcp/app/.venv/bin/uvicorn mcp_server.app.server:app --host 127.0.0.1 --port 8000
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
存成:
sudo nano /etc/systemd/system/job-mcp.service
然後執行:
sudo systemctl daemon-reload
sudo systemctl enable job-mcp.service
sudo systemctl start job-mcp.service
sudo systemctl status job-mcp.service --no-pager
看 log:
journalctl -u job-mcp.service -n 100 --no-pager
到這一步,你才算把「一段 Python 程式」變成「一個長駐服務」。
第 4 階段:讓 nginx 對外提供 /mcp
FastMCP 在內部跑著,下一步才輪到 nginx。
我現在偏好的 public shape 是:
https://mcp.example.com/mcp
→ nginx :443
→ 127.0.0.1:8000/mcp
一個可讀的 nginx 參考設定
server {
listen 443 ssl http2;
server_name mcp.example.com;
ssl_certificate /etc/ssl/certs/cloudflare-origin.crt;
ssl_certificate_key /etc/ssl/private/cloudflare-origin.key;
location /mcp {
proxy_pass http://127.0.0.1:8000/mcp;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
如果你也要做 HTTP → HTTPS redirect,可以再加一個 listen 80 的 server block。
測設定:
sudo nginx -t
sudo systemctl reload nginx
Cloudflare 在這裡到底在做什麼
很多人把 Cloudflare 想成「綁網域的地方」。
這樣講不算錯,但不夠工程。
在這個部署裡,Cloudflare 至少做了兩件重要的事:
-
DNS
- 把
mcp.example.com指到你的 Oracle VM
- 把
-
Proxy / edge-facing HTTPS
- 當你把 DNS record 設成 proxied(橘雲)時,HTTP/HTTPS 流量會先進 Cloudflare,再到 origin
這件事有一個很實際的含義:
你的 Oracle origin 不再直接面對所有訪客流量,它看到的是來自 Cloudflare 的流量。
這也是為什麼 Cloudflare 官方一直在講:
- proxied records
- origin certificates
- authenticated origin pulls
- edge 與 origin 的 TLS 關係
Cloudflare Origin CA 真的很好用
如果你的流量是經過 Cloudflare 代理,再進你的 Oracle VM,那一個很實用的做法,就是在 origin 端用 Cloudflare Origin CA 憑證。
好處是:
- 很適合 Cloudflare ↔ origin 這段
- 不用一開始就去折騰公開 CA
- 配合
Full (strict)模式比較順
但也要記得:
如果你把 SSL mode 拉到 Full 或 Full (strict),origin 端就真的要能處理 443 HTTPS。否則你很容易看到 525 之類的 handshake 錯誤。
接進 ChatGPT 之前,先做 smoke tests
到這裡,先別急著打開 ChatGPT。
我會先要求自己把幾個最小驗證做完:
Smoke test 1:機器內部 app 存活
curl -i http://127.0.0.1:8000/mcp
Smoke test 2:nginx 反代正常
curl -ik https://mcp.example.com/mcp
Smoke test 3:service 能重啟
sudo systemctl restart job-mcp.service
sudo systemctl status job-mcp.service --no-pager
Smoke test 4:機器重開後 service 還在
sudo reboot
重連後:
systemctl status job-mcp.service --no-pager
systemctl status nginx --no-pager
如果這幾關都還沒過,就先不要把錯怪到 ChatGPT 或 Developer Mode 上。
最後才是 ChatGPT Developer Mode
OpenAI 現在的 Developer Mode 文件已經明講,remote MCP app / connector 的建立流程,是在 ChatGPT Apps 設定裡建立 app,然後填你自己的 remote MCP server。官方也明確列出:
- 支援的協定:SSE 與 streaming HTTP
- 驗證方式:OAuth、No Authentication、Mixed Authentication
所以如果你現在是第一版、內網已經有其他保護,而且 server 沒做 OAuth,你可以先選 No Authentication 做開發驗證。
但這只是第一版策略,不是長期安全策略。
連線前,我會先確認這三件事
/mcp是真的 public HTTPS,可從外部到達- host 看到的是 skill-level tools,不是 raw backend tools
- 錯誤至少能用 logs 看出是:
- DNS / TLS / proxy 問題
- app 問題
- backend adapter 問題
這一路最值得記下來的幾個坑
我把這篇最值得寫進 blog 的坑,收成四個:
坑 1:你以為是 FastMCP 壞掉,其實是 Oracle 網路根本沒通
這是最常見的錯覺。
public IP 有了,不代表 subnet route、IGW、security list、NSG 都對。
坑 2:你以為程式有跑,就等於服務可用
不對。
有跑的 Python process,不等於可維運服務。
systemd、restart policy、logs,這些不是加分題,是基本盤。
坑 3:你以為 nginx 通了,TLS 就一定沒問題
不對。
Cloudflare、origin cert、Full / Full (strict)、443 ingress、redirect loop,這些任一個不對,你都可能卡在 525 或奇怪的連線失敗。
坑 4:你以為 ChatGPT 連不上,一定是 ChatGPT 的問題
通常不是。
更多時候,問題在:
- endpoint 不夠公開
- proxy path 沒對齊
- transport 或 auth 設定不一致
- server tool surface 不乾淨
我現在會怎麼看這一題
如果一定要把這篇再濃縮成一句工程判準,那就是:
部署 remote MCP server 時,真正的工作量不在 tool decorator,而在把網路層、程序層、與 capability surface 一起做對。
FastMCP 當然重要。
但在 Oracle VM 這種 self-hosted 場景裡,它只是整條鏈中的一層。
真正讓系統變穩的,是你有沒有把這幾件事分清楚:
- 哪一層對外
- 哪一層只聽內部 port
- 哪一層負責 TLS
- 哪一層負責 skill exposure
- 哪一層負責 backend execution
下一篇會開始談框架比較
做到這裡,很多人下一個問題通常是:
那為什麼你最後選 FastMCP,而不是別的 MCP framework?
這就會是 Part 3 要談的主題。
我會把比較重點放在:
- framework 重不重
- transport / auth / deployment 夠不夠成熟
- 你是在做 demo server,還是在做會長大的 public server
- 你到底要的是「快做完」,還是「之後不會痛」