SSD Nodes Learn
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-07-24

Запуск MCP серверів на VPS для AI агентів

Налаштуйте stdio та remote HTTP транспорт для MCP на VPS. Використовуйте systemd та TLS для безпечного доступу AI агентів до інструментів через JSON-RPC.

Що ви створюєте

Дві робочі конфігурації MCP на одному VPS. Спочатку — stdio сервер: інструмент для роботи з файловою системою або базою даних, який Claude Code запускає як дочірній процес і з яким взаємодіє через pipe. Потім — remote HTTP сервер: мережевий сервіс, що працює у фоновому режимі під керуванням systemd та nginx (reverse proxy з TLS); він доступний для будь-якого MCP клієнта. Процес встановлення для обох варіантів невеликий. Основна складність цього посібника полягає у двох речах: забезпеченні чистоти потоку JSON-RPC та уникненні публічного доступу до неавтентифікованих ендпоінтів інструментів через інтернет.

Що таке MCP насправді

Model Context Protocol — це стандартний спосіб, за допомогою якого AI-клієнт — Claude Code, Claude Desktop, Gemini CLI на VPS або ваш власний скрипт — викликає зовнішні інструменти та зчитує зовнішні ресурси. Сама модель нічого не запускає. Вона надсилає запит клієнту, клієнт передає JSON-RPC 2.0 на server MCP, а сервер виконує інструмент і повертає результат. Використання одного протоколу дозволяє серверу, написаному один раз, працювати з будь-яким клієнтом, що підтримує MCP.

Існує два типи транспорту, і подальший посібник розділено саме за ними:

  • stdio. Клієнт запускає сервер як дочірній процес і обмінюється повідомленнями JSON-RPC, розділеними символом нового рядка, через стандартні потоки введення (stdin) та виведення (stdout). Без мережі, портів та автентифікації — межею довіри є сам процес. Майже всі локальні інструменти постачаються саме в такому вигляді.
  • Streamable HTTP (та його попередник, HTTP+SSE). Сервер є вебсервісом, що працює у фоновому режимі. Клієнт підключається через HTTP, а сервер може передавати відповіді потоком за допомогою Server-Sent Events. Це дозволяє використовувати один сервер для багатьох клієнтів або запускати інструмент, який має постійно працювати на хост-системі.

Вибирайте stdio, якщо інструмент призначений для однієї машини та одного користувача. Вибирайте HTTP, якщо це спільний сервіс.

Prerequisites and the honest gotchas

Припускайте, що у вас є чиста Ubuntu 24.04 KVM VPS з правами root або sudo. Крім цього:

  • Середовище виконання (runtime), на якому написаний сервер. Більшість еталонних серверів використовують Node або Python. Ubuntu 24.04 постачається з Node 18, але багатьом сучасним MCP-пакетам потрібен Node 20 або новіша версія. Тому встановлюйте актуальну LTS-версію з NodeSource або nvm, а не покладайтеся на apt. Python 3.12 вже встановлено.
  • Домен та DNS A-запис, але тільки для віддаленого HTTP-сервера — для TLS потрібне ім'я, яке резолвиться на цей VPS. Прикладу зі stdio DNS взагалі не потрібен.
  • 512 MB RAM достатньо. MCP-сервери — це легкі JSON-RPC процеси; споживання пам'яті залежить від вашого інструменту (драйвер бази даних, кеш файлів), а не від протоколу.
  • Специфікація нова і постійно змінюється. Редакція від 2025-03-26 замінила HTTP+SSE на Streamable HTTP і позначила SSE як deprecated. SSE все ще працює, і багато серверів його підтримують, тому сприймайте будь-яку фіксацію транспорту як те, що потрібно перевірити згідно з release notes сервера, а не як абсолютну істину.

Step 1: підключення stdio сервера до Claude Code

Почніть із сервера файлової системи — він є офіційним, активно підтримується і потребує лише 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 ... як дочірній процес і виконає MCP handshake через stdin/stdout цього процесу. Перевірте успішне підключення:

claude mcp list

Працюючий сервер виводить свою команду та зелену позначку — filesystem: npx -y @modelcontextprotocol/server-filesystem ... - ✓ Connected. Під час сесії slash-команда /mcp виведе список інструментів, які надає сервер (read_file, write_file, list_directory), і тепер агент може викликати їх за дозволеними вами шляхами. Інструмент для бази даних працює за таким самим принципом — змініть пакет і передайте рядок підключення як останній аргумент — але перевірте репозиторій сервера на наявність актуальної назви пакета, оскільки посилання на Postgres server змінювалося кілька разів.

У цьому полягає основна перевага запуску агента на хості: сесія Claude Code працює на VPS усередині tmux, а її stdio сервери працюють поруч із нею, маючи прямий доступ до файлів проєкту та локальних сервісів без мережевих затримок.

Step 2: створення віддаленого HTTP сервера

Сервер типу stdio завершує роботу разом із батьківським процесом. Якщо вам потрібен інструмент, який залишається активним для кожного клієнта — спільний інструмент адміністрування, шлюз до бази даних або сервіс, до якого звертаються і ваш ноутбук, і CI — вам потрібен транспорт HTTP та повноцінний сервіс. Нижче наведено мінімальний Python-сервер з використанням офіційного SDK, що надає один інструмент:

# /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")

Зверніть увагу на host="127.0.0.1". Сервер прив'язаний лише до localhost — жоден зовнішній пристрій не може звернутися до нього напряму, що є необхідним до налаштування автентифікації. Встановіть його в окреме 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]"

Step 3: keep it alive with 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.target

Абсолютний шлях до Python у venv у ExecStart є обов'язковим — вкажіть /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/mcp

status має повертати active (running). curl повертає HTTP/1.1 400 Bad Request з помилкою JSON-RPC у тілі — запит не містив сесії та валідного JSON-payload — і це саме те, що потрібно: це підтверджує, що порт відповідає та використовує протокол. Connection refused або порожня відповідь означає, що процес не прив'язаний до очікуваного порту; перевірте journalctl -u mcp-ops -n 50.

Step 4: Налаштування TLS та зворотного проксі

Сервер прослуховує localhost. Щоб отримати до нього доступ ззовні, необхідно термінувати TLS на nginx та використати проксіювання. Встановіть nginx, отримайте сертифікат за допомогою Certbot and Let's Encrypt on nginx, а потім налаштуйте блок 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. Якщо ви використовуєте багато контейнерів, цю задачу може виконати Traefik reverse proxy with automatic TLS — він автоматично видає сертифікати та маршрутизує трафік за hostname. У такому разі достатньо додати labels до контейнера MCP. У будь-якому випадку, зворотний проксі є єдиним компонентом, що відкритий на публічному порту, і він вказує на ще не захищений сервіс. Налаштуйте безпеку сервісу перед реєстрацією URL.

Step 5: головне правило безпеки для цієї теми

Ніколи не відкривайте доступ до неавтентифікованого MCP endpoint. MCP server — це не лише API для читання. Він надає доступ до інструментів (tools): ваших файлів, бази даних, а іноді й до shell. Відкритий /mcp в інтернеті — це сторонній користувач з такими ж можливостями, як ваш AI agent: він перелічує ваші інструменти, а потім викликає їх. Ставтеся до цього так само, як до неавтентифікованого admin socket, бо це саме він.

Три методи захисту (у порядку пріоритетності):

  1. Не публікуйте його. Залиште 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. Жодні дані не будуть відкриті.
  2. Використовуйте приватну мережу. Використовуйте тунельну адресу self-hosted WireGuard VPN і дозволяйте доступ лише VPN peers. Для публічного інтернету порт буде закритим.
  3. Якщо публічний доступ необхідний, використовуйте токен. Правильний варіант — MCP OAuth flow, який нативно підтримується HTTP transport. Прагматичний мінімум — спільний bearer token, який перевіряється на рівні proxy. Це просто і повністю захищає від випадкових атак:
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 і ніколи не прив'язуйте server безпосередньо до 0.0.0.0 без одного з цих методів захисту. Client має надсилати токен у заголовку (header). У Claude Code:

claude mcp add --scope project --transport http ops-tools https://mcp.example.com/mcp \
  --header 'Authorization: Bearer ${MCP_TOKEN}'

Встановіть MCP_TOKEN у вашому shell, щоб секрет не зберігався у .mcp.json у відкритому вигляді — Claude Code розгортає ${MCP_TOKEN} з середовища (environment) під час читання.

Step 6: debug with the MCP Inspector

Якщо сервер працює некоректно, не намагайтеся вгадати причину через агент — використовуйте Inspector, офіційний веб-клієнт для тестування. Для stdio-сервера використовуйте ту саму команду, яку запускає агент:

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

Inspector запускає UI на http://localhost:6274 (у нових версіях виводиться URL з параметром MCP_PROXY_AUTH_TOKEN — використовуйте саме це посилання, інакше UI відхилить запит) та проксі на порту 6277. Натисніть Connect, потім List Tools, а потім Call Tool з реальними аргументами. Якщо в Inspector все працює, а в агенті — ні, помилка у конфігурації клієнта, а не в сервері. Для віддаленого HTTP-сервера виберіть транспорт Streamable HTTP, введіть https://mcp.example.com/mcp, додайте заголовок Authorization і підключіться — це найшвидший спосіб перевірити правильність авторизації та проксі до підключення агента.

Keeping servers updated

MCP швидко розвивається, тому встановлюйте графік встановлення патчів. Node сервери, запущені з npx -y, завантажують останню версію під час кожного запуску. Це зручно, але не забезпечує відтворюваність. Фіксуйте точну версію, яку ви протестували: прочитайте її з npm view @modelcontextprotocol/server-filesystem version і додайте до назви пакета в .mcp.json (@modelcontextprotocol/server-filesystem@<version>). Це важливо для стабільності сервера; оновлюйте версію свідомо. Python сервери під керуванням systemd оновлюються за допомогою sudo -H -u mcp /opt/mcp-ops/.venv/bin/pip install -U "mcp[cli]" з подальшим виконанням sudo systemctl restart mcp-ops. При оновленні перевіряйте ревізію специфікації вашого SDK. Перехід через межу SSE-to-Streamable-HTTP може змінити тип транспорту, який мають запитувати клієнти.

Режими відмови та відповідні повідомлення

Агент повідомляє про помилку сервера. claude mcp list виводить ✗ Failed to connect, а TUI повідомляє MCP server 'filesystem' failed to start. Запустіть claude --debug, і зазвичай ви побачите Error: spawn npx ENOENT — команда відсутня у PATH агента. Середовище виконання (runtime) відсутнє або знаходиться не в тій директорії, яку перевіряє агент: Node не встановлено, npx відсутній, або вказано Python у virtualenv за короткою назвою. Виправте шлях до команди на абсолютний або встановіть runtime, після чого перепідключіться.

stdio-сервер підключається і миттєво розриває з'єднання. Клієнт реєструє помилку парсингу JSON — наприклад, Unexpected token 'S', "Server sta"... is not valid JSON або Failed to parse message. Причина завжди однакова: сервер записав рядок логу у stdout. У режимі stdio stdout є каналом JSON-RPC, тому будь-який зайвий текст пошкоджує потік, і рукостискання (handshake) переривається. У Node console.log надсилається у stdout — використовуйте console.error. У Python звичайний print() надсилається у stdout — використовуйте logging з налаштуванням на sys.stderr або передайте file=sys.stderr. Правило є безумовним: у режимі stdio у stdout має бути лише JSON-RPC, а весь текст для читання людиною — у stderr.

Віддалений сервер перериває з'єднання за таймаутом або під час рукостискання. Клієнт видає помилку MCP error -32000: Connection closed, або Inspector зависає на стадії Connect і не відображає список інструментів. Якщо сервер працює за nginx, причиною є буферизація: проксі-сервер утримує SSE-потік замість того, щоб негайно його відправляти, тому клієнт чекає на відповідь, яка не надходить. Додайте proxy_buffering off; (та решту блоку з Кроку 4) до location. Перевірте це за допомогою curl -N через публічну URL — дані подій мають надходити поступово, а не всі разом у кінці.

Відмова в авторизації. Клієнт повідомляє Error POSTing to endpoint (HTTP 401) або просто 401 Unauthorized. Причина: заголовок відсутній, токен невірний або змінна оболонки (shell variable) була порожньою під час читання конфігурації клієнтом. Це поширена помилка, оскільки ${MCP_TOKEN} розгортається в порожнечу, якщо змінна не встановлена, і nginx отримує Bearer без значення. Виведіть значення змінної через echo, повторно додайте заголовок і переконайтеся, що байти точно збігаються з токеном у nginx if.

Сервіс не запускається через systemd. journalctl -u mcp-ops показує ModuleNotFoundError: No module named 'mcp'ExecStart вказує на системний Python замість інтерпретатора у venv. Або Address already in use — інший процес займає порт 8000; знайдіть його за допомогою sudo ss -ltnp | grep 8000.

FAQ

Що таке MCP server?

Це програма, яка надає інструменти та ресурси AI-клієнту через Model Context Protocol за допомогою JSON-RPC 2.0. AI-модель не виконує інструмент самостійно — вона надсилає запит клієнту, клієнт викликає MCP server, а сервер виконує дію та повертає результат. Завдяки стандартизації протоколу один сервер працює з будь-яким відповідним клієнтом, наприклад, Claude Code, Claude Desktop або Gemini CLI.

Яка різниця між stdio та HTTP transport?

Сервер stdio запускається клієнтом як дочірній процес і взаємодіє через stdin/stdout. Він працює лише в межах одного клієнта на одній машині та не потребує мережі або автентифікації. HTTP server — це мережева служба, що працює постійно і доступна багатьом клієнтам одночасно, тому вона потребує TLS та автентифікації. Використовуйте stdio для локальних інструментів для одного користувача; використовуйте HTTP (Streamable HTTP на поточних серверах) для спільних або постійних сервісів.

Як захистити віддалений MCP server?

Припустимо, що сервер надає доступ до ваших файлів, баз даних або shell; ніколи не залишайте його без автентифікації. Найкращий варіант — прив'язати сервер до localhost і підключатися через SSH-тунель або приватний VPN. Якщо сервер має бути публічним, розмістіть його за reverse proxy, який використовує bearer token або MCP OAuth flow. Згенеруйте токен за допомогою openssl rand -hex 32 і ніколи не прив'язуйте сервер до 0.0.0.0 без одного з цих методів захисту.

Як відлагодити сервер, який не запускається?

Спочатку перевірте claude mcp list — помилка ✗ Failed to connect з spawn ... ENOENT означає, що команда або середовище виконання відсутні; виправте шлях або встановіть їх. Якщо з'єднання встановлюється, але потім переривається з помилкою JSON parse error, це означає, що сервер пише логи в stdout, що псує потік JSON-RPC; перенесіть усі логи в stderr. Для інших випадків запустіть команду саме через MCP Inspector; він запускає сервер ізольовано, що дозволяє відрізнити помилку сервера від помилки конфігурації клієнта.