如何在 VPS 上執行 MCP 伺服器
在自己的 VPS 執行 MCP 伺服器,掌握 stdio 與遠端 HTTP 傳輸、systemd、TLS、驗證設定,並排查 JSON-RPC 串流與公開端點的常見錯誤。
建置內容
在同一台 VPS 上建置 2 種可運作的 MCP 設定。第一種是 stdio 伺服器,例如檔案系統或資料庫工具。Claude Code 會將它作為子程序啟動,並透過管線與它通訊。第二種是遠端 HTTP 伺服器。它會在 systemd 與具備 TLS 的 nginx 反向代理後方,作為長時間執行的網路服務運作。任何指向該服務的 MCP 用戶端都能連線。任一種設定的安裝程序都不長。本指南大部分內容著重於 2 個實際容易出問題的部分:保持 JSON-RPC 串流內容乾淨,以及絕不將未經驗證的工具端點暴露在公開網際網路上。
MCP 實際上是什麼
Model Context Protocol 是一種標準方式,讓 AI client、Claude Code、Claude Desktop、VPS 上的 Gemini CLI 或你自行撰寫的 script 呼叫外部工具並讀取外部資源。模型本身不會執行任何操作。模型會向 client 提出要求,由 client 透過 JSON-RPC 2.0 與 MCP server 通訊,再由 server 執行工具並傳回結果。這個 client 就是人們所稱的 agent harness:它是圍繞模型運作的迴圈,負責管理工具清單、權限檢查與工作階段狀態;MCP 只是用來擴充其中工具部分的方式。採用同一套協定後,你只需撰寫一次 server,就能搭配所有支援 MCP 的 client 使用。如果你不熟悉這種分工,尤其不清楚模型究竟如何決定是否使用工具,建議先花一小時閱讀 循序了解 agent 基礎,再將實際憑證交給這類 server。
這裡有兩種傳輸方式,本指南其餘內容也會依此分為兩部分:
- stdio。 client 會將 server 以子程序啟動,並透過標準輸入與標準輸出交換以換行分隔的 JSON-RPC 訊息。不使用網路、連接埠或驗證;信任邊界就是該程序本身。幾乎所有本機工具都採用這種方式。
- Streamable HTTP(以及較舊的 HTTP+SSE)。server 是長時間執行的 Web 服務。client 透過 HTTP 連線,而 server 可透過 Server-Sent Events 串流傳回回應。這適合讓多個 client 共用一個 server,或執行必須永久常駐於主機上的工具。
工具只隸屬於一台機器和一位使用者時,請選擇 stdio。工具是共用服務時,請選擇 HTTP。
先決條件與務實注意事項
假設您使用的是全新的 Ubuntu 24.04 KVM VPS,並具備 root 或 sudo 權限。除此之外,還需要:
- 伺服器所使用的執行環境。 多數參考伺服器使用 Node 或 Python。Ubuntu 24.04 隨附 Node 18,而目前有數個 MCP 套件要求 Node 20 或更新版本,因此請透過 NodeSource 或 nvm 安裝目前的 LTS 版本,不要直接使用
apt。Python 3.12 已預先安裝。 - 網域與 DNS A 記錄。 遠端 HTTP 伺服器需要使用可解析至此 VPS 的名稱來設定 TLS。stdio 範例完全不需要 DNS。
- 512 MB RAM 已經足夠。 MCP 伺服器是精簡的 JSON-RPC 程序;記憶體用量取決於工具實際使用的元件,例如資料庫驅動程式或檔案快取,而不是通訊協定本身。
- 規格仍在發展中。 2025-03-26 版本以 Streamable HTTP 取代 HTTP+SSE,並將 SSE 標示為已棄用。SSE 仍可運作,許多伺服器也仍支援它。因此,請將任何固定的傳輸方式視為需要依伺服器版本說明重新確認的設定,不要視為絕對規則。
步驟 1:將 stdio 伺服器接入 Claude Code
先使用 filesystem server。它是官方伺服器,持續維護中,且只需要 Node。以下單一指令會將它註冊到 Claude Code,並限定在目前專案中,因此設定會寫入可提交至版本控制的檔案:
cd /home/matt/projects/api
claude mcp add --scope project --transport stdio filesystem \
-- npx -y @modelcontextprotocol/server-filesystem /home/matt/projects/api-- 分隔符很重要:其後的所有內容都是 Claude Code 將執行的指令,不是傳給 Claude Code 的旗標。這會在專案根目錄建立 .mcp.json:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/home/matt/projects/api"
]
}
}
}目前尚未啟動任何程序。下次在此目錄啟動 Claude Code 時,代理程式會讀取 .mcp.json,以子程序方式啟動 npx -y @modelcontextprotocol/server-filesystem ...,並透過該程序的 stdin/stdout 完成 MCP handshake。確認設定已生效:
claude mcp list正常運作的伺服器會顯示其指令與綠色勾號 filesystem: npx -y @modelcontextprotocol/server-filesystem ... - ✔ Connected。在工作階段中,/mcp 斜線指令會列出伺服器提供的工具(read_text_file、write_file、list_directory),代理程式現在即可呼叫這些工具,存取你允許的路徑。資料庫工具的設定方式相同;替換套件,並將連線字串作為最後一個參數傳入。不過,請查閱該伺服器自己的 repository,以確認目前的套件名稱,因為參考用的 Postgres server 已不只一次更換維護者。
這正是讓代理程式在該主機上執行的重點:Claude Code 工作階段會在 VPS 的 tmux 中執行,其 stdio 伺服器也在旁邊執行,可直接存取專案檔案與本機服務,不需要經過網路往返。代理程式同時具備 write_file 與 read_text_file 後,值得搭配 引導它採用能運作的最小變更的 skill,因為 filesystem tool 會讓大幅改寫與兩行修正的成本完全相同。同樣的接線方式也能延伸到本機檔案之外:如果你已在 VPS 上執行 search engine,就可以 將自己的 SearXNG instance 提供給代理程式作為搜尋工具,讓查詢留在你的主機上,同時直接將不受信任的頁面文字帶入代理程式隨後會據以採取行動的 context。
步驟 2:建立遠端 HTTP 伺服器
stdio 伺服器會隨父程序結束,且每個用戶端都會各自啟動一個執行個體。因此,如果您在同一台主機上執行 兩個 Claude Code 工作階段,並讓它們彼此交接工作,每個工作階段都會取得該工具的私有副本。若要讓所有用戶端都能使用持續執行的工具,例如共用的作業工具、資料庫閘道,或同時供筆記型電腦與 CI 呼叫的服務,就需要 HTTP 傳輸與實際的服務。以下是使用官方 SDK 的最小 Python 伺服器範例,會公開一個工具:
# /opt/mcp-ops/server.py
from mcp.server.mcpserver import MCPServer
import subprocess
mcp = MCPServer("ops-tools")
@mcp.tool()
def disk_free() -> str:
"""Return `df -h` for the server."""
out = subprocess.run(["df", "-h"], capture_output=True, text=True)
return out.stdout
if __name__ == "__main__":
# Serves Streamable HTTP at /mcp on 127.0.0.1:8000
mcp.run(transport="streamable-http", host="127.0.0.1", port=8000)注意 host="127.0.0.1"。伺服器只繫結至 localhost,主機外部無法直接連線,這正是尚未建立驗證機制時所需的設定。這是 pip install "mcp[cli]" 目前拉取的 2.x SDK(截至 2026-10-20 為 2.3.0)。該版本將 FastMCP 重新命名為 MCPServer,並將 host 與 port 從建構函式移至 run();舊的 mcp.server.fastmcp 匯入現在會因 ModuleNotFoundError 而失敗。請將它安裝在專用的 virtualenv 中,讓 systemd 使用穩定的直譯器路徑:
sudo useradd --system --home /opt/mcp-ops --shell /usr/sbin/nologin mcp
sudo install -d -o mcp -g mcp /opt/mcp-ops
sudo -H -u mcp python3 -m venv /opt/mcp-ops/.venv
sudo -H -u mcp /opt/mcp-ops/.venv/bin/pip install "mcp[cli]"步驟 3:使用 systemd 維持服務執行
代理程式需要工具時,工具卻已停止,這比沒有工具更糟。當用戶端本身是長時間執行的程序時,這點尤其重要:可持續執行並在重新開機後保留記憶與排程的代理程式會在無人監看的情況下依排程呼叫這些工具,因此伺服器也必須自行恢復執行。建立 /etc/systemd/system/mcp-ops.service:
[Unit]
Description=MCP ops-tools server
After=network.target
[Service]
Type=simple
User=mcp
WorkingDirectory=/opt/mcp-ops
ExecStart=/opt/mcp-ops/.venv/bin/python /opt/mcp-ops/server.py
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
[Install]
WantedBy=multi-user.targetExecStart中的 venv Python 絕對路徑不可省略。將它指向 /usr/bin/python3,並以 ModuleNotFoundError: No module named 'mcp'啟動程序,因為系統直譯器從未看過你的 pip install。啟用並檢查:
sudo systemctl daemon-reload
sudo systemctl enable --now mcp-ops
sudo systemctl status mcp-ops
curl -si -H 'Accept: application/json, text/event-stream' \
-H 'Content-Type: application/json' \
-X POST http://127.0.0.1:8000/mcpstatus應顯示 active (running)。curl會以 HTTP/1.1 400 Bad Request 回應,且本文包含 JSON-RPC 錯誤;請求沒有工作階段,也沒有有效的 JSON 內容。這正是預期結果:它證明該埠能回應,且使用正確的通訊協定。Connection refused或空白回應表示程序未在你預期的位置繫結;請查看 journalctl -u mcp-ops -n 50。
步驟 4:在前方配置 TLS 與反向代理
伺服器監聽 localhost。若要從任何位置存取,請在 nginx 終止 TLS,再將請求代理至內部。安裝 nginx,使用 Certbot 與 nginx 上的 Let's Encrypt 取得憑證,然後撰寫 location 區塊。關鍵是停用緩衝,因為 nginx 的預設行為會等到回應完整後才傳送,導致 SSE 串流永久停滯:
server {
listen 443 ssl;
server_name mcp.example.com;
# ssl_certificate lines managed by Certbot
location /mcp {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
# The four lines that make SSE work through nginx:
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
chunked_transfer_encoding off;
}
}使用 sudo nginx -t && sudo systemctl reload nginx 重新載入。如果您已經執行多個容器,具備自動 TLS 的 Traefik 反向代理 可以代為處理相同工作。它會簽發憑證並依主機名稱路由,您只需為 MCP 容器加入 labels。無論採用哪種方式,反向代理現在都是公開連接埠上唯一可存取的服務,但它指向的服務尚未完成安全設定。請先修正此問題,再將 URL 註冊到任何位置。
第 5 步:主導本主題的安全規則
絕對不要暴露未經驗證的 MCP endpoint。 MCP server 不只是唯讀 API。它會授予工具存取權,讓工具能存取你的檔案、資料庫,有時甚至是 shell。公開網際網路上的開放 /mcp,等同於讓陌生人擁有與 AI agent 相同的存取範圍:對方可以列出你的工具,接著呼叫這些工具。請將它視為未經驗證的管理 socket,因為它本質上就是如此。遭竊的 token 能造成多大影響,也取決於後方的 server:隨 openGym workout tracker 一併提供的唯讀 MCP server 只能回傳訓練資料;而檔案系統或 shell 工具則可能交出整台主機的存取權。
以下 3 種防禦措施依優先順序排列:
- 不要公開發布。 將 server 保留在
127.0.0.1,再透過 SSH tunnel 從筆記型電腦連線:ssh -L 8000:127.0.0.1:8000 matt@vps,接著讓 client 指向http://127.0.0.1:8000/mcp。這樣完全不會對外暴露。 - 放在私有網路上。 將 tunnel 位址綁定到 自行託管的 WireGuard VPN,只允許 VPN peer 存取。公開網際網路只能看到關閉的埠。
- 如果必須公開,則要求 token。 正規作法是使用 HTTP transport 原生支援的 MCP OAuth 流程。務實的最低要求是在 proxy 檢查共用 bearer token。這種作法成本低,且能完全阻止未經授權的隨機存取:
location /mcp {
if ($http_authorization != "Bearer REPLACE_WITH_LONG_RANDOM") {
return 401;
}
proxy_pass http://127.0.0.1:8000;
# ...buffering-off block from above...
}使用 openssl rand -hex 32 產生 token,並且絕對不要在未設置其中一項防護的情況下,直接將 server 綁定到 0.0.0.0。接著由 client 將 token 作為 header 傳送。在 Claude Code 中:
claude mcp add --scope project --transport http ops-tools https://mcp.example.com/mcp \
--header 'Authorization: Bearer ${MCP_TOKEN}'在 shell 中設定 MCP_TOKEN,讓 secret 不會以明文寫入 .mcp.json。Claude Code 會在讀取時從環境中展開 ${MCP_TOKEN}。如果該變數未設定,Claude Code 不會代換成空字串,而是會在 claude mcp list 中標示缺少變數,並使用字面上的 ${MCP_TOKEN} 文字。也請自行選擇名稱,因為在遠端 server 的 headers 中,Claude Code 會刻意將自身及雲端供應商的 credential 變數(例如 ANTHROPIC_API_KEY)視為空值,也會將環境中攜帶的其他 credential(例如 NPM_TOKEN)視為空值。
上述每項防禦措施保護的都是 endpoint,而不是已經持有 token 的 agent;後者是問題的另一面:如果你的 client 是 DeepSeek Harness,限制 agent 可呼叫哪些工具,並掃描工具輸出中的注入指示的 plugins 就能處理這一面。
步驟 6:使用 MCP Inspector 偵錯
伺服器行為異常時,不要在 agent 內部猜測。請使用官方網頁測試用戶端 Inspector 直接操作伺服器。對於 stdio 伺服器,將 agent 執行的相同命令提供給它:
npx @modelcontextprotocol/inspector \
npx -y @modelcontextprotocol/server-filesystem /tmpInspector 會在 http://localhost:6274 上由單一網頁伺服器提供使用者介面,並輸出包含 MCP_INSPECTOR_API_TOKEN 查詢字串的 URL;請開啟該連結,不要憑記憶輸入網址。自 v2 起(截至 2026-10-20,目前版本為 2.11.0),後端也會在每次載入所提供的頁面時注入 token,因此重新載入後仍可正常運作。這個 token 會保護可建立程序的 /api/* 路由。獨立的 v1 proxy(使用 6277)已移除,但網頁伺服器並不是唯一的監聽器:v2 也會在 MCP_SANDBOX_PORT 啟動 MCP Apps 的 sandbox(預設為 6275),並在 MCP_APP_ORIGIN_PORT 啟動專用的 app-origin 伺服器(預設為 6278)。後者會提供 UI resource 宣告 _meta.ui.domain 的任何 MCP App。若透過 SSH tunnel 連線至 Inspector,或在 container 中執行 Inspector,且需要使用 Apps 分頁,請連同 6274 一起轉送 6275 和 6278。建立連線後,開啟 Tools 分頁,選取工具,填入實際引數並呼叫工具。若工具在 Inspector 中可正常運作,但在 agent 中失敗,問題出在用戶端設定,而不是伺服器。對於遠端 HTTP 伺服器,請不要提供命令來啟動 Inspector,改用 Streamable HTTP transport 和 https://mcp.example.com/mcp 新增伺服器,並在其設定中加入 Authorization header,然後建立連線。這是在交由 agent 處理前,最快確認驗證與 proxy 設定正確的方法。Inspector 會將 header 依原樣儲存在伺服器目錄中,因此該 token 現在也會存放在 ~/.mcp-inspector/mcp.json。
讓伺服器保持更新
MCP 變動快速,因此請依排程套用修補程式。使用未固定版本的 npx -y 啟動 Node 伺服器時,會執行 npx 解析出的版本:如果本機專案已安裝,則使用該版本;否則會將副本安裝到 npm 快取。-y 只會略過安裝提示,不會強制更新,因此您既無法追蹤最新版本,也無法確定目前執行的是哪個版本。請固定使用已測試的確切版本,從 npm view @modelcontextprotocol/server-filesystem version 讀取版本,並在 .mcp.json 中將版本附加至套件名稱後方(@modelcontextprotocol/server-filesystem@<version>)。伺服器開始具備重要性後,請審慎升級版本。使用 systemd 管理的 Python 伺服器,先執行 sudo -H -u mcp /opt/mcp-ops/.venv/bin/pip install -U "mcp[cli]",再執行 sudo systemctl restart mcp-ops。進行主要版本升級前,請先閱讀版本資訊:Python SDK 從 1.x 升級至 2.x 時,會在匯入階段使 1.x 程式碼失效;SDK 自身的遷移指南建議先設定 <2 上限,例如 "mcp[cli]<2",直到完成遷移。升級時請確認 SDK 所針對的規格修訂版本。跨越 SSE 至 Streamable-HTTP 的轉換界線時,客戶端必須請求的傳輸方式可能會改變。
失敗模式與實際顯示的字串
代理程式顯示伺服器失敗。 claude mcp list 輸出 ✘ Failed to connect;目前版本會在該行附加失敗詳細資訊。執行 claude --debug,通常會看到 Error: spawn npx ENOENT,表示該命令不在代理程式的 PATH 中。執行環境遺失,或不在代理程式搜尋的位置:尚未安裝 Node、缺少 npx,或以簡短名稱引用了 virtualenv 的 Python。將命令修正為絕對路徑,或安裝執行環境,然後重新連線。
stdio 伺服器連線後立即中斷。 用戶端日誌會記錄 JSON 剖析錯誤,例如 Unexpected token 'S', "Server sta"... is not valid JSON 或 Failed to parse message。原因固定是伺服器將日誌行寫入 stdout。在 stdio 模式中,stdout 就是 JSON-RPC 通道,因此任何多餘文字都會破壞資料流,導致交握失敗。在 Node 中,console.log 會輸出至 stdout,請改用 console.error。在 Python 中,單獨使用 print() 會輸出至 stdout,請使用設定為 sys.stderr 的 logging 寫入日誌,或傳入 file=sys.stderr。規則很明確:stdio 的 stdout 只能傳送 JSON-RPC,所有供人閱讀的內容都必須傳送至 stderr。
遠端伺服器逾時,或在交握期間中斷連線。 用戶端會以 MCP error -32000: Connection closed 失敗,或 Inspector 停在 Connect,且始終不列出工具。在 nginx 後方時,原因是 buffering:代理伺服器暫存 SSE 串流而未立即傳送,因此用戶端一直等待永遠不會到達的回應。將 proxy_buffering off;(以及 Step 4 中其餘的區塊)加入 location。使用 curl -N 對公開 URL 進行確認;事件資料應逐步抵達,而不是最後一次全部出現。
驗證遭到拒絕。 用戶端會回報 Error POSTing to endpoint (HTTP 401),或直接顯示 401 Unauthorized。可能是標頭遺失、token 錯誤,或變數未展開;這是常見陷阱。如果用戶端讀取設定時 MCP_TOKEN 未設定,Claude Code 會在 claude mcp list 中警告變數遺失,並傳送字面文字,因此 nginx 會看到 Bearer ${MCP_TOKEN}。若變數名稱與 Claude Code、雲端服務供應商或環境中的其他憑證名稱相同,讀取結果會是空值,nginx 則會看到沒有值的 Bearer 。輸出該變數、重新加入標頭,並確認實際位元組與 nginx if 中的 token 完全相同。
服務無法在 systemd 下啟動。 journalctl -u mcp-ops 顯示 ModuleNotFoundError: No module named 'mcp',而 ExecStart 指向 system Python,而非 venv interpreter。或者 Address already in use,表示其他程序已占用 8000;使用 sudo ss -ltnp | grep 8000 找出該程序。
FAQ
MCP 伺服器究竟是什麼?
MCP 伺服器是透過 Model Context Protocol,使用 JSON-RPC 2.0,向 AI 用戶端公開工具與資源的程式。AI 模型不會直接執行工具,而是要求其用戶端執行;用戶端呼叫 MCP 伺服器,再由伺服器執行並回傳結果。由於此協定是標準,任何相容的用戶端都能使用同一台伺服器,包括 Claude Code、Claude Desktop 或 Gemini CLI。
stdio 與 HTTP 傳輸有何不同?
stdio 伺服器由用戶端以子程序啟動,透過 stdin/stdout 通訊,因此只會在單一機器上隨單一用戶端啟動與結束,且不需要網路或驗證。HTTP 伺服器則是長時間執行的網路服務,可同時接受多個用戶端連線,因此需要 TLS 與驗證。單一使用者的本機工具應使用 stdio;共用或需持續執行的服務則應使用 HTTP(目前的伺服器使用 Streamable HTTP)。
如何保護遠端 MCP 伺服器?
請假設它可存取您的檔案、資料庫或 shell 工具,絕不要在未驗證的情況下公開。最佳做法是讓伺服器僅繫結至 localhost,再透過 SSH tunnel 或私有 VPN 存取;若必須公開,請將其置於會強制要求 bearer token 或 MCP OAuth 流程的反向代理後方。使用 openssl rand -hex 32 產生 token,且在前方未配置上述其中一種防護時,絕不要將伺服器繫結至 0.0.0.0。
如何偵錯無法啟動的伺服器?
先檢查 claude mcp list;使用 spawn ... ENOENT 執行 ✘ Failed to connect 表示缺少命令或執行環境,因此請修正路徑或安裝相關元件。若伺服器建立連線後因 JSON 剖析錯誤而中斷,表示伺服器將日誌寫入 stdout,導致 JSON-RPC 串流損毀;請將所有日誌移至 stderr。其他情況則請在 MCP Inspector 下執行完全相同的命令。MCP Inspector 會隔離驅動伺服器,讓您判斷問題是伺服器錯誤還是用戶端設定錯誤。