SSD Nodes Learn Hosting plans →
Mga Gabay Matt ConnorNi Matt Connor · Na-update 2026-08-31

Paano Magpatakbo ng MCP Server sa VPS para sa AI

Alamin ang stdio at remote HTTP MCP setup sa VPS, kasama ang systemd, nginx TLS, authentication, at pag-aayos ng JSON-RPC error na nagiging sanhi ng failed connections.

Ang ginagawa mo

Dalawang gumaganang MCP setup sa isang VPS. Una, isang stdio server: filesystem o database tool na inilulunsad ng Claude Code bilang child process at kinakausap sa pamamagitan ng pipe. Pagkatapos, isang remote HTTP server na tumatakbo bilang matagalang network service sa likod ng systemd at nginx reverse proxy na may TLS. Maa-access ito ng anumang MCP client na ituturo mo rito. Maliit ang installation para sa alinman sa mga ito. Ang pinakamahahalagang bahagi ng gabay na ito ay ang dalawang problemang karaniwang nagdudulot ng abala: panatilihing malinis ang JSON-RPC stream at huwag kailanman maglagay ng tool endpoint na walang authentication sa public internet.

Ano talaga ang MCP

Ang Model Context Protocol ay isang standard na paraan para ang isang AI client—Claude Code, Claude Desktop, ang Gemini CLI sa isang VPS, o sarili mong script—ay tumawag sa external tools at magbasa ng external resources. Ang model mismo ay walang direktang pinapatakbo. Humihingi ito sa client, nakikipag-usap ang client sa isang MCP server gamit ang JSON-RPC 2.0, pinapatakbo ng server ang tool, at ibinabalik nito ang resulta. Ang client na ito ang tinutukoy ng mga tao kapag sinasabi nilang agent harness: ito ang loop sa paligid ng model na nagmamay-ari ng tool list, permission checks, at session state. Ang MCP ay simpleng paraan para ma-extend ang tool layer nito. Iisa ang protocol, kaya ang server na isang beses mong isinulat ay gagana sa bawat client na gumagamit ng MCP. Kung bago sa iyo ang paghahating ito, lalo na ang tanong kung paano nagpapasya ang isang model na gumamit ng tool, sulit na paglaanan ng isang oras ang sunod-sunod na pag-aaral sa mga pundasyon ng agent bago mo bigyan ng totoong credentials ang alinman sa mga server na ito.

May dalawang transport, at ayon sa mga ito hinahati ang buong natitirang bahagi ng gabay na ito:

  • stdio. Sini-spawn ng client ang server bilang child process at nagpapalitan sila ng newline-delimited JSON-RPC messages sa standard input at standard output nito. Walang network, port, o auth; ang process mismo ang trust boundary. Halos lahat ng local tool ay ipinapamahagi sa ganitong paraan.
  • Streamable HTTP (at ang mas lumang katumbas nitong HTTP+SSE). Ang server ay isang matagalang web service. Kumokonekta ang client sa pamamagitan ng HTTP, at maaaring mag-stream ang server ng mga response pabalik bilang Server-Sent Events. Ito ang paraan para maibahagi ang isang server sa maraming client, o magpatakbo ng tool na kailangang manatiling permanenteng gumagana sa machine.

Piliin ang stdio kapag para sa iisang machine at iisang user ang tool. Piliin ang HTTP kapag shared service ito.

Mga prerequisite at mahahalagang limitasyon

Ipagpalagay na bagong Ubuntu 24.04 KVM VPS ito na may root o sudo. Bukod dito:

  • Isang runtime kung saan nakasulat ang server. Karamihan sa mga reference server ay Node o Python. May Node 18 ang Ubuntu 24.04, ngunit nangangailangan ang ilang kasalukuyang MCP package ng Node 20 o mas bago. Kaya mag-install ng kasalukuyang LTS mula sa NodeSource o nvm sa halip na umasa sa apt. Kasama na sa system ang Python 3.12.
  • Isang domain at DNS A record, ngunit kailangan lamang ito para sa remote HTTP server. Kailangan ng TLS ng pangalan na nagre-resolve sa VPS na ito. Hindi nangangailangan ng DNS ang stdio example.
  • Sapat na ang 512 MB RAM. Mga magagaan na JSON-RPC process ang MCP server. Ang ginagamit na memory ay nakadepende sa tool na ina-access nito, gaya ng database driver o file cache, hindi sa protocol.
  • Bago pa at patuloy na nagbabago ang spec. Pinalitan ng revision na 2025-03-26 ang HTTP+SSE ng Streamable HTTP at itinuring na deprecated ang SSE. Gumagana pa rin ang SSE at marami pa ring server ang gumagamit nito. Kaya ituring na kailangang muling suriin ang anumang transport pin batay sa release notes ng server, hindi bilang permanenteng tuntunin.

Hakbang 1: ikabit ang isang stdio server sa Claude Code

Magsimula sa filesystem server. Official ito, aktibong mina-maintain, at Node lang ang kailangan. Nirerehistro ito ng command sa ibaba sa Claude Code at nililimitahan sa kasalukuyang project para maisulat ito sa isang file na maaaring i-commit:

cd /home/matt/projects/api
claude mcp add --scope project --transport stdio filesystem \
  -- npx -y @modelcontextprotocol/server-filesystem /home/matt/projects/api

Mahalaga ang -- separator: ang lahat ng kasunod nito ay ang command na tatakbuhin ng Claude Code, hindi flag para sa Claude Code. Isinusulat nito ang .mcp.json sa project root:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/home/matt/projects/api"
      ]
    }
  }
}

Wala pang tumatakbo. Sa susunod na simulan mo ang Claude Code sa directory na ito, babasahin ng agent ang .mcp.json, sisimulan ang npx -y @modelcontextprotocol/server-filesystem ... bilang child process, at isasagawa ang MCP handshake sa stdin/stdout ng process na iyon. Kumpirmahing naging matagumpay ito:

claude mcp list

Nagpi-print ang maayos na server ng command nito at berdeng check mark, filesystem: npx -y @modelcontextprotocol/server-filesystem ... - ✓ Connected. Sa loob ng session, inililista ng /mcp slash command ang mga tool na inilalantad ng server (read_file, write_file, list_directory), at maaari na itong tawagin ng agent sa mga path na pinayagan mo. Pareho ang anyo ng database tool: palitan ang package at ipasa ang connection string bilang huling argument nito. Suriin pa rin ang sariling repository ng server para sa kasalukuyang pangalan ng package, dahil ilang beses nang nagpalit ng maintainer ang reference Postgres server.

Ito ang buong pakinabang ng pagpapatakbo ng agent sa server: nasa VPS sa loob ng tmux ang Claude Code session, at tumatakbo ang mga stdio server nito sa tabi mismo nito na may direktang access sa project files at local services, nang walang network round-trip. Kapag hawak na ng agent ang write_file pati ang read_file, makabubuting ipares ang lawak ng access na ito sa isang skill na nagtutulak dito na piliin ang pinakamaliit na gumaganang pagbabago, dahil kasing dali ng two-line fix para sa filesystem tool ang isang malawakang rewrite. Umaabot din ang ganitong wiring sa mga local file: kung nagpapatakbo ka na ng search engine sa VPS, maaari mong ibigay sa agent ang sarili mong SearXNG instance bilang search tool, para manatili sa server mo ang mga query ngunit direktang maipasok sa context ang hindi pinagkakatiwalaang page text bago ito aksyunan ng agent.

Hakbang 2: bumuo ng remote HTTP server

Nawawala ang stdio server kapag natapos ang parent process nito, at isang instance ito na ginagawa para sa bawat client. Kaya kung magpapatakbo ka ng dalawang Claude Code session sa server na nagpapasa ng trabaho sa isa’t isa, may sarili at hiwalay na kopya ng tool ang bawat isa. Kung kailangan mo ng tool na patuloy na tumatakbo para sa lahat ng client, gaya ng shared ops tool o database gateway na tinatawagan ng laptop at CI mo, kailangan mo ng HTTP transport at aktuwal na service. Narito ang isang minimal na Python server na gumagamit ng official SDK at naglalantad 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")

Tandaan ang host="127.0.0.1". Sa localhost lamang nakikinig ang server. Walang makaka-access dito nang direkta mula sa labas ng server, na siyang tamang setup bago magkaroon ng authentication. I-install ito sa sarili nitong virtualenv upang magkaroon ang systemd ng stable na path papunta sa interpreter nito:

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]"

Hakbang 3: panatilihin itong gumagana gamit ang systemd

Mas masama pa sa walang tool ang tool na naka-down kapag kailangan ito ng agent. Pinakamahalaga ito kapag ang client mismo ay isang matagalang proseso: tatawagan ng isang always-on agent na nagpapanatili ng memory at mga schedule nito sa kabila ng mga reboot ang mga tool na ito ayon sa iskedyul nang walang nagbabantay, kaya kailangang awtomatikong bumalik online ang server. Isulat 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.target

Hindi opsyonal ang absolute path papunta sa venv Python sa ExecStart. Ituro ito sa /usr/bin/python3 upang magsimula ang proseso gamit ang ModuleNotFoundError: No module named 'mcp', dahil hindi nakita ng system interpreter ang iyong pip install. I-enable at suriin ito:

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/mcp

Dapat magbasa ang status ng active (running). Bumabalik ang curl nang HTTP/1.1 400 Bad Request na may JSON-RPC error sa body. Walang session at walang valid na JSON payload ang request, at iyon mismo ang gusto mo: pinatutunayan nitong tumatanggap ng koneksyon ang port at sinasalita nito ang protocol. Ang Connection refused o isang walang-lamang reply ay nangangahulugang hindi naka-bind ang proseso sa lugar na inaakala mo; basahin ang journalctl -u mcp-ops -n 50.

Hakbang 4: maglagay ng TLS at reverse proxy sa harap

Nakikinig ang server sa localhost. Upang ma-access ito mula sa kahit saan, i-terminate ang TLS sa nginx at i-proxy ang traffic papasok. I-install ang nginx, kumuha ng certificate gamit ang Certbot at Let's Encrypt sa nginx, pagkatapos ay isulat ang location block. Ang kritikal na bahagi ay ang pag-disable ng buffering, dahil sa default na behavior ng nginx, hinahawakan nito ang response hanggang sa makumpleto ito. Dahil dito, tuluyang nabibinbin ang 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 nagpapatakbo ka na ng fleet ng containers, awtomatikong ginagawa ng Traefik reverse proxy na may awtomatikong TLS ang parehong trabaho. Ini-issue nito ang certificate at nire-route ang traffic batay sa hostname. Magdagdag ka lamang ng labels sa MCP container. Sa alinmang paraan, ang reverse proxy na lamang ang nasa public port, at nakaturo ito sa isang service na hindi mo pa sine-secure. Ayusin muna iyon bago mo i-register ang URL saanman.

Hakbang 5: ang panuntunang panseguridad na pinakamahalaga sa paksang ito

Huwag kailanman maglantad ng MCP endpoint na walang authentication. Ang MCP server ay hindi read-only API. Nagbibigay ito ng access sa mga tool para sa iyong mga file, database, at kung minsan ay shell. Ang bukas na /mcp sa public internet ay nagbibigay sa sinumang estranghero ng kaparehong access ng iyong AI agent: ililista nila ang iyong mga tool at tatawagin ang mga ito. Ituring ito na parang isang admin socket na walang authentication, dahil iyon mismo ang katangian nito. Ang makukuha mula sa ninakaw na token ay nakadepende rin sa server na nasa likod nito: ang read-only MCP server na kasama sa openGym workout tracker ay maaari lamang magbalik ng training data, samantalang ang filesystem o shell tool ay nagbibigay ng access sa buong server.

Tatlong depensa, ayon sa pagkakasunud-sunod ng kagustuhan:

  1. Huwag itong i-publish. Panatilihin ang server sa 127.0.0.1 at ma-access ito mula sa iyong laptop gamit ang SSH tunnel: ssh -L 8000:127.0.0.1:8000 matt@vps, pagkatapos ay ituro ang client sa http://127.0.0.1:8000/mcp. Walang anumang nalalantad.
  2. Ilagay ito sa private network. I-bind ang tunnel address ng isang self-hosted WireGuard VPN at payagan lamang ang mga VPN peer na ma-access ito. Makikita ng public internet ang isang saradong port.
  3. Kung kailangan itong maging public, mangailangan ng token. Ang tamang solusyon ay ang MCP OAuth flow na native na sinusuportahan ng HTTP transport. Ang praktikal na minimum ay isang shared bearer token na sinusuri sa proxy. Mababa ang gastos nito at ganap nitong hinaharang ang mga random na pagtatangkang kumonekta:
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...
}

Bumuo ng token gamit ang openssl rand -hex 32, at huwag kailanman i-bind ang mismong server sa 0.0.0.0 nang walang isa sa mga depensang ito sa harap nito. Ipapadala ng client ang token bilang 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 upang hindi mapunta ang secret sa .mcp.json bilang plaintext. Ie-expand ng Claude Code ang ${MCP_TOKEN} mula sa environment kapag babasahin ito.

Ang bawat depensa sa itaas ay nagpoprotekta sa endpoint, hindi sa agent na may hawak na token. Iyon ang kabilang bahagi ng problema: kung DeepSeek Harness ang iyong client, ang mga plugin na naglilimita sa mga tool na maaaring tawagin ng agent at nagsusuri sa tool output para sa injected instructions ang tumutugon sa bahaging iyon.

Hakbang 6: mag-debug gamit ang MCP Inspector

Kapag hindi maayos ang kilos ng server, huwag manghula mula sa loob ng agent. Direktang gamitin ang Inspector, ang opisyal na web-based test client. Para sa stdio server, ibigay dito ang parehong command na pinapatakbo ng agent:

npx @modelcontextprotocol/inspector \
  npx -y @modelcontextprotocol/server-filesystem /tmp

Nagsisimula ito ng UI sa http://localhost:6274. Sa mga bagong bersyon, nagpi-print ito ng URL na may MCP_PROXY_AUTH_TOKEN query string. Gamitin ang eksaktong link na iyon; kung hindi, tatanggihan ka ng UI. Nagsisimula rin ito ng proxy sa 6277. I-click ang Connect, pagkatapos ang List Tools, at saka ang Call Tool gamit ang mga aktuwal na argument. Kung gumagana ito sa Inspector pero nabibigo sa agent, nasa client config ang bug, hindi sa server. Para sa remote HTTP server, piliin ang Streamable HTTP transport, ilagay ang https://mcp.example.com/mcp, idagdag ang Authorization header, at kumonekta. Ito ang pinakamabilis na paraan upang mapatunayan na tama ang auth at proxy bago makipag-ugnayan ang anumang agent.

Pagpapanatiling updated ng mga server

Mabilis magbago ang MCP, kaya mag-apply ng patch ayon sa iskedyul. Kinukuha ng mga Node server na inilunsad gamit ang npx -y ang pinakabagong bersyon sa bawat spawn. Maginhawa ito, pero hindi reproducible ang resulta. I-pin ang eksaktong bersyong sinubukan mo. Basahin ito mula sa npm view @modelcontextprotocol/server-filesystem version at idagdag sa pangalan ng package sa .mcp.json (@modelcontextprotocol/server-filesystem@<version>) kapag mahalaga na ang isang server. Pagkatapos, i-bump ito nang sinasadya. Para sa mga Python server sa ilalim ng systemd, mag-update gamit ang sudo -H -u mcp /opt/mcp-ops/.venv/bin/pip install -U "mcp[cli]" na sinusundan ng sudo systemctl restart mcp-ops. Suriin ang revision ng spec na tina-target ng SDK mo kapag nag-upgrade ka. Ang pagtalon mula sa SSE papunta sa Streamable-HTTP ay maaaring magbago sa transport na kailangang i-request ng mga client mo.

Mga failure mode, kasama ang mga string na makikita mo

Ipinapakitang failed ang server ng agent. Ipi-print ng claude mcp list ang ✗ Failed to connect, at iuulat ng TUI ang MCP server 'filesystem' failed to start. Patakbuhin ang claude --debug at karaniwan mong makikita ang Error: spawn npx ENOENT: wala ang command sa PATH ng agent. Nawawala ang runtime o wala ito sa lokasyong hinahanap ng agent: hindi naka-install ang Node, wala ang npx, o bare name ang ginamit para sa Python ng virtualenv. Itama ang command gamit ang absolute path o i-install ang runtime, saka muling kumonekta.

Kumokonekta ang stdio server, pagkatapos ay agad na napuputol. Nagla-log ang client ng JSON parse error, gaya ng Unexpected token 'S', "Server sta"... is not valid JSON o Failed to parse message. Iisa ang sanhi: may log line na isinulat ang server sa stdout. Sa stdio, ang stdout ang JSON-RPC channel, kaya sinisira ng anumang sobrang text ang stream at namamatay ang handshake. Sa Node, napupunta sa stdout ang console.log; gamitin ang console.error. Sa Python, napupunta sa stdout ang bare na print(); magsulat ng logs gamit ang logging na naka-configure sa sys.stderr, o ipasa ang file=sys.stderr. Walang exception sa panuntunan: sa stdio, JSON-RPC lamang ang nasa stdout; lahat ng text na para sa tao ay nasa stderr.

Nagti-time out o nagsasara sa kalagitnaan ng handshake ang remote server. Nagfa-fail ang client gamit ang MCP error -32000: Connection closed, o nagha-hang ang Inspector sa Connect at hindi kailanman naglilista ng tools. Sa likod ng nginx, buffering ang sanhi: hinahawakan ng proxy ang SSE stream sa halip na i-flush ito, kaya naghihintay ang client ng response na hindi dumarating. Idagdag ang proxy_buffering off; (at ang natitirang bahagi ng block sa Step 4) sa location. Kumpirmahin gamit ang curl -N laban sa public URL; dapat mong makitang dumarating nang paisa-isa ang event data, hindi sabay-sabay sa pinakadulo.

Tinatanggihan ang authentication. Iniuulat ng client ang Error POSTing to endpoint (HTTP 401) o tahasang 401 Unauthorized. Maaaring nawawala ang header, mali ang token, o walang laman ang shell variable noong binasa ng client ang configuration. Karaniwang problema ito dahil nag-e-expand sa walang laman ang ${MCP_TOKEN} kapag unset ang variable, at nakikita ng nginx ang Bearer na walang value. I-print ang variable, idagdag muli ang header, at tiyaking eksaktong magkakatugma ang bytes sa token sa if ng nginx.

Hindi nagsisimula ang service sa ilalim ng systemd. Ipinapakita ng journalctl -u mcp-ops ang ModuleNotFoundError: No module named 'mcp', at itinuturo ng ExecStart ang system Python sa halip na sa interpreter ng venv. O kaya, Address already in use: ginagamit ng ibang process ang 8000; hanapin ito gamit ang sudo ss -ltnp | grep 8000.

FAQ

Ano nga ba ang MCP server?

Isa itong program na naglalantad ng mga tool at resource sa isang AI client sa pamamagitan ng Model Context Protocol, gamit ang JSON-RPC 2.0. Hindi direktang pinapatakbo ng AI model ang tool. Humihingi ito sa client nito, tinatawagan ng client ang MCP server, at isinasagawa ng server ang operasyon bago ibalik ang resulta. Dahil standard ang protocol, gumagana ang isang server sa anumang compliant client, gaya ng Claude Code, Claude Desktop, o Gemini CLI.

Ano ang pagkakaiba ng stdio at HTTP transport?

Inilulunsad ng client ang stdio server bilang child process. Nakikipag-ugnayan ito sa pamamagitan ng stdin/stdout, kaya tumatakbo at humihinto ito kasama ng isang client sa isang machine at hindi nangangailangan ng network o authentication. Ang HTTP server ay isang long-running network service na maaaring maabot ng maraming client nang sabay-sabay. Dahil dito, kailangan nito ng TLS at authentication. Gamitin ang stdio para sa mga local, single-user tool. Gamitin ang HTTP (Streamable HTTP sa mga kasalukuyang server) para sa anumang shared o persistent na serbisyo.

Paano ko ise-secure ang remote MCP server?

Ipagpalagay na nagbibigay ito ng access sa mga tool para sa iyong mga file, database, o shell, at huwag itong ilantad nang walang authentication. Pinakamainam na i-bind ito sa localhost at i-access sa pamamagitan ng SSH tunnel o private VPN. Kung kailangan itong maging public, ilagay ito sa likod ng reverse proxy na nagpapatupad ng bearer token o ng MCP OAuth flow. I-generate ang token gamit ang openssl rand -hex 32 at huwag kailanman i-bind ang server sa 0.0.0.0 nang walang isa sa mga ito sa harap nito.

Paano ko ide-debug ang server na hindi nagsisimula?

Suriin muna ang claude mcp list at ✗ Failed to connect gamit ang spawn ... ENOENT. Ibig sabihin nito, nawawala ang command o runtime, kaya ayusin ang path o i-install ito. Kung kumokonekta ito ngunit agad na nadidiskonekta na may JSON parse error, nagla-log ang server sa stdout at nasisira ang JSON-RPC stream. Ilipat ang lahat ng logging sa stderr. Para sa iba pang problema, patakbuhin ang eksaktong command gamit ang MCP Inspector. Pinapatakbo nito ang server nang hiwalay upang matukoy mo kung server bug o client-config bug ang sanhi.