MCP 伺服器是外部服務,可為 LLM 提供背景資訊、資料或功能。這項技術可連結資料庫和 Web 服務等外部系統,將系統回覆轉換成 LLM 可理解的格式,協助開發人員提供多元功能。
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 等)
提供跨組織共享的工具
需要高可用性的微服務
https://ide.cloud.google.com/?hl=zh-tw
gcloud config set project [PROJECT_ID]
回覆訊息
Updated property [core/project].
gcloud services enable \
run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com
回覆類似這樣的成功訊息
Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.
建立名為 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 檔案。
在 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,
)
)
建立並開啟新的 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 伺服器的權限
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_TOKEN"
}
}
},
"selectedAuthType": "cloud-shell",
"hasSeenIdeIntegrationNudge": true
}
在 Cloud Shell 中啟動 Gemini CLI
gemini
Gemini 列出其環境中可用的 MCP 工具
/mcp
Gemini 在動物園中尋找特定物品
Where can I find penguins?
選取
Yes, always allow all tools from server "zoo-remote"
用 Cloud Run 成功部署了 安全的遠端 MCP 伺服器,等於把 MCP 的能力從地端延伸到雲端!