- Дата публикации
Как масштабировать MCP‑серверы в Azure App Service без боли с сессиями
Что нового
Microsoft показала, как запускать и масштабировать MCP‑серверы (Model Context Protocol) в Azure App Service как обычные веб‑API — без «липких» сессий и ручной возни с балансировкой.
Ключевые новшества и факты:
- MCP‑спецификация в ревизии 2025-06-18 формализовала статичный HTTP‑транспорт, текущая ревизия 2025-11-25 это сохраняет.
- Каждый запрос теперь самодостаточен: серверу не нужно хранить состояние клиента между запросами.
- Это позволяет ставить MCP‑сервер за встроенный load balancer App Service и горизонтально масштабировать его как любой REST API.
- Есть готовый рабочий пример: github.com/seligj95/app-service-mcp-stateless-scale-python.
- Один
azd upподнимает:- MCP‑сервер на FastAPI;
- 3 инстанса App Service (P0v3) за балансировщиком;
- staging‑слот для деплоя без простоя;
- Application Insights для телеметрии;
- скрипт k6 для нагрузки и визуализации распределения трафика по инстансам.
- MCP‑инструмент
whoamiпоказывает ID инстанса (WEBSITE_INSTANCE_ID), что позволяет проверить, как балансировщик раскидывает запросы. - В Bicep‑шаблоне критичный параметр:
clientAffinityEnabled: false— он отключает ARRAffinity‑cookie и делает балансировку по‑настоящему равномерной. - План Premium v3 (P0v3) — минимальный уровень, который даёт:
- Always On (процесс не выгружается);
- deployment slots (staging/production) для zero‑downtime деплоя.
Как это работает
Статичный HTTP‑транспорт MCP
Раньше MCP часто использовал постоянные соединения: SSE или WebSocket‑сессии, где сервер держал в памяти состояние клиента — открытые инструменты, подписки, частичные стримы. Это удобно для локального процесса рядом с IDE, но плохо масштабируется: если следующий запрос прилетает на другой инстанс, сессия рвётся.
Новый статичный HTTP‑транспорт решает это:
- Каждый запрос — полноценный JSON‑RPC‑конверт (
initialize,tools/list,tools/call). - Каждый ответ самодостаточен.
- Сервер может забыть клиента между запросами.
- Любой инстанс может обслужить любой вызов.
Это именно то поведение, которое нужен L4/L7‑балансировщику: нет привязки к конкретной машине.
В примере все инструменты реализованы как чистые функции от аргументов:
whoami— сообщает, какой инстанс обслужил запрос;lookup_fact— читает данные из статического словаря;compute_primes— считает простые числа через решето.
Они не трогают память, привязанную к конкретному клиенту. Это не требование протокола, а дисциплина, которая сохраняет статичность.
Почему именно App Service, а не Functions или AKS
Microsoft делает ставку на App Service для MCP‑серверов по нескольким причинам:
-
Always On
- MCP‑инструменты часто вызывают LLM и внешние API.
- Латентность легко улетает в несколько секунд и больше.
- Azure Functions по умолчанию ограничивает выполнение одним запросом до 10 минут и агрессивно скейлит воркеры до нуля между всплесками.
- App Service держит процесс в памяти, не убивает его между запросами и не ломает кэши.
-
Горизонтальный скейлинг одной настройкой
- Выбираете Premium SKU, ставите
capacity = N— получаете N инстансов за управляемым балансировщиком. - Не нужно настраивать VM Scale Set, Ingress‑контроллер и Kubernetes Service.
- Выбираете Premium SKU, ставите
-
Deployment slots
- Готовите staging‑слот, прогреваете его и делаете swap в production без простоя.
- Это критично, когда MCP‑endpoint — это «поверхность инструментов» для агента, который сейчас работает.
-
Easy Auth
- Включаете аутентификацию в App Service, настраиваете Entra ID.
- Получаете OAuth 2.1 перед MCP‑endpoint без ручной реализации flow.
- В примере авторизацию отключили, чтобы развернуть всё одной командой, но включается она парой кликов.
В итоге App Service ведёт себя как PaaS, который умеет держать долго живущий процесс и масштабировать его по горизонтали. MCP‑сервер как раз такой процесс.
Статичный MCP‑сервер на FastAPI: один endpoint
Весь транспорт MCP укладывается в один POST‑обработчик:
@app.post("/mcp")
async def mcp_endpoint(request: Request):
body = await request.json()
method = body.get("method", "")
msg_id = body.get("id")
if method == "initialize":
return {
"jsonrpc": "2.0",
"id": msg_id,
"result": _server_info(),
}
if method == "tools/list":
return {
"jsonrpc": "2.0",
"id": msg_id,
"result": {"tools": [
# ...
]},
}
if method == "tools/call":
params = body.get("params", {})
result = await MCP_TOOLS[params["name"]]["function"](
**params.get("arguments", {})
)
return {
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"content": [
{
"type": "text",
"text": json.dumps(result),
}
]
},
}
``
Что важно:
- Нет таблицы сессий.
- Нет `client_id` в cookie.
- Нет `AsyncIterator`, который живёт между запросами.
- `initialize`, `tools/list` и `tools/call` — **один запрос — один ответ**.
Это ровно тот паттерн, на который рассчитывает балансировщик App Service.
### Диагностика: инструмент `whoami`
Самый полезный инструмент в примере — `whoami`:
```python
async def tool_whoami() -> Dict[str, Any]:
return {
"instance_id": os.environ.get("WEBSITE_INSTANCE_ID", "local"),
"hostname": socket.gethostname(),
# ...
}
``
`WEBSITE_INSTANCE_ID` — уникальный идентификатор для каждого воркера App Service.
Если вы несколько раз вызываете `whoami` из MCP‑клиента и видите, как `instance_id` меняется, — балансировщик распределяет трафик по инстансам. Если не меняется, трафик что‑то «приклеило» к одному инстансу. В App Service это почти всегда **ARRAffinity‑cookie**.
### Инфраструктура на Bicep: что именно даёт масштабирование
Пример использует план **P0v3** с тремя инстансами, веб‑приложение с отключённой аффинностью и staging‑слот:
```bicep
resource appServicePlan 'Microsoft.Web/serverfarms@2024-04-01' = {
name: name
sku: {
name: 'P0v3'
capacity: instanceCount // 3 by default
}
properties: {
reserved: true
}
}
resource web 'Microsoft.Web/sites@2024-04-01' = {
name: name
properties: {
serverFarmId: appServicePlanId
httpsOnly: true
clientAffinityEnabled: false // ← the one line that matters
siteConfig: {
linuxFxVersion: 'PYTHON|3.11'
alwaysOn: true
healthCheckPath: '/health'
appCommandLine: 'python -m uvicorn main:app --host 0.0.0.0 --port 8000'
}
}
}
resource staging 'Microsoft.Web/sites/slots@2024-04-01' = {
parent: web
name: 'staging'
properties: {
/* same shape — separate hostname, same plan */
}
}
Ключевые детали:
-
clientAffinityEnabled: false— самая важная строка.- По умолчанию App Service включает её и ставит ARRAffinity‑cookie.
- Cookie привязывает клиента к инстансу, который обработал первый запрос.
- Это нужно старым ASP.NET‑приложениям с сессиями в памяти.
- Для статичного MCP это вредно: балансировщик перестаёт равномерно раскидывать трафик.
-
alwaysOn: true— процесс не выгружается между запросами. -
linuxFxVersion: 'PYTHON|3.11'+appCommandLine— запуск FastAPI через Uvicorn. -
Premium v3 (P0v3) нужен, чтобы получить Always On и deployment slots. Ниже этого тарифа их нет.
Application Insights без ручной телеметрии
В main.py добавлена одна строка для подключения Azure Monitor OpenTelemetry:
from azure.monitor.opentelemetry import configure_azure_monitor
if os.environ.get("APPLICATIONINSIGHTS_CONNECTION_STRING"):
configure_azure_monitor(logger_name="mcp")
Дальше всё делает дистрибутив OpenTelemetry для Azure:
- Автоматически инструментирует FastAPI и исходящие HTTP‑запросы.
- Каждый span запроса помечается тегом
cloud_RoleInstance. - Application Insights заполняет этот тег из
WEBSITE_INSTANCE_ID.
Проверка распределения трафика по инстансам превращается в один запрос в Logs:
requests
| where timestamp > ago(15m)
| where name contains "/mcp"
| summarize count() by cloud_RoleInstance
| order by count_ desc
Если видите три строки с примерно одинаковым числом запросов — балансировка работает. Если одна строка, клиент шлёт ARRAffinity‑cookie, и нужно отключить affinity и переразвернуть приложение.
Что это значит для вас
Для кого это полезно
-
Команды, которые строят продакшен‑агентов на MCP.
- Вы можете запускать MCP‑инструменты в App Service, не думая о сессиях и липких подключениях.
- Масштабирование по горизонтали превращается в настройку
capacityи правил автоскейла.
-
Разработчики IDE‑плагинов и внутренних тулов.
- Если MCP‑сервер перестал помещаться на одной машине, можно перенести его в App Service и плавно увеличивать число инстансов.
-
Техлиды, которые не хотят заводить Kubernetes ради одного сервиса.
- App Service закрывает сценарий MCP‑серверов без управления кластерами, ingress и манифестами.
Для каких задач это хорошо
-
MCP‑инструменты, которые:
- не зависят от состояния сессии;
- могут восстановить контекст из запроса (например, передаёте нужные ID и параметры);
- делают тяжёлые вычисления (
compute_primes) или ходят в медленные внешние API.
-
Агентные системы, которые вызывают MCP через HTTP и готовы жить с моделью «один запрос — один ответ».
-
Сценарии, где важны:
- безотказность при деплое — staging‑слот + swap;
- масштабирование во время пиков — автоскейл по CPU/задержкам;
- наблюдаемость — Application Insights и распределение трафика по инстансам.
Когда это не подойдёт
-
Вам нужен стриминг или долгие двусторонние сессии поверх WebSocket/SSE, где сервер держит в памяти состояние диалога.
- В этом случае придётся либо проектировать state store вне процесса, либо идти в более низкоуровневые решения (AKS, собственный Nginx/Envoy и т.п.).
-
MCP‑сервер жёстко завязан на in‑memory сессии и вы не готовы их выносить наружу.
-
Вы не можете использовать Azure (регуляторика, политика компании) или сервис недоступен из вашей инфраструктуры без VPN.
Ограничения и нюансы для России
- Azure App Service и сопутствующие сервисы (Entra ID, Application Insights) официально относятся к облачной инфраструктуре Microsoft.
- Для доступа из России могут потребоваться VPN, корпоративные каналы или инфраструктура вне юрисдикции РФ.
- Если вы строите продукт для локального рынка и у вас нет стабильного доступа к Azure, архитектура останется концептуальным ориентиром, но придётся искать аналог в других облаках.
Место на рынке
По сравнению с Azure Functions
Плюсы App Service для MCP:
- Постоянный процесс (Always On) против агрессивного scale‑to‑zero в Functions.
- Нет лимита исполнения в 10 минут, который может мешать сложным MCP‑инструментам.
- Проще контролировать версию рантайма и команду запуска (
appCommandLine).
Минусы:
- Потенциально дороже, чем безсерверная модель при редких запросах.
- Нужно самим думать о масштабировании (или настраивать автоскейл), а не полагаться на «магический» скейл Functions.
Конкретных чисел по цене в материале нет, но по модели биллинга App Service Premium v3 обычно дороже «холодных» функций при низкой нагрузке и выгоднее при постоянном трафике.
По сравнению с AKS и кастомным Kubernetes
Где выигрывает App Service:
- Меньше инфраструктурной сложности: нет кластера, ingress, сервис‑манифестов.
- Быстрый старт: один
azd upвместо развёртывания и поддержки Kubernetes.
Где Kubernetes сильнее:
- Тонкий контроль над сетевой топологией, балансировкой и sidecar‑паттернами.
- Легче объединять десятки микросервисов с разными требованиями в один кластер.
Если у вас уже есть AKS и сильная DevOps‑команда, MCP можно вписать туда. Если нет — App Service закрывает задачу MCP‑серверов с гораздо меньшей ценой входа.
По сравнению с локальными MCP‑серверми
Локальный процесс рядом с IDE или агентом:
- Проще в отладке.
- Не требует облака.
- Но не умеет переживать пики нагрузки, падения машины и деплой без простоя.
App Service‑подход даёт:
- Масштабирование по горизонтали.
- Балансировку без «липких» сессий.
- Наблюдаемость и логирование на уровне платформы.
Это логичный шаг, когда MCP переходит из «игрушки для локальной разработки» в продакшен‑сервис.
Установка
Для развёртывания примера Microsoft предлагает использовать Azure Developer CLI (azd).
Базовые шаги:
-
Установите
azdи залогиньтесь в Azure. -
Клонируйте репозиторий:
git clone https://github.com/seligj95/app-service-mcp-stateless-scale-python cd app-service-mcp-stateless-scale-python -
Аутентифицируйтесь в Azure Developer CLI:
azd auth login -
Разверните инфраструктуру и код одной командой:
azd up
Команда создаст:
- ресурсную группу;
- план App Service (P0v3) с тремя инстансами;
- веб‑приложение с отключённой аффинностью;
- staging‑слот;
- Log Analytics workspace;
- ресурс Application Insights;
- деплой Python‑приложения через Oryx.
В выводе вы увидите WEB_URI и WEB_STAGING_URI.
Откройте WEB_URI в браузере — на главной странице отобразится instance_id инстанса, который обслужил запрос. Обновляйте страницу и смотрите, как ID меняется.
Как запустить и проверить масштабирование
Swap staging → production без простоя
Чтобы поменять местами staging и production‑слоты:
az webapp deployment slot swap \
--resource-group <rg> --name <app> \
--slot staging --target-slot production
App Service прогреет staging‑инстансы, переключит трафик, а старый production станет новым staging. Это классический blue‑green‑деплой без ручной настройки.
Нагрузочное тестирование с k6
В репозитории есть скрипт k6, который шлёт запросы на /mcp с методом tools/call и помечает каждый ответ по instance_id, который вернул сервер.
Пример запуска:
BASE_URL=https://<your-app>.azurewebsites.net \
k6 run --summary-export=summary.json loadtest/k6-mcp.js
jq '.metrics.mcp_instance_hits.values' summary.json
Ожидаемый результат на плане с тремя инстансами и 60‑секундной нагрузкой:
{
"count": 1842,
"instance0d3e2f...": 614,
"instance7a91bc...": 612,
"instance19f0c4...": 616
}
Каждый инстанс получает примерно 33% запросов. Балансировщик App Service равномерно раздаёт новые подключения, без участия приложения.
Что сделать дальше
Microsoft предлагает два очевидных шага развития архитектуры:
-
Подключить Easy Auth
- Включить аутентификацию в App Service.
- Выбрать Entra ID.
- Требовать авторизацию на
/mcp. - Токен прилетит в заголовках, а обработчики инструментов смогут распознавать вызывающего агента без собственной реализации OAuth.
-
Включить автоскейл по CPU
- Текущий
instanceCount: 3— стартовая точка. - Добавить ресурс
Microsoft.Insights/autoscalesettingsдля плана. - Разрешить масштабирование, например, с 3 до 10 инстансов при высоком CPU на инструменте
compute_primes. - Архитектура уже это поддерживает — в этом и смысл статичности.
- Текущий
Полезные ссылки
- Пример: github.com/seligj95/app-service-mcp-stateless-scale-python
- Спецификация MCP: modelcontextprotocol.io/specification/2025-11-25
- Документация App Service: learn.microsoft.com/azure/app-service/overview
Если вы строите MCP‑инструменты для агентов и устали от ручной балансировки и падений при деплое, этот паттерн с App Service даёт понятную дорожную карту: статичные инструменты, отключённая аффинность, Always On и автоскейл по метрикам.