View a markdown version of this page

在网关上使用引发 AgentCore - Amazon Bedrock AgentCore

在网关上使用引发 AgentCore

Elicitation 是一项 MCP 功能,允许 MCP 服务器在工具调用期间向客户端请求其他信息。当工具需要用户确认、身份验证或其他输入才能继续操作时,服务器会向客户端发送引用请求。 AgentCore Gateway 将来自 MCP 服务器目标的引发请求转发给您的客户端,将请求id替换为网关生成的标识符。

先决条件

要在网关上使用激发,您必须具备:

  • 会话已启用 — Elication 需要会话支持。请参阅在您的网关上使用 MCP 会话

  • 启用响应流-在打开的连接期间,Elicitation 请求作为 Server-Sent 事件 (SSE) 区块发送。truestreamingConfiguration.enableResponseStreaming您的网关中设置为protocolConfiguration.mcp

  • MCP 服务器目标类型-只有 MCP 服务器目标支持激发。引发源自 MCP 服务器,并通过网关转发到客户端。

  • 客户端声明引发能力 — 在请求网关转发引发initialize请求期间,客户端必须声明支持引发。

支持的激发模式

AgentCore Gateway 支持 MCP 规范定义的三种激发模式:

Mode 说明

表单模式

服务器发送结构化表单,其中包含供客户填写的字段。用于收集用户确认、首选项或输入数据。在等待响应期间,请求仍处于打开状态。

URL 模式(基于请求)

服务器发送一个 URL,用户必须访问该网址才能完成操作(通常是身份验证)。在等待操作完成期间,请求仍处于打开状态。

URL 模式(基于异常)

服务器会抛出一个URLElicitationRequiredError包含 URL 的。请求关闭,用户在 URL 上完成操作,客户端重试原始工具调用。

能力谈判

只有在以下情况下,网关才会向 MCP 服务器目标声明引发支持:

  1. 客户在此期间宣布了招标支持。initialize

  2. MCP 协议版本支持激发模式 — 模式需要版本或更高版本,formurl模式需要版本2025-03-26或更高版本2025-11-25

  3. 网关与客户端声明的特定激发能力(表单、网址或两者兼而有之)相匹配。

表单模式激发流

  1. 客户端发送带有Mcp-Session-Id标头的tools/call请求。

  2. Gateway 将工具调用转发给 MCP 服务器目标。

  3. 目标打开一个 SSE 流并发送elicitation/create请求作为第一个事件。

  4. Gateway 将elicitation/create请求转发给 SSE 流上的客户端,取代请求id

  5. 客户端向用户展示表单并收集响应。

  6. 客户端使用相同的引发响应(操作:acceptdecline)发送新请求。Mcp-Session-Id

  7. 网关将响应转发给 MCP 服务器目标。

  8. 目标通过已接受 HTTP 202 进行确认。

  9. 目标完成工具调用,并在原始 SSE 流上发送最终结果。

  10. Gateway 将最终结果转发给客户端并关闭直播。

URL 模式(基于异常)引发流

  1. 客户端发送带有Mcp-Session-Id标头的tools/call请求。

  2. Gateway 将工具调用转发给 MCP 服务器目标。

  3. 目标将URLElicitationRequiredError作为 JSON-RPC 错误抛出,其中包含网址和引用 ID。

  4. Gateway 将转发URLElicitationRequiredError给客户端,替换请求id

  5. 客户端将用户重定向到提供的网址以完成操作(通常为 OAuth 身份验证)。

  6. 用户完成操作后,客户端会重试原始tools/call请求。

  7. Gateway 将重试转发给目标。自URL引用完成以来,目标完成了工具调用。

  8. Gateway 将最终的工具结果转发给客户端。

带邀请的并行工具调用

一个客户端可以在同一个会话中发起多个tools/call请求,即使在等待招标时也是如此。每个诱发都由其独立跟踪。id发送引发响应时,客户端必须在请求中包含网关发送的引发响应。id elicitation/create

MCP 服务器目标开发者指南

重要

发送引发请求的 MCP 服务器目标将引发调用封装在 try-catch 块中,并处理客户端不支持引发的情况。如果网关的客户端未声明激发能力,则网关不会向目标声明该能力。如果目标还是发送了引发,则网关会向目标返回-32601(未找到方法)错误。

当激发不可用时,服务器应实现回退路径(例如使用默认值或跳过操作)。

错误处理

场景 错误 说明

当没有待处理的招标时,客户端会发送招标响应

JSON-RPC -32600(请求无效)

找不到与该会话匹配的激发。

客户端发送的引发响应与待处理id的邀请不匹配

JSON-RPC -32600(请求无效)

id必须与网关在elicitation/create请求中发送的相匹配。

网关和 MCP 服务器目标之间的连接中断

JSON-RPC 错误 DependencyFailedException

客户端应重试原始的工具调用请求。

客户端和网关之间的连接中断

N/A

待处理的招标已被清理。客户端应重试该工具调用。

MCP 服务器发送了邀请,但网关未声明支持

JSON-RPC -32601(未找到方法)

已返回到 MCP 服务器目标。参阅故障排除

问题排查

错误:“调用工具'sample_tool'时出错:找不到方法:” elicitation/create

当 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
  1. 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
  1. 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
  1. 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
  1. 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,用户必须访问该网址才能完成操作(通常为 OAuth 身份验证)。在等待用户在 URL 上完成操作时,请求保持打开状态。

Python requests package
  1. 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
  1. 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
  1. 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
  1. 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"}]} )