Перейти к основному содержимому
ИнженерияMar 28, 2026

MCP в продакшене: решение проблем транспорта, аутентификации и масштабирования

OS
Open Soft Team

Engineering Team

От прототипа к продакшену: что меняется

Создать MCP-сервер, работающий на вашем ноутбуке, несложно. Запустить сервер, обрабатывающий тысячи одновременных сессий AI-агентов в распределённой инфраструктуре — это совсем другая инженерная задача. Продакшен-развёртывания MCP должны решать пять проблем, которые прототипы могут игнорировать: масштабируемость транспорта, аутентификация и авторизация, управление сессиями в масштабе, аудит-трейлы и оркестрация нескольких серверов.

Эта статья — техническое руководство для инженерных команд, переводящих MCP-серверы из разработки в продакшен. Мы предполагаем, что вы уже создали хотя бы один MCP-сервер и понимаете основы протокола. Если нет, начните с нашей сопутствующей статьи о создании первого MCP-сервера.

Масштабируемость транспорта: stdio vs SSE vs Streamable HTTP

MCP определяет три транспортных механизма. Выбор правильного для продакшена — ваше первое архитектурное решение.

Транспорт stdio

Транспорт stdio общается через стандартные потоки ввода/вывода. Хост-приложение запускает MCP-сервер как дочерний процесс и обменивается JSON-RPC сообщениями через stdin/stdout.

Преимущества:

  • Нулевая сетевая конфигурация
  • Изоляция на уровне процесса
  • Отсутствие конфликтов портов
  • Минимальная задержка (без сетевого стека)

Ограничения:

  • Сервер должен работать на той же машине, что и хост
  • Один серверный процесс на клиентскую сессию
  • Невозможна балансировка нагрузки
  • Невозможно горизонтальное масштабирование

Лучше всего для: локальных инструментов разработки, расширений IDE, однопользовательских десктопных приложений.

Транспорт SSE (Server-Sent Events)

SSE-транспорт использует HTTP для сообщений клиент-сервер и Server-Sent Events для сообщений сервер-клиент. Сервер работает как HTTP-сервис.

Преимущества:

  • Сетевой доступ (удалённые серверы)
  • Совместимость с существующей HTTP-инфраструктурой
  • Поддержка нескольких одновременных клиентов
  • Работает через файрволы и прокси

Ограничения:

  • Однонаправленная потоковая передача (сервер-клиент только через SSE)
  • Требуется привязка сессий (стейтфул-соединения)
  • Некоторые балансировщики плохо работают с долгоживущими SSE-соединениями
  • Нет встроенной семантики переподключения в протоколе

Лучше всего для: малых и средних развёртываний, внутренних инструментов, команд с существующей HTTP-инфраструктурой.

Транспорт Streamable HTTP

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

Преимущества:

  • Полностью статлесс модель запрос/ответ
  • Работает с любым HTTP-балансировщиком
  • Встроенное управление сессиями через заголовок Mcp-Session-Id
  • Поддержка потоковых и непотоковых ответов
  • Совместимость с CDN и прокси

Ограничения:

  • Требуется внешнее хранилище сессий (Redis, база данных)
  • Чуть выше оверхед на сообщение, чем у stdio
  • Более новый транспорт — меньше инструментов экосистемы

Лучше всего для: продакшен-развёртываний в облаке, мультитенантных платформ, корпоративных сред.

Сравнительная матрица транспортов

ХарактеристикаstdioSSEStreamable HTTP
Сетевой доступТолько локальныйУдалённыйУдалённый
Балансировка нагрузкиНевозможнаСтики-сессииСтандартный HTTP LB
Горизонтальное масштабированиеНетОграниченоДа
Через файрволН/ДДаДа
ПереподключениеН/ДВручнуюВстроено
Одновременные клиенты1МногоМного
ЗадержкаМинимальнаяНизкаяНизкая
СтатлессСтейтфулСтейтфулВозможен статлесс
Готовность к продакшенуТолько разработкаСредняяВысокая

Аутентификация: SSO-интеграция, API-ключи и OAuth

MCP-серверы в продакшене должны аутентифицировать как AI-хост-приложение, так и конечного пользователя, от имени которого AI действует.

OAuth 2.0 для удалённых серверов

Спецификация MCP включает встроенный поток OAuth 2.0 для удалённых (HTTP-based) серверов. Процесс выглядит так:

  1. MCP-клиент отправляет запрос initialize без учётных данных.
  2. Сервер отвечает HTTP 401 с заголовком WWW-Authenticate, указывающим на OAuth-эндпоинт авторизации.
  3. Хост-приложение открывает браузер для аутентификации пользователя.
  4. После успешной аутентификации хост получает токен доступа.
  5. Последующие MCP-запросы включают токен в заголовке Authorization: Bearer.
// Серверный OAuth middleware для Axum/Express
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";

const app = express();

app.use("/mcp", async (req, res, next) => {
  const token = req.headers.authorization?.replace("Bearer ", "");
  if (!token) {
    res.status(401).json({
      error: "unauthorized",
      oauth_url: "https://auth.example.com/oauth/authorize",
    });
    return;
  }

  // Валидация токена через провайдер аутентификации
  const user = await validateToken(token);
  if (!user) {
    res.status(403).json({ error: "invalid_token" });
    return;
  }

  // Прикрепление контекста пользователя для авторизации инструментов
  req.mcpUser = user;
  next();
});

Аутентификация через API-ключи

Для межсервисного MCP-взаимодействия (без участия человека) API-ключи проще:

const API_KEYS = new Map([
  ["sk-prod-abc123", { name: "analytics-service", scopes: ["read"] }],
  ["sk-prod-def456", { name: "admin-service", scopes: ["read", "write"] }],
]);

function authenticateApiKey(key: string) {
  return API_KEYS.get(key) || null;
}

Паттерн SSO-интеграции

Для корпоративных развёртываний — интеграция с существующим SSO (SAML, OIDC):

Пользователь -> AI-хост -> MCP-клиент -> MCP-сервер -> SSO-провайдер
                                              |
                                              v
                                      Валидация OIDC-токена
                                      Извлечение ролей
                                      Применение RBAC на уровне инструментов

Каждый MCP-инструмент может проверять роли аутентифицированного пользователя перед выполнением:

server.tool(
  "delete_record",
  "Удалить запись из базы данных по ID",
  { table: z.string(), id: z.string() },
  async ({ table, id }, { authContext }) => {
    if (!authContext.roles.includes("admin")) {
      return {
        content: [{ type: "text", text: "Запрещено: требуется роль admin" }],
        isError: true,
      };
    }
    // Выполнение удаления
  }
);

Масштабирование: стейтфул-сессии vs балансировщики нагрузки

MCP-сессии по своей природе стейтфулны: хендшейк initialize согласовывает возможности, и сервер может хранить контекст между вызовами инструментов в рамках сессии. Это создаёт противоречие с горизонтальным масштабированием.

Архитектура хранилища сессий

Вынесите состояние сессий из серверного процесса во внешнее хранилище:

interface McpSession {
  id: string;
  userId: string;
  capabilities: ServerCapabilities;
  createdAt: Date;
  lastActivityAt: Date;
  metadata: Record<string, unknown>;
}

class RedisSessionStore {
  constructor(private redis: Redis) {}

  async create(session: McpSession): Promise<void> {
    await this.redis.set(
      `mcp:session:${session.id}`,
      JSON.stringify(session),
      "EX",
      3600 // TTL 1 час
    );
  }

  async get(sessionId: string): Promise<McpSession | null> {
    const data = await this.redis.get(`mcp:session:${sessionId}`);
    return data ? JSON.parse(data) : null;
  }

  async touch(sessionId: string): Promise<void> {
    await this.redis.expire(`mcp:session:${sessionId}`, 3600);
  }
}

Горизонтальное масштабирование со Streamable HTTP

С вынесенными сессиями и транспортом Streamable HTTP можно запускать несколько экземпляров MCP-сервера за стандартным балансировщиком:

                    ┌──────────────┐
                    │  Балансировщик│
     MCP-клиент ──>│  нагрузки    │
                    │  (nginx/ALB) │
                    └──────┬───────┘
                           │
              ┌────────────┼────────────┐
              v            v            v
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │ MCP      │ │ MCP      │ │ MCP      │
        │ Сервер 1 │ │ Сервер 2 │ │ Сервер 3 │
        └────┬─────┘ └────┬─────┘ └────┬─────┘
             │             │             │
             v             v             v
        ┌────────────────────────────────────┐
        │          Redis (сессии)            │
        └────────────────────────────────────┘

Стики-маршрутизация не требуется. Любой экземпляр сервера может обработать любой запрос, загрузив сессию из Redis.

Конфигурация автомасштабирования

Пример Kubernetes HPA для MCP-серверов:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: mcp-server
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: mcp-server
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Pods
      pods:
        metric:
          name: mcp_active_sessions
        target:
          type: AverageValue
          averageValue: "100"

Аудит-трейлы и логирование

Каждый вызов MCP-инструмента в продакшене должен логироваться для безопасности, комплаенса и отладки.

Схема структурированного логирования

interface McpAuditLog {
  timestamp: string;
  sessionId: string;
  userId: string;
  toolName: string;
  toolInput: Record<string, unknown>;
  toolOutput: string;
  durationMs: number;
  success: boolean;
  errorMessage?: string;
  ipAddress: string;
  userAgent: string;
}

// Middleware, оборачивающий каждый вызов инструмента
function auditMiddleware(server: McpServer, logger: Logger) {
  const originalTool = server.tool.bind(server);

  server.tool = (name, description, schema, handler) => {
    originalTool(name, description, schema, async (args, context) => {
      const start = Date.now();
      try {
        const result = await handler(args, context);
        logger.info({
          event: "mcp_tool_call",
          tool: name,
          input: args,
          durationMs: Date.now() - start,
          success: !result.isError,
          sessionId: context.sessionId,
          userId: context.authContext?.userId,
        });
        return result;
      } catch (error) {
        logger.error({
          event: "mcp_tool_error",
          tool: name,
          input: args,
          error: (error as Error).message,
          durationMs: Date.now() - start,
          sessionId: context.sessionId,
        });
        throw error;
      }
    });
  };
}

Требования комплаенса

ТребованиеРеализация
Доступ к данным GDPRЛогирование какие данные пользователей AI получил через MCP
Аудит-трейл SOC 2Неизменяемые логи всех вызовов инструментов с метками времени
Защита PIIРедактирование чувствительных полей во входных данных перед логированием
Хранение данныхУстановка TTL логов в соответствии с требованиями комплаенса
Ревью доступаЛогирование событий аутентификации и проверок разрешений

Паттерны шлюзов для многосерверных развёртываний

Корпоративные среды часто нуждаются в десятках MCP-серверов — по одному для каждой внутренней системы. MCP-шлюз централизует управление:

                          ┌──────────────┐
                          │  MCP-шлюз    │
        MCP-клиент ──────>│              │
                          │  - Auth      │
                          │  - Роутинг   │
                          │  - Лимиты    │
                          │  - Аудит     │
                          └──────┬───────┘
                                 │
              ┌──────────────────┼──────────────────┐
              v                  v                  v
        ┌──────────┐     ┌──────────┐      ┌──────────┐
        │ Jira MCP │     │ Slack MCP│      │ DB MCP   │
        │ Сервер   │     │ Сервер   │      │ Сервер   │
        └──────────┘     └──────────┘      └──────────┘

Шлюз обеспечивает:

  1. Единую аутентификацию: один поток аутентификации для всех бэкенд-серверов.
  2. Маршрутизацию пространств имён инструментов: инструменты имеют префикс имени сервера (jira.create_issue, slack.send_message).
  3. Централизованное ограничение частоты: лимиты по пользователю, инструменту и серверу.
  4. Маршрутизацию запросов: направление вызовов инструментов к соответствующему бэкенд-серверу.
  5. Circuit breaking: если бэкенд-сервер недоступен, шлюз возвращает грейсфул-ошибки вместо зависания.

Реализация базового шлюза

class McpGateway {
  private backends = new Map<string, McpClient>();

  async registerBackend(prefix: string, url: string) {
    const client = new McpClient({ name: `gateway-${prefix}` });
    const transport = new StreamableHTTPClientTransport(new URL(url));
    await client.connect(transport);
    this.backends.set(prefix, client);
  }

  async routeToolCall(
    toolName: string,
    args: Record<string, unknown>
  ) {
    // формат toolName: "prefix.actualTool"
    const [prefix, ...rest] = toolName.split(".");
    const actualTool = rest.join(".");
    const backend = this.backends.get(prefix);

    if (!backend) {
      throw new Error(`Неизвестный бэкенд: ${prefix}`);
    }

    return backend.callTool({ name: actualTool, arguments: args });
  }
}

FAQ

В: Какой транспорт использовать в продакшене — stdio или HTTP? О: Используйте Streamable HTTP для любого развёртывания, которое должно масштабироваться за пределы одной машины. Используйте stdio только для локальных десктопных интеграций, где AI-хост и MCP-сервер работают на одной машине.

В: Сколько одновременных сессий может обрабатывать один MCP-сервер? О: С транспортом Streamable HTTP и внешним хранилищем сессий один экземпляр Node.js-сервера обычно обрабатывает 500–1000 одновременных сессий. На серверах Rust или Go достижимо 5000–10 000. Масштабируйте горизонтально за этими пределами.

В: Как обрабатывать простой MCP-сервера? О: Реализуйте health-проверки (эндпоинт /health), используйте liveness/readiness пробы Kubernetes и настройте шлюз с circuit breaker’ами. MCP-клиент получит ошибки соединения, которые AI-хост может показать как «инструмент временно недоступен».

В: Могут ли MCP-серверы вызывать другие MCP-серверы? О: Да. MCP-сервер также может быть MCP-клиентом, создавая цепочку. Это основа межагентной коммуникации. Используйте этот паттерн осторожно — глубокие цепочки увеличивают задержку и вероятность отказа.

В: Как версионировать API MCP-сервера? О: Используйте поле версии сервера в ответе initialize. Для ломающих изменений (удаление инструментов, изменение схем) поднимайте мажорную версию и поддерживайте обе версии в период миграции. Версия самого протокола MCP согласовывается при инициализации.

В: Каков максимальный размер MCP-сообщения? О: Протокол не определяет максимума, но практические ограничения зависят от транспорта. Для Streamable HTTP держите ответы менее 10 МБ. Для stdio системные буферы пайпов (обычно 64 КБ) могут потребовать чанкинга для больших ответов.