本指南将引导您在 Google Cloud Run 上设置和部署一个 MCP(模型上下文协议)服务器。
gcloud)uv Python 包管理器首先,选择您的 Google Cloud 项目:
gcloud projects list
设置您的项目:
gcloud config set project [PROJECT_ID]
启用所需的 Google Cloud API:
gcloud services enable \
run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com
创建 MCP 服务器的目录:
mkdir mcp
cd mcp/
使用 uv 初始化一个新的 Python 项目:
uv init --description "在 Cloud Run 上部署 MCP 服务器的示例" --bare --python 3.13
这会执行以下操作:
uv,这是一个现代的 Python 包和项目管理器pyproject.toml 文件(而不是 requirements.txt)--bare 标志创建一个干净的环境,不包含额外的文件(如 main.py、.venv 或 README)添加 FastMCP 作为依赖项:
uv add fastmcp==2.12.4 --no-sync
打开文件编辑器
cloudshell edit ~/mcp/server.py
创建 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("GDG MCP 服务器 🦁🐧🐻")
# GDG Kochi 的演讲者字典
GDG_KOCHI = [
{
"title": "注册开始",
"time": "上午 09:30 - 09:35",
"speaker": None,
"role": None,
"organization": None,
"duration": None,
"location": "主厅",
"notes": None,
},
{
"title": "欢迎致辞",
"time": "上午 09:35 - 09:40",
"speaker": "Iqbal P B",
"role": "组织者",
"organization": "GDG Cochin",
"duration": "5 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "主题演讲 — 人工智能?不,与 Google AI Studio 创造",
"time": "上午 09:40 - 10:00",
"speaker": "Merin K Jacob",
"role": "技术账户经理",
"organization": "Google",
"duration": "20 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "主题演讲 — 不要叫我开发者:设计加速职业生涯",
"time": "上午 10:05 - 10:25",
"speaker": "Eric Hole",
"role": "副总裁",
"organization": "Onix",
"duration": "20 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "赞助商演讲",
"time": "上午 10:30 - 10:40",
"speaker": "Dr. Tom M Joseph",
"role": None,
"organization": "Jain",
"duration": "10 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "休息时间",
"time": "上午 10:50 - 11:05",
"speaker": None,
"role": None,
"organization": None,
"duration": "15 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "技术演讲 — 快速发布:使用 Gemini、Firebase Studio 和零基础设施的 AI 应用",
"time": "上午 11:35 - 12:00",
"speaker": "Vishnu K S",
"role": "高级云工程师",
"organization": "DP World",
"duration": "25 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "技术演讲 — 使用 ADK、A2A 和 MCP 构建对话代理",
"time": "下午 12:05 - 12:30",
"speaker": "Nishi Ajmera",
"role": "解决方案架构师",
"organization": "Publicis Sapient (UK)",
"duration": "25 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "技术演讲 — 在任何地方运行 SQL:Presto 遇到 BigQuery 和云存储",
"time": "下午 12:40 - 01:25",
"speaker": "Saurabh Mahawar",
"role": "开发者关系工程师",
"organization": "IBM",
"duration": "45 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "午餐时间",
"time": "下午 12:40 - 01:25",
"speaker": None,
"role": None,
"organization": None,
"duration": "45 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "技术演讲 — 具有安全性、评估和记忆的生产就绪 AI 代理",
"time": "下午 01:25 - 01:50",
"speaker": "Nikhilesh Tayal",
"role": "Google 开发者专家",
"organization": "—",
"duration": "25 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "技术演讲 — 使用 Gemini 进行 Google Cloud 安全威胁狩猎",
"time": "下午 01:55 - 02:20",
"speaker": "Harisuthan S",
"role": "高级安全工程师",
"organization": "Renault Nissan Technology",
"duration": "25 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "技术演讲 — 在 Kubernetes 中运行 Go 应用程序的经验教训",
"time": "下午 02:25 - 02:50",
"speaker": "Adarsh K Kumar",
"role": "首席产品工程师",
"organization": "Rapido",
"duration": "25 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "技术演讲 — 使用 Apache Airflow 编排现代数据堆栈",
"time": "下午 02:55 - 03:20",
"speaker": "Jeevitha",
"role": "高级软件工程师",
"organization": "Juniper Networks (HP)",
"duration": "25 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "技术演讲 — 使用 Gemma 构建可扩展和道德的 AI 解决方案",
"time": "下午 03:25 - 03:50",
"speaker": "Geeta Kakrani",
"role": "Google 开发者专家",
"organization": "AI",
"duration": "25 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "技术演讲 — 保护您的 LLMs",
"time": "下午 03:55 - 04:20",
"speaker": "Dharmesh Vaya",
"role": "高级解决方案工程师",
"organization": "Wiz",
"duration": "25 分钟",
"location": "主厅",
"notes": None,
},
{
"title": "闭幕致辞",
"time": "下午 04:25 - 04:30",
"speaker": "Malavika",
"role": "组织者",
"organization": "GDG Cochin",
"duration": "5 分钟",
"location": "主厅",
"notes": None,
},
# 工作坊 / 并行轨道(尽力而为)
{
"title": "工作坊 — 如何在 Cloud Run 上部署一个安全的 MCP 服务器?",
"time": "下午 02:00",
"speaker": "Ebin Babu",
"role": "CNCF 大使",
"organization": "Linux Foundation Projects",
"duration": None,
"location": "工作坊",
"notes": "并行工作坊轨道",
},
]
@mcp.tool()
def search_sessions(query: str) -> List[Dict[str, Any]]:
"""搜索 GDG Kochi 会议。
对会议标题和演讲者名称进行大小写不敏感的子字符串搜索,并返回匹配的会议字典列表。
参数:
query: 用于匹配标题或演讲者的搜索字符串。
返回值:
会议字典列表(可能为空)。
"""
logger.info(f">>> 🛠️ 工具:'search_sessions' 调用查询='{query}'")
q = (query or "").strip().lower()
if not q:
return []
results: List[Dict[str, Any]] = []
for sess in GDG_KOCHI:
title = (sess.get("title") or "").lower()
speaker = (sess.get("speaker") or "").lower()
if q in title or q in speaker:
results.append(sess)
return results
@mcp.tool()
def get_session_details(identifier: str) -> Dict[str, Any]:
"""通过精确的标题或演讲者名称返回单个会议的详细信息。
尝试精确匹配(大小写不敏感)`identifier` 对应的会议标题,然后是演讲者名称。如果没有找到精确匹配,则返回第一个部分匹配。如果没有任何匹配,则返回空字典。
参数:
identifier: 要查找的会议标题或演讲者名称。
返回值:
会议字典或未找到时的空字典。
"""
logger.info(f">>> 🛠️ 工具:'get_session_details' 调用'{identifier}'")
if not identifier:
return {}
ident = identifier.strip().lower()
# 精确标题匹配
for sess in GDG_KOCHI:
if (sess.get("title") or "").strip().lower() == ident:
return sess
# 精确演讲者匹配
for sess in GDG_KOCHI:
if (sess.get("speaker") or "").strip().lower() == ident:
return sess
# 部分匹配回退(标题或演讲者)
for sess in GDG_KOCHI:
title = (sess.get("title") or "").lower()
speaker = (sess.get("speaker") or "").lower()
if ident in title or ident in speaker:
return sess
return {}
if __name__ == "__main__":
port = int(os.getenv("PORT", 8080))
logger.info(f"🚀 MCP 服务器已在端口 {port} 启动")
asyncio.run(
mcp.run_async(
transport="http",
host="0.0.0.0",
port=port,
)
)
创建一个 Dockerfile:
# 使用官方的 Python 镜像
FROM python:3.13-slim
# 安装 uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# 将项目安装到 /app
COPY . /app
WORKDIR /app
# 允许语句和日志消息立即出现在日志中
ENV PYTHONUNBUFFERED=1
# 安装依赖项
RUN uv sync
EXPOSE $PORT
# 运行 FastMCP 服务器
CMD ["uv", "run", "server.py"]
将您的 MCP 服务器部署到 Cloud Run:
gcloud run deploy gdg-kochi-mcp-server \
--no-allow-unauthenticated \
--region=asia-south1 \
--source=. \
--labels=dev-tutorial=codelab-mcp
注意: 根据需要调整 --region 参数。
添加 IAM 策略绑定以允许自己调用 Cloud Run 服务:
gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
--member=user:$(gcloud config get-value account) \
--role='roles/run.invoker'
保存项目编号和 ID 令牌作为环境变量:
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 设置文件 ~/.gemini/settings.json:
{
"ide": {
"hasSeenNudge": true
},
"mcpServers": {
"gdg-remote": {
"httpUrl": "https://gdg-kochi-mcp-server-$PROJECT_NUMBER.europe-west1.run.app/mcp",
"headers": {
"Authorization": "Bearer $ID_TOKEN"
}
}
},
"security": {
"auth": {
"selectedType": "cloud-shell"
}
}
}
注意: 确保:
$PROJECT_NUMBER 为您实际的项目编号$ID_TOKEN 为您实际的 ID 令牌启动 Gemini CLI:
gemini
在 Gemini CLI 中导航到:
/mcp
尝试提问类似的问题:
当提示时,允许来自服务器“gdg-kochi-remote”的所有工具。
ID_TOKEN 是最新的(令牌会在一段时间后过期)settings.json 中的 Cloud Run 服务 URL 与您的实际部署区域匹配