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.11.1 --no-sync
uv.lock
文件添加到您的项目中。 - 创建并打开新的
server.py
文件,用于存放 MCP 服务器源代码:cloudshell edit 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__": 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。
- 创建并打开新的
Dockerfile
以部署到 Cloud Run:cloudshell edit 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 使用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 CLI 设置文件
cloudshell edit ~/.gemini/settings.json
- 替换 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 }
- 在 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
来调用 MCP 提示,其中 prompt_name
是 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"
您应该会看到 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.
可用于自我测试的拓展目标
如需增加难度,请尝试按照相同的步骤创建提示,让 Gemini 返回动物园中特定动物物种的趣闻。
或者,为了进一步检验所学知识,您可以想出一个自己会经常使用的工具,然后部署第二个远程 MCP 服务器。然后将其添加到 Gemini CLI 设置中,看看是否有效。
11. 总结
恭喜!您已成功部署并连接到安全的远程 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