Cara jalankan MCP server pada VPS
Pelajari cara konfigurasi MCP server menggunakan stdio dan remote HTTP melalui systemd serta TLS untuk kegunaan ejen AI pada VPS anda sendiri.
Apa yang anda bina
Dua tetapan MCP yang berfungsi pada satu VPS. Pertama, pelayan stdio — alat sistem fail atau pangkalan data yang dilancarkan oleh Claude Code sebagai proses anak dan berkomunikasi melalui paip (pipe). Kedua, pelayan remote HTTP yang berjalan sebagai perkhidmatan rangkaian jangka panjang di bawah systemd dan nginx reverse proxy dengan TLS, yang boleh dicapai oleh mana-mana klien MCP yang disambungkan kepadanya. Pemasangan bagi kedua-duanya adalah ringkas. Kebanyakan panduan ini memfokuskan kepada dua perkara utama: memastikan aliran JSON-RPC bersih, dan tidak meletakkan titik akhir (endpoint) alat tanpa pengesahan pada internet awam.
Apa itu MCP sebenarnya
Model Context Protocol ialah cara standard untuk klien AI — Claude Code, Claude Desktop, Gemini CLI pada VPS, atau skrip anda sendiri — memanggil alatan luaran dan membaca sumber luaran. Model itu sendiri tidak menjalankan apa-apa. Ia meminta klien, klien menghantar JSON-RPC 2.0 kepada server MCP, dan server menjalankan alatan tersebut serta menyerahkan hasilnya semula. Satu protokol bermaksud server yang anda tulis sekali boleh berfungsi dengan setiap klien yang menyokong MCP.
Terdapat dua jenis pengangkutan (transports), dan seluruh panduan ini dibahagikan mengikutnya:
- stdio. Klien memulakan server sebagai proses anak (child process) dan bertukar mesej JSON-RPC yang diakhiri dengan baris baharu melalui input standard dan output standardnya. Tiada rangkaian, tiada port, tiada pengesahan — sempadan kepercayaan adalah proses itu sendiri. Hampir setiap alatan tempatan dihantar melalui cara ini.
- Streamable HTTP (dan versi lamanya, HTTP+SSE). Server ialah perkhidmatan web yang berjalan berterusan. Klien menyambung melalui HTTP dan server boleh menghantar respons secara penstriman sebagai Server-Sent Events. Ini adalah cara untuk berkongsi satu server dengan banyak klien, atau menjalankan alatan yang mesti berada pada mesin secara kekal.
Pilih stdio apabila alatan tersebut hanya untuk satu mesin dan satu pengguna. Pilih HTTP apabila ia merupakan perkhidmatan kongsi.
Prasyarat dan perkara penting yang perlu diperhatikan
Anggap anda menggunakan VPS Ubuntu 24.04 KVM yang baru dengan akses root atau sudo. Selain itu:
- Runtime yang digunakan oleh pelayan. Kebanyakan pelayan rujukan menggunakan Node atau Python. Ubuntu 24.04 menyertakan Node 18, manakala beberapa pakej MCP semasa memerlukan Node 20 atau lebih baharu. Oleh itu, pasang versi LTS terkini daripada NodeSource atau nvm berbanding bergantung kepada
apt. Python 3.12 sudah tersedia. - Domain dan rekod DNS A, tetapi hanya untuk pelayan HTTP jauh — TLS memerlukan nama yang diselesaikan ke VPS ini. Contoh stdio tidak memerlukan DNS langsung.
- 512 MB RAM sudah mencukupi. Pelayan MCP adalah proses JSON-RPC yang ringan; penggunaan memori bergantung kepada alat yang anda gunakan (pemandu pangkalan data, cache fail), bukan protokol tersebut.
- Spesifikasi masih baharu dan berubah. Semakan 2025-03-26 telah menggantikan HTTP+SSE dengan Streamable HTTP dan menandakan SSE sebagai usang (deprecated). SSE masih berfungsi dan banyak pelayan masih menggunakannya, jadi semak semula sebarang kekangan pengangkutan terhadap nota keluaran pelayan dan jangan anggap ia sebagai tetap.
Langkah 1: sambungkan stdio server ke dalam Claude Code
Mulakan dengan filesystem server — ia adalah rasmi, diselenggara secara aktif, dan hanya memerlukan Node. Perintah di bawah mendaftarkannya dengan Claude Code dan mengehadkan skopnya kepada projek semasa supaya ia disimpan dalam fail yang boleh dikomit:
cd /home/matt/projects/api
claude mcp add --scope project --transport stdio filesystem \
-- npx -y @modelcontextprotocol/server-filesystem /home/matt/projects/apiPemisah -- adalah penting: semua yang hadir selepasnya adalah perintah yang akan dijalankan oleh Claude Code, bukan flag untuk Claude Code. Ini akan menulis .mcp.json pada akar projek:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/home/matt/projects/api"
]
}
}
}Tiada apa yang sedang berjalan lagi. Apabila anda memulakan Claude Code seterusnya dalam direktori ini, ejen akan membaca .mcp.json, melancarkan npx -y @modelcontextprotocol/server-filesystem ... sebagai proses anak, dan melakukan jabat tangan MCP melalui stdin/stdout proses tersebut. Sahkan ia telah berjaya:
claude mcp listServer yang berfungsi dengan baik akan mencetak perintahnya dan tanda rait hijau — filesystem: npx -y @modelcontextprotocol/server-filesystem ... - ✓ Connected. Di dalam sesi, perintah slash /mcp menyenaraikan alatan yang didedahkan oleh server (read_file, write_file, list_directory), dan ejen kini boleh memanggilnya pada laluan yang anda benarkan. Alatan pangkalan data mempunyai struktur yang sama — tukar pakej dan masukkan string sambungan sebagai argumen terakhirnya — tetapi semak repositori server itu sendiri untuk nama pakej semasa, kerana server Postgres rujukan telah bertukar pemilik lebih daripada sekali.
Inilah tujuan utama menjalankan ejen pada mesin tersebut: sesi Claude Code berjalan pada VPS di dalam tmux, dan stdio servernya berjalan bersebelahan dengannya dengan akses terus ke fail projek dan perkhidmatan tempatan, tanpa perjalanan rangkaian.
Langkah 2: bina pelayan HTTP jauh
Pelayan stdio akan terhenti apabila proses induknya terhenti. Apabila anda memerlukan alatan yang sentiasa aktif untuk setiap klien — seperti alatan operasi perkongsian, gerbang pangkalan data, atau sesuatu yang dipanggil oleh komputer riba dan CI anda — anda memerlukan pengangkutan HTTP dan perkhidmatan sebenar. Berikut adalah pelayan Python minimal menggunakan SDK rasmi, yang mendedahkan satu alatan:
# /opt/mcp-ops/server.py
from mcp.server.fastmcp import FastMCP
import subprocess
mcp = FastMCP("ops-tools", host="127.0.0.1", port=8000)
@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")Perhatikan host="127.0.0.1". Pelayan ini hanya terikat pada localhost — tiada peranti luar boleh mencapainya secara terus, yang mana merupakan tetapan yang diingini sebelum sistem pengesahan wujud. Pasang ia dalam virtualenv sendiri supaya systemd mempunyai laluan interpreter yang stabil:
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]"Langkah 3: kekalkan ia aktif dengan systemd
Alatan yang tidak berfungsi apabila ejen mencarinya adalah lebih buruk daripada tiada alatan langsung. Tulis /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.targetLaluan mutlak ke Python venv dalam ExecStart adalah wajib — halakan ke /usr/bin/python3 dan proses akan bermula dengan ModuleNotFoundError: No module named 'mcp', kerana interpreter sistem tidak dapat mengesan pip install anda. Aktifkan dan semak:
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 sepatutnya memaparkan active (running). curl akan memulangkan HTTP/1.1 400 Bad Request dengan ralat JSON-RPC pada bahagian badan — permintaan tersebut tidak mempunyai sesi dan tiada muatan JSON yang sah — dan itulah yang anda mahukan: ia membuktikan port tersebut bertindak balas dan menggunakan protokol tersebut. Connection refused atau balasan kosong bermaksud proses tidak terikat pada lokasi yang anda sangka; baca journalctl -u mcp-ops -n 50.
Langkah 4: letakkan TLS dan reverse proxy di hadapan
Pelayan mendengar pada localhost. Untuk mengaksesnya dari mana-mana sahaja, tamatkan TLS pada nginx dan proxy ke dalam. Pasang nginx, dapatkan sijil dengan Certbot dan Let's Encrypt pada nginx, kemudian tulis blok location. Bahagian kritikal adalah melumpuhkan buffering, kerana tingkah laku lalai nginx adalah menyimpan respons sehingga ia selesai, yang akan menyekat aliran SSE selamanya:
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;
}
}Muat semula dengan sudo nginx -t && sudo systemctl reload nginx. Jika anda sudah menjalankan rangkaian kontena, tugas yang sama dilakukan untuk anda oleh reverse proxy Traefik dengan TLS automatik — ia mengeluarkan sijil dan menghala mengikut hostname, dan anda hanya perlu menambah label pada kontena MCP. Dalam kedua-dua cara, reverse proxy kini merupakan satu-satunya perkara pada port awam, dan ia menghala ke perkhidmatan yang belum anda amankan. Selesaikan perkara itu sebelum anda mendaftarkan URL di mana-mana.
Step 5: peraturan keselamatan utama bagi topik ini
Jangan sesekali dedahkan endpoint MCP tanpa pengesahan. Pelayan MCP bukan sekadar API baca-sahaja. Ia memberikan akses kepada alatan — kepada fail, pangkalan data, dan kadangkala shell anda. /mcp yang terbuka pada internet awam adalah ancaman yang mempunyai capaian yang sama dengan ejen AI anda: mereka menyenaraikan alatan anda, kemudian memanggilnya. Anggap ia sama seperti soket admin tanpa pengesahan, kerana itulah hakikatnya.
Tiga pertahanan, mengikut urutan keutamaan:
- Jangan terbitkan. Kekalkan pelayan pada
127.0.0.1dan akses dari komputer riba anda menggunakan terowong SSH:ssh -L 8000:127.0.0.1:8000 matt@vps, kemudian halakan klien kehttp://127.0.0.1:8000/mcp. Tiada apa yang didedahkan. - Letakkan pada rangkaian peribadi. Gunakan alamat terowong VPN WireGuard self-hosted dan benarkan hanya rakan VPN sahaja yang boleh mengaksesnya. Internet awam hanya akan melihat port yang tertutup.
- Jika perlu awam, wajibkan token. Jawapan yang tepat ialah aliran MCP OAuth yang disokong secara asli oleh pengangkutan HTTP. Pilihan minimum yang praktikal ialah token bearer kongsi yang disemak pada proxy — ia mudah, dan ia menghalang serangan secara total:
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...
}Jana token dengan openssl rand -hex 32, dan jangan sesekali mengikat pelayan itu sendiri pada 0.0.0.0 tanpa salah satu kaedah ini di hadapannya. Klien kemudian menghantar token tersebut sebagai header. Dalam Claude Code:
claude mcp add --scope project --transport http ops-tools https://mcp.example.com/mcp \
--header 'Authorization: Bearer ${MCP_TOKEN}'Tetapkan MCP_TOKEN dalam shell anda supaya rahsia tersebut tidak disimpan dalam .mcp.json dalam bentuk teks biasa — Claude Code akan mengembangkan ${MCP_TOKEN} daripada persekitaran semasa masa pembacaan.
Step 6: debug dengan MCP Inspector
Apabila pelayan tidak berfungsi dengan betul, jangan membuat tekaan dari dalam ejen — gunakan Inspector, klien ujian berasaskan web rasmi, untuk mengujinya secara langsung. Untuk pelayan stdio, gunakan arahan yang sama seperti yang dijalankan oleh ejen:
npx @modelcontextprotocol/inspector \
npx -y @modelcontextprotocol/server-filesystem /tmpIa akan memulakan UI pada http://localhost:6274 (versi terkini akan memaparkan URL dengan rentetan pertanyaan MCP_PROXY_AUTH_TOKEN — gunakan pautan tepat tersebut atau UI akan menolak anda) dan proksi pada 6277. Klik Connect, kemudian List Tools, kemudian Call Tool dengan argumen sebenar. Jika ia berfungsi dalam Inspector tetapi gagal dalam ejen, pepijat tersebut berada pada konfigurasi klien anda, bukan pada pelayan. Untuk pelayan HTTP jarak jauh, pilih pengangkutan Streamable HTTP, masukkan https://mcp.example.com/mcp, tambah pengepala Authorization, dan sambung — ini adalah cara terpantas untuk membuktikan pengesahan dan proksi adalah betul sebelum melibatkan mana-mana ejen.
Mengemas kini pelayan secara berkala
MCP berkembang dengan pantas, jadi lakukan tampalan mengikut jadual. Pelayan Node yang dilancarkan dengan npx -y akan mengambil versi terkini setiap kali ia dijalankan; ini memudahkan kerja tetapi tidak boleh dihasilkan semula (non-reproducible). Tetapkan versi tepat yang telah anda uji — baca versi tersebut daripada npm view @modelcontextprotocol/server-filesystem version dan tambahkan pada nama pakej dalam .mcp.json (@modelcontextprotocol/server-filesystem@<version>) — apabila pelayan sudah stabil, lakukan kemas kini secara sengaja. Pelayan Python di bawah systemd dikemas kini dengan sudo -H -u mcp /opt/mcp-ops/.venv/bin/pip install -U "mcp[cli]" diikuti dengan sudo systemctl restart mcp-ops. Perhatikan semakan spesifikasi yang disasarkan oleh SDK anda apabila anda menaik taraf — perubahan melangkaui sempadan SSE-ke-Streamable-HTTP boleh mengubah jenis penghantaran (transport) yang perlu diminta oleh klien anda.
Mod kegagalan, dengan rentetan yang akan anda lihat
Ejen menunjukkan pelayan gagal. claude mcp list mencetak ✗ Failed to connect, dan TUI melaporkan MCP server 'filesystem' failed to start. Jalankan claude --debug dan anda biasanya akan melihat Error: spawn npx ENOENT — arahan tersebut tiada dalam PATH ejen. Runtime hilang atau tidak berada di lokasi yang dicari oleh ejen: Node tidak dipasang, npx tiada, atau venv Python dirujuk menggunakan nama ringkas. Betulkan arahan kepada laluan mutlak atau pasang runtime tersebut, kemudian sambung semula.
Pelayan stdio bersambung, kemudian terputus serta-merta. Klien mencatat ralat parsing JSON — seperti Unexpected token 'S', "Server sta"... is not valid JSON atau Failed to parse message. Punca kegagalan adalah sama: pelayan menulis baris log ke stdout. Pada stdio, stdout adalah saluran JSON-RPC, jadi sebarang teks asing akan merosakkan aliran data dan proses handshake akan terhenti. Dalam Node, console.log pergi ke stdout — gunakan console.error. Dalam Python, print() ringkas pergi ke stdout — tulis log dengan logging yang dikonfigurasi ke sys.stderr, atau hantar file=sys.stderr. Peraturannya adalah mutlak: pada stdio, hanya JSON-RPC pada stdout, semua input manusia pada stderr.
Pelayan jauh mengalami timeout atau tertutup semasa mid-handshake. Klien gagal dengan MCP error -32000: Connection closed, atau Inspector tergantung pada Connect dan tidak menyenaraikan alatan. Di belakang nginx ini adalah masalah buffering: proxy menahan aliran SSE dan bukannya melakukan flush, menyebabkan klien menunggu respons yang tidak kunjung tiba. Tambah proxy_buffering off; (dan baki blok dalam Langkah 4) ke dalam location. Sahkan dengan curl -N terhadap URL awam — anda sepatutnya melihat data acara tiba secara berperingkat, bukan semuanya sekali gus di penghujung.
Pengesahan (Auth) ditolak. Klien melaporkan Error POSTing to endpoint (HTTP 401) atau secara jelas 401 Unauthorized. Sama ada pengepala (header) hilang, token salah, atau pemboleh ubah shell adalah kosong semasa klien membaca konfigurasi — ini adalah perangkap biasa, kerana ${MCP_TOKEN} berkembang menjadi kosong jika pemboleh ubah tidak ditetapkan dan nginx kemudian melihat Bearer tanpa nilai. Gunakan echo pada pemboleh ubah, tambah semula pengepala, dan sahkan bait yang tepat sepadan dengan token dalam nginx if.
Perkhidmatan tidak dapat dimulakan di bawah systemd. journalctl -u mcp-ops menunjukkan ModuleNotFoundError: No module named 'mcp' — ExecStart merujuk kepada Python sistem dan bukannya interpreter venv. Atau Address already in use — proses lain sedang menggunakan port 8000; cari proses tersebut dengan sudo ss -ltnp | grep 8000.
FAQ
Apakah sebenarnya pelayan MCP?
Ia adalah program yang mendedahkan alatan dan sumber kepada klien AI melalui Model Context Protocol, menggunakan JSON-RPC 2.0. Model AI tidak menjalankan alatan itu sendiri — ia meminta klien, klien memanggil pelayan MCP, dan pelayan melaksanakan serta mengembalikan hasil. Oleh sebab protokol ini adalah standard, satu pelayan boleh berfungsi dengan mana-mana klien yang patuh, sama ada Claude Code, Claude Desktop, atau Gemini CLI.
Apakah perbezaan antara pengangkutan stdio dan HTTP?
Pelayan stdio dilancarkan oleh klien sebagai proses anak dan berkomunikasi melalui stdin/stdout, jadi ia hanya aktif selagi klien pada satu mesin itu aktif dan tidak memerlukan rangkaian atau pengesahan. Pelayan HTTP adalah perkhidmatan rangkaian yang berjalan lama yang boleh dicapai oleh banyak klien secara serentak, sebab itulah ia memerlukan TLS dan pengesahan. Gunakan stdio untuk alatan tempatan bagi pengguna tunggal; gunakan HTTP (Streamable HTTP pada pelayan semasa) untuk apa-apa yang dikongsi atau bersifat kekal.
Bagaimanakah saya mengamankan pelayan MCP jarak jauh?
Anggaplah ia memberikan akses alatan kepada fail, pangkalan data, atau shell anda, dan jangan sesekali mendedahkannya tanpa pengesahan. Cara terbaik adalah dengan mengekalkannya terikat pada localhost dan mengaksesnya melalui terowong SSH atau VPN peribadi; jika ia mesti bersifat awam, letakkannya di belakang reverse proxy yang mewajibkan bearer token atau aliran MCP OAuth. Jana token dengan openssl rand -hex 32 dan jangan sesekali mengikat pelayan kepada 0.0.0.0 tanpa salah satu daripada kaedah ini di hadapannya.
Bagaimanakah saya menyahpepijat pelayan yang tidak mahu bermula?
Pertama, semak claude mcp list — ✗ Failed to connect dengan spawn ... ENOENT bermaksud arahan atau runtime tidak ditemui, jadi betulkan laluan (path) atau pasang ia. Jika ia bersambung kemudian terputus dengan ralat parse JSON, pelayan sedang log ke stdout dan merosakkan aliran JSON-RPC; pindahkan semua log ke stderr. Untuk perkara lain, jalankan arahan tepat di bawah MCP Inspector, yang menjalankan pelayan secara terasing supaya anda boleh membezakan pepijat pelayan daripada pepijat konfigurasi klien.