Создание и самостоятельный хостинг MCP-сервера на VPS

9 мин чтения·Matthieu·mcptypescriptnodejsnginxself-hostingai-agents|

Создайте MCP-сервер на TypeScript с нуля, разверните его на VPS с systemd и Nginx с TLS. Подключите Claude Desktop, Claude Code и Cursor к собственному серверу.

Каждый туториал по MCP-серверам заканчивается на "запустите локально через stdio." Этот проходит весь путь: сборка MCP-сервера на TypeScript, деплой на VPS за Nginx с TLS, защита аутентификацией и файрволом, подключение MCP-клиентов через интернет.

К концу этого руководства у вас будет продакшен-готовый MCP-сервер, к которому смогут подключаться Claude Desktop, Claude Code, Cursor или любой MCP-совместимый клиент.

Что мы будем строить?

MCP-клиенты отправляют JSON-RPC запросы по HTTPS. Nginx терминирует TLS и проксирует запросы к процессу MCP-сервера на локальном порту. MCP-сервер валидирует bearer-токен, обрабатывает запрос и возвращает результат.

MCP Client (Claude, Cursor, ...)
        |
        | HTTPS (port 443)
        v
   Nginx (TLS termination + reverse proxy)
        |
        | HTTP (port 3000, localhost only)
        v
   MCP Server (Node.js + Express)
        |
        v
   SQLite database

Рабочий пример --- сервер заметок. Он предоставляет два инструмента (поиск заметок, создание заметки) и один ресурс (список всех заметок). Достаточно простой для понимания, достаточно полезный для расширения.

Какой транспорт использовать для удалённого MCP-сервера?

Streamable HTTP --- текущий стандарт спецификации MCP для удалённых деплоев. Он заменил устаревший SSE-транспорт и работает через обычные HTTP POST запросы. Stdio остаётся транспортом для локальных серверов, где клиент запускает сервер как дочерний процесс.

Transport Сценарий использования Работает удалённо Поддержка клиентами
stdio Локальные инструменты, CLI-интеграции Нет Все клиенты
SSE (устарел) Старые удалённые серверы Да Большинство клиентов (выводится из обращения)
Streamable HTTP Удалённые серверы, продакшен Да Claude Desktop, Claude Code, Cursor

Streamable HTTP отправляет каждое сообщение клиента как HTTP POST на один эндпоинт (например, /mcp). Сервер отвечает application/json для простых ответов или text/event-stream, когда нужно вернуть несколько сообщений потоком. Это значит, что он работает со стандартной HTTP-инфраструктурой, включая reverse proxy и балансировщики нагрузки.

Предварительные требования

  • VPS с Debian 12 или Ubuntu 24.04 (в этом руководстве используется Debian 12)
  • Доменное имя с DNS-записью, указывающей на ваш VPS (например, mcp.example.com)
  • Node.js 20+ на VPS
  • Базовое знакомство с TypeScript и Linux

Как настроить проект TypeScript MCP-сервера?

Начните на локальной машине или сразу на VPS. Инициализируйте проект, установите MCP SDK и настройте TypeScript.

mkdir mcp-notes-server && cd mcp-notes-server
npm init -y
npm install @modelcontextprotocol/sdk zod express better-sqlite3
npm install -D typescript @types/node @types/express @types/better-sqlite3

Создайте конфигурацию TypeScript:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Сохраните как tsconfig.json в корне проекта.

Обновите package.json, добавив скрипты сборки и запуска, и укажите тип модуля:

{
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node build/index.js"
  }
}

Создайте директорию с исходным кодом:

mkdir src

Как реализовать MCP-инструменты с валидацией входных данных?

MCP-сервер предоставляет AI-клиентам инструменты (tools), ресурсы (resources) и промпты (prompts). Инструменты --- функции, которые AI может вызывать. Ресурсы --- данные только для чтения. Мы реализуем оба варианта.

Создайте src/index.ts с настройкой базы данных и фабричной функцией, которая создаёт новый экземпляр MCP-сервера для каждого запроса. Stateless Streamable HTTP требует новый экземпляр McpServer на каждый запрос, потому что SDK не позволяет переподключить один и тот же сервер к другому транспорту.

import express from "express";
import { randomUUID } from "crypto";
import Database from "better-sqlite3";
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";

// --- Database setup ---

const db = new Database("notes.db");
db.pragma("journal_mode = WAL");
db.exec(`
  CREATE TABLE IF NOT EXISTS notes (
    id TEXT PRIMARY KEY,
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    created_at TEXT DEFAULT (datetime('now'))
  )
`);

// --- MCP Server factory ---
// Each stateless request needs its own McpServer + transport pair.
// The SDK binds a server to a single transport on connect() and
// throws if you try to reuse it. A factory function keeps it clean.

function createServer(): McpServer {
  const server = new McpServer({
    name: "notes-server",
    version: "1.0.0",
  });

Теперь зарегистрируйте инструменты внутри фабрики. Каждый инструмент использует Zod для валидации входных данных. SDK автоматически валидирует входные данные до вызова вашего обработчика.

  // --- Tools ---

  server.tool(
    "search_notes",
    "Search notes by keyword in title or content",
    {
      query: z.string().min(1).describe("Search keyword"),
    },
    async ({ query }) => {
      const stmt = db.prepare(
        "SELECT id, title, content, created_at FROM notes WHERE title LIKE ? OR content LIKE ? ORDER BY created_at DESC LIMIT 20"
      );
      const rows = stmt.all(`%${query}%`, `%${query}%`);
      return {
        content: [
          {
            type: "text" as const,
            text: JSON.stringify(rows, null, 2),
          },
        ],
      };
    }
  );

  server.tool(
    "create_note",
    "Create a new note with a title and content",
    {
      title: z.string().min(1).max(200).describe("Note title"),
      content: z.string().min(1).describe("Note content"),
    },
    async ({ title, content }) => {
      const id = randomUUID();
      const stmt = db.prepare(
        "INSERT INTO notes (id, title, content) VALUES (?, ?, ?)"
      );
      stmt.run(id, title, content);
      return {
        content: [
          {
            type: "text" as const,
            text: `Note created with id: ${id}`,
          },
        ],
      };
    }
  );

Каждый ввод валидируется Zod до попадания в обработчик. Если клиент отправит { query: "" }, SDK автоматически вернёт ошибку валидации. Ручной парсинг не нужен.

Как добавить ресурсы в MCP-сервер?

Ресурсы (resources) дают AI-клиентам доступ к данным только для чтения. Они адресуются по URI. Добавьте ресурс со списком всех заметок, по-прежнему внутри фабрики createServer:

  // --- Resources ---

  server.resource(
    "all-notes",
    "notes://all",
    {
      description: "List all notes in the database",
      mimeType: "application/json",
    },
    async (uri) => {
      const rows = db.prepare(
        "SELECT id, title, created_at FROM notes ORDER BY created_at DESC LIMIT 100"
      ).all();
      return {
        contents: [
          {
            uri: uri.href,
            mimeType: "application/json",
            text: JSON.stringify(rows, null, 2),
          },
        ],
      };
    }
  );

  return server;
}

MCP-клиенты могут обнаружить этот ресурс и прочитать его без вызова инструмента. Claude Desktop показывает ресурсы в меню вложений. Claude Code предоставляет к ним доступ через @-упоминания.

Как защитить MCP-сервер токенной аутентификацией?

Продакшен MCP-серверу нужна аутентификация. Без неё любой, кто обнаружит ваш эндпоинт, сможет вызывать ваши инструменты. Используйте bearer-токен, проверяемый в Express middleware до того, как запросы дойдут до MCP-транспорта.

Сгенерируйте токен:

openssl rand -base64 32

Сохраните результат. Он понадобится и для конфигурации сервера, и для конфигурации клиента.

Добавьте middleware аутентификации и маршруты Express в src/index.ts:

// --- Auth middleware ---

const AUTH_TOKEN = process.env.MCP_AUTH_TOKEN;
if (!AUTH_TOKEN) {
  console.error("MCP_AUTH_TOKEN environment variable is required");
  process.exit(1);
}

function authenticateRequest(
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
): void {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    res.status(401).json({
      jsonrpc: "2.0",
      error: { code: -32001, message: "Missing or invalid authorization" },
      id: null,
    });
    return;
  }
  const token = authHeader.slice(7);
  if (token !== AUTH_TOKEN) {
    res.status(403).json({
      jsonrpc: "2.0",
      error: { code: -32001, message: "Invalid token" },
      id: null,
    });
    return;
  }
  next();
}

// --- Express app ---

const app = express();
app.use(express.json());
app.use("/mcp", authenticateRequest);

Middleware выполняется перед каждым запросом к /mcp. Он проверяет наличие Bearer-токена в заголовке Authorization и отклоняет неавторизованные запросы с JSON-RPC ошибкой.

Как настроить Streamable HTTP транспорт?

Добавьте настройку транспорта и обработчики маршрутов. Каждый запрос создаёт новый McpServer через фабрику и новый экземпляр транспорта:

// --- Streamable HTTP transport ---

app.post("/mcp", async (req, res) => {
  const server = createServer();
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });
  await server.connect(transport);
  try {
    await transport.handleRequest(req, res, req.body);
  } catch (error) {
    console.error("MCP request error:", error);
    if (!res.headersSent) {
      res.status(500).json({
        jsonrpc: "2.0",
        error: { code: -32603, message: "Internal server error" },
        id: null,
      });
    }
  }
});

app.get("/mcp", (_req, res) => {
  res.status(405).json({
    jsonrpc: "2.0",
    error: { code: -32000, message: "Method not allowed. Use POST." },
    id: null,
  });
});

app.delete("/mcp", (_req, res) => {
  res.status(405).json({
    jsonrpc: "2.0",
    error: { code: -32000, message: "Method not allowed." },
    id: null,
  });
});

// --- Health check ---

app.get("/health", (_req, res) => {
  res.json({ status: "ok" });
});

// --- Start ---

const PORT = parseInt(process.env.PORT || "3000", 10);
app.listen(PORT, "127.0.0.1", () => {
  console.log(`MCP server listening on http://127.0.0.1:${PORT}/mcp`);
});

sessionIdGenerator: undefined создаёт stateless-сервер. Каждый запрос независим, со своей парой McpServer + транспорт. Это проще в деплое и хорошо работает за reverse proxy без sticky sessions. Подключение к базе данных (db) живёт на уровне модуля и разделяется между запросами, так что данные сохраняются между вызовами.

Сервер слушает на 127.0.0.1, а не 0.0.0.0. Он принимает только локальные подключения. Nginx будет единственным способом добраться до него извне.

Соберите и проверьте локально:

npm run build

Убедитесь, что сборка прошла без ошибок.

Как протестировать MCP-сервер локально?

MCP Inspector --- официальный инструмент для тестирования. Он подключается к серверу и позволяет вызывать инструменты интерактивно.

Запустите сервер в одном терминале:

MCP_AUTH_TOKEN=test-token-local npm start

В другом терминале проверьте через curl:

curl -X POST http://127.0.0.1:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer test-token-local" \
  -d '{
    "jsonrpc": "2.0",
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-03-26",
      "capabilities": {},
      "clientInfo": { "name": "test-client", "version": "1.0.0" }
    },
    "id": 1
  }'

Вы должны увидеть SSE-ответ (event: message с последующей строкой data:), содержащий capabilities сервера, включая объекты tools и resources. Это подтверждает, что сервер принимает подключения и отвечает на MCP initialization handshake.

Протестируйте инструмент create_note:

curl -X POST http://127.0.0.1:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer test-token-local" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "create_note",
      "arguments": { "title": "Test Note", "content": "Hello from curl" }
    },
    "id": 2
  }'

Ответ должен содержать "Note created with id: ...". Теперь протестируйте поиск:

curl -X POST http://127.0.0.1:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer test-token-local" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "params": {
      "name": "search_notes",
      "arguments": { "query": "Test" }
    },
    "id": 3
  }'

В результатах должна быть только что созданная заметка. Проверьте, что запрос без заголовка Authorization возвращает ошибку 401:

curl -s -o /dev/null -w "%{http_code}" -X POST http://127.0.0.1:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}'

Должно вывести 401. Если так, middleware аутентификации работает.

Также можно использовать MCP Inspector для интерактивного тестирования через веб-интерфейс:

npx @modelcontextprotocol/inspector

Inspector откроет браузерный интерфейс, где можно подключиться к серверу, просмотреть инструменты и ресурсы и проверить их работу.

Как развернуть MCP-сервер на VPS?

Перенесите проект на VPS через rsync, scp или запушьте в Git-репозиторий и склонируйте на сервере. Дальнейшие шаги предполагают, что вы залогинены на VPS.

Создайте выделенного пользователя

Никогда не запускайте приложения от root. Создайте сервисного пользователя:

sudo useradd -r -m -s /usr/sbin/nologin mcpserver

Установите Node.js

Если Node.js 20+ ещё не установлен:

curl -fsSL https://deb.nodesource.com/setup_20.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
sudo apt-get install -y nodejs
node --version
npm --version

Первая команда скачивает скрипт установки в файл, чтобы вы могли проверить его перед запуском. Вывод должен показать v20.x.x или выше для Node.js и 10.x.x для npm. MCP SDK требует Node.js 18+, но 20 LTS рекомендуется для продакшена.

Настройте директорию проекта

sudo mkdir -p /opt/mcp-notes-server
sudo cp -r /path/to/your/project/* /opt/mcp-notes-server/
cd /opt/mcp-notes-server
sudo npm ci --omit=dev
sudo npm run build
sudo chown -R mcpserver:mcpserver /opt/mcp-notes-server

Проверьте владельца:

ls -la /opt/mcp-notes-server/

Все файлы должны принадлежать mcpserver:mcpserver.

Создайте файл окружения

Сохраните токен аутентификации в защищённом файле:

sudo mkdir -p /etc/mcp-notes-server
echo "MCP_AUTH_TOKEN=$(openssl rand -base64 32)" | sudo tee /etc/mcp-notes-server/env > /dev/null
sudo chmod 600 /etc/mcp-notes-server/env
sudo chown mcpserver:mcpserver /etc/mcp-notes-server/env

Прочитайте сгенерированный токен и сохраните его в безопасном месте. Он понадобится для настройки клиентов:

sudo cat /etc/mcp-notes-server/env

Создайте systemd-сервис

sudo tee /etc/systemd/system/mcp-notes-server.service > /dev/null << 'EOF'
[Unit]
Description=MCP Notes Server
After=network.target

[Service]
Type=simple
User=mcpserver
Group=mcpserver
WorkingDirectory=/opt/mcp-notes-server
EnvironmentFile=/etc/mcp-notes-server/env
ExecStart=/usr/bin/node build/index.js
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

# Hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/mcp-notes-server
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

EnvironmentFile загружает MCP_AUTH_TOKEN из защищённого файла. Секция hardening ограничивает возможности процесса. NoNewPrivileges предотвращает повышение привилегий. ProtectSystem=strict делает файловую систему read-only, кроме путей в ReadWritePaths. ProtectHome блокирует доступ к домашним директориям пользователей.

Активируйте и запустите сервис:

sudo systemctl enable --now mcp-notes-server

enable заставляет сервис запускаться при загрузке. --now запускает его немедленно.

Проверьте статус:

sudo systemctl status mcp-notes-server

Должно быть active (running). Если сервис упал, проверьте логи:

journalctl -u mcp-notes-server -n 50 --no-pager

Проверьте, что сервер отвечает локально:

TOKEN=$(sudo grep MCP_AUTH_TOKEN /etc/mcp-notes-server/env | cut -d= -f2)
curl -s http://127.0.0.1:3000/health

Должно вернуть {"status":"ok"}.

Как запустить MCP-сервер за Nginx с TLS?

Nginx терминирует TLS и проксирует запросы к MCP-серверу. MCP-сервер не обрабатывает TLS напрямую. Это стандартная схема для продакшен-деплоя Node.js.

Установите Nginx и Certbot

sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx

Настройте серверный блок Nginx

sudo tee /etc/nginx/sites-available/mcp.example.com.conf > /dev/null << 'NGINX'
server {
    listen 80;
    listen [::]:80;
    server_name mcp.example.com;

    # Certbot will add the redirect to HTTPS
    location / {
        return 444;
    }
}
NGINX

Замените mcp.example.com на ваш домен.

Активируйте сайт и получите TLS-сертификат:

sudo ln -s /etc/nginx/sites-available/mcp.example.com.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d mcp.example.com

Certbot модифицирует конфигурацию, добавляя TLS-настройки и редирект с HTTP на HTTPS. Теперь обновите конфигурацию, добавив reverse proxy для MCP-эндпоинта:

sudo tee /etc/nginx/sites-available/mcp.example.com.conf > /dev/null << 'NGINX'
server {
    listen 80;
    listen [::]:80;
    server_name mcp.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name mcp.example.com;

    ssl_certificate /etc/letsencrypt/live/mcp.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mcp.example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # Hide Nginx version
    server_tokens off;

    # MCP endpoint
    location /mcp {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        # Required for Streamable HTTP / SSE responses
        proxy_buffering off;
        proxy_cache off;
        chunked_transfer_encoding off;

        # Keep connection open for streaming responses
        proxy_set_header Connection "";

        # Extended timeouts for long-running tool calls
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;

        # Forward client info
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Pass through Authorization header
        proxy_set_header Authorization $http_authorization;
    }

    # Health check (no auth required)
    location /health {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
    }

    # Block everything else
    location / {
        return 444;
    }
}
NGINX

Директивы, важные для совместимости с MCP:

  • proxy_buffering off не даёт Nginx буферизовать SSE-ответы. Без этого потоковые ответы зависают до закрытия соединения.
  • proxy_cache off исключает кеширование динамических ответов.
  • chunked_transfer_encoding off предотвращает проблемы с chunked-кодировкой в SSE-потоках.
  • Connection "" держит соединение открытым без запуска WebSocket upgrade.
  • proxy_read_timeout 300s позволяет вызовам инструментов выполняться до 5 минут. Стандартные 60 секунд слишком мало для сложных операций.

Проверьте и перезагрузите:

sudo nginx -t
sudo systemctl reload nginx

Проверьте с локальной машины (не с сервера):

curl -s https://mcp.example.com/health

Должно вернуть {"status":"ok"}. Если так, Nginx корректно проксирует запросы к MCP-серверу через TLS.

Правила файрвола

Заблокируйте прямой доступ к порту 3000 извне. Только Nginx (на localhost) должен обращаться к MCP-серверу:

sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Проверьте, что порт 3000 недоступен снаружи:

# From your local machine, this should time out or be refused:
curl -s --connect-timeout 5 http://YOUR_VPS_IP:3000/health

Подключение должно упасть. MCP-сервер доступен только через Nginx на порту 443.

Как подключить MCP-клиенты к удалённому серверу?

Сервер запущен за Nginx с TLS. Настроим MCP-клиенты для подключения.

Claude Code

Добавьте удалённый сервер через CLI:

claude mcp add --transport http \
  --header "Authorization: Bearer YOUR_TOKEN_HERE" \
  notes-server https://mcp.example.com/mcp

Замените YOUR_TOKEN_HERE на токен из /etc/mcp-notes-server/env.

Проверьте подключение:

claude mcp list

В Claude Code наберите /mcp, чтобы проверить статус сервера. notes-server должен отображаться как подключённый с доступными инструментами search_notes и create_note.

Claude Desktop

Claude Desktop подключается к удалённым MCP-серверам через Settings UI. Перейдите в Settings > Connectors и добавьте новый удалённый сервер с URL https://mcp.example.com/mcp.

Если нужно передать bearer-токен, можно также настроить через локальный файл .mcp.json или команду claude mcp add (показана выше) и импортировать сервер в Claude Desktop:

claude mcp add-from-claude-desktop

Cursor

Откройте настройки Cursor и перейдите к конфигурации MCP. Добавьте новый сервер в файле mcp.json (или через Cursor settings UI):

{
  "mcpServers": {
    "notes-server": {
      "url": "https://mcp.example.com/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN_HERE"
      }
    }
  }
}

Cursor сначала пробует Streamable HTTP при подключении к URL, поэтому дополнительная настройка транспорта не нужна.

Проверьте сквозное подключение

Из любого подключённого клиента попросите AI:

  1. Создать заметку: "Create a note titled 'Server Test' with content 'Connected successfully'"
  2. Найти её: "Search my notes for 'Server Test'"

Если обе операции вернули результаты, вся цепочка работает: клиент -> Nginx -> MCP-сервер -> SQLite и обратно.

Справочник MCP-примитивов

Примитив Назначение Пример
Tools (инструменты) Функции, которые AI может вызывать со структурированными входными данными search_notes, create_note
Resources (ресурсы) Данные только для чтения, на которые AI может ссылаться notes://all список заметок
Prompts (промпты) Готовые шаблоны для типовых задач Не используются в этом туториале

Tools --- самый часто реализуемый примитив. Resources полезны для предоставления справочных данных без вычислений. Prompts позволяют создавать многоразовые шаблоны, которые направляют AI через конкретные рабочие процессы.

Что-то пошло не так?

Сервер не запускается. Проверьте журнал:

journalctl -u mcp-notes-server -n 50 --no-pager

Частые причины: отсутствует переменная окружения MCP_AUTH_TOKEN, неправильные права на notes.db, устаревшая версия Node.js.

Nginx возвращает 502 Bad Gateway. MCP-сервер не запущен или не слушает порт 3000. Проверьте:

sudo systemctl status mcp-notes-server
curl -s http://127.0.0.1:3000/health

Клиент получает 401 Unauthorized. Токен в конфигурации клиента не совпадает с токеном на сервере. Перепроверьте:

sudo cat /etc/mcp-notes-server/env

Потоковые ответы зависают. Увеличьте proxy_read_timeout в конфигурации Nginx. 300 секунд покрывают большинство случаев, но долгим инструментам может понадобиться больше.

Ошибки сертификата. Проверьте валидность сертификата для вашего домена:

sudo certbot certificates

Если сертификат истёк, обновите его:

sudo certbot renew

Ошибка блокировки базы данных. SQLite WAL хорошо справляется с параллельным чтением, но параллельная запись может конфликтовать. Для высоконагруженных серверов рассмотрите переход на PostgreSQL.

Permission denied на notes.db. Пользователь mcpserver должен иметь права на запись в рабочую директорию для SQLite. Проверьте:

ls -la /opt/mcp-notes-server/notes.db

Файл должен принадлежать mcpserver:mcpserver. Если он был создан root при тестировании, исправьте:

sudo chown mcpserver:mcpserver /opt/mcp-notes-server/notes.db
sudo chmod 640 /opt/mcp-notes-server/notes.db

Клиент показывает "connection refused" или "timeout". Проверьте, что DNS A-запись указывает на IP вашего VPS и порты 80/443 открыты в файрволе:

sudo ufw status
dig +short mcp.example.com

Вывод dig должен показать IP-адрес вашего VPS. Если нет, обновите DNS-записи и подождите распространения.

Дальнейшие шаги

MCP-сервер запущен и защищён. Куда двигаться дальше:

  • Добавьте больше инструментов для работы с вашими базами данных, API или файловыми системами
  • Реализуйте OAuth 2.1 вместо bearer-токенов для многопользовательского доступа
  • Добавьте rate limiting в Express или Nginx для защиты от злоупотреблений
  • Настройте мониторинг через journalctl -u mcp-notes-server -f и алертинг
  • Разверните несколько MCP-серверов за одним Nginx на разных путях

Официальная документация MCP SDK описывает продвинутые паттерны, включая stateful-сессии, отчёты о прогрессе и схемы структурированного вывода.


Copyright 2026 Virtua.Cloud. Vse prava zashchishcheny.

Готовы попробовать?

Разверните свой сервер за секунды. Linux, Windows или FreeBSD.

Смотреть тарифы VPS