iT邦幫忙

2025 iThome 鐵人賽

DAY 21
0
生成式 AI

我的 AI 學習之路:30天 Gemma 與 Gemini系列 第 21

我的 AI 學習之路:第21天 Gemma 與 Gemini - MCP 部署 Cloud Run , 加入Cli

  • 分享至 

  • xImage
  •  

MCP 伺服器

MCP 伺服器是外部服務,可為 LLM 提供背景資訊、資料或功能。這項技術可連結資料庫和 Web 服務等外部系統,將系統回覆轉換成 LLM 可理解的格式,協助開發人員提供多元功能。

MCP 伺服器部署

MCP server 可以根據需求,部署在 地端(on-premise) 或 雲端(cloud)。差別主要在 傳輸方式、存取控制與安全性。
1. 地端(On-premise)
特點

 - 傳輸方式:通常用 Stdio 或本地 socket,Host(AI 助理)和 Server 在同一台機器上。

 - 用途:

    存取本地檔案系統

    查詢公司內部資料庫

    操作需要安全隔離的內部系統
2. 雲端(Cloud)
特點

 - 傳輸方式:常用 HTTP / SSE,Host 可以在不同地點呼叫 Server。

 - 用途:

    存取 SaaS API(Google Drive、Slack、GitHub 等)

    提供跨組織共享的工具

    需要高可用性的微服務
    
    

實作範例

開啟 Cloud Shell 編輯器

https://ide.cloud.google.com/?hl=zh-tw

https://ithelp.ithome.com.tw/upload/images/20250922/20121643sSsOE5M49Z.png

在終端機中,使用下列指令設定專案

    gcloud config set project [PROJECT_ID]

回覆訊息
Updated property [core/project].

在終端機中啟用 API:

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

回覆類似這樣的成功訊息
Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.

準備 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 檔案。

建立動物園 MCP 伺服器

在 pyproject.toml 檔案中新增 FastMCP 做為依附元件:

uv add fastmcp==2.11.1 --no-sync

建立並開啟新的 server.py 檔案,用於 MCP 伺服器原始碼:

cloudshell edit server.py

在 server.py 檔案中新增下列 Zoo 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,
        )
    )

部署至 Cloud Run

建立並開啟新的 Dockerfile

cloudshell edit 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"]

執行 gcloud 指令,將應用程式部署至 Cloud Run

gcloud run deploy zoo-mcp-server \
    --no-allow-unauthenticated \
    --region=europe-west1 \
    --source=. \
    --labels=dev-tutorial=codelab-mcp

幾分鐘後,您會看到類似以下的訊息:
Service [zoo-mcp-server] revision [zoo-mcp-server-12345-abc] has been deployed and is serving 100 percent of traffic.

將遠端 MCP 伺服器新增至 Gemini CLI

授予使用者帳戶呼叫遠端 MCP 伺服器的權限

gcloud projects add-iam-policy-binding $GOOGLE_CLOUD_PROJECT \
    --member=user:$(gcloud config get-value account) \
    --role='roles/run.invoker'

將 Google Cloud 憑證和專案編號儲存於環境變數中

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

新增 Cloud Run MCP 伺服器

{
  "mcpServers": {
    "zoo-remote": {
      "httpUrl": "https://zoo-mcp-server-$PROJECT_NUMBER.europe-west1.run.app/mcp/",
      "headers": {
        "Authorization": "Bearer $ID![https://ithelp.ithome.com.tw/upload/images/20250922/20121643PWnZnxxbS0.png](https://ithelp.ithome.com.tw/upload/images/20250922/20121643PWnZnxxbS0.png)_TOKEN"
      }
    }
  },
  "selectedAuthType": "cloud-shell",
  "hasSeenIdeIntegrationNudge": true
}

在 Cloud Shell 中啟動 Gemini CLI

 gemini

https://ithelp.ithome.com.tw/upload/images/20250922/201216433t4RdHz7i8.png

Gemini 列出其環境中可用的 MCP 工具

/mcp

Gemini 在動物園中尋找特定物品

Where can I find penguins?

https://ithelp.ithome.com.tw/upload/images/20250922/20121643r5lD9yV3HU.png

選取
Yes, always allow all tools from server "zoo-remote"

https://ithelp.ithome.com.tw/upload/images/20250922/20121643Sbculpw5Ac.png

總結

用 Cloud Run 成功部署了 安全的遠端 MCP 伺服器,等於把 MCP 的能力從地端延伸到雲端!

參考

https://codelabs.developers.google.com/codelabs/cloud-run/how-to-deploy-a-secure-mcp-server-on-cloud-run?hl=zh-tw#0


上一篇
我的 AI 學習之路:第20天 Gemma 與 Gemini - Agent Development Kit
下一篇
我的 AI 學習之路:第22天 Gemma 與 Gemini - ADK 使用Cloud Run的MCP
系列文
我的 AI 學習之路:30天 Gemma 與 Gemini22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言