View a markdown version of this page

AgentCore 게이트웨이에서 유도 사용 - Amazon Bedrock AgentCore

AgentCore 게이트웨이에서 유도 사용

유도는 MCP 서버가 도구 호출 중에 클라이언트에 추가 정보를 요청할 수 있도록 하는 MCP 기능입니다. 도구를 진행하기 위해 사용자 확인, 인증 또는 추가 입력이 필요한 경우 서버는 유도 요청을 클라이언트로 다시 보냅니다. AgentCore Gateway는 MCP 서버 대상의 유도 요청을 클라이언트로 전달하여 요청을 게이트웨이 생성 식별자id로 바꿉니다.

사전 조건

게이트웨이에서 유도를 사용하려면 다음이 필요합니다.

  • 활성화된 세션 - 유도에는 세션 지원이 필요합니다. 게이트웨이에서 MCP 세션 사용을 참조하세요.

  • 응답 스트리밍 활성화됨 - 유도 요청은 열린 연결 중에 Server-Sent Events(SSE) 청크로 전송됩니다. 게이트웨이의 true에서를 streamingConfiguration.enableResponseStreaming로 설정합니다protocolConfiguration.mcp.

  • MCP 서버 대상 유형 - 유도는 MCP 서버 대상에 대해서만 지원됩니다. 유도는 MCP 서버에서 시작되며 게이트웨이를 통해 클라이언트로 전달됩니다.

  • 클라이언트가 유도 기능 선언 - 클라이언트는 게이트웨이가 유도 요청을 전달할 수 있도록 initialize 요청 중에 유도에 대한 지원을 선언해야 합니다.

지원되는 유도 모드

AgentCore Gateway는 MCP 사양에 정의된 세 가지 유도 모드를 지원합니다.

Mode 설명

양식 모드

서버는 클라이언트가 작성할 필드가 포함된 구조화된 양식을 전송합니다. 사용자 확인, 기본 설정 또는 입력 데이터를 수집하는 데 사용됩니다. 요청은 응답을 기다리는 동안 열린 상태로 유지됩니다.

URL 모드(요청 기반)

서버는 사용자가 작업(일반적으로 인증)을 완료하기 위해 방문해야 하는 URL을 보냅니다. 작업이 완료될 때까지 기다리는 동안 요청은 열린 상태로 유지됩니다.

URL 모드(예외 기반)

서버는 URL이 URLElicitationRequiredError 포함된를 발생시킵니다. 요청이 종료되고, 사용자가 URL에서 작업을 완료하고, 클라이언트가 원래 도구 호출을 재시도합니다.

역량 협상

게이트웨이는 다음과 같은 경우에만 MCP 서버 대상에 대한 유도 지원을 선언합니다.

  1. 클라이언트는 중 유도 지원을 선언했습니다initialize.

  2. MCP 프로토콜 버전은 유도 모드를 지원합니다. form 모드에는 버전 2025-03-26 이상이 필요하고 url 모드에는 버전 2025-11-25 이상이 필요합니다.

  3. 게이트웨이는 클라이언트가 선언한 특정 유도 기능(형식, URL 또는 둘 다)과 일치합니다.

양식 모드 유도 흐름

  1. 클라이언트는 Mcp-Session-Id 헤더와 함께 tools/call 요청을 보냅니다.

  2. Gateway는 도구 호출을 MCP 서버 대상으로 전달합니다.

  3. 대상이 SSE 스트림을 열고 첫 번째 이벤트로 elicitation/create 요청을 보냅니다.

  4. Gateway는 SSE 스트림의 클라이언트에 elicitation/create 요청을 전달하여 요청을 교체합니다id.

  5. 클라이언트는 사용자에게 양식을 제공하고 응답을 수집합니다.

  6. 클라이언트는 동일한를 사용하여 유도 응답(작업: accept 또는 decline)과 함께 새 요청을 보냅니다Mcp-Session-Id.

  7. Gateway는 응답을 MCP 서버 대상에 전달합니다.

  8. 대상이 HTTP 202 Accepted로 승인합니다.

  9. 대상이 도구 호출을 완료하고 원래 SSE 스트림에서 최종 결과를 전송합니다.

  10. Gateway는 최종 결과를 클라이언트에 전달하고 스트림을 닫습니다.

URL 모드(예외 기반) 유도 흐름

  1. 클라이언트는 Mcp-Session-Id 헤더와 함께 tools/call 요청을 보냅니다.

  2. Gateway는 도구 호출을 MCP 서버 대상으로 전달합니다.

  3. 대상은 URL과 유도 ID가 포함된를 JSON-RPC 오류URLElicitationRequiredError로 발생시킵니다.

  4. Gateway는를 클라이언트에 전달URLElicitationRequiredError하여 요청을 바꿉니다id.

  5. 클라이언트는 사용자를 제공된 URL로 리디렉션하여 작업을 완료합니다(일반적으로 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(잘못된 요청)

elicitation/create 요청에서 게이트웨이가 보낸 것과 일치해야 id 합니다.

게이트웨이와 MCP 서버 대상 간의 연결 중단

DependencyFailedException을 사용한 JSON-RPC 오류

클라이언트는 원래 도구 호출 요청을 다시 시도해야 합니다.

클라이언트와 게이트웨이 간의 연결 중단

해당 사항 없음

보류 중인 유도가 정리됩니다. 클라이언트는 도구 호출을 다시 시도해야 합니다.

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
  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 모드에서 서버는 사용자가 작업을 완료하기 위해 방문해야 하는 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"}]} )