MCP Server 终于能"记住"用户了:AgentCore 有状态会话实战
搞 MCP 的都知道一个蛋疼的问题:server 不记状态。用户上一轮说了啥,下一轮完全不知道。
上周亚马逊云科技给 Bedrock AgentCore 加了有状态 MCP 支持,我第一时间试了试。三个新能力:Elicitation(主动追问)、Sampling(反向调 LLM)、Progress(进度通知)。跑通之后确实挺惊喜的。
痛点在哪
想象你在做一个订机票的 MCP tool。用户说"去巴黎",然后你需要知道日期、人数、舱位。
原来的做法?要么让用户一次性传完所有参数,要么自己在外面搞个 Redis 存状态。server 本身就是个无状态函数。
现在不用了。ctx.elicit() 让 server 可以在 tool 执行过程中暂停下来问用户,拿到回答再继续。
核心代码
环境:
pip install "fastmcp>=2.10.0" mcp
Server 端关键部分:
from fastmcp import FastMCP, Context
from enum import Enum
mcp = FastMCP("Travel-Booking-Agent")
class TripType(str, Enum):
BUSINESS = "business"
LEISURE = "leisure"
FAMILY = "family"
DESTINATIONS = {
"paris": {"name": "Paris, France", "flight": 450, "hotel": 180,
"highlights": ["Eiffel Tower", "Louvre", "Notre-Dame"]},
"tokyo": {"name": "Tokyo, Japan", "flight": 900, "hotel": 150,
"highlights": ["Shibuya", "Senso-ji Temple", "Mt. Fuji day trip"]},
}
@mcp.tool()
async def plan_trip(ctx: Context) -> str:
# 追问目的地
dest_result = await ctx.elicit(
message="去哪儿?可选: Paris, Tokyo",
response_type=str
)
if dest_result.action != "accept":
return "已取消"
dest = DESTINATIONS.get(dest_result.data.lower().strip())
# 追问旅行类型
type_result = await ctx.elicit(
message="旅行类型?business / leisure / family",
response_type=TripType
)
# 追问天数
days_result = await ctx.elicit(
message="待几天?(3-14)",
response_type=int
)
days = max(3, min(14, days_result.data))
# 进度通知
for i in range(1, 6):
await ctx.report_progress(progress=i, total=5)
await asyncio.sleep(0.4)
# 反向调 LLM 生成推荐
try:
response = await ctx.sample(
messages=f"Give 3 tips for {dest['name']}, {days} days",
max_tokens=150
)
tips = response.text
except Exception:
tips = f"Visit {dest['highlights'][0]}!"
return f"Booked: {dest['name']}, {days} days\nTips: {tips}"
启动时记得关无状态模式:
mcp.run(
transport="streamable-http",
host="0.0.0.0",
port=8000,
stateless_http=False # 必须!
)
客户端怎么接
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport
from fastmcp.client.elicitation import ElicitResult
from mcp.types import CreateMessageResult, TextContent
async def elicit_handler(message, response_type, params, ctx):
print(f"\n>>> {message}")
response = input("回答: ").strip()
if response_type == int:
response = int(response)
return ElicitResult(action="accept", content={"value": response})
async def sampling_handler(messages, params, ctx):
return CreateMessageResult(
role="assistant",
content=TextContent(type="text", text="试试当地小吃!"),
model="test-model",
stopReason="endTurn"
)
async def progress_handler(progress, total, message):
pct = int((progress / total) * 100) if total else 0
print(f"\r 进度: {pct}%", end="", flush=True)
if progress == total:
print(" ✅")
transport = StreamableHttpTransport(url="http://localhost:8000/mcp")
client = Client(transport,
elicitation_handler=elicit_handler,
sampling_handler=sampling_handler,
progress_handler=progress_handler)
部署到 AgentCore
pip install bedrock-agentcore-starter-toolkit
agentcore configure -e travel_server.py -p MCP -n travel_agent_demo
agentcore deploy
每个 session 跑在独立 microVM 里,通过 Mcp-Session-Id 维持上下文。
踩坑
- stateless_http 默认是 True:不改的话 elicit 直接报错,错误信息还很含糊
- Session 会超时:过期后客户端收到 404,要重新 initialize
- Sampling handler 不能返回空文本:server 端拼字符串会炸
适用场景
- 多步骤表单 / 配置向导
- 审批流程(执行到一半等人确认)
- 交互式调试
- 个性化推荐(收集偏好 → LLM 推荐 → 确认下单)
14 个区域可用,亚太有孟买、首尔、新加坡、悉尼、东京。
如果你在搞 AI agent 开发,这个功能值得试试。