Як запустити MCP server на VPS для AI-агентів
Налаштуйте MCP на VPS: stdio та remote HTTP, systemd, nginx із TLS і auth. Розберіть чистий JSON-RPC та помилку відкритого endpoint без автентифікації.
Що ви налаштуєте
Дві робочі конфігурації MCP на одному VPS. Спочатку stdio-сервер — інструмент для роботи з файловою системою або базою даних, який Claude Code запускає як дочірній процес і взаємодіє з ним через pipe. Потім remote HTTP-сервер — довготривалий мережевий сервіс за systemd і reverse proxy nginx із TLS. До нього можна підключити будь-який MCP-клієнт. Встановлення кожної конфігурації займає небагато часу. Основна частина цього посібника присвячена двом проблемам, які найчастіше спричиняють збої: потік JSON-RPC має залишатися чистим, а endpoint інструмента в жодному разі не можна публікувати в інтернеті без автентифікації.
Що насправді таке MCP
Model Context Protocol — це стандартний спосіб, за допомогою якого AI-клієнт, Claude Code, Claude Desktop, Gemini CLI на VPS або ваш власний скрипт викликає зовнішні інструменти й читає зовнішні ресурси. Сама модель нічого не запускає. Вона надсилає запит клієнту, клієнт обмінюється даними з MCP server через JSON-RPC 2.0, а server запускає інструмент і повертає результат. Саме цей клієнт мають на увазі, коли говорять про agent harness: це цикл навколо моделі, який керує списком інструментів, перевірками дозволів і станом сесії. MCP — лише спосіб розширити його частину, що відповідає за інструменти. Протокол один, тому server, який ви написали один раз, працює з кожним клієнтом, що підтримує MCP. Якщо цей поділ для вас новий, особливо якщо ви не знаєте, як модель узагалі вирішує скористатися інструментом, перед передаванням такому server реальних облікових даних варто витратити годину на послідовний шлях вивчення основ agent.
Є два транспорти, і весь подальший посібник поділено відповідно до них:
- stdio. Клієнт запускає server як дочірній процес і обмінюється повідомленнями JSON-RPC, розділеними символом нового рядка, через стандартні потоки введення та виведення. Мережа, порт і автентифікація не потрібні; межею довіри є сам процес. Майже кожен локальний інструмент постачається в такому режимі.
- Streamable HTTP (і старіший варіант HTTP+SSE). Server працює як довготривалий web-сервіс. Клієнт підключається через HTTP, а server може передавати відповіді як Server-Sent Events. Так можна використовувати один server спільно з багатьма клієнтами або запускати інструмент, який має постійно працювати на сервері.
Обирайте stdio, якщо інструмент призначений для одного комп’ютера й одного користувача. Обирайте HTTP, якщо це спільний сервіс.
Передумови та важливі застереження
Припускаємо, що ви використовуєте новий KVM VPS з Ubuntu 24.04 і маєте доступ через root або sudo. Крім цього, потрібні такі умови:
- Середовище виконання, на якому написано сервер. Більшість серверів у прикладах написано для 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 позначено як застарілий. SSE досі працює, і багато серверів усе ще його підтримують. Тому будь-яку фіксацію транспорту перевіряйте за release notes сервера, а не сприймайте її як остаточне правило.
Крок 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), і агент може викликати їх для дозволених шляхів. Для database tool використовується така сама схема: замініть пакет і передайте connection string як останній аргумент. Перевірте репозиторій самого сервера, щоб дізнатися актуальну назву пакета, оскільки reference Postgres server уже не раз переходив до інших супроводжувачів.
У цьому й полягає основна перевага запуску агента безпосередньо на сервері: сесія Claude Code працює на VPS усередині tmux, а його stdio-сервери працюють поруч із ним і мають прямий доступ до файлів проєкту та локальних сервісів без мережевого round-trip. Коли агент має write_file, а також read_file, варто поєднати цей доступ із skill, який спрямовує його до найменшої працездатної зміни, оскільки filesystem tool робить масштабне переписування таким самим простим, як виправлення у два рядки. Така сама схема працює не лише з локальними файлами: якщо на VPS уже запущено пошукову систему, можна передати агенту власний екземпляр SearXNG як search tool. Запити залишатимуться на вашому сервері, але текст недовірених сторінок безпосередньо потраплятиме в контекст, на основі якого агент виконуватиме подальші дії.
Крок 2: створення віддаленого HTTP-сервера
Сервер stdio завершує роботу разом із батьківським процесом і запускається окремо для кожного клієнта. Тому якщо ви запустите два сеанси Claude Code на сервері, які передають роботу один одному, кожен сеанс отримає власну ізольовану копію інструмента. Якщо потрібен інструмент, який працює для всіх клієнтів, спільний ops-інструмент, шлюз до бази даних або сервіс, до якого одночасно звертаються ваш ноутбук і 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]"Крок 3: забезпечте роботу за допомогою 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Абсолютний шлях до venv Python у 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/mcpstatus має повертати active (running). curl повертається як HTTP/1.1 400 Bad Request з помилкою JSON-RPC у тілі відповіді: запит не містив сесії та корисного JSON-даних. Саме це вам і потрібно: так ви підтверджуєте, що порт відповідає та підтримує цей протокол. Connection refused або порожня відповідь означає, що процес не прослуховує порт, який ви очікуєте. Перегляньте journalctl -u mcp-ops -n 50.
Крок 4: налаштуйте TLS і reverse proxy перед сервісом
Сервер слухає localhost. Щоб отримати доступ до нього з будь-якого місця, завершуйте TLS на nginx і проксуюйте запити всередину. Встановіть nginx, отримайте сертифікат за допомогою Certbot і Let's Encrypt у 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. Якщо ви вже використовуєте багато контейнерів, те саме завдання виконає reverse proxy Traefik з автоматичним TLS: він отримає сертифікат і маршрутизує запити за іменем хоста, а вам потрібно лише додати labels до контейнера MCP. У будь-якому разі reverse proxy тепер буде єдиним компонентом на публічному порту, і він вказуватиме на сервіс, який ще не захищено. Виправте це, перш ніж реєструвати URL будь-де.
Крок 5: правило безпеки, яке визначає весь цей підхід
Ніколи не відкривайте MCP endpoint без автентифікації. MCP server — це не API лише для читання. Він надає доступ до tools: ваших файлів, бази даних, а іноді й shell. Відкритий /mcp у публічному інтернеті дає сторонній особі такий самий доступ, як у вашого AI agent: вона переглядає доступні tools, а потім викликає їх. Ставтеся до нього як до неавтентифікованого admin socket, оскільки саме ним він і є. Наслідки викрадення token також залежать від server, який працює за ним: read-only MCP server, що постачається з трекером тренувань openGym може повертати лише дані про тренування, тоді як filesystem або shell tool надає доступ до всієї системи.
Три способи захисту в порядку пріоритетності:
- Не публікуйте його. Залиште server на
127.0.0.1і підключайтеся до нього з ноутбука через SSH tunnel:ssh -L 8000:127.0.0.1:8000 matt@vps, а потім вкажіть клієнтуhttp://127.0.0.1:8000/mcp. Нічого не відкривається назовні. - Розмістіть його в приватній мережі. Прив’яжіть адресу tunnel до self-hosted WireGuard VPN і дозвольте доступ лише VPN peers. Публічний інтернет бачитиме закритий порт.
- Якщо endpoint має бути публічним, вимагайте token. Правильний варіант — 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...
}Згенеруйте token за допомогою openssl rand -hex 32 і ніколи не прив’язуйте сам server до 0.0.0.0 без одного з цих засобів захисту перед ним. Клієнт надсилає token у заголовку. У Claude Code:
claude mcp add --scope project --transport http ops-tools https://mcp.example.com/mcp \
--header 'Authorization: Bearer ${MCP_TOKEN}'Встановіть MCP_TOKEN у shell, щоб secret не потрапив до .mcp.json у відкритому вигляді. Claude Code розгортає ${MCP_TOKEN} зі змінної середовища під час читання.
Кожен із наведених способів захищає endpoint, а не agent, який уже має token. Це друга частина проблеми: якщо вашим клієнтом є DeepSeek Harness, plugins, які обмежують tools, що їх може викликати agent, і перевіряють вивід tools на наявність ін’єкційованих інструкцій захищають саме цю частину.
Крок 6: налагодження за допомогою MCP Inspector
Якщо сервер працює неправильно, не намагайтеся вгадати причину з agent. Керуйте ним безпосередньо через Inspector — офіційний вебклієнт для тестування. Для сервера stdio передайте йому ту саму команду, яку запускає agent:
npx @modelcontextprotocol/inspector \
npx -y @modelcontextprotocol/server-filesystem /tmpInspector запускає UI на http://localhost:6274. У новіших версіях він виводить URL із рядком запиту MCP_PROXY_AUTH_TOKEN. Використовуйте саме це посилання, інакше UI відхилить підключення. Він також запускає proxy на 6277. Натисніть Connect, потім List Tools, а далі Call Tool і передайте реальні аргументи. Якщо в Inspector усе працює, а в agent виникає помилка, проблема в конфігурації клієнта, а не в сервері. Для віддаленого HTTP-сервера виберіть транспорт Streamable HTTP, введіть https://mcp.example.com/mcp, додайте заголовок Authorization і підключіться. Це найшвидший спосіб перевірити автентифікацію та proxy до підключення будь-якого agent.
Оновлення серверів
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 і Streamable-HTTP може змінити транспорт, який клієнти мають запитувати.
Типові причини збоїв і повідомлення, які ви побачите
Агент повідомляє, що сервер не запустився. claude mcp list виводить ✗ Failed to connect, а TUI повідомляє MCP server 'filesystem' failed to start. Виконайте claude --debug. Зазвичай ви побачите Error: spawn npx ENOENT: команда відсутня в PATH агента. Середовище виконання відсутнє або розташоване не там, де його шукає агент: Node не встановлено, відсутній npx або вказано Python із virtualenv за коротким іменем. Виправте команду, вказавши абсолютний шлях, або встановіть середовище виконання, а потім підключіться повторно.
Сервер stdio підключається, а потім одразу розриває з’єднання. Клієнт записує помилку синтаксичного аналізу JSON, наприклад Unexpected token 'S', "Server sta"... is not valid JSON або Failed to parse message. Причина завжди одна: сервер записав рядок журналу в stdout. У режимі stdio stdout є каналом JSON-RPC, тому будь-який сторонній текст пошкоджує потік і руйнує рукостискання. У 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. Причиною може бути відсутній заголовок, неправильний токен або порожня змінна оболонки на момент зчитування конфігурації клієнтом. Це поширена помилка: ${MCP_TOKEN} перетворюється на порожній рядок, якщо змінну не встановлено, і nginx бачить Bearer без значення. Виведіть значення змінної, повторно додайте заголовок і перевірте, що точна послідовність байтів збігається з токеном у if nginx.
Сервіс не запускається під systemd. journalctl -u mcp-ops показує ModuleNotFoundError: No module named 'mcp', а ExecStart вказує на системний Python замість інтерпретатора virtualenv. Або Address already in use: інший процес уже використовує порт 8000. Знайдіть його за допомогою sudo ss -ltnp | grep 8000.
FAQ
Що саме таке MCP server?
Це програма, яка надає AI client доступ до tools і resources через Model Context Protocol, використовуючи JSON-RPC 2.0. AI model не запускає tool самостійно. Вона надсилає запит своєму client, client викликає MCP server, а server виконує операцію та повертає результат. Оскільки протокол стандартизований, один server працює з будь-яким сумісним client, зокрема Claude Code, Claude Desktop або Gemini CLI.
У чому різниця між stdio та HTTP transport?
stdio server запускається client як дочірній процес і взаємодіє через stdin/stdout. Тому він працює лише разом з одним client на одному комп’ютері та не потребує мережі або автентифікації. HTTP server — це довготривалий мережевий сервіс, до якого одночасно можуть підключатися багато client. Саме тому йому потрібні TLS та автентифікація. Використовуйте stdio для локальних інструментів одного користувача. Для спільних або постійно доступних сервісів використовуйте HTTP, а на сучасних server — Streamable HTTP.
Як захистити віддалений MCP server?
Вважайте, що він надає доступ до tools для роботи з файлами, базою даних або shell, і ніколи не відкривайте його без автентифікації. Найкраще залишити його прив’язаним до localhost і підключатися через SSH tunnel або приватну VPN. Якщо server має бути публічно доступним, розмістіть його за reverse proxy, який перевіряє bearer token або використовує MCP OAuth flow. Створіть token за допомогою openssl rand -hex 32 і ніколи не прив’язуйте server до 0.0.0.0 без одного з цих механізмів попереду.
Як налагодити server, який не запускається?
Спочатку перевірте claude mcp list, ✗ Failed to connect за допомогою spawn ... ENOENT. Якщо відсутні команда або runtime, виправте шлях або встановіть їх. Якщо server підключається, а потім завершує роботу з помилкою аналізу JSON, він записує журнали в stdout і пошкоджує потік JSON-RPC. Перенесіть усі журнали в stderr. В інших випадках запустіть точну команду через MCP Inspector. Він запускає server ізольовано, тому можна визначити, чи проблема пов’язана з server, чи з конфігурацією client.