Menjalankan Server MCP di VPS untuk AI Coding Agent
Panduan menjalankan server MCP di VPS: bandingkan transport stdio dan HTTP remote, gunakan systemd, TLS, dan autentikasi agar endpoint tool tidak terbuka.
Yang Anda bangun
Dua setup MCP yang berfungsi pada satu VPS. Pertama, server stdio, yaitu tool filesystem atau database yang dijalankan Claude Code sebagai child process dan berkomunikasi melalui pipe. Berikutnya, server HTTP remote yang berjalan sebagai network service jangka panjang di belakang systemd dan reverse proxy nginx dengan TLS. Server ini dapat diakses oleh MCP client mana pun yang diarahkan ke sana. Instalasi untuk masing-masing setup singkat. Sebagian besar panduan ini membahas dua hal yang paling sering menyebabkan masalah: menjaga stream JSON-RPC tetap bersih dan tidak pernah menempatkan endpoint tool tanpa autentikasi di Internet publik.
Apa sebenarnya MCP
Model Context Protocol adalah cara standar bagi klien AI, Claude Code, Claude Desktop, Gemini CLI pada VPS, atau skrip Anda sendiri, untuk memanggil tool eksternal dan membaca resource eksternal. Model itu sendiri tidak menjalankan apa pun. Model meminta klien, lalu klien berkomunikasi dengan server MCP melalui JSON-RPC 2.0. Server menjalankan tool dan mengembalikan hasilnya. Klien tersebut adalah komponen yang dimaksud ketika orang menyebut agent harness: loop di sekitar model yang mengelola daftar tool, pemeriksaan izin, dan status sesi. MCP hanya merupakan cara untuk memperluas bagian tool tersebut. Dengan satu protokol, server yang Anda tulis satu kali dapat digunakan oleh setiap klien yang mendukung MCP. Jika pembagian ini masih baru bagi Anda, terutama pertanyaan tentang cara model memutuskan untuk menggunakan tool, panduan bertahap tentang dasar-dasar agent layak dipelajari selama satu jam sebelum Anda memberikan kredensial sungguhan kepada server semacam ini.
Ada dua transport, dan bagian lain dalam panduan ini terbagi berdasarkan keduanya:
- stdio. Klien menjalankan server sebagai proses anak, lalu bertukar pesan JSON-RPC yang dipisahkan oleh baris baru melalui input standar dan output standarnya. Tidak ada jaringan, port, atau autentikasi. Batas kepercayaan berada pada proses itu sendiri. Hampir semua tool lokal didistribusikan dengan cara ini.
- Streamable HTTP (dan pendahulunya, HTTP+SSE). Server merupakan web service yang berjalan terus-menerus. Klien terhubung melalui HTTP, dan server dapat mengalirkan respons kembali sebagai Server-Sent Events. Cara ini digunakan untuk berbagi satu server dengan banyak klien atau menjalankan tool yang harus selalu aktif pada mesin tersebut.
Pilih stdio jika tool hanya diperlukan oleh satu mesin dan satu pengguna. Pilih HTTP jika tool tersebut merupakan service bersama.
Prasyarat dan hal penting yang perlu diketahui
Anggap Anda menggunakan KVM VPS Ubuntu 24.04 yang baru dengan akses root atau sudo. Selain itu:
- Runtime yang digunakan untuk menjalankan server. Sebagian besar server referensi menggunakan Node atau Python. Ubuntu 24.04 menyertakan Node 18, sedangkan beberapa paket MCP terbaru memerlukan Node 20 atau yang lebih baru. Karena itu, instal LTS terbaru dari NodeSource atau nvm, bukan mengandalkan
apt. Python 3.12 sudah tersedia. - Domain dan DNS A record, tetapi hanya server HTTP jarak jauh yang memerlukannya; TLS memerlukan nama yang mengarah ke VPS ini. Contoh stdio sama sekali tidak memerlukan DNS.
- RAM 512 MB sudah lebih dari cukup. Server MCP adalah proses JSON-RPC yang ringan. Penggunaan memorinya ditentukan oleh komponen yang diakses tool Anda, seperti driver database atau cache file, bukan oleh protokolnya.
- Spesifikasinya masih baru dan terus berubah. Revisi 2025-03-26 mengganti HTTP+SSE dengan Streamable HTTP dan menandai SSE sebagai deprecated. SSE masih berfungsi dan banyak server masih menggunakannya. Karena itu, anggap konfigurasi transport apa pun sebagai hal yang perlu diperiksa ulang berdasarkan release notes server, bukan sebagai aturan mutlak.
Langkah 1: sambungkan server stdio ke Claude Code
Mulai dengan server filesystem. Server ini resmi, aktif dipelihara, dan hanya memerlukan Node. Satu perintah berikut mendaftarkannya ke Claude Code dan membatasi cakupannya ke project saat ini sehingga konfigurasi tersimpan dalam file yang dapat di-commit:
cd /home/matt/projects/api
claude mcp add --scope project --transport stdio filesystem \
-- npx -y @modelcontextprotocol/server-filesystem /home/matt/projects/apiPemisah -- penting: semua bagian setelahnya adalah perintah yang akan dijalankan Claude Code, bukan flag untuk Claude Code. Perintah tersebut menulis .mcp.json di root project:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/home/matt/projects/api"
]
}
}
}Belum ada proses yang berjalan. Saat Anda menjalankan Claude Code di direktori ini berikutnya, agent membaca .mcp.json, menjalankan npx -y @modelcontextprotocol/server-filesystem ... sebagai proses child, lalu melakukan handshake MCP melalui stdin/stdout proses tersebut. Pastikan konfigurasi berhasil diterapkan:
claude mcp listServer yang berjalan normal menampilkan perintahnya dan tanda centang hijau, filesystem: npx -y @modelcontextprotocol/server-filesystem ... - ✓ Connected. Di dalam session, slash command /mcp menampilkan daftar tool yang disediakan server (read_file, write_file, list_directory), dan agent kini dapat memanggilnya pada path yang Anda izinkan. Tool database memiliki pola yang sama: ganti package dan berikan connection string sebagai argumen terakhirnya. Namun, periksa repository server tersebut untuk mengetahui nama package terbaru, karena reference server Postgres telah berpindah pengelola lebih dari satu kali.
Inilah alasan utama menjalankan agent di server tersebut: session Claude Code berjalan di VPS di dalam tmux, dan server stdio-nya berjalan tepat di sebelahnya dengan akses langsung ke file project serta service lokal, tanpa round-trip jaringan. Setelah agent memiliki write_file dan read_file, sebaiknya pasangkan akses tersebut dengan skill yang mengarahkannya untuk menerapkan perubahan terkecil yang berfungsi, karena filesystem tool membuat penulisan ulang besar sama mudahnya dengan perbaikan dua baris. Integrasi yang sama juga berlaku di luar file lokal: jika Anda sudah menjalankan search engine di VPS, Anda dapat memberikan instance SearXNG milik Anda sendiri kepada agent sebagai search tool. Dengan begitu, query tetap diproses di server Anda, tetapi teks halaman yang tidak tepercaya langsung dimasukkan ke context yang kemudian digunakan agent untuk bertindak.
Langkah 2: membuat server HTTP jarak jauh
Server stdio berhenti bersama proses induknya dan dibuat satu kali untuk setiap klien. Jadi, jika Anda menjalankan dua sesi Claude Code pada server yang saling menyerahkan pekerjaan, masing-masing sesi mendapatkan salinan pribadi alat tersebut. Jika Anda memerlukan alat yang tetap berjalan untuk setiap klien, alat operasi bersama, gateway database, atau sesuatu yang dipanggil oleh laptop dan CI, Anda memerlukan transport HTTP dan service yang benar-benar berjalan. Berikut server Python minimal yang menggunakan SDK resmi dan menyediakan satu alat:
# /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". Server hanya melakukan binding ke localhost. Tidak ada koneksi dari luar server yang dapat mengaksesnya secara langsung. Ini adalah konfigurasi yang tepat sebelum autentikasi tersedia. Instal server ini dalam virtualenv khusus agar systemd memiliki path 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: menjaganya tetap berjalan dengan systemd
Tool yang mati ketika agent membutuhkannya lebih buruk daripada tidak memiliki tool sama sekali. Hal ini terutama penting ketika client itu sendiri merupakan proses yang berjalan lama: agent yang selalu aktif dan mempertahankan memori serta jadwalnya setelah reboot akan memanggil tool ini sesuai jadwal tanpa ada yang mengawasi. Karena itu, server juga harus dapat aktif kembali secara mandiri. 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.targetPath absolut ke Python venv di ExecStart bersifat wajib. Arahkan ke /usr/bin/python3 agar proses dimulai dengan ModuleNotFoundError: No module named 'mcp', karena interpreter sistem tidak pernah melihat pip install. Aktifkan dan periksa:
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 harus menampilkan active (running). curl menghasilkan HTTP/1.1 400 Bad Request dengan error JSON-RPC di dalam body. Request tersebut tidak membawa session dan tidak memiliki payload JSON yang valid. Itulah hasil yang diharapkan: hasil tersebut membuktikan bahwa port merespons dan berbicara menggunakan protokol tersebut. Connection refused atau respons kosong berarti proses tidak terikat pada alamat yang Anda perkirakan. Baca journalctl -u mcp-ops -n 50.
Langkah 4: tempatkan TLS dan reverse proxy di depan
Server mendengarkan pada localhost. Agar dapat diakses dari mana saja, lakukan TLS termination di nginx lalu teruskan trafik ke dalam. Instal nginx, dapatkan sertifikat menggunakan Certbot dan Let's Encrypt pada nginx, lalu tulis blok location. Bagian yang penting adalah menonaktifkan buffering karena perilaku default nginx menahan respons hingga selesai. Hal ini dapat menghentikan aliran SSE tanpa batas waktu:
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 ulang dengan sudo nginx -t && sudo systemctl reload nginx. Jika Anda sudah menjalankan banyak container, pekerjaan yang sama dapat dilakukan oleh Traefik reverse proxy dengan TLS otomatis. Traefik menerbitkan sertifikat dan merutekan trafik berdasarkan hostname. Anda hanya perlu menambahkan label ke container MCP. Bagaimanapun, reverse proxy kini menjadi satu-satunya komponen pada port publik dan mengarah ke service yang belum Anda amankan. Perbaiki hal tersebut sebelum mendaftarkan URL di mana pun.
Langkah 5: aturan keamanan yang paling penting dalam topik ini
Jangan pernah mengekspos endpoint MCP tanpa autentikasi. Server MCP bukan API hanya-baca. Server ini memberikan akses ke tool, file, database, dan terkadang shell. /mcp yang terbuka di Internet publik memungkinkan orang asing memiliki akses yang sama seperti agen AI Anda: mereka dapat mencantumkan tool Anda, lalu memanggilnya. Perlakukan endpoint tersebut persis seperti socket admin tanpa autentikasi karena memang itulah fungsinya. Dampak token yang dicuri juga bergantung pada server di baliknya: server MCP hanya-baca yang disertakan dalam pelacak latihan openGym hanya dapat mengembalikan data latihan, sedangkan tool filesystem atau shell dapat memberikan akses ke mesin.
Tiga pertahanan berikut diurutkan berdasarkan preferensi:
- Jangan publikasikan endpoint tersebut. Pertahankan server pada
127.0.0.1dan akses dari laptop melalui tunnel SSH:ssh -L 8000:127.0.0.1:8000 matt@vps, lalu arahkan client kehttp://127.0.0.1:8000/mcp. Tidak ada bagian yang terekspos. - Tempatkan server pada jaringan privat. Bind alamat tunnel dari VPN WireGuard yang di-host sendiri dan izinkan hanya peer VPN untuk mengaksesnya. Internet publik akan melihat port yang tertutup.
- Jika harus dipublikasikan, wajibkan token. Pilihan yang tepat adalah alur OAuth MCP yang didukung secara native oleh transport HTTP. Minimum praktisnya adalah shared bearer token yang diperiksa pada proxy. Cara ini murah dan sepenuhnya menghentikan akses coba-coba:
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...
}Buat token dengan openssl rand -hex 32, dan jangan pernah melakukan bind server itu sendiri ke 0.0.0.0 tanpa salah satu perlindungan tersebut di depannya. Client kemudian mengirimkan token 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 di shell agar secret tidak pernah tersimpan sebagai teks biasa di .mcp.json. Claude Code memperluas ${MCP_TOKEN} dari environment saat membaca nilainya.
Setiap pertahanan di atas melindungi endpoint, bukan agen yang sudah memegang token. Itu adalah bagian lain dari masalah ini: jika client Anda adalah DeepSeek Harness, plugin yang membatasi tool yang boleh dipanggil agen dan memindai output tool untuk mencari instruksi yang disisipkan melindungi sisi tersebut.
Langkah 6: lakukan debug dengan MCP Inspector
Jika server bermasalah, jangan menebak dari dalam agent. Jalankan pengujian secara langsung dengan Inspector, yaitu klien pengujian resmi berbasis web. Untuk server stdio, berikan perintah yang sama dengan yang dijalankan agent:
npx @modelcontextprotocol/inspector \
npx -y @modelcontextprotocol/server-filesystem /tmpInspector memulai UI pada http://localhost:6274 (versi terbaru menampilkan URL dengan query string MCP_PROXY_AUTH_TOKEN; gunakan tautan persis tersebut, atau UI akan menolak koneksi) dan proxy pada 6277. Klik Connect, lalu List Tools, kemudian Call Tool dengan argumen nyata. Jika berhasil di Inspector tetapi gagal di agent, masalahnya terdapat pada konfigurasi klien, bukan pada server. Untuk server HTTP jarak jauh, pilih transport Streamable HTTP, masukkan https://mcp.example.com/mcp, tambahkan header Authorization, lalu lakukan koneksi. Ini adalah cara tercepat untuk memastikan autentikasi dan proxy sudah benar sebelum agent digunakan.
Memperbarui server
MCP berkembang cepat, jadi terapkan patch sesuai jadwal. Server Node yang diluncurkan dengan npx -y mengambil versi terbaru setiap kali dibuat. Cara ini praktis, tetapi tidak menghasilkan build yang reproducible. Setelah sebuah server menjadi penting, tetapkan versi persis yang telah diuji, baca versinya dari npm view @modelcontextprotocol/server-filesystem version, lalu tambahkan versi tersebut ke nama paket dalam .mcp.json (@modelcontextprotocol/server-filesystem@<version>). Naikkan versinya secara sengaja. Server Python di bawah systemd diperbarui dengan sudo -H -u mcp /opt/mcp-ops/.venv/bin/pip install -U "mcp[cli]", kemudian sudo systemctl restart mcp-ops. Saat melakukan upgrade, perhatikan revisi spesifikasi yang menjadi target SDK Anda. Perubahan yang melintasi batas SSE ke Streamable-HTTP dapat mengubah transport yang harus diminta klien Anda.
Mode kegagalan, beserta string yang akan Anda lihat
Agent menunjukkan bahwa server 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: perintah tersebut tidak ada di PATH agent. Runtime tidak tersedia atau berada di lokasi yang tidak diperiksa agent: Node belum diinstal, npx tidak ada, atau Python dari virtualenv dirujuk menggunakan nama singkat. Perbaiki perintah agar menggunakan path absolut atau instal runtime tersebut, lalu hubungkan ulang.
Server stdio terhubung, lalu langsung terputus. Client mencatat error penguraian JSON, misalnya Unexpected token 'S', "Server sta"... is not valid JSON atau Failed to parse message. Penyebabnya selalu sama: server menulis baris log ke stdout. Pada stdio, stdout adalah channel JSON-RPC. Karena itu, teks tambahan apa pun akan merusak stream dan menyebabkan handshake gagal. Di Node, console.log mengarah ke stdout; gunakan console.error. Di Python, print() tanpa konfigurasi mengarah ke stdout. Tulis log menggunakan logging yang dikonfigurasi ke sys.stderr, atau teruskan file=sys.stderr. Aturannya mutlak: pada stdio, stdout hanya boleh berisi JSON-RPC; semua teks untuk manusia harus dikirim ke stderr.
Server remote mengalami timeout atau menutup koneksi di tengah handshake. Client gagal dengan MCP error -32000: Connection closed, atau Inspector berhenti merespons pada Connect dan tidak pernah menampilkan tools. Jika menggunakan nginx, penyebabnya adalah buffering: proxy menahan stream SSE dan tidak segera mengirimkannya, sehingga client menunggu respons yang tidak pernah tiba. Tambahkan proxy_buffering off; (beserta blok lainnya pada Step 4) ke location. Konfirmasi dengan curl -N terhadap URL publik. Data event seharusnya tiba secara bertahap, bukan sekaligus pada akhir koneksi.
Autentikasi ditolak. Client melaporkan Error POSTing to endpoint (HTTP 401) atau secara langsung 401 Unauthorized. Penyebabnya dapat berupa header yang tidak ada, token yang salah, atau variabel shell yang kosong ketika client membaca konfigurasi. Ini adalah masalah umum karena ${MCP_TOKEN} akan menghasilkan nilai kosong jika variabel tersebut belum ditetapkan, lalu nginx menerima Bearer tanpa nilai. Tampilkan nilai variabel, tambahkan kembali header, dan verifikasi bahwa byte-nya sama persis dengan token dalam if nginx.
Service tidak dapat dijalankan oleh systemd. journalctl -u mcp-ops menampilkan ModuleNotFoundError: No module named 'mcp', sedangkan ExecStart menunjuk ke Python sistem, bukan interpreter venv. Atau, Address already in use menunjukkan bahwa proses lain menggunakan 8000; cari proses tersebut dengan sudo ss -ltnp | grep 8000.
FAQ
Apa sebenarnya server MCP itu?
Server MCP adalah program yang mengekspos tool dan resource kepada client AI melalui Model Context Protocol dengan menggunakan JSON-RPC 2.0. Model AI tidak pernah menjalankan tool secara langsung. Model meminta client untuk menjalankannya, client memanggil server MCP, lalu server mengeksekusi permintaan dan mengembalikan hasilnya. Karena protokol ini bersifat standar, satu server dapat digunakan oleh client apa pun yang kompatibel, baik Claude Code, Claude Desktop, maupun Gemini CLI.
Apa perbedaan antara transportasi stdio dan HTTP?
Server stdio diluncurkan oleh client sebagai proses anak dan berkomunikasi melalui stdin/stdout. Karena itu, server tersebut hanya berjalan selama satu client pada satu mesin masih berjalan dan tidak memerlukan jaringan atau autentikasi. Server HTTP adalah layanan jaringan yang berjalan terus-menerus dan dapat diakses oleh banyak client secara bersamaan. Karena itu, server ini memerlukan TLS dan autentikasi. Gunakan stdio untuk tool lokal dengan satu pengguna. Gunakan HTTP (Streamable HTTP pada server saat ini) untuk layanan yang digunakan bersama atau harus berjalan terus-menerus.
Bagaimana cara mengamankan server MCP jarak jauh?
Anggap server tersebut memberikan akses tool ke file, database, atau shell Anda. Jangan pernah mengeksposnya tanpa autentikasi. Pilihan terbaik adalah mengikatnya hanya ke localhost, lalu mengaksesnya melalui tunnel SSH atau VPN privat. Jika server harus tersedia secara publik, tempatkan server di belakang reverse proxy yang menerapkan bearer token atau alur OAuth MCP. Buat token dengan openssl rand -hex 32 dan jangan pernah mengikat server ke 0.0.0.0 tanpa salah satu mekanisme tersebut di depannya.
Bagaimana cara men-debug server yang tidak mau start?
Pertama, periksa claude mcp list. ✗ Failed to connect dengan spawn ... ENOENT berarti command atau runtime tidak tersedia. Perbaiki path atau instal komponen tersebut. Jika server berhasil terhubung lalu terputus dengan error parsing JSON, berarti server menulis log ke stdout dan merusak stream JSON-RPC. Pindahkan semua logging ke stderr. Untuk masalah lainnya, jalankan command yang sama persis melalui MCP Inspector. Tool ini menjalankan server secara terisolasi sehingga Anda dapat membedakan bug pada server dari bug pada konfigurasi client.