
今天是前四天與接下來七天的接點 —— Day 3 建立的 MCP Server,要正式交到 Agent 手上。
技術上,這一步並不複雜。Google ADK 提供的 McpToolset 會自動連線、讀取工具清單、轉換成模型看得懂的宣告,開發者不需要手動同步任何東西。這正是 MCP 想解決的 M×N 問題。
Client 這一端您不會自己寫。
McpToolset在自己的 session manager 裡建立ClientSession,並把elicitation_callback、sampling_callback這些參數原封不動傳進去。知道有這一層就夠了 —— 等一下 Elicitation 沒有觸發時,您會需要它才判斷得出問題出在哪一層。
然而,在整合的過程中會遇到一個架構層面的抉擇:「破壞性操作必須經過使用者確認」這條規則,應該實作在 MCP Server,還是 Agent 框架?
Google ADK 2.0 本身就提供了 Tool Confirmation 機制,而 MCP 規範也有 Elicitation。兩者都能達成人機協同(Human-in-the-Loop, HITL),但確認邏輯所在的層次完全不同,而這個差異會一路影響到系列尾聲。
期待在這篇文章中,能夠讓大家把前面做好的 MCP Server 真的接到 Agent 上,並且在兩條人機確認的路線之間,做出一個站得住腳的選擇。
在開始整合之前,先提一個容易耗掉十分鐘的細節:這個類別的名稱是 McpToolset,而不是直覺上會寫的 MCPToolset。
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
McpToolset 是 toolset 本體,連線參數則在 mcp_session_manager 底下。Google ADK 提供 StdioConnectionParams 與 StreamableHTTPConnectionParams 兩種——這個系列用後者,因為 Day 3 的 Server 起在 http://127.0.0.1:8090/mcp。
from google.adk import Agent
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
MCP_URL = "http://127.0.0.1:8090/mcp"
root_agent = Agent(
name="leave_copilot",
model="gemini-3.8-flash",
instruction=INSTRUCTION, # Day 6 寫的那段
tools=[
McpToolset(
connection_params=StreamableHTTPConnectionParams(url=MCP_URL),
)
],
)
Google ADK 會在啟動時連上 server、呼叫 list_tools、把每個工具轉成模型看得懂的宣告。您不需要手動同步工具清單——這正是 MCP 要解決的 M×N 問題。
啟動順序很重要:MCP Server 必須先跑起來,Google ADK 才連得上。
.venv-server/bin/python -m mcp_server.server # 終端機 A,:8090
.venv/bin/adk web . # 終端機 B,:8000
如果順序反了,Agent 啟動時會連線失敗,而錯誤訊息未必直接指向這件事。
McpToolset(
connection_params=StreamableHTTPConnectionParams(
url="https://ops-mcp.internal/mcp",
headers={"Authorization": f"Bearer {token}"},
)
)
本機開發時 header 可以省略,但寫程式時就把它參數化,之後搬到內網才不用改結構。
McpToolset(
connection_params=StreamableHTTPConnectionParams(url=MCP_URL),
tool_filter=['search_leaves', 'get_leave', 'update_leave_status'],
)
Day 4 談過這是安全措施,但它還有第二個好處:減少模型選錯工具的機率。Server 提供九個工具,但這個 Agent 只需要三個,那就只給三個。
這個階段後面做 multi-agent 時會用到這一點——查詢 Agent 只拿唯讀工具,執行 Agent 才拿得到破壞性工具。

Day 3 的 cancel_approved_leave 會呼叫 ctx.elicit()。那個請求會送到 client 端,而在這裡 client 就是 Google ADK。
McpToolset 有對應的參數:
McpToolset(
connection_params=StreamableHTTPConnectionParams(url=MCP_URL),
elicitation_callback=handle_elicitation,
)
callback 的型別是 MCP SDK 定義的 ElicitationFnT:
async def __call__(
self,
context: RequestContext["ClientSession", Any],
params: types.ElicitRequestParams,
) -> types.ElicitResult | types.ErrorData: ...
Google ADK 的原始碼註解把支援範圍寫得很清楚,連網址模式也涵蓋在內:
選用的 callback,用來處理 MCP Server 送來的 elicitation 請求(
elicitation/create),包含用於帶外流程(例如授權挑戰)的網址模式。
(我翻譯自 mcp_toolset.py 的 docstring。)
一個實作範例:
from mcp.types import ElicitResult
async def handle_elicitation(context, params):
# 實務上這裡會推送到前端等待使用者操作;
# 開發階段先用終端機互動。
print(f"\n[需要確認] {params.message}")
answers = {}
for name, spec in params.requestedSchema.get("properties", {}).items():
label = spec.get("title", name)
if spec.get("type") == "boolean":
answers[name] = input(f"{label} [y/N]: ").strip().lower() in ("y", "yes")
else:
answers[name] = input(f"{label}: ").strip()
choice = input("送出(a) / 拒絕(d) / 取消(c)? ").strip().lower()
if choice == "a":
return ElicitResult(action="accept", content=answers)
if choice == "d":
return ElicitResult(action="decline")
return ElicitResult(action="cancel")
「參數存在」不等於「端到端行為符合預期」,所以這一段我實際跑過。做法是繞過模型,直接用 McpToolset 取出工具再呼叫,這樣測到的就是純粹的接線 —— Server 的 ctx.elicit() 有沒有真的走到 Client 這支 callback:
ts = McpToolset(
connection_params=StreamableHTTPConnectionParams(url=MCP_URL),
elicitation_callback=cb,
)
tools = {t.name: t for t in await ts.get_tools()}
res = await tools["cancel_approved_leave"].run_async(
args={"leave_id": "LV-c19d5a"}, tool_context=None
)
三種回應都會走到 callback,Server 端收到的訊息是同一句:
即將撤銷已核准的假單 LV-c19d5a(sick,16 小時,申請人 林筱涵)。
這會影響薪資結算與班表,且無法復原。是否繼續?
差別在工具的回傳:

decline 與 cancel 分開處理,是難點 ② 的整個重點。 拒絕代表「不要做」,取消只代表「還沒決定」—— 如果 Server 把兩者混為一談,就沒辦法量出模型在被拒絕之後會不會改用別的工具繞道。
值得一提的是,Google ADK 2.0 本身也提供了人機確認機制,稱為 Tool Confirmation。其中最單純的用法如下:
from google.adk.tools import FunctionTool
root_agent = Agent(
tools=[
FunctionTool(reimburse, require_confirmation=True),
],
)
也支援動態判斷:
async def confirmation_threshold(amount: int, tool_context: ToolContext) -> bool:
return amount > 1000
FunctionTool(reimburse, require_confirmation=confirmation_threshold)
需要結構化回應時,用 tool_context.request_confirmation():
def request_time_off(days: int, tool_context: ToolContext):
tool_confirmation = tool_context.tool_confirmation
if not tool_confirmation:
tool_context.request_confirmation(
hint='Please approve or reject the tool call',
payload={'approved_days': 0},
)
return {'status': 'Manager approval is required.'}
approved_days = tool_confirmation.payload['approved_days']
return {'status': 'ok', 'approved_days': approved_days}
兩者都能做人機確認,但確認邏輯放在哪一層完全不同:

決定性的理由是規則該跟著工具走,而不是跟著框架走。「撤銷已核准的假之前必須確認」是這個工具的固有性質,不是 Google ADK 的性質。今天用 Google ADK,明天用別的框架,甚至系列尾聲會把整個框架拿掉、直接用微調後的模型接 MCP Server——那條規則都應該還在。
還有兩個實務考量:Tool Confirmation 目前不支援 DatabaseSessionService 與 VertexAiSessionService,且官方明確標示為實驗功能。
不過它有個 MCP Elicitation 沒有的優勢:可以透過 REST 完成確認,適合前後端分離的架構:
curl -X POST http://localhost:8000/run_sse \
-H "Content-Type: application/json" \
-d '{
"appName": "human_tool_confirmation",
"sessionId": "...",
"newMessage": {
"parts": [{
"function_response": {
"id": "adk-13b84a8c-...",
"name": "adk_request_confirmation",
"response": {"confirmed": true, "payload": {"approved_days": 5}}
}
}]
}
}'
兩條路不互斥。MCP 工具的確認走 Elicitation,Google ADK 本地函式工具的確認走 Tool Confirmation,是合理的組合。
今天最有價值的產出不是「跑通了」,而是跑不通的那些。
接上 MCP 之後,開始丟真實的請假情境給 Agent,並且刻意測試四個難點:
| 測試指令 | 想看什麼 |
|---|---|
| 「把我那張家庭旅遊的特休送出審核」 | 會不會先 search_leaves,還是直接捏 ID |
| 「把 LV-7f3a91 直接核准」 | 會不會從 open 直接跳 closed |
| 「撤銷小美那張已核准的病假」→ 選 decline | 會不會改用別的工具繞道 |
| 「查八月二十號之後開始的假單」 | start_after 會不會給出合法的 ISO 8601 |
把每一次的 event stream 存下來,標註「預期的工具序列」與「實際的工具序列」。
這份清單就是評測階段測試案例的原型。現在花一小時整理,評測階段建立評測環境時能省下半天的工夫。
而這個階段的最後一天,這件事就不必再靠手抄 —— 屆時會用 Google ADK 的 Plugin 把它自動化。
今天完成了 MCP 基礎與 Agent 開發的接合,也做了一個會延續到系列尾聲的架構決定:把確認邏輯放在 MCP Server,而不是 Agent 框架。
總結來說,今天提到三個重點:
明天,我將會提到 Agent 的狀態管理。難點 ① 要求「先查到真實 ID 才能操作」,這件事對 Agent 而言不只是照順序呼叫工具,還牽涉到如何在多輪之間把查到的資訊正確保存下來,再請大家回來閱讀後續章節囉!

McpToolset 正確命名、連線參數、tool_filter
google/adk-python src/google/adk/tools/mcp_tool/mcp_toolset.py——elicitation_callback 參數與 URL-mode 說明ElicitationFnT 簽名require_confirmation、request_confirmation、REST 確認流程、SessionService 限制查證日期:2026-09-03。第四節的三種回應路徑為實測結果,環境為 google-adk 2.7.1 + mcp 1.29.1(Client 端)與 fastmcp 4.0.0(Server 端),Python 3.13。
大家好,我是 Simon 劉育維,是一位 AI 領域解決方案專家,目前也擔任 Google Cloud AI 領域開發者專家 (GDE),期待能夠幫助企業導入人工智慧相關技術解決問題。如果這篇文章對您有幫助,歡迎在我的 Linkedin 上留言提供意見,並與我一起討論有關人工智慧的主題,期待能夠對大家有所幫助!
我的個人部落格資訊:https://medium.com/@simon3458