
Когда MCP-серверов стало шесть, а коллег — десять, фраза «просто запусти npx локально» перестала работать. Не у всех есть желания ставить Node.js, у менеджеров нет Docker, а локальный claude_desktop_config.json начинает напоминать хранилище секретов от всех систем.
Я прошёл путь от remote MCP → локальный запуск → Docker → Kubernetes с единым Helm-чартом и JWT-аутентификацией через Envoy. Расскажу, на что напоролся, что получилось, и что еще предстоит решить.
Уровень 1: Remote MCP — когда вендор постарался за тебя
Первое знакомство с MCP было максимально простым. Я добавил atlassian-mcp-server в Claude как remote mcp, прошел аутентификацию и мог им пользоваться. Все выглядело достаточно просто и доступно, Atlassian сам предоставлял возможность подключения и аутентификации.
{ "mcpServers": { "atlassian": { "type": "http", "url": "https://mcp.atlassian.com/v1/sse" } } }
Уровень 2: Локальный запуск — первые компромиссы
Дальше я захотел связать свою IDE с Kubernetes. Здесь уже дело оказалось сложнее, потому, что Kubernetes не предоставляет доступ через MCP из коробки. Тут уже пришлось ставить зависимости:
{ "mcpServers": { "kubernetes": { "command": "npx", "args": ["-y", "kubernetes-mcp-server@latest"] } } }

Заработало, но для одного сервера нужен Node.js, для другого — Python и uvx, для третьего — Go-бинарник. Зоопарк рантаймов на рабочей машине множится с каждым новым MCP. Совсем не хочется забивать этим все рабочий компьютер, тем более, что я даже не разработчик)
Уровень 3: Docker — изоляция без боли
Логичный следующий шаг — контейнеры. Каждый MCP-сервер со своим рантаймом, без мусора на хосте:
{ "mcpServers": { "grafana": { "command": "docker", "args": [ "run", "--rm", "-i", "-e", "GRAFANA_URL", "-e", "GRAFANA_SERVICE_ACCOUNT_TOKEN", "grafana/mcp-grafana", "-t", "stdio" ], "env": { "GRAFANA_URL": "https://grafana.example.com", "GRAFANA_SERVICE_ACCOUNT_TOKEN": "<token>" } } } }
На этом можно было бы и остановится. Для SaaS решений использовать их remote-mcp. Для self-hosted решений, запускаемые локально в контейнере. Для одного инженера на одной машине — достаточно. Но когда это нужно десяти людям, возникают вопросы:
Токены от продакшн-систем разбросаны по ноутбукам.
Автоматизированным workflow (n8n, CI/CD) тоже нужен доступ к MCP — а они работают удалённо.
Менеджеры и аналитики хотят использовать AI-инструменты, но не готовы разбираться с
docker run.
Всё это привело к одному выводу: MCP-серверы надо выносить в общую инфраструктуру.
Уровень 4: Kubernetes — централизованный деплой
Изначально идея была простой: развернуть удаленные MCP сервера во внутреннем контуре вашей инфраструктуры. Здесь мы как минимум можем ограничить доступ через корпоративный VPN.
Все кто занимался подобной задачей сталкивались с тем, как организовать доступ к серверу принимающему данные через stdin удаленно. Например mcp-digitalocean
Здесь на помощь приходят решения вроде MCP Proxy, которые могут наладить коммуникацию между HTTP и stdin. Так как MCP сервера в основном однотипные stateless (если отключить нотификации об изменении инструментов и промптов) сервисы их удобно запускать в Kubernetes.

Схема выглядит так: клиент (Claude Desktop, IDE, n8n) обращается по HTTPS к API Gateway. Gateway проксирует запрос в Kubernetes Service. В поде рядом с MCP-сервером крутится MCP Gateway — принимает HTTP и передаёт в stdin процесса.
Универсальный Helm-чарт
Чтобы не писать манифесты под каждый MCP-сервер, я сделал универсальный Helm-чарт: mcp-helm-chart на ArtifactHub.
Что он умеет:
mode: proxy — запускает MCP Gateway sidecar-контейнером рядом с MCP-сервером, транслирует HTTP ↔ stdio.
mode: native — для серверов, которые уже поддерживают HTTP (без sidecar).
Интеграция с Hashicorp Vault и ExternalSecrets для секретов.
Поддержка Gateway API и классического Ingress.
HPA для горизонтального масштабирования.
Установка mcp-digitalocean c Ingress-nginx без аутентификации
helm repo add mcp https://javdet.github.io/mcp-helm-chart helm install my-mcp mcp/mcp -f values.yaml
В values.yaml из важного указать
--- mode: proxy proxy: image: repository: node tag: "20-bookworm" pullPolicy: IfNotPresent gateway: package: "@michlyn/mcpgateway" stdioCommand: "npx -y @digitalocean/mcp --services apps,droplets,doks,networking" outputTransport: streamable-http port: 8080 httpPath: /mcp # Токен можно хранить в Hashicorp Vault и получать через Vault Webhook vault: enabled: true role: "mcp" path: "kubernetes_dev-fra1-01" env: - name: DIGITALOCEAN_API_TOKEN value: vault:devops/data/ai/mcp/digitalocean#token ingress: enabled: true className: "internal" annotations: nginx.ingress.kubernetes.io/proxy-buffering: "off" nginx.ingress.kubernetes.io/proxy-http-version: "1.1" nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" nginx.ingress.kubernetes.io/use-regex: "true" nginx.ingress.kubernetes.io/rewrite-target: /$2 hosts: - host: aitool.example.com paths: - path: /digitalocean(/|$)(.*) pathType: ImplementationSpecific tls: - secretName: ssl-certificate hosts: - aitool.example.com
Инсталляция будет выглядеть следующим образом

MCP-серверы в режиме Streamable HTTP — stateless. Горизонтально масштабируются штатным HPA без каких-либо проблем. Самым насущным вопросом тут остается аутентификация, а еще лучше авторизация. Сами MCP сервера не всегда поддерживают входящую аутентификацию, значит нужно решать это самостоятельно.
Аутентификация: JWT через Envoy
Basic-аутентификация немногим лучше, чем ничего, поэтому сразу JWT. Я использовал Envoy API Gateway — он нативно поддерживает JWT-валидацию и уже был в нашем стеке.
Генерация ключей и токена
# 1. Генерируем RSA-ключи openssl genrsa -out mcp-jwt-private.pem 4096 openssl rsa -in mcp-jwt-private.pem -pubout -out mcp-jwt-public.pem # 2. Генерируем Key ID KID=$(openssl rand -hex 16) # 3. Формируем JWT header (base64url) HEADER=$(echo -n "{\"alg\":\"RS256\",\"typ\":\"JWT\",\"kid\":\"${KID}\"}" \ | base64 -w0 | tr '+/' '-_' | tr -d '=') # 4. Формируем JWT payload (срок — 1 год) PAYLOAD=$(echo -n "{\"sub\":\"claude-desktop\",\"aud\":\"mcp-servers\",\"iss\":\"https://your-domain.com\",\"iat\":$(date +%s),\"exp\":$(( $(date +%s) + 31536000 ))}" \ | base64 -w0 | tr '+/' '-_' | tr -d '=') # 5. Подписываем SIGNATURE=$(echo -n "${HEADER}.${PAYLOAD}" \ | openssl dgst -sha256 -sign mcp-jwt-private.pem \ | base64 -w0 | tr '+/' '-_' | tr -d '=') # 6. Финальный токен echo "${HEADER}.${PAYLOAD}.${SIGNATURE}"
Публичный ключ упаковывается в JWKS и кладётся в ConfigMap. Envoy валидирует каждый входящий запрос, проверяя issuer, audience и подпись.
Конфигурация аутентификации в values чарта (вариант с Gateway API):
gatewayApi: enabled: true parentRefs: - name: internal # Тут уже должен быть создан Gateway с именем internal namespace: ai-infra sectionName: https hostnames: - mcptools.example.com timeouts: request: "3600s" backendRequest: "3600s" rules: - matches: - path: type: PathPrefix value: /digitalocean filters: - type: URLRewrite urlRewrite: path: type: ReplacePrefixMatch replacePrefixMatch: / auth: type: jwt jwt: providers: - name: mcp-jwt-auth issuer: mcp-issuser audiences: - mcptools.example.com localJWKS: type: ValueRef valueRef: group: "" kind: ConfigMap name: jwks-config # Если пользуетесь External Secret Operator секреты можно получать через него externalSecrets: enabled: true refreshInterval: 1h secretStoreRef: name: aws kind: ClusterSecretStore target: creationPolicy: Owner dataFrom: - extract: key: infra/mcp/digitalocean

Сейчас доступ к целевым системам (DigitalOcean, Grafana, Kubernetes) осуществляется через единый сервисный аккаунт. Для read-only задач этого хватает: мониторинг, диагностика, получение информации. Для write-операций — вопрос открытый.
Автоматизированный доступ
Периодические задачи (n8n-воркфлоу, CI/CD-пайплайны) подключаются к тем же MCP-серверам по Streamable HTTP с отдельными сервисными JWT-токенами. Схема идентична — отличается только subject в payload токена и, при необходимости, scope доступа на уровне Gateway.
Итого
MCP инструментам и инфраструктуре еще предстоит сделать несколько шагов на встречу друг к другу, чтобы использование стало по настоящему простым, надежным и безопасным.
Текущая схема работает: шесть MCP-серверов в Kubernetes, единый Helm-чарт, JWT-аутентификация через Envoy, секреты в Vault. Коллеги подключаются к remote MCP-серверам без локальных зависимостей, автоматизация использует те же эндпоинты.
Чего пока не хватает:
Per-user авторизация. Протокол MCP не предусматривает передачу контекста пользователя. Пока живём с сервисными аккаунтами.
Audit log. Кто какой инструмент вызвал и с какими параметрами — пока не логируется на уровне MCP. Можно собирать на уровне Envoy, но без контекста вызова.
Стандарт аутентификации. Каждый вендор делает по-своему. OAuth, API Key, Bearer — единого подхода нет. Но в некоторых серверах уже появляется сквозная аутентификация, это радует.
Как вы решаете per-user авторизацию для MCP? Мы пока живём с единым сервисным аккаунтом — буду рад услышать, кто продвинулся дальше.
Ранее писал о разных интересных использования таких инструментов
