MCP服务器部署工具

一个用于在Google Cloud Run上部署和设置MCP(Model Context Protocol)服务器的工具,支持会话搜索和详情查询功能。

  • by ebinbabu
  • Python
  • 本地部署
  • 5 星标
  • 更新于
在 GitHub 查看 ebinbabu/mcp-lab

使用说明

MCP服务器设置指南

本指南将引导您在Google Cloud Run上设置和部署MCP(模型上下文协议)服务器。

先决条件

  • 谷歌云SDK(gcloud)已安装并配置
  • 访问Google Cloud项目
  • uv 已安装Python包管理器
  • 已安装Gemini CLI(用于测试)

MCP服务器设置

项目选择

首先,选择您的Google Cloud项目:

gcloud projects list

设置您的项目:

gcloud config set project [PROJECT_ID]

启用API

启用所需的Google Cloud API:

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

创建项目目录

为您的MCP服务器创建一个目录:

mkdir mcp

cd mcp/

初始化Python环境

使用 uv 初始化一个新的Python项目:

uv init --description "Example of deploying an MCP server on Cloud Run" --bare --python 3.13

这有什么作用:

  • 用途 uv,一个现代的Python包和项目管理器
  • 在当前目录中初始化新的Python项目
  • 创建一个 pyproject.toml 文件(而不是 requirements.txt)
  • 将Python 3.13设置为所需版本
  • 设置项目元数据,如描述
  • 这 --bare flag创建了一个没有额外文件的全新状态(如 main.py, .venv,或 README)

添加FastMCP依赖关系

将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 Server 🦁🐧🐻")

# Dictionary of Speakers at the GDG Kochi 
GDG_KOCHI = [
    {
        "title": "Registration Starts",
        "time": "09:30 AM - 09:35 AM",
        "speaker": None,
        "role": None,
        "organization": None,
        "duration": None,
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Welcome Note",
        "time": "09:35 AM - 09:40 AM",
        "speaker": "Iqbal P B",
        "role": "Organiser",
        "organization": "GDG Cochin",
        "duration": "5 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Keynote — Artificially intelligent? No, Creative with Google AI Studio",
        "time": "09:40 AM - 10:00 AM",
        "speaker": "Merin K Jacob",
        "role": "Technical Account Manager",
        "organization": "Google",
        "duration": "20 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Keynote — Dont Call Me a Developer: Designing an AI-Accelerated Career",
        "time": "10:05 AM - 10:25 AM",
        "speaker": "Eric Hole",
        "role": "Vice President",
        "organization": "Onix",
        "duration": "20 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Sponsor Talk",
        "time": "10:30 AM - 10:40 AM",
        "speaker": "Dr. Tom M Joseph",
        "role": None,
        "organization": "Jain",
        "duration": "10 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Break",
        "time": "10:50 AM - 11:05 AM",
        "speaker": None,
        "role": None,
        "organization": None,
        "duration": "15 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Tech Talk — Ship It Fast: AI Apps with Gemini, Firebase Studio, and $0 Infra",
        "time": "11:35 AM - 12:00 PM",
        "speaker": "Vishnu K S",
        "role": "Senior Cloud Engineer",
        "organization": "DP World",
        "duration": "25 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Tech Talk — Building Conversational Agents using ADK, A2A and MCP",
        "time": "12:05 PM - 12:30 PM",
        "speaker": "Nishi Ajmera",
        "role": "Solutions Architect",
        "organization": "Publicis Sapient (UK)",
        "duration": "25 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Tech Talk — Run SQL Everywhere: Presto Meets BigQuery and Cloud Storage",
        "time": "12:40 PM - 01:25 PM",
        "speaker": "Saurabh Mahawar",
        "role": "Developer Relations Engineer",
        "organization": "IBM",
        "duration": "45 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Lunch Break",
        "time": "12:40 PM - 01:25 PM",
        "speaker": None,
        "role": None,
        "organization": None,
        "duration": "45 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Tech Talk — Production ready AI Agents with Security, Evaluation and Memory",
        "time": "01:25 PM - 01:50 PM",
        "speaker": "Nikhilesh Tayal",
        "role": "Google Developer Expert",
        "organization": "—",
        "duration": "25 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Tech Talk — Securing the Google Cloud: AI-Driven Threat Hunting Using Gemini",
        "time": "01:55 PM - 02:20 PM",
        "speaker": "Harisuthan S",
        "role": "Senior Security Engineer",
        "organization": "Renault Nissan Technology",
        "duration": "25 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Tech Talk — Lessons from running Go applications in Kubernetes",
        "time": "02:25 PM - 02:50 PM",
        "speaker": "Adarsh K Kumar",
        "role": "Principal Product Engineer",
        "organization": "Rapido",
        "duration": "25 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Tech Talk — Orchestrating the Modern Data Stack with Apache Airflow",
        "time": "02:55 PM - 03:20 PM",
        "speaker": "Jeevitha",
        "role": "Staff Software Engineer",
        "organization": "Juniper Networks (HP)",
        "duration": "25 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Tech Talk — Building Scalable and Ethical AI Solutions with Gemma",
        "time": "03:25 PM - 03:50 PM",
        "speaker": "Geeta Kakrani",
        "role": "Google Developer Expert",
        "organization": "AI",
        "duration": "25 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Tech Talk — Securing your LLM's",
        "time": "03:55 PM - 04:20 PM",
        "speaker": "Dharmesh Vaya",
        "role": "Senior Solutions Engineer",
        "organization": "Wiz",
        "duration": "25 min",
        "location": "Main Hall",
        "notes": None,
    },
    {
        "title": "Closing Note",
        "time": "04:25 PM - 04:30 PM",
        "speaker": "Malavika",
        "role": "Organiser",
        "organization": "GDG Cochin",
        "duration": "5 min",
        "location": "Main Hall",
        "notes": None,
    },
    # Workshop / parallel track (best-effort)
    {
        "title": "Workshop — How to deploy a secure MCP server on Cloud Run?",
        "time": "02:00 PM",
        "speaker": "Ebin Babu",
        "role": "CNCF Ambassador",
        "organization": "Linux Foundation Projects",
        "duration": None,
        "location": "Workshop",
        "notes": "Parallel workshop track",
    },
]

@mcp.tool()
def search_sessions(query: str) -> List[Dict[str, Any]]:
    """Search GDG Kochi sessions.

    Performs a case-insensitive substring search over session titles and
    speaker names and returns matching session dicts from `GDG_KOCHI`.

    Args:
        query: A search string to match against title or speaker.

    Returns:
        A list of session dictionaries (may be empty).
    """
    logger.info(f">>> 🛠️ Tool: 'search_sessions' called for query='{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]:
    """Return a single session's details by exact title or speaker name.

    Tries to match `identifier` exactly (case-insensitive) against the
    session title first, then speaker name. If no exact match is found,
    returns the first partial match. If nothing matches, returns an empty dict.

    Args:
        identifier: Session title or speaker name to look up.

    Returns:
        A session dict or an empty dict when not found.
    """
    logger.info(f">>> 🛠️ Tool: 'get_session_details' called for '{identifier}'")
    if not identifier:
        return {}
    ident = identifier.strip().lower()

    # Exact title match
    for sess in GDG_KOCHI:
        if (sess.get("title") or "").strip().lower() == ident:
            return sess

    # Exact speaker match
    for sess in GDG_KOCHI:
        if (sess.get("speaker") or "").strip().lower() == ident:
            return sess

    # Partial match fallback (title or speaker)
    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 server started on port {port}")
    asyncio.run(
        mcp.run_async(
            transport="http",
            host="0.0.0.0",
            port=port,
        )
    )

创建Dockerfile

创建一个 Dockerfile:

# 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"]

部署到云端运行

将MCP服务器部署到Cloud Run:

gcloud run deploy gdg-kochi-mcp-server \
    --no-allow-unauthenticated \
    --region=asia-south1 \
    --source=. \
    --labels=dev-tutorial=codelab-mcp

注: 调整 --region 参数到您的首选区域。

配置Gemini CLI

授予权限

添加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配置

创建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令牌

测试MCP服务器

启动Gemini CLI:

gemini

在Gemini CLI中,导航到:

/mcp

例题

试着问以下问题:

  • “显示超过20分钟的会话。”
  • “如果我想参加关于人工智能和安全的讲座,请制定我的个人日程。”
  • “写一条280个字符的推特,宣布梅林的主题演讲。”
  • “为‘随处运行SQL’的演讲创建五个建议的听众问题。”
  • “哪些会议重叠或平行(研讨会与主会场)?”
  • “给我一份所有发言者及其组织的名单。”
  • “哪些演讲者是谷歌员工?”
  • “谁在上午9点40分讲话?”

出现提示时,允许从服务器“gdg-kochi-remote”使用所有工具。

故障排除

  • 如果遇到身份验证错误,请确保 ID_TOKEN 是最新的(令牌在一段时间后过期)
  • 确保中的Cloud Run服务URL settings.json 与您的实际部署区域匹配
  • 验证IAM策略绑定是否已正确应用