1. 简介
概览
在本实验中,您将构建并部署 Model Context Protocol (MCP) 服务器。MCP 服务器可用于为 LLM 提供对外部工具和服务的访问权限。您将它配置为 Cloud Run 上可供多个客户端访问的安全的生产就绪型服务。然后,您将从 Gemini CLI 连接到远程 MCP 服务器。
您将执行的操作
我们将使用 FastMCP 创建一个包含两个工具(get_animals_by_species
和 get_animal_details
)的 zoo MCP 服务器。FastMCP 提供了一种快速、Python 式的方法来构建 MCP 服务器和客户端。
学习内容
- 将 MCP 服务器部署到 Cloud Run。
- 通过要求对所有请求进行身份验证来保护服务器的端点,确保只有经过授权的客户端和代理才能与该端点通信。
- 通过 Gemini CLI 连接到安全的 MCP 服务器端点
2. 项目设置
- 如果您还没有 Google 账号,则必须先创建一个 Google 账号。
- 请改用个人账号,而不是工作账号或学校账号。工作账号和学校账号可能存在限制,导致您无法启用本实验所需的 API。
- 登录 Google Cloud 控制台。
- 在 Cloud 控制台中启用结算功能。
- 完成本实验的 Cloud 资源费用应不到 1 美元。
- 您可以按照本实验结束时的步骤删除资源,以避免产生更多费用。
- 新用户符合参与 $300 USD 免费试用计划的条件。
- 创建新项目或选择重复使用现有项目。
3. 打开 Cloud Shell Editor
- 点击此链接可直接前往 Cloud Shell 编辑器
- 如果系统在今天任何时间提示您进行授权,请点击授权继续。
- 如果终端未显示在屏幕底部,请打开它:
- 点击查看
- 点击终端
- 在终端中,使用以下命令设置项目:
- 格式:
gcloud config set project [PROJECT_ID]
- 示例:
gcloud config set project lab-project-id-example
- 如果您不记得项目 ID,请执行以下操作:
- 您可以使用以下命令列出所有项目 ID:
gcloud projects list | awk '/PROJECT_ID/{print $2}'
- 您可以使用以下命令列出所有项目 ID:
- 格式:
- 您应会看到以下消息:
如果您看到Updated property [core/project].
WARNING
并被问到Do you want to continue (Y/n)?
,则很可能是您输入的项目 ID 有误。按n
,按Enter
,然后尝试再次运行gcloud config set project
命令。
4. 启用 API
在终端中,启用以下 API:
gcloud services enable \
run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com
如果系统提示您进行授权,请点击授权继续。
此命令可能需要几分钟时间才能完成,但最终应会生成类似如下所示的成功消息:
Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.
5. 准备 Python 项目
- 创建一个名为
mcp-on-cloudrun
的文件夹,用于存储要部署的源代码:mkdir mcp-on-cloudrun && cd mcp-on-cloudrun
- 使用
uv
工具创建一个 Python 项目,以生成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. 创建动物园 MCP 服务器
为了提供有价值的背景信息,以便改进 LLM 与 MCP 的搭配使用,请使用 FastMCP(一种用于处理 Model Context Protocol 的标准框架)设置动物园 MCP 服务器。FastMCP 提供了一种使用 Python 快速构建 MCP 服务器和客户端的方法。此 MCP 服务器提供有关虚构动物园中动物的数据。为简单起见,我们将数据存储在内存中。对于生产 MCP 服务器,您可能需要提供来自数据库或 API 等来源的数据。
- 运行以下命令,将 FastMCP 添加为
pyproject.toml
文件中的依赖项: 这会将uv add fastmcp==2.12.4 --no-sync
uv.lock
文件添加到您的项目中。 - 为 MCP 服务器源代码创建并打开新的
server.py
文件:cloudshell edit ~/mcp-on-cloudrun/server.py
cloudshell edit
命令会在终端上方的编辑器中打开server.py
文件。 - 在
server.py
文件中添加以下动物园 MCP 服务器源代码: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__": port = int(os.getenv("PORT", 8080)) logger.info(f"🚀 MCP server started on port {port}") asyncio.run( mcp.run_async( transport="http", host="0.0.0.0", port=port, ) )
您的代码已完成!现在,您可以将 MCP 服务器部署到 Cloud Run 了。
7. 部署到 Cloud Run
现在,直接从源代码将 MCP 服务器部署到 Cloud Run。
- 创建并打开新的
Dockerfile
以部署到 Cloud Run:cloudshell edit ~/mcp-on-cloudrun/Dockerfile
- 在 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"]
- 运行
gcloud
命令,将应用部署到 Cloud Run 使用cd ~/mcp-on-cloudrun gcloud run deploy zoo-mcp-server \ --no-allow-unauthenticated \ --region=europe-west1 \ --source=. \ --labels=dev-tutorial=codelab-mcp
--no-allow-unauthenticated
标志可要求进行身份验证。这对于确保安全性至关重要。如果您不需要进行身份验证,则任何人都可以调用您的 MCP 服务器,并可能会对您的系统造成损害。 - 确认已创建新的 Artifact Registry 代码库由于您是首次从源代码部署到 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
,系统会为您的部署创建一个 Artifact Registry 代码库。这是存储 Cloud Run 服务的 MCP 服务器 Docker 容器所必需的。 - 几分钟后,您会看到类似以下内容的消息:
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)连接到该服务器。在本部分中,我们将使用 Gemini CLI 建立与新远程 MCP 服务器的连接。
- 向您的用户账号授予调用远程 MCP 服务器的权限
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \ --member=user:$(gcloud config get-value account) \ --role='roles/run.invoker'
- 将您的 Google Cloud 凭据和项目编号保存在环境变量中,以便在 Gemini 设置文件中使用:
export PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format="value(projectNumber)") export ID_TOKEN=$(gcloud auth print-identity-token)
- 创建
.gemini
文件夹(如果尚未创建)mkdir -p ~/.gemini
- 打开 Gemini CLI 设置文件
cloudshell edit ~/.gemini/settings.json
- 替换 Gemini CLI 设置文件以添加 Cloud Run MCP 服务器
{ "ide": { "hasSeenNudge": true }, "mcpServers": { "zoo-remote": { "httpUrl": "https://zoo-mcp-server-$PROJECT_NUMBER.europe-west1.run.app/mcp", "headers": { "Authorization": "Bearer $ID_TOKEN" } } }, "security": { "auth": { "selectedType": "cloud-shell" } } }
- 在 Cloud Shell 中启动 Gemini CLI
您可能需要按gemini
Enter
接受某些默认设置。 - 让 Gemini 列出其上下文中可用的 MCP 工具
/mcp
- 让 Gemini 在动物园中寻找某样东西
Gemini CLI 应该知道要使用Where can I find penguins?
zoo-remote
MCP 服务器,并会询问您是否要允许执行 MCP。 - 使用向下键,然后按
Enter
选择Yes, always allow all tools from server "zoo-remote"
输出应显示正确答案,以及一个显示 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
。
- 输入
/quit
,然后按Enter
退出 Gemini CLI。 - 在终端中设置项目
gcloud config set project [PROJECT_ID]
- 从上述第 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 提示转换为自定义斜杠命令,以便您可以通过输入 /prompt_name
(其中 prompt_name
是 MCP 提示的名称)来调用 MCP 提示。
创建 MCP 提示,以便您通过在 Gemini CLI 中输入 /find animal
快速找到动物园中的动物。
- 将此代码添加到
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." )
- 将应用重新部署到 Cloud Run
gcloud run deploy zoo-mcp-server \ --no-allow-unauthenticated \ --region=europe-west1 \ --source=. \ --labels=dev-tutorial=codelab-mcp
- 刷新远程 MCP 服务器的 ID_TOKEN
export ID_TOKEN=$(gcloud auth print-identity-token)
- 部署应用的新版本后,启动 Gemini CLI。
gemini
- 在提示中使用您创建的新自定义命令:
或/find --animal="lions"
/find 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.
11. (可选)使用 Gemini Flash Lite 可更快获得回答
借助 Gemini CLI,您可以选择要使用的模型。
- Gemini 2.5 Pro 是 Google 最先进的思考型模型,能够使用长上下文对代码、数学和 STEM 领域中的复杂问题进行推理,并分析大型数据集、代码库和文档。
- Gemini 2.5 Flash 在性价比方面是 Google 的最佳模型,可提供全面的功能。2.5 Flash 最适合大规模处理需要思考的低延迟、高数据量任务,以及代理应用场景。
- Gemini 2.5 Flash Lite 是 Google 速度最快的 Flash 模型,经过优化,可实现高成本效益和高吞吐量。
由于与寻找动物园动物相关的请求不需要思考或推理,因此可以尝试使用速度更快的模型来加快处理速度。
创建 MCP 提示,以便您通过在 Gemini CLI 中输入 /find animal
快速找到动物园中的动物。
- 部署应用的新版本后,启动 Gemini CLI。
gemini --model=gemini-2.5-flash-lite
- 在提示中使用您创建的新自定义命令:
/find 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.
调试
如果您看到类似以下内容的错误:
✕ Unknown command: /find --animal="lions"
尝试运行 /mcp
,如果输出 zoo-remote - Disconnected
,您可能需要重新部署,或再次运行以下命令:
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
--member=user:$(gcloud config get-value account) \
--role='roles/run.invoker'
export PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format="value(projectNumber)")
export ID_TOKEN=$(gcloud auth print-identity-token)
12. 总结
恭喜!您已成功部署并连接到安全的远程 MCP 服务器。
继续执行下一个实验
本实验是三部分系列实验中的第一个实验。在第二个实验中,您将使用通过 ADK 智能体创建的 MCP 服务器。
将 Cloud Run 上的 MCP 服务器与 ADK 代理搭配使用
(可选)清理
如果您不打算继续学习下一个实验,并且想要清理已创建的内容,可以删除您的 Cloud 项目,以免产生额外费用。
虽然 Cloud Run 不会对未在使用中的服务计费,但您可能仍然需要支付将容器映像存储在 Artifact Registry 中而产生的相关费用。删除 Cloud 项目后,系统即会停止对该项目中使用的所有资源计费。
如果您愿意,可以删除项目:
gcloud projects delete $GOOGLE_CLOUD_PROJECT
您可能还需要从 Cloud Shell 磁盘中删除不必要的资源。您可以:
- 删除 Codelab 项目目录:
rm -rf ~/mcp-on-cloudrun
- 警告!接下来要执行的操作无法撤消!如果您想删除 Cloud Shell 中的所有内容以释放空间,可以删除整个主目录。请务必将要保留的所有内容保存到其他位置。
sudo rm -rf $HOME