搭配 AgentCore 閘道使用引出
Elicitation 是一種 MCP 功能,可讓 MCP 伺服器在工具呼叫期間向用戶端請求其他資訊。當工具需要使用者確認、身分驗證或其他輸入才能繼續時,伺服器會將請求傳回給用戶端。AgentCore Gateway 會將請求從 MCP 伺服器目標轉送到您的用戶端,id以閘道產生的識別符取代請求。
先決條件
若要搭配閘道使用引動,您必須具有:
-
工作階段已啟用 — 請求需要工作階段支援。請參閱搭配閘道使用 MCP 工作階段。
-
啟用回應串流 — 在開放連線期間,請求會以伺服器傳送事件 (SSE) 區塊的形式傳送。在閘道的 true中streamingConfiguration.enableResponseStreaming將 設定為 protocolConfiguration.mcp。
-
MCP 伺服器目標類型 — 僅 MCP 伺服器目標支援請求。引出源自 MCP 伺服器,並透過閘道轉送至用戶端。
-
用戶端宣告引出功能 — 用戶端必須在請求閘道轉送引出initialize請求期間宣告對引出的支援。
支援的引出模式
AgentCore Gateway 支援 MCP 規格定義的三種引出模式:
| Mode |
說明 |
|
表單模式
|
伺服器會傳送結構化表單,其中包含要由用戶端填寫的欄位。用於收集使用者確認、偏好設定或輸入資料。請求在等待回應時保持開啟狀態。
|
|
URL 模式 (以請求為基礎)
|
伺服器會傳送使用者必須造訪的 URL,以完成動作 (通常是身分驗證)。請求在等待動作完成時保持開啟狀態。
|
|
URL 模式 (例外類型)
|
伺服器擲回URLElicitationRequiredError包含 URL 的 。請求關閉,使用者在 URL 完成動作,用戶端會重試原始工具呼叫。
|
功能交涉
如果符合下列條件,閘道只會宣告對 MCP 伺服器目標的引出支援:
-
用戶端在 期間宣告引出支援initialize。
-
MCP 通訊協定版本支援引出模式 — form 模式需要版本 2025-03-26或更新版本,url模式需要版本 2025-11-25 或更新版本。
-
閘道符合用戶端宣告的特定引出功能 (形式、url 或兩者)。
-
用戶端使用 Mcp-Session-Id標頭傳送tools/call請求。
-
Gateway 會將工具呼叫轉送至 MCP 伺服器目標。
-
目標會開啟 SSE 串流,並以第一個事件傳送elicitation/create請求。
-
Gateway 會將elicitation/create請求轉送至 SSE 串流上的用戶端,取代請求 id。
-
用戶端會將表單呈現給使用者,並收集回應。
-
用戶端會使用相同的 傳送具有引出回應 (動作: accept或 decline) 的新請求Mcp-Session-Id。
-
Gateway 會將回應轉送至 MCP 伺服器目標。
-
目標確認 HTTP 202 已接受。
-
目標完成工具呼叫,並在原始 SSE 串流上傳送最終結果。
-
Gateway 會將最終結果轉送至用戶端並關閉串流。
URL 模式 (例外型) 引出流程
-
用戶端使用 Mcp-Session-Id標頭傳送tools/call請求。
-
Gateway 會將工具呼叫轉送至 MCP 伺服器目標。
-
目標擲回 URLElicitationRequiredError做為 JSON-RPC 錯誤,其中包含 URL 和引出 ID。
-
Gateway 會將 轉送URLElicitationRequiredError到用戶端,取代請求 id。
-
用戶端會將使用者重新導向至提供的 URL 以完成動作 (通常是 OAuth 身分驗證)。
-
使用者完成動作後,用戶端會重試原始tools/call請求。
-
Gateway 會將重試轉送到目標。目標會在完成 URL 引出後完成工具呼叫。
-
Gateway 會將最終工具結果轉送至用戶端。
具有引出的平行工具呼叫
即使正在等待引出,用戶端也可以在同一個工作階段中啟動多個tools/call請求。每個引動都會由其 獨立追蹤id。傳送引出回應時,用戶端必須包含閘道在elicitation/create請求中id傳送的相同 。
MCP 伺服器目標開發人員指南
傳送引出請求的 MCP 伺服器目標應將引出呼叫包裝在 try-catch 區塊中,並處理用戶端不支援引出的情況。如果閘道的用戶端未宣告引出功能,閘道不會將其宣告至目標。如果目標仍然傳送引出,閘道會將 -32601(找不到方法) 錯誤傳回目標。
當引出不可用時,伺服器應實作備用路徑 (例如使用預設值或略過操作)。
錯誤處理
| 案例 |
錯誤 |
說明 |
|
用戶端在未等待引出時傳送引出回應
|
JSON-RPC -32600(無效的請求)
|
找不到此工作階段的相符引出。
|
|
用戶端使用不符合待定引出id的 傳送引出回應
|
JSON-RPC -32600(無效的請求)
|
id 必須與elicitation/create請求中閘道傳送的 相符。
|
|
閘道與 MCP 伺服器目標之間的連線中斷
|
具有 DependencyFailedException 的 JSON-RPC 錯誤
|
用戶端應重試原始工具呼叫請求。
|
|
用戶端和閘道之間的連線中斷
|
N/A
|
待定引出已清除。用戶端應重試工具呼叫。
|
|
MCP 伺服器傳送引出,但閘道未宣告支援
|
JSON-RPC -32601(找不到方法)
|
已傳回 MCP 伺服器目標。請參閱故障診斷。
|
疑難排解
錯誤:「錯誤呼叫工具 'sample_tool':找不到方法:引出/建立」
當 MCP 伺服器目標傳送引出請求,但閘道的用戶端未在 期間宣告引出功能時,就會發生此錯誤initialize。閘道會將 -32601(找不到方法) 錯誤傳回至目標,而目標可能會將此做為工具執行錯誤傳回給用戶端。
若要解決問題:
-
如果您是 MCP 伺服器開發人員:請新增引出呼叫的錯誤處理。不支援引出時實作備用路徑:
try:
result = await context.session.create_elicitation(
message="Confirm this action?",
requested_schema={"type": "object", "properties": {"confirm": {"type": "boolean"}}}
)
except Exception as e:
# Fallback when client doesn't support elicitation
logger.warning(f"Elicitation not supported: {e}")
result = default_action()
-
如果您是閘道用戶端開發人員:確保您的用戶端在 期間宣告引出功能initialize:
{
"capabilities": {
"elicitation": {
"form": {},
"url": {}
}
}
}
程式碼範例
在表單模式中,伺服器會傳送結構式結構描述供用戶端填寫。請求在等待回應時保持開啟狀態。
範例
- Python requests package
-
-
import requests
import json
import sseclient
gateway_url = "https://mygateway-abcdefghij.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp"
headers = {
"Content-Type": "application/json",
"Accept": "text/event-stream",
"Authorization": "Bearer YOUR_ACCESS_TOKEN"
}
# Step 1: Initialize with elicitation capability
init_response = requests.post(gateway_url, headers=headers, json={
"jsonrpc": "2.0",
"id": "init-request",
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {"elicitation": {"form": {}, "url": {}}},
"clientInfo": {"name": "my-agent", "version": "1.0.0"}
}
})
session_id = init_response.headers["Mcp-Session-Id"]
headers["Mcp-Session-Id"] = session_id
# Step 2: Call tool (streaming response)
response = requests.post(gateway_url, headers=headers, json={
"jsonrpc": "2.0",
"id": "tool-call-1",
"method": "tools/call",
"params": {
"name": "use_aws",
"arguments": {"command": "aws s3 rm s3://my-bucket/important-file"}
}
}, stream=True)
# Step 3: Process SSE events
client = sseclient.SSEClient(response)
for event in client.events():
data = json.loads(event.data)
if data.get("method") == "elicitation/create":
elicitation_id = data["id"]
print(f"Form elicitation received: {data['params']['message']}")
# Step 4: Send elicitation response
requests.post(gateway_url, headers=headers, json={
"jsonrpc": "2.0",
"id": elicitation_id,
"result": {"action": "accept", "content": {"confirm": True}}
})
elif "result" in data:
print(f"Tool result: {data['result']}")
break
- MCP Client
-
-
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
import asyncio
async def elicitation_handler(request):
"""Handle form elicitation requests from the server."""
print(f"Elicitation: {request.params.message}")
return {"action": "accept", "content": {"confirm": True}}
async def use_elicitation(url, token):
headers = {"Authorization": f"Bearer {token}"}
async with streamablehttp_client(url=url, headers=headers) as (
read_stream, write_stream, _
):
async with ClientSession(
read_stream, write_stream,
elicitation_handler=elicitation_handler
) as session:
await session.initialize()
result = await session.call_tool(
name="use_aws",
arguments={"command": "aws s3 rm s3://my-bucket/important-file"}
)
print(f"Tool result: {result}")
return result
asyncio.run(use_elicitation(
url="https://mygateway-abcdefghij.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp",
token="YOUR_ACCESS_TOKEN"
))
- Strands MCP Client
-
-
from mcp.client.streamable_http import streamablehttp_client
from mcp.types import ElicitResult
from strands import Agent
from strands.tools.mcp import MCPClient
mcp_url = "https://mygateway-abcdefghij.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp"
access_token = "YOUR_ACCESS_TOKEN"
async def elicitation_callback(context, params):
"""Handle form elicitation requests from the MCP server target."""
print(f"Elicitation: {params.message}")
user_response = get_user_input(params) # Your UI logic
return ElicitResult(action="accept", content=user_response)
mcp_client = MCPClient(
lambda: streamablehttp_client(
mcp_url, headers={"Authorization": f"Bearer {access_token}"}
),
elicitation_callback=elicitation_callback,
)
with mcp_client:
agent = Agent(tools=mcp_client.list_tools_sync())
response = agent("Delete the file s3://my-bucket/important-file using AWS CLI")
print(response)
- LangGraph MCP Client
-
-
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_mcp_adapters.callbacks import Callbacks, CallbackContext
from langchain.agents import create_agent
from mcp.shared.context import RequestContext
from mcp.types import ElicitRequestParams, ElicitResult
async def on_elicitation(
mcp_context: RequestContext,
params: ElicitRequestParams,
context: CallbackContext,
) -> ElicitResult:
"""Handle elicitation requests from MCP servers."""
print(f"[{context.server_name}] Elicitation: {params.message}")
# Prompt user for input based on params.requestedSchema
return ElicitResult(
action="accept",
content={"confirm": True},
)
client = MultiServerMCPClient(
{
"gateway": {
"url": "https://mygateway-abcdefghij.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp",
"transport": "http",
"headers": {"Authorization": "Bearer YOUR_ACCESS_TOKEN"},
}
},
callbacks=Callbacks(on_elicitation=on_elicitation),
)
tools = await client.get_tools()
agent = create_agent("claude-sonnet-4-20250514", tools)
result = await agent.ainvoke(
{"messages": [{"role": "user", "content": "Delete s3://my-bucket/important-file"}]}
)
URL 模式範例
在 URL 模式中,伺服器會傳送使用者必須造訪的 URL,以完成動作 (通常是 OAuth 身分驗證)。請求在等待使用者在 URL 完成動作時保持開啟狀態。
範例
- Python requests package
-
-
import requests
import json
import sseclient
import webbrowser
gateway_url = "https://mygateway-abcdefghij.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp"
headers = {
"Content-Type": "application/json",
"Accept": "text/event-stream",
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"Mcp-Session-Id": "session-abc123def456"
}
# Call tool that triggers URL elicitation
response = requests.post(gateway_url, headers=headers, json={
"jsonrpc": "2.0",
"id": "tool-call-2",
"method": "tools/call",
"params": {
"name": "access_github_repo",
"arguments": {"repo": "my-org/my-repo"}
}
}, stream=True)
# Process SSE events
client = sseclient.SSEClient(response)
for event in client.events():
data = json.loads(event.data)
if data.get("method") == "elicitation/create":
elicitation_id = data["id"]
url = data["params"]["url"]
print(f"URL elicitation: {data['params']['message']}")
# Open browser for user to complete authentication
webbrowser.open(url)
input("Press Enter after completing authentication...")
# Send elicitation response
requests.post(gateway_url, headers=headers, json={
"jsonrpc": "2.0",
"id": elicitation_id,
"result": {"action": "accept"}
})
elif "result" in data:
print(f"Tool result: {data['result']}")
break
- MCP Client
-
-
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
import asyncio
import webbrowser
async def elicitation_handler(request):
"""Handle URL elicitation by opening the browser."""
if hasattr(request.params, 'url') and request.params.url:
print(f"Opening URL for authentication: {request.params.url}")
webbrowser.open(request.params.url)
input("Press Enter after completing authentication...")
return {"action": "accept"}
# Form mode fallback
return {"action": "accept", "content": {}}
async def use_url_elicitation(url, token):
headers = {"Authorization": f"Bearer {token}"}
async with streamablehttp_client(url=url, headers=headers) as (
read_stream, write_stream, _
):
async with ClientSession(
read_stream, write_stream,
elicitation_handler=elicitation_handler
) as session:
await session.initialize()
result = await session.call_tool(
name="access_github_repo",
arguments={"repo": "my-org/my-repo"}
)
print(f"Tool result: {result}")
return result
asyncio.run(use_url_elicitation(
url="https://mygateway-abcdefghij.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp",
token="YOUR_ACCESS_TOKEN"
))
- Strands MCP Client
-
-
from mcp.client.streamable_http import streamablehttp_client
from mcp.types import ElicitResult
from strands import Agent
from strands.tools.mcp import MCPClient
import webbrowser
mcp_url = "https://mygateway-abcdefghij.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp"
access_token = "YOUR_ACCESS_TOKEN"
async def elicitation_callback(context, params):
"""Handle URL elicitation by opening the browser."""
if hasattr(params, 'url') and params.url:
print(f"Opening URL: {params.url}")
webbrowser.open(params.url)
input("Press Enter after completing authentication...")
return ElicitResult(action="accept")
return ElicitResult(action="accept", content={})
mcp_client = MCPClient(
lambda: streamablehttp_client(
mcp_url, headers={"Authorization": f"Bearer {access_token}"}
),
elicitation_callback=elicitation_callback,
)
with mcp_client:
agent = Agent(tools=mcp_client.list_tools_sync())
response = agent("Access the my-org/my-repo GitHub repository")
print(response)
- LangGraph MCP Client
-
-
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_mcp_adapters.callbacks import Callbacks, CallbackContext
from langchain.agents import create_agent
from mcp.shared.context import RequestContext
from mcp.types import ElicitRequestParams, ElicitResult
import webbrowser
async def on_elicitation(
mcp_context: RequestContext,
params: ElicitRequestParams,
context: CallbackContext,
) -> ElicitResult:
"""Handle URL elicitation by opening the browser."""
if params.url:
print(f"[{context.server_name}] Opening URL: {params.url}")
webbrowser.open(params.url)
input("Press Enter after completing authentication...")
return ElicitResult(action="accept")
return ElicitResult(action="accept", content={})
client = MultiServerMCPClient(
{
"gateway": {
"url": "https://mygateway-abcdefghij.gateway.bedrock-agentcore.us-west-2.amazonaws.com/mcp",
"transport": "http",
"headers": {"Authorization": "Bearer YOUR_ACCESS_TOKEN"},
}
},
callbacks=Callbacks(on_elicitation=on_elicitation),
)
tools = await client.get_tools()
agent = create_agent("claude-sonnet-4-20250514", tools)
result = await agent.ainvoke(
{"messages": [{"role": "user", "content": "Access the my-org/my-repo GitHub repository"}]}
)