Paano mag-setup ng MCP server sa VPS
Matutunan ang pag-deploy ng stdio at remote HTTP transports gamit ang systemd at nginx. Alamin ang tamang paraan para sa TLS at auth sa iyong MCP setup.
Ang iyong bubuuin
Dalawang gumaganap na MCP setup sa isang VPS. Una ay isang stdio server — isang filesystem o database tool na ilulunsad ng Claude Code bilang child process at kakausapin sa pamamagitan ng pipe. Pangalawa ay isang remote HTTP server na tumatakbo bilang isang long-lived network service sa likod ng systemd at isang nginx reverse proxy na may TLS, na maaaring ma-access ng anumang MCP client na ituturo mo rito. Maliit lamang ang installation para sa alinman sa dalawa. Ang karamihan sa gabay na ito ay tungkol sa dalawang mahahalagang bagay: ang pagpapanatiling malinis ng JSON-RPC stream, at ang hindi paglalagay ng unauthenticated tool endpoint sa public internet.
Ano ang tunay na MCP
Ang Model Context Protocol ay isang standard na paraan para sa isang AI client — gaya ng Claude Code, Claude Desktop, ang Gemini CLI sa isang VPS, o ang sarili mong script — upang tumawag ng mga external tool at magbasa ng mga external resource. Ang model mismo ay hindi nagpapatakbo ng kahit ano. Nagtatanong ito sa client, ang client ay magpapadala ng JSON-RPC 2.0 sa isang MCP server, at ang server ang magpapatakbo ng tool at ibabalik ang resulta. Dahil iisang protocol lang ang gamit, ang server na isinulat mo nang isang beses ay gagana sa lahat ng client na sumusuporta sa MCP.
Mayroong dalawang transport, at ang natitirang bahagi ng guide na ito ay nakabase sa mga ito:
- stdio. I-spawn ng client ang server bilang isang child process at magpapalitan ng newline-delimited JSON-RPC messages sa pamamagitan ng standard input at standard output nito. Walang network, walang port, at walang auth — ang trust boundary ay ang mismong process. Halos lahat ng local tool ay gumagamit nito.
- Streamable HTTP (at ang mas lumang bersyon nito, HTTP+SSE). Ang server ay isang long-running web service. Kumokonekta ang client via HTTP at maaaring mag-stream ang server ng mga response bilang Server-Sent Events. Ito ang paraan para magamit ang isang server ng maraming client, o para magpatakbo ng tool na kailangang manatili sa machine nang permanente.
Gamitin ang stdio kung ang tool ay para sa iisang machine at iisang user lamang. Gamitin ang HTTP kung ito ay isang shared service.
Mga Prerequisites at ang mga dapat tandaan
I-assume na mayroon kang bagong Ubuntu 24.04 KVM VPS na may root o sudo access. Bukod doon:
- Runtime na ginamit sa server. Karamihan sa mga reference server ay Node o Python. Ang Ubuntu 24.04 ay may kasamang Node 18, ngunit maraming kasalukuyang MCP packages ang nangangailangan ng Node 20 o mas bago. Mag-install ng current LTS mula sa NodeSource o nvm sa halip na umasa sa
apt. Ang Python 3.12 ay naka-install na. - Domain at DNS A record, para lamang sa remote HTTP server — kailangan ng TLS ng pangalan na nagre-resolve sa VPS na ito. Hindi kailangan ng stdio example ang anumang DNS.
- Sapat na ang 512 MB RAM. Ang mga MCP server ay light JSON-RPC processes; ang memory cost ay depende sa tool na ginagamit mo (halimbawa, database driver o file cache), hindi sa protocol.
- Bago pa ang spec at patuloy na nagbabago. Pinalitan ng 2025-03-26 revision ang HTTP+SSE ng Streamable HTTP at idineklara ang SSE bilang deprecated. Gumagana pa rin ang SSE at marami pang servers ang gumagamit nito, kaya i-verify muli ang anumang transport pin laban sa release notes ng server sa halip na ituring itong absolute.
Step 1: i-wire ang isang stdio server sa Claude Code
Magsimula sa filesystem server — ito ay official, laging may update, at Node lang ang kailangan. Ang command sa ibaba ay ire-register ito sa Claude Code at i-scope sa kasalukuyang project para ma-save sa isang committable file:
cd /home/matt/projects/api
claude mcp add --scope project --transport stdio filesystem \
-- npx -y @modelcontextprotocol/server-filesystem /home/matt/projects/apiMahalaga ang -- separator: ang lahat ng kasunod nito ay ang command na tatakbo sa Claude Code, hindi ito flag para sa Claude Code. Isusulat nito ang isang .mcp.json sa project root:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/home/matt/projects/api"
]
}
}
}Wala pang tumatakbong proseso. Sa susunod na i-start mo ang Claude Code sa directory na ito, babasahin ng agent ang .mcp.json, i-spawn ang npx -y @modelcontextprotocol/server-filesystem ... bilang child process, at gagawin ang MCP handshake sa pamamagitan ng stdin/stdout ng prosesong iyon. I-verify kung gumana ito:
claude mcp listAng maayos na server ay magpi-print ng command nito at isang green tick — filesystem: npx -y @modelcontextprotocol/server-filesystem ... - ✓ Connected. Sa loob ng session, ililista ng /mcp slash command ang mga tools na ibinibigay ng server (read_file, write_file, list_directory), at maaari nang gamitin ng agent ang mga ito sa mga path na pinayagan mo. Ganito rin ang structure ng isang database tool — palitan lang ang package at magpasa ng connection string bilang huling argument — pero i-check ang repository ng server para sa tamang package name, dahil ang reference Postgres server ay nagbago na ng maintainer nang higit sa isang beses.
Ito ang pangunahing dahilan ng pagtakbo ng agent sa machine: ang Claude Code session ay tumatakbo sa VPS sa loob ng tmux, at ang mga stdio server nito ay tumatakbo sa tabi nito na may direct access sa mga project files at local services, nang walang network round-trip.
Step 2: bumuo ng remote HTTP server
Namatay ang stdio server kasama ang parent process nito. Kapag kailangan mo ng tool na laging tumatakbo para sa bawat client — gaya ng shared ops tool, database gateway, o anumang tinatawag ng iyong laptop at CI — kailangan mo ng HTTP transport at isang tunay na service. Narito ang isang minimal na Python server gamit ang official SDK, na nag-e-expose ng isang tool:
# /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")Tingnan ang host="127.0.0.1". Ang server ay naka-bind sa localhost lamang — walang makaka-access dito mula sa labas, na siyang tamang configuration bago magkaroon ng auth. I-install ito sa sarili nitong virtualenv para magkaroon ang systemd ng stable na interpreter path:
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]"Step 3: panatilihin itong alive gamit ang systemd
Mas malala ang tool na down kapag tinatawag ito ng agent kaysa sa walang tool. I-write ang /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.targetHindi optional ang absolute path sa venv Python sa ExecStart — ituro ito sa /usr/bin/python3 at magsisimula ang process gamit ang ModuleNotFoundError: No module named 'mcp', dahil hindi nakikita ng system interpreter ang iyong pip install. I-enable at i-check:
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/mcpDapat ang status ay active (running). Ang curl ay babalik ng HTTP/1.1 400 Bad Request na may JSON-RPC error sa body — walang session at walang valid JSON payload ang request — at iyan ang kailangan mo: pinapatunayan nito na sumasagot ang port at gumagana ang protocol. Ang Connection refused o empty reply ay nangangahulugang hindi naka-bind ang process sa inaasahang lugar; basahin ang journalctl -u mcp-ops -n 50.
Step 4: Maglagay ng TLS at reverse proxy sa harap
Nakikinig ang server sa localhost. Para ma-access ito mula sa kahit saan, i-terminate ang TLS sa nginx at i-proxy ang request papasok. I-install ang nginx, kumuha ng certificate gamit ang Certbot at Let's Encrypt sa nginx, at isulat ang location block. Ang kritikal na bahagi ay ang pag-disable ng buffering. Ang default na behavior ng nginx ay hinahawakan ang response hanggang sa mabuo ito, na magiging sanhi ng paghinto ng SSE stream:
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;
}
}I-reload gamit ang sudo nginx -t && sudo systemctl reload nginx. Kung gumagamit ka na ng fleet ng mga container, magagawa ito nang awtomatiko ng Traefik reverse proxy na may automatic TLS — naglalabas ito ng certificate at nag-i-route base sa hostname, at kailangan mo lang magdagdag ng mga label sa MCP container. Sa alinmang paraan, ang reverse proxy na lamang ang nakabukas sa public port, at nakaturo ito sa isang service na hindi mo pa secured. Ayusin ito bago i-register ang URL sa kahit saan.
Step 5: ang security rule na pinakaimportante sa paksang ito
Huwag mag-expose ng unauthenticated MCP endpoint. Ang MCP server ay hindi lamang isang read-only API. Nagbibigay ito ng tool access — sa iyong mga files, database, at kung minsan ay shell. Ang isang bukas na /mcp sa public internet ay parang isang estranghero na may kaparehong access gaya ng iyong AI agent: ililista nila ang iyong mga tools, at pagkatapos ay tatawagin ang mga ito. Ituring ito na parang isang unauthenticated admin socket, dahil iyon talaga ito.
Tatlong depensa, ayon sa pagkakasunod-sunod ng preference:
- Huwag itong i-publish. Panatilihin ang server sa
127.0.0.1at i-access ito mula sa iyong laptop gamit ang SSH tunnel:ssh -L 8000:127.0.0.1:8000 matt@vps, pagkatapos ay i-point ang client sahttp://127.0.0.1:8000/mcp. Walang anumang exposed. - Ilagay ito sa isang private network. I-bind ang tunnel address ng isang self-hosted WireGuard VPN at hayaan lamang ang mga VPN peers ang makaka-access dito. Ang public internet ay makakakita lamang ng closed port.
- Kung kailangang public, mangailangan ng token. Ang tamang solusyon ay ang MCP OAuth flow na natively na suportado ng HTTP transport. Ang pinakapraktikal na minimum ay isang shared bearer token na chine-check sa proxy — madali itong i-setup, at pinipigilan nito ang mga drive-by attack:
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...
}I-generate ang token gamit ang openssl rand -hex 32, at huwag kailanman i-bind ang server mismo sa 0.0.0.0 nang walang isa sa mga ito sa harap nito. Ipadadala ng client ang token bilang isang header. Sa Claude Code:
claude mcp add --scope project --transport http ops-tools https://mcp.example.com/mcp \
--header 'Authorization: Bearer ${MCP_TOKEN}'I-set ang MCP_TOKEN sa iyong shell para hindi ma-save ang secret sa .mcp.json sa plaintext — kukunin ng Claude Code ang ${MCP_TOKEN} mula sa environment sa read time.
Step 6: i-debug gamit ang MCP Inspector
Kapag may error sa server, huwag manghula mula sa loob ng agent — gamitin nang direkta ang Inspector, ang official web-based test client. Para sa stdio server, gamitin ang command na katulad ng pinapatakbo ng agent:
npx @modelcontextprotocol/inspector \
npx -y @modelcontextprotocol/server-filesystem /tmpMagbubukas ito ng UI sa http://localhost:6274 (ang mga bagong version ay naglalabas ng URL na may MCP_PROXY_AUTH_TOKEN query string — gamitin ang eksaktong link na iyon para hindi ma-reject ang UI) at isang proxy sa 6277. I-click ang Connect, pagkatapos ay List Tools, at pagkatapos ay Call Tool gamit ang mga totoong arguments. Kung gumagana ito sa Inspector pero fail sa agent, ang bug ay nasa client config at hindi sa server. Para sa remote HTTP server, piliin ang Streamable HTTP transport, i-enter ang https://mcp.example.com/mcp, idagdag ang Authorization header, at i-connect — ito ang pinakamabilis na paraan para ma-verify kung tama ang auth at ang proxy bago isama ang agent.
Pagpapanatili ng updated na mga server
Mabilis ang pagbabago sa MCP, kaya mag-patch ayon sa schedule. Ang mga Node server na ni-launch gamit ang npx -y ay kumukuha ng pinakabagong version sa bawat spawn. Convenient ito pero hindi ito reproducible; i-pin ang eksaktong version na iyong na-test — basahin ito mula sa npm view @modelcontextprotocol/server-filesystem version at idagdag sa package name sa .mcp.json (@modelcontextprotocol/server-filesystem@<version>) — mahalaga ito kapag tumatakbo na ang server, at i-update ito nang may intensyon. Ang mga Python server sa ilalim ng systemd ay nag-uupdate gamit ang sudo -H -u mcp /opt/mcp-ops/.venv/bin/pip install -U "mcp[cli]" na sinusundan ng sudo systemctl restart mcp-ops. Bantayan ang spec revision na target ng iyong SDK kapag nag-uupgrade — ang pagtalon sa SSE-to-Streamable-HTTP boundary ay maaaring magpabago sa transport na dapat i-request ng iyong mga client.
Failure modes, with the strings you will see
Nagpapakita ang agent na failed ang server. Nagpi-print ang claude mcp list ng ✗ Failed to connect, at ang TUI ay nag-uulat ng MCP server 'filesystem' failed to start. Patakbuhin ang claude --debug at karaniwang makikita ang Error: spawn npx ENOENT — wala ang command sa PATH ng agent. Missing ang runtime o hindi ito nasa lokasyon na hinahanap ng agent: hindi naka-install ang Node, wala ang npx, o ang Python virtualenv ay tinatawag gamit ang bare name. Ayusin ang command gamit ang absolute path o i-install ang runtime, pagkatapos ay mag-reconnect.
Nag-connect ang isang stdio server, pero agad itong nag-drop. Nag-log ang client ng JSON parse error — gaya ng Unexpected token 'S', "Server sta"... is not valid JSON o Failed to parse message. Laging iisa ang sanhi nito: may isinulat na log line ang server sa stdout. Sa stdio, ang stdout ang JSON-RPC channel, kaya ang anumang stray text ay nakakasira sa stream at nagpapatigil sa handshake. Sa Node, ang console.log ay pumupunta sa stdout — gamitin ang console.error. Sa Python, ang bare print() ay pumupunta sa stdout — isulat ang mga log gamit ang logging na naka-configure sa sys.stderr, o ipasa ang file=sys.stderr. Ang rule ay absolute: sa stdio, JSON-RPC lang ang dapat nasa stdout, at lahat ng human-readable text ay dapat nasa stderr.
Nag-timeout o nag-close ang remote server habang nasa gitna ng handshake. Nag-fail ang client nang may MCP error -32000: Connection closed, o nag-hang ang Inspector sa Connect at hindi nagpapakita ng mga tools. Kapag nasa likod ng nginx, ito ay buffering: hinahawakan ng proxy ang SSE stream sa halip na i-flush ito, kaya naghihintay ang client sa response na hindi darating. Idagdag ang proxy_buffering off; (at ang iba pang bahagi ng block sa Step 4) sa location. I-verify gamit ang curl -N laban sa public URL — dapat makita ang incremental na pagdating ng event data, hindi lahat nang sabay-sabay sa dulo.
Rejected ang Auth. Nag-uulat ang client ng Error POSTing to endpoint (HTTP 401) o 401 Unauthorized. Maaaring missing ang header, mali ang token, o walang laman ang shell variable noong binasa ng client ang config — isang karaniwang trap, dahil ang ${MCP_TOKEN} ay nagiging empty kung unset ang variable at makikita ng nginx ang Bearer na walang value. I-echo ang variable, i-add muli ang header, at i-verify kung ang eksaktong bytes ay tugma sa token sa nginx if.
Hindi mag-start ang service sa ilalim ng systemd. Nagpapakita ang journalctl -u mcp-ops ng ModuleNotFoundError: No module named 'mcp' — itinuturo ng ExecStart ang system Python sa halip na ang venv interpreter. O Address already in use — may ibang process na gumagamit ng port 8000; hanapin ito gamit ang sudo ss -ltnp | grep 8000.
FAQ
Ano ba talaga ang MCP server?
Isa itong program na naglalabas ng mga tools at resources sa isang AI client gamit ang Model Context Protocol sa pamamagitan ng JSON-RPC 2.0. Hindi direktang pinapatakbo ng AI model ang tool — humihiling ito sa client, tatawagin ng client ang MCP server, at isasagawa ng server ang request at ibabalik ang resulta. Dahil standard ang protocol, gagana ang isang server sa anumang compliant na client, gaya ng Claude Code, Claude Desktop, o Gemini CLI.
Ano ang pagkakaiba ng stdio at HTTP transport?
Ang stdio server ay pinapatakbo ng client bilang isang child process at nakikipag-communicate sa pamamagitan ng stdin/stdout. Dahil dito, nakadepende ito sa isang client sa isang machine at hindi nangangailangan ng network o authentication. Ang HTTP server ay isang long-running network service na maaaring ma-access ng maraming client nang sabay-sabay, kaya kailangan nito ng TLS at authentication. Gamitin ang stdio para sa mga local at single-user na tools; gamitin ang HTTP (Streamable HTTP sa kasalukuyang mga server) para sa anumang shared o persistent na serbisyo.
Paano i-secure ang isang remote MCP server?
Isipin na nagbibigay ang server ng access sa iyong mga files, database, o shell, kaya huwag itong hayaang ma-access nang walang authentication. Pinakamainam na i-bind ito sa localhost at i-access gamit ang SSH tunnel o private VPN. Kung kailangang i-expose sa publiko, ilagay ito sa likod ng isang reverse proxy na nagpapatupad ng bearer token o MCP OAuth flow. I-generate ang token gamit ang openssl rand -hex 32 at huwag i-bind ang server sa 0.0.0.0 nang walang ganitong proteksyon sa harap nito.
Paano i-debug ang server na hindi nag-uumpisa?
Unang i-check ang claude mcp list — ang ✗ Failed to connect kasama ang spawn ... ENOENT ay nangangahulugang missing ang command o runtime, kaya ayusin ang path o i-install ito. Kung kumokonekta ito pero nagka-crash dahil sa JSON parse error, ang server ay naglalabas ng logs sa stdout na sumisira sa JSON-RPC stream; ilipat ang lahat ng logging sa stderr. Para sa iba pang isyu, patakbuhin ang eksaktong command gamit ang MCP Inspector. Pinapatakbo nito ang server nang isolated para malaman kung ang bug ay nasa server o nasa client-config.