Главная / Статьи

Серверы MCP для DevOps: что это такое и два варианта, которые вы можете создать на этой неделе

Серверы MCP для DevOps: предоставляют Claude прямой доступ к Kubernetes, AWS и руководствам по работе. Три типа серверов, сервер MCP Kubernetes с режимом только для чтения и распространённые ошибки.

Издатель: REACTAPP.TOP10 мин чтения2253 слов

Источники: What Is an MCP Server and Why Every DevOps Team Should Have One in 2026

MCP, протокол контекста модели, — это открытый стандарт от Anthropic, позволяющий ИИ-моделям структурированно взаимодействовать с внешними системами: инструментами, API, базами данных и сервисами. Без него Claude работает изолированно, и каждый факт приходится вводить вручную. С его помощью Claude может напрямую обращаться к этим системам.

«Сервер» в MCP — это всего лишь небольшая программа. Она предоставляет Claude доступ к инструментам и данным, обрабатывает взаимодействие и работает там, где находятся данные, будь то ноутбук, кластер или сервер. Это не тяжелая инфраструктура, и рабочий сервер MCP может представлять собой скрипт на Python из 50 строк.

Аналогия, помогающая понять

Интеграции с Slack позволяют провести хорошее сравнение. Раньше кто-то вручную публиковал сообщение «Начато развертывание в продакшен». После внедрения CI/CD-пайплайнов оно публикуется автоматически, а Slack становится центром, отслеживающим происходящее. MCP делает то же самое для Claude: раньше человек вручную предоставлял информацию об инфраструктуре, а теперь система поддерживает прямое соединение и может проверять данные самостоятельно.

Почему команды DevOps получают наибольшую выгоду

Большинство примеров использования MCP предназначены для разработчиков приложений. Однако DevOps-инженеры, SRE и инженеры платформ получают ещё большую выгоду, поскольку их основная работа заключается в управлении системами, уже имеющими API: Kubernetes, AWS, Datadog или Grafana, PagerDuty, Terraform Cloud, GitHub, Confluence и Notion. Каждая из этих систем может быть подключена к Claude через MCP. После этого не нужно переключаться между инструментами для сбора информации — достаточно просто задавать вопросы.

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

  • Вопросы о кластерах. При запросе о том, какие производственные развертывания работают более 30 дней без обновлений, Claude обращается к серверу MCP Kubernetes, который запрашивает информацию у кластера и возвращает ответ. Для этого не требуется использование kubectl и копирование-вставка текста.
  • Вопросы о затратах. При запросе о том, какие сервисы AWS показали рост нагрузки на этой неделе по сравнению с прошлой, Claude обращается к серверу AWS и инструменту Cost Explorer, после чего получает ответ в простом языке.
  • Контекст инцидентов. При запросе о инцидентах в PagerDuty за последние 24 часа и о том, открыты ли ещё какие-либо из них, система получает актуальную информацию с сервера PagerDuty и дает краткое резюме.
  • Поиск руководства. При запросе руководства по проблеме исчерпания пула подключений к базе данных система ищет соответствующий документ в базе знаний через сервер Confluence и возвращает именно его.
  • Аудиты безопасности. Проверка того, разрешает ли какая-либо группа безопасности доступ с адреса 0.0.0.0/0 по портам, отличным от 443, позволяет серверу AWS проанализировать эти группы и выявить нарушения.
  • Три вида серверов

    Команды, которые начинают работу, обычно создают три типа серверов.

    Серверы для отслеживания только для чтения

    Они соединяют Claude с API мониторинга и инфраструктуры, чтобы он мог описывать текущее состояние системы. Типичными примерами являются сервер состояния кластера Kubernetes, сервер инвентаризации AWS, сервер метрик Datadog и сервер логов CloudWatch. Риск минимален, поскольку Claude может выполнять только операции чтения. Именно с этого следует начинать, чтобы завоевать доверие перед предоставлением прав на запись.

    Серверы баз знаний

    Эти ссылки связывают Claude с внутренней документацией для поиска и извлечения информации: сервером руководств Confluence или Notion, сервером wiki GitHub, папкой с локальными файлами в формате markdown или хранилищем записей о принятых архитектурных решениях. Риск практически равен нулю, поскольку речь идет о поиске в документах. Они важны, потому что создают впечатление, что Claude действительно хорошо знает компанию.

    Серверы действий

    Они позволяют Claude выполнять такие задачи, как создание тикетов, запуск пайплайнов или перезапуск сервисов. Примерами являются создание тикетов в Jira, управление инцидентами в PagerDuty, создание pull-запросов в GitHub и уведомления в Slack. Риск находится на уровне от среднего до высокого, поскольку теперь Claude вносит изменения. Перед операциями записи всегда должны пройти процедуры утверждения, так что перезапуск пода никогда не происходит без шага подтверждения.

    Создание сервера MCP для Kubernetes

    Далее приведен рабочий сервер Kubernetes только для чтения на Python. Сначала установите SDK:

    pip install mcp kubernetes --break-system-packages
    

    Затем сохраните сервер как k8s_mcp_server.py:

    import asyncio
    from mcp.server import Server
    from mcp.server.stdio import stdio_server
    from mcp import types
    from kubernetes import client, config as k8s_config
    
    # Load kubeconfig
    try:
        k8s_config.load_incluster_config()
    except:
        k8s_config.load_kube_config()
    v1 = client.CoreV1Api()
    apps_v1 = client.AppsV1Api()
    
    # Create MCP server
    app = Server("k8s-devops-server")
    @app.list_tools()
    async def list_tools() -> list[types.Tool]:
        """Tell Claude what tools are available."""
        return [
            types.Tool(
                name="get_pods",
                description="Get all pods in a namespace with their status, restarts, and age.",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "namespace": {
                            "type": "string",
                            "description": "Kubernetes namespace (e.g. production, staging)"
                        }
                    },
                    "required": ["namespace"]
                }
            ),
    
            types.Tool(
                name="get_pod_logs",
                description="Get recent logs from a pod or deployment.",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "name": {
                            "type": "string",
                            "description": "Pod or deployment name"
                        },
                        "namespace": {
                            "type": "string",
                            "description": "Kubernetes namespace"
                        },
                        "lines": {
                            "type": "integer",
                            "description": "Number of log lines to return (default 50)"
                        }
                    },
                    "required": ["name", "namespace"]
                }
            ),
    
            types.Tool(
                name="get_deployments",
                description="List all deployments with replica counts and status.",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "namespace": {
                            "type": "string",
                            "description": "Kubernetes namespace"
                        }
                    },
                    "required": ["namespace"]
                }
            ),
    
            types.Tool(
                name="get_events",
                description="Get recent Kubernetes events. Useful for diagnosing issues.",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "namespace": {
                            "type": "string",
                            "description": "Kubernetes namespace"
                        }
                    },
                    "required": ["namespace"]
                }
            ),
        ]
    
    @app.call_tool()
    async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
        """Handle tool calls from Claude."""
    
        if name == "get_pods":
            namespace = arguments["namespace"]
            pods = v1.list_namespaced_pod(namespace=namespace)
    
            lines = [f"Pods in {namespace}:\n"]
            for pod in pods.items:
                phase = pod.status.phase
                restarts = sum(
                    cs.restart_count
                    for cs in (pod.status.container_statuses or [])
                )
                name_str = pod.metadata.name
                lines.append(f"  {name_str}: {phase} | restarts: {restarts}")
            return [types.TextContent(type="text", text="\n".join(lines))]
    
        elif name == "get_pod_logs":
            pod_name = arguments["name"]
            namespace = arguments["namespace"]
    
            lines = arguments.get("lines", 50)
            import subprocess
            result = subprocess.run(
                f"kubectl logs {pod_name} -n {namespace} --tail={lines}",
                shell=True, capture_output=True, text=True
            )
            output = result.stdout or result.stderr or "No logs found"
            return [types.TextContent(type="text", text=output)]
    
        elif name == "get_deployments":
            namespace = arguments["namespace"]
            deployments = apps_v1.list_namespaced_deployment(namespace=namespace)
    
            lines = [f"Deployments in {namespace}:\n"]
            for d in deployments.items:
                desired = d.spec.replicas or 0
                available = d.status.available_replicas or 0
                status = "✅" if desired == available else "⚠️"
                lines.append(
                    f"  {status} {d.metadata.name}: {available}/{desired} replicas"
                )
            return [types.TextContent(type="text", text="\n".join(lines))]
    
        elif name == "get_events":
            namespace = arguments["namespace"]
            events = v1.list_namespaced_event(namespace=namespace)
    
            # Sort by last timestamp, show recent ones
            sorted_events = sorted(
                events.items,
                key=lambda e: e.last_timestamp or e.event_time,
                reverse=True
            )[:20]
    
            lines = [f"Recent events in {namespace}:\n"]
            for e in sorted_events:
                lines.append(
                    f"  [{e.type}] {e.reason}: {e.message[:100]}"
                )
    
            return [types.TextContent(type="text", text="\n".join(lines))]
    
        return [types.TextContent(type="text", text=f"Unknown tool: {name}")]
    
    async def main():
        async with stdio_server() as (read_stream, write_stream):
            await app.run(read_stream, write_stream, app.create_initialization_options())
    
    if __name__ == "__main__":
        asyncio.run(main())
    

    Чтобы подключить его к Claude Desktop, отредактируйте файл конфигурации, который находится по одному из следующих путей:

    Mac: ~/Library/Application Support/Claude/claude_desktop_config.json
    Windows: %APPDATA%\Claude\claude_desktop_config.json
    

    и добавьте запись сервера:

    {
      "mcpServers": {
        "kubernetes": {
          "command": "python",
          "args": ["/path/to/k8s_mcp_server.py"]
        }
      }
    }
    

    Перезапустите Claude Desktop — и он будет готов. Запрос о том, сколько подов сейчас работает в производстве, заставляет Claude фактически проверить это.

    Создание сервера руководств по эксплуатации

    Этот инструмент может оказаться ещё более ценным. Он указывает Claude на руководства команды, поэтому во время инцидента Claude находит нужное руководство вместо того, чтобы кто-то вручную искал его в Confluence.

    import asyncio
    import os
    import glob
    from mcp.server import Server
    from mcp.server.stdio import stdio_server
    from mcp import types
    
    app = Server("runbook-server")
    
    RUNBOOKS_PATH = os.path.expanduser("~/runbooks")  # point to your runbooks folder
    
    
    def load_all_runbooks() -> dict[str, str]:
        """Load all markdown runbooks from the folder."""
        runbooks = {}
        for filepath in glob.glob(f"{RUNBOOKS_PATH}/**/*.md", recursive=True):
            with open(filepath, 'r', errors='replace') as f:
                content = f.read()
            name = os.path.relpath(filepath, RUNBOOKS_PATH)
            runbooks[name] = content
        return runbooks
    
    @app.list_tools()
    async def list_tools() -> list[types.Tool]:
        return [
            types.Tool(
                name="search_runbooks",
                description=(
                    "Search your team's runbooks for a specific topic, service, or error. "
                    "Use this when someone asks about a procedure, a known issue, "
                    "or how to fix something."
    
               ),
                inputSchema={
                    "type": "object",
                    "properties": {
                        "query": {
                            "type": "string",
                            "description": "What to search for (e.g. 'database connection pool', 'nginx 502', 'SSL cert renewal')"
                        }
                    },
                    "required": ["query"]
                }
            ),
    
            types.Tool(
                name="list_runbooks",
                description="List all available runbooks by category.",
                inputSchema={
                    "type": "object",
                    "properties": {}
                }
            ),
    
            types.Tool(
                name="get_runbook",
                description="Get the full content of a specific runbook by name.",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "name": {
                            "type": "string",
                            "description": "Runbook filename (from list_runbooks)"
                        }
                    },
                    "required": ["name"]
                }
            )
        ]
    
    @app.call_tool()
    async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
        runbooks = load_all_runbooks()
    
        if name == "list_runbooks":
            names = sorted(runbooks.keys())
            output = "Available runbooks:\n" + "\n".join(f"  - {n}" for n in names)
            return [types.TextContent(type="text", text=output)]
        elif name == "search_runbooks":
            query = arguments["query"].lower()
            matches = []
            for rb_name, content in runbooks.items():
                if query in content.lower() or query in rb_name.lower():
                    # Return first 500 chars as preview
                    preview = content[:500].strip()
                    matches.append(f"📄 {rb_name}\n{preview}\n...")
            if not matches:
                return [types.TextContent(
                    type="text",
                    text=f"No runbooks found for '{query}'"
                )]
            return [types.TextContent(
                type="text",
                text=f"Found {len(matches)} runbook(s):\n\n" + "\n\n---\n\n".join(matches)
            )]
    
        elif name == "get_runbook":
            rb_name = arguments["name"]
            content = runbooks.get(rb_name)
            if not content:
                return [types.TextContent(type="text", text=f"Runbook not found: {rb_name}")]
            return [types.TextContent(type="text", text=content)]
        return [types.TextContent(type="text", text=f"Unknown tool: {name}")]
    
    async def main():
        async with stdio_server() as (read_stream, write_stream):
            await app.run(read_stream, write_stream, app.create_initialization_options())
    
    if __name__ == "__main__":
        asyncio.run(main())
    

    Добавьте его в конфигурацию Claude Desktop рядом с другим сервером:

    {
      "mcpServers": {
        "kubernetes": {
          "command": "python",
          "args": ["/path/to/k8s_mcp_server.py"]
        },
        "runbooks": {
          "command": "python",
          "args": ["/path/to/runbook_mcp_server.py"]
        }
      }
    }
    

    После этого запрос вроде «Каков руководство по работе с высоким потреблением памяти в API-подах?» возвращает собственное руководство команды, а не общий результат поиска.

    Существующие серверы

    Не всё нужно создавать с нуля. Официальный набор серверов-образцов на GitHub в организации modelcontextprotocol включает GitHub (репозитории, pull-запросы, задачи и коммиты), Postgres, Slack, поиск в базе знаний AWS, доступ к локальной файловой системе и поиск в Brave. Существуют также серверы, созданные сообществом, для Datadog (метрики, дашборды и мониторы), PagerDuty (инциденты и уведомления) и Jira (заявки и спринты). Стоит сначала ознакомиться с этим набором, ведь кто-то уже может иметь то, что нужно.

    Ошибки, которых следует избегать

    • Чрезмерно раннее предоставление прав на запись. Начните с режима только для чтения. После того как будет установлена доверие, постепенно добавляйте инструменты для записи.
    • Создание одного огромного сервера. Маленькие, специализированные серверы легче обслуживать, их проще отлаживать, а также их проще передавать конкретным пользователям. k8s-server должен содержать только инструменты Kubernetes, aws-server — только инструменты AWS, а runbook-server — только документацию.
    • Игнорирование логирования. Фиксируйте каждый вызов инструмента, включая то, что запрашивал Claude, когда это происходило и что было получено в ответ, особенно до добавления инструментов для записи.
    • Игнорирование проверки записей. Любое действие, которое создает заявку, перезапускает сервис или объединяет pull request, требует этапа подтверждения, при котором Claude сначала показывает, что собирается сделать.

    Утро с правильной настройкой

    Типичная сессия начинается с запроса о состоянии производства. Claude проверяет сервер Kubernetes и докладывает о развертываниях и подах, выделяет поды с частыми перезагрузками, а затем занимается решением проблемы.

    You: What's the state of production right now?
    
    Claude: [checks kubernetes MCP server]
      3 deployments all healthy, 47/47 pods running.
      One pod in payments namespace has 8 restarts - api-worker-6d4f9b.
      Everything else looks stable.
    You: Why is that pod restarting?
    
    Claude: [checks pod logs via MCP]
      Logs show OOMKilled. Memory limit is 256Mi but it's hitting
      248Mi regularly. Looks like it needs a bump to at least 512Mi.
      Want me to check if we have a runbook for this?
    You: Yes
    
    Claude: [searches runbook MCP server]
      Found it - "memory-limit-tuning.md"
      The runbook says: increase limit in Helm values,
      run helm upgrade, monitor for 10 minutes.
      Last updated by Rahul on March 12th.
    You: What did our AWS bill look like this week?
    
    Claude: [checks AWS MCP server]
      $4,823 this week vs $4,210 last week - up 14.5%.
      Spike is from EC2 in us-east-1. Looks like
      3 r6g.2xlarge instances were added on Tuesday.
    

    Весь этот процесс занимает примерно 90 секунд, без переключения вкладок, без копирования-вставки и без поиска.

    Начиная с этой недели

    Создание пяти серверов сразу — ошибка. Выберите один. Для большинства команд DevOps лучшим первым вариантом является сервер с руководствами, поскольку он имеет режим только для чтения, сразу приносит пользу и работает с уже существующими документами. Для запуска требуется три шага:

    1. Соберите руководства команды в одну папку, например ~/runbooks/.
    2. Возьмите приведенный выше код сервера с руководствами и настройте его на работу с этой папкой.
    3. Добавьте его в настройки Claude Desktop и перезагрузите программу.

    Это позволит создать помощника, который знает процедуры команды. Когда он окажется надежным, добавьте сервер Kubernetes, затем AWS, а потом все остальные.

    Более широкая перспектива

    MCP ещё молод, и экосистема развивается быстро, но уже сейчас ясно несколько вещей. Команды, использующие серверы MCP, получают совершенно иной опыт работы с ИИ по сравнению с командами, которые ими не пользуются. Чтобы перейти от чат-окна к инструменту, функционирующему как коллега по команде, необходимо наличие сервера, предоставляющего доступ к нужным системам. Команды, создающие такие решения сейчас, задают стандарты для использования ИИ в работе с инфраструктурой. Это не исследовательский проект — команды уже используют их в производственных условиях, причём вышеупомянутый сервер Kubernetes может быть запущен примерно за 20 минут.

    Связанные статьи

  • MCP-серверы с защитой по умолчанию для Kubernetes, Kafka и облачных операций — Восемь MCP-серверов позволяют агентам работать с Kubernetes, Kafka, ClickHouse и Azure при использовании режима только для чтения по умолчанию, списков разрешенных элементов, флагов удаления, журналов аудита и аннотаций.