Как развернуть безопасный сервер MCP в Cloud Run,Как развернуть безопасный сервер MCP в Cloud Run

1. Введение

Обзор

В этой лабораторной работе вы создадите и развернёте сервер Model Context Protocol (MCP). Серверы MCP полезны для предоставления магистрам уровня магистратуры (LLM) доступа к внешним инструментам и сервисам. Вы настроите его как безопасный, готовый к использованию сервис в Cloud Run, к которому можно будет получить доступ с нескольких клиентов. Затем вы подключитесь к удалённому серверу MCP через Gemini CLI.

Что ты будешь делать?

Мы будем использовать FastMCP для создания MCP-сервера Zoo с двумя инструментами: get_animals_by_species и get_animal_details . FastMCP предоставляет быстрый способ создания MCP-серверов и клиентов на Python.

Графика сервера Zoo MCP

Чему вы научитесь

  • Разверните сервер MCP в Cloud Run.
  • Защитите конечную точку вашего сервера, потребовав аутентификацию для всех запросов, гарантируя, что только авторизованные клиенты и агенты смогут взаимодействовать с ним.
  • Подключитесь к защищенной конечной точке сервера MCP из Gemini CLI

2. Настройка проекта

  1. Если у вас еще нет учетной записи Google, вам необходимо ее создать .
    • Используйте личную учётную запись вместо рабочей или учебной. Рабочие и учебные учётные записи могут иметь ограничения, которые не позволят вам включить API, необходимые для этой лабораторной работы.
  2. Войдите в Google Cloud Console .
  3. Включите выставление счетов в Cloud Console.
    • Выполнение этой лабораторной работы обойдется менее чем в 1 доллар США в виде облачных ресурсов.
    • Вы можете следовать инструкциям в конце этой лабораторной работы, чтобы удалить ресурсы и избежать дальнейших расходов.
    • Новые пользователи имеют право на бесплатную пробную версию стоимостью 300 долларов США .
  4. Создайте новый проект или выберите повторное использование существующего проекта.

3. Откройте редактор Cloud Shell

  1. Нажмите эту ссылку, чтобы перейти непосредственно в редактор Cloud Shell.
  2. Если сегодня в какой-либо момент вам будет предложено авторизоваться, нажмите «Авторизовать» , чтобы продолжить. Нажмите, чтобы авторизовать Cloud Shell
  3. Если терминал не отображается внизу экрана, откройте его:
    • Нажмите «Просмотр»
    • Нажмите «Терминал» Откройте новый терминал в Cloud Shell Editor
  4. В терминале настройте свой проект с помощью этой команды:
    • Формат:
      gcloud config set project [PROJECT_ID]
      
    • Пример:
      gcloud config set project lab-project-id-example
      
    • Если вы не можете вспомнить идентификатор своего проекта:
      • Вы можете вывести список всех идентификаторов своих проектов с помощью:
        gcloud projects list | awk '/PROJECT_ID/{print $2}'
        
      Установить идентификатор проекта в терминале Cloud Shell Editor
  5. Вы должны увидеть это сообщение:
    Updated property [core/project].
    
    Если вы видите WARNING и вопрос Do you want to continue (Y/n)? , вероятно, вы неправильно ввели идентификатор проекта. Нажмите n , затем Enter и попробуйте снова выполнить команду gcloud config set project .

4. Включите API

В терминале включите API:

gcloud services enable \
  run.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com

Если будет предложено авторизоваться, нажмите «Авторизовать» , чтобы продолжить. Нажмите, чтобы авторизовать Cloud Shell

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

Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.

5. Подготовьте свой проект Python

  1. Создайте папку с именем mcp-on-cloudrun для хранения исходного кода для развертывания:
      mkdir mcp-on-cloudrun && cd mcp-on-cloudrun
    
  2. Создайте проект Python с помощью инструмента uv для генерации файла pyproject.toml :
      uv init --description "Example of deploying an MCP server on Cloud Run" --bare --python 3.13
    
    Команда uv init создает файл pyproject.toml для вашего проекта. Чтобы просмотреть содержимое файла, выполните следующее:
    cat pyproject.toml
    
    Вывод должен выглядеть следующим образом:
    [project]
    name = "mcp-on-cloudrun"
    version = "0.1.0"
    description = "Example of deploying an MCP server on Cloud Run"
    requires-python = ">=3.13"
    dependencies = []
    

6. Создайте сервер Zoo MCP.

Чтобы обеспечить ценный контекст для улучшения использования LLM с MCP, настройте сервер MCP Zoo с помощью FastMCP — стандартной платформы для работы с протоколом контекста модели. FastMCP обеспечивает быстрый способ создания серверов и клиентов MCP на Python. Этот сервер MCP предоставляет данные о животных в вымышленном зоопарке. Для простоты мы храним данные в памяти. Для сервера MCP в рабочем режиме вам, вероятно, потребуется предоставлять данные из таких источников, как базы данных или API.

  1. Выполните следующую команду, чтобы добавить FastMCP в качестве зависимости в файле pyproject.toml :
    uv add fastmcp==2.11.1 --no-sync
    
    Это добавит файл uv.lock в ваш проект.
  2. Создайте и откройте новый файл server.py для исходного кода сервера MCP:
    cloudshell edit server.py
    
    Команда cloudshell edit откроет файл server.py в редакторе над терминалом.
  3. Добавьте следующий исходный код сервера Zoo MCP в файл server.py :
    import asyncio
    import logging
    import os
    from typing import List, Dict, Any
    
    from fastmcp import FastMCP
    
    logger = logging.getLogger(__name__)
    logging.basicConfig(format="[%(levelname)s]: %(message)s", level=logging.INFO)
    
    mcp = FastMCP("Zoo Animal MCP Server 🦁🐧🐻")
    
    # Dictionary of animals at the zoo
    ZOO_ANIMALS = [
        {
            "species": "lion",
            "name": "Leo",
            "age": 7,
            "enclosure": "The Big Cat Plains",
            "trail": "Savannah Heights"
        },
        {
            "species": "lion",
            "name": "Nala",
            "age": 6,
            "enclosure": "The Big Cat Plains",
            "trail": "Savannah Heights"
        },
        {
            "species": "lion",
            "name": "Simba",
            "age": 3,
            "enclosure": "The Big Cat Plains",
            "trail": "Savannah Heights"
        },
        {
            "species": "lion",
            "name": "King",
            "age": 8,
            "enclosure": "The Big Cat Plains",
            "trail": "Savannah Heights"
        },
        {
            "species": "penguin",
            "name": "Waddles",
            "age": 2,
            "enclosure": "The Arctic Exhibit",
            "trail": "Polar Path"
        },
        {
            "species": "penguin",
            "name": "Pip",
            "age": 4,
            "enclosure": "The Arctic Exhibit",
            "trail": "Polar Path"
        },
        {
            "species": "penguin",
            "name": "Skipper",
            "age": 5,
            "enclosure": "The Arctic Exhibit",
            "trail": "Polar Path"
        },
        {
            "species": "penguin",
            "name": "Chilly",
            "age": 3,
            "enclosure": "The Arctic Exhibit",
            "trail": "Polar Path"
        },
        {
            "species": "penguin",
            "name": "Pingu",
            "age": 6,
            "enclosure": "The Arctic Exhibit",
            "trail": "Polar Path"
        },
        {
            "species": "penguin",
            "name": "Noot",
            "age": 1,
            "enclosure": "The Arctic Exhibit",
            "trail": "Polar Path"
        },
        {
            "species": "elephant",
            "name": "Ellie",
            "age": 15,
            "enclosure": "The Pachyderm Sanctuary",
            "trail": "Savannah Heights"
        },
        {
            "species": "elephant",
            "name": "Peanut",
            "age": 12,
            "enclosure": "The Pachyderm Sanctuary",
            "trail": "Savannah Heights"
        },
        {
            "species": "elephant",
            "name": "Dumbo",
            "age": 5,
            "enclosure": "The Pachyderm Sanctuary",
            "trail": "Savannah Heights"
        },
        {
            "species": "elephant",
            "name": "Trunkers",
            "age": 10,
            "enclosure": "The Pachyderm Sanctuary",
            "trail": "Savannah Heights"
        },
        {
            "species": "bear",
            "name": "Smokey",
            "age": 10,
            "enclosure": "The Grizzly Gulch",
            "trail": "Polar Path"
        },
        {
            "species": "bear",
            "name": "Grizzly",
            "age": 8,
            "enclosure": "The Grizzly Gulch",
            "trail": "Polar Path"
        },
        {
            "species": "bear",
            "name": "Barnaby",
            "age": 6,
            "enclosure": "The Grizzly Gulch",
            "trail": "Polar Path"
        },
        {
            "species": "bear",
            "name": "Bruin",
            "age": 12,
            "enclosure": "The Grizzly Gulch",
            "trail": "Polar Path"
        },
        {
            "species": "giraffe",
            "name": "Gerald",
            "age": 4,
            "enclosure": "The Tall Grass Plains",
            "trail": "Savannah Heights"
        },
        {
            "species": "giraffe",
            "name": "Longneck",
            "age": 5,
            "enclosure": "The Tall Grass Plains",
            "trail": "Savannah Heights"
        },
        {
            "species": "giraffe",
            "name": "Patches",
            "age": 3,
            "enclosure": "The Tall Grass Plains",
            "trail": "Savannah Heights"
        },
        {
            "species": "giraffe",
            "name": "Stretch",
            "age": 6,
            "enclosure": "The Tall Grass Plains",
            "trail": "Savannah Heights"
        },
        {
            "species": "antelope",
            "name": "Speedy",
            "age": 2,
            "enclosure": "The Tall Grass Plains",
            "trail": "Savannah Heights"
        },
        {
            "species": "antelope",
            "name": "Dash",
            "age": 3,
            "enclosure": "The Tall Grass Plains",
            "trail": "Savannah Heights"
        },
        {
            "species": "antelope",
            "name": "Gazelle",
            "age": 4,
            "enclosure": "The Tall Grass Plains",
            "trail": "Savannah Heights"
        },
        {
            "species": "antelope",
            "name": "Swift",
            "age": 5,
            "enclosure": "The Tall Grass Plains",
            "trail": "Savannah Heights"
        },
        {
            "species": "polar bear",
            "name": "Snowflake",
            "age": 7,
            "enclosure": "The Arctic Exhibit",
            "trail": "Polar Path"
        },
        {
            "species": "polar bear",
            "name": "Blizzard",
            "age": 5,
            "enclosure": "The Arctic Exhibit",
            "trail": "Polar Path"
        },
        {
            "species": "polar bear",
            "name": "Iceberg",
            "age": 9,
            "enclosure": "The Arctic Exhibit",
            "trail": "Polar Path"
        },
        {
            "species": "walrus",
            "name": "Wally",
            "age": 10,
            "enclosure": "The Walrus Cove",
            "trail": "Polar Path"
        },
        {
            "species": "walrus",
            "name": "Tusker",
            "age": 12,
            "enclosure": "The Walrus Cove",
            "trail": "Polar Path"
        },
        {
            "species": "walrus",
            "name": "Moby",
            "age": 8,
            "enclosure": "The Walrus Cove",
            "trail": "Polar Path"
        },
        {
            "species": "walrus",
            "name": "Flippers",
            "age": 9,
            "enclosure": "The Walrus Cove",
            "trail": "Polar Path"
        }
    ]
    
    @mcp.tool()
    def get_animals_by_species(species: str) -> List[Dict[str, Any]]:
        """
        Retrieves all animals of a specific species from the zoo.
        Can also be used to collect the base data for aggregate queries
        of animals of a specific species - like counting the number of penguins
        or finding the oldest lion.
    
        Args:
            species: The species of the animal (e.g., 'lion', 'penguin').
    
        Returns:
            A list of dictionaries, where each dictionary represents an animal
            and contains details like name, age, enclosure, and trail.
        """
        logger.info(f">>> 🛠️ Tool: 'get_animals_by_species' called for '{species}'")
        return [animal for animal in ZOO_ANIMALS if animal["species"].lower() == species.lower()]
    
    @mcp.tool()
    def get_animal_details(name: str) -> Dict[str, Any]:
        """
        Retrieves the details of a specific animal by its name.
    
        Args:
            name: The name of the animal.
    
        Returns:
            A dictionary with the animal's details (species, name, age, enclosure, trail)
            or an empty dictionary if the animal is not found.
        """
        logger.info(f">>> 🛠️ Tool: 'get_animal_details' called for '{name}'")
        for animal in ZOO_ANIMALS:
            if animal["name"].lower() == name.lower():
                return animal
        return {}
    
    if __name__ == "__main__":
        logger.info(f"🚀 MCP server started on port {os.getenv('PORT', 8080)}")
        asyncio.run(
            mcp.run_async(
                transport="http",
                host="0.0.0.0",
                port=os.getenv("PORT", 8080),
            )
        )
    

Ваш код готов! Теперь пора развернуть сервер MCP в Cloud Run.

7. Развертывание в Cloud Run

Теперь разверните сервер MCP в Cloud Run непосредственно из исходного кода.

  1. Создайте и откройте новый Dockerfile для развертывания в Cloud Run:
    cloudshell edit Dockerfile
    
  2. Включите следующий код в Dockerfile, чтобы использовать инструмент uv для запуска файла server.py :
    # Use the official Python image
    FROM python:3.13-slim
    
    # Install uv
    COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
    
    # Install the project into /app
    COPY . /app
    WORKDIR /app
    
    # Allow statements and log messages to immediately appear in the logs
    ENV PYTHONUNBUFFERED=1
    
    # Install dependencies
    RUN uv sync
    
    EXPOSE $PORT
    
    # Run the FastMCP server
    CMD ["uv", "run", "server.py"]
    
  3. Выполните команду gcloud для развертывания приложения в Cloud Run.
    gcloud run deploy zoo-mcp-server \
        --no-allow-unauthenticated \
        --region=europe-west1 \
        --source=. \
        --labels=dev-tutorial=codelab-mcp
    
    Используйте флаг --no-allow-unauthenticated , чтобы потребовать аутентификацию. Это важно из соображений безопасности. Если вы не требуете аутентификацию, любой может обратиться к вашему MCP-серверу и потенциально нанести вред вашей системе.
  4. Подтвердите создание нового репозитория реестра артефактов. Поскольку это ваш первый опыт развертывания в Cloud Run из исходного кода, вы увидите:
    Deploying from source requires an Artifact Registry Docker repository to store built containers. A repository named 
    [cloud-run-source-deploy] in region [europe-west1] will be created.
    
    Do you want to continue (Y/n)?
    
    Введите Y и нажмите Enter . Это создаст репозиторий реестра артефактов для вашего развертывания. Он необходим для хранения Docker-контейнера сервера MCP для сервиса Cloud Run.
  5. Через несколько минут вы увидите сообщение вроде:
    Service [zoo-mcp-server] revision [zoo-mcp-server-12345-abc] has been deployed and is serving 100 percent of traffic.
    

Вы развернули свой MCP-сервер. Теперь вы можете его использовать.

8. Добавьте удаленный сервер MCP в Gemini CLI.

Теперь, когда вы успешно развернули удалённый сервер MCP, вы можете подключиться к нему с помощью различных приложений, таких как Google Code Assist или Gemini CLI. В этом разделе мы установим подключение к вашему новому удалённому серверу MCP с помощью Gemini CLI .

  1. Предоставьте вашей учетной записи разрешение на вызов удаленного сервера MCP.
    gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
        --member=user:$(gcloud config get-value account) \
        --role='roles/run.invoker'
    
  2. Сохраните свои учетные данные Google Cloud и номер проекта в переменных среды для использования в файле настроек Gemini:
    export PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format="value(projectNumber)")
    export ID_TOKEN=$(gcloud auth print-identity-token)
    
  3. Откройте файл настроек Gemini CLI.
    cloudshell edit ~/.gemini/settings.json
    
  4. Замените файл настроек Gemini CLI, чтобы добавить сервер Cloud Run MCP.
    {
      "mcpServers": {
        "zoo-remote": {
          "httpUrl": "https://zoo-mcp-server-$PROJECT_NUMBER.europe-west1.run.app/mcp/",
          "headers": {
            "Authorization": "Bearer $ID_TOKEN"
          }
        }
      },
      "selectedAuthType": "cloud-shell",
      "hasSeenIdeIntegrationNudge": true
    }
    

  1. Запустите Gemini CLI в Cloud Shell
    gemini
    
    Возможно, вам придется нажать Enter , чтобы принять некоторые настройки по умолчанию. Первоначальный вид Gemini CLI
  2. Попросите Gemini перечислить инструменты MCP, доступные ему в его контексте.
    /mcp
    
  3. Попросите близнецов найти что-нибудь в зоопарке.
    Where can I find penguins?
    
    Gemini CLI должен знать, как использовать сервер MCP zoo-remote и спросит, хотите ли вы разрешить выполнение MCP.
  4. Используйте стрелку вниз, затем нажмите Enter , чтобы выбрать
    Yes, always allow all tools from server "zoo-remote"
    
    Gemini CLI позволяет использовать инструменты удаленного доступа Zoo

На выходе должен быть отображен правильный ответ и поле, показывающее, что использовался сервер MCP.

Gemini CLI показывает результат сервера Zoo MCP

Вы это сделали! Вы успешно развернули удалённый сервер MCP в Cloud Run и протестировали его с помощью Gemini CLI.

Когда вы будете готовы завершить сеанс, введите /quit , а затем нажмите Enter , чтобы выйти из Gemini CLI.

Отладка

Если вы видите такую ​​ошибку:

🔍 Attempting OAuth discovery for 'zoo-remote'...
❌ 'zoo-remote' requires authentication but no OAuth configuration found
Error connecting to MCP server 'zoo-remote': MCP server 'zoo-remote' requires authentication. Please configure OAuth or check server settings.

Вероятно, истек срок действия ID-токена и требуется повторная настройка ID_TOKEN .

  1. Введите /quit и нажмите Enter , чтобы выйти из Gemini CLI.
  2. Установите свой проект в терминале
    gcloud config set project [PROJECT_ID]
    
  3. Перезапустите с шага 2 выше.

9. (Необязательно) Проверьте вызовы инструментов в журналах сервера.

Чтобы убедиться, что ваш сервер Cloud Run MCP был вызван, проверьте журналы сервиса.

gcloud run services logs read zoo-mcp-server --region europe-west1 --limit=5

Вы должны увидеть журнал выходных данных, подтверждающий, что был выполнен вызов инструмента. 🛠️

2025-08-05 19:50:31 INFO:     169.254.169.126:39444 - "POST /mcp/ HTTP/1.1" 200 OK
2025-08-05 19:50:31 [INFO]: Processing request of type CallToolRequest
2025-08-05 19:50:31 [INFO]: >>> 🛠️ Tool: 'get_animals_by_species' called for 'penguin'

10. (Необязательно) Добавьте запрос MCP на сервер

Подсказка MCP может ускорить рабочий процесс для часто запускаемых подсказок, создав сокращение для более длинной подсказки.

Gemini CLI автоматически преобразует запросы MCP в пользовательские слэш-команды , чтобы вы могли вызвать запрос MCP, введя /prompt_name , где prompt_name — имя вашего запроса MCP.

Создайте командную строку MCP, чтобы можно было быстро найти животное в зоопарке, введя команду /find animal в Gemini CLI.

  1. Добавьте этот код в файл server.py над основным сторожем ( if __name__ == "__main__": )
    @mcp.prompt()
    def find(animal: str) -> str:
        """
        Find which exhibit and trail a specific animal might be located.
        """
    
        return (
            f"Please find the exhibit and trail information for {animal} in the zoo. "
            f"Respond with '[animal] can be found in the [exhibit] on the [trail].'"
            f"Example: Penguins can be found in The Arctic Exhibit on the Polar Path."
        )
    
  2. Повторно разверните свое приложение в Cloud Run
    gcloud run deploy zoo-mcp-server \
        --no-allow-unauthenticated \
        --region=europe-west1 \
        --source=. \
        --labels=dev-tutorial=codelab-mcp
    
  3. Обновите свой ID_TOKEN для вашего удаленного сервера MCP.
    export ID_TOKEN=$(gcloud auth print-identity-token)
    
  4. После развертывания новой версии приложения запустите Gemini CLI.
    gemini
    
  5. В командной строке используйте новую пользовательскую команду, которую вы создали:
    /find --animal="lions"
    

Вы должны увидеть, что Gemini CLI вызывает инструмент get_animals_by_species и форматирует ответ в соответствии с инструкциями MCP!

╭───────────────────────────╮
│  > /find --animal="lion"  │
╰───────────────────────────╯

 ╭───────────────────────────────────────────────────────────────────────────────────────────────────╮
 │ ✔  get_animals_by_species (zoo-remote MCP Server) get_animals_by_species (zoo-remote MCP Server)  │
 │                                                                                                   │
 │    [{"species":"lion","name":"Leo","age":7,"enclosure":"The Big Cat                               │
 │    Plains","trail":"Savannah                                                                      │
 │    Heights"},{"species":"lion","name":"Nala","age":6,"enclosure":"The Big Cat                     │
 │    Plains","trail":"Savannah                                                                      │
 │    Heights"},{"species":"lion","name":"Simba","age":3,"enclosure":"The Big Cat                    │
 │    Plains","trail":"Savannah                                                                      │
 │    Heights"},{"species":"lion","name":"King","age":8,"enclosure":"The Big Cat                     │
 │    Plains","trail":"Savannah Heights"}]                                                           │
 ╰───────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ Lions can be found in The Big Cat Plains on the Savannah Heights.

Ставьте перед собой сложные цели, чтобы проверить себя

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

Или, в качестве ещё более масштабного эксперимента, чтобы проверить полученные знания, придумайте идею инструмента, который вы будете часто использовать, и разверните второй удалённый сервер MCP. Затем добавьте его в настройки Gemini CLI и проверьте, работает ли он.

11. Заключение

Поздравляем! Вы успешно развернули систему и подключились к защищённому удалённому серверу MCP.

Перейти к следующей лаборатории

Эта лабораторная работа — первая из трёх частей. Во второй части вы будете использовать сервер MCP, созданный с помощью агента ADK.

Используйте сервер MCP в облаке с агентом ADK

(Необязательно) Очистка

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

Хотя Cloud Run не взимает плату, когда сервис не используется, с вас может взиматься плата за хранение образа контейнера в Artifact Registry. Удаление проекта Cloud прекращает выставление счетов за все ресурсы, используемые в этом проекте.

Если хотите, удалите проект:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

Вы также можете удалить ненужные ресурсы с диска CloudShell. Вы можете:

  1. Удалить каталог проекта codelab:
    rm -rf ~/mcp-on-cloudrun
    
  2. Внимание! Следующее действие нельзя отменить! Если вы хотите удалить всё из Cloud Shell, чтобы освободить место, вы можете удалить весь домашний каталог . Убедитесь, что всё, что вы хотите сохранить, сохранено где-то ещё.
    sudo rm -rf $HOME