内容
# 行程规划器 — MCP 服务器 + LangGraph 代理
一个使用 FastMCP 构建的行程规划 MCP 服务器,以及一个与其连接的 LangGraph 代理,同时连接两个公共 MCP 服务器。代理管理旅行者的行程安排:行程(整个旅行)、行程中的城市停留以及每个停留内的计划项目(景点、餐饮、活动)。
## 架构

用户提出问题。LangGraph 代理思考:如果需要工具,它会调用工具,获取结果,然后再次思考 — 重复直到有足够的信息来回答。`MultiServerMCPClient` 在代理启动时连接到所有三个 MCP 服务器,并将发现的每个工具交给代理。一个服务器是我们自己的,另外两个是公共服务器。
## 数据模型、工具、资源、提示
SQLite,三个表,每个表有一个嵌套级别:
| 表 | 持有 |
|---|---|
| `journeys` | 整个行程 — 标题、出发城市、日期、预算 |
| `stops` | 行程中的一个城市阶段 — 城市、国家、到达日期、停留夜数、交通方式 |
| `plan_items` | 停留内的一个景点/餐饮/活动 — 标题、类别、时间窗口、优先级、状态 |
**工具** (`backend/mcpserver/TripPlannerServer.py`) — 完整的 CRUD 操作以及实时天气查询:
| 工具 | CRUD |
|---|---|
| `create_journey` | 创建 |
| `add_stop` | 创建 |
| `add_plan_item` | 创建 |
| `list_stops` | 读取 |
| `update_plan_status` | 更新 |
| `delete_stop` | 删除(级联删除其计划项目) |
| `get_destination_weather` | 调用免费的 Open-Meteo API(地理编码 + 当前天气),无需密钥 |
**资源**:
- `journeys://all` — 每个行程的一行摘要
- `journey://{journey_id}/itinerary` — 模板化资源,一个行程的每日行程(每个停留及其下的计划项目)
**提示**:
- `suggest_itinerary(stop_id)` — 一个可重用的模板,将一个停留的保存计划项目转化为每小时的日程计划
## 使用的公共服务器及原因
- **`time`** (`uvx mcp-server-time`, stdio) — 官方参考 MCP 服务器,用于时区/本地时间查询。简单,无风险,没有真正的远程替代方案,因此 stdio 是正确选择。
- **`tavily`** (Streamable HTTP, `https://mcp.tavily.com/mcp`) — Tavily 的真正**托管远程** MCP 服务器(完全没有本地进程),Tavily 是一家在 LLM 工具领域有声誉的公司(与 LangChain 自己的社区工具使用的搜索后端相同)。它需要一个免费的 API 密钥(tavily.com,无需信用卡)。选择它而不是普通的页面 `fetch` 服务器,因为它进行实时、排名的网络搜索(景点、旅行建议、本地事实),而不是获取一个原始页面 — 更适合行程规划。故意避免直接从公共 MCP 注册表(`registry.modelcontextprotocol.io`, Smithery, Glama)中拉取服务器 — 这些目录未经审查,搜索它们发现了带有可疑名称的条目。在开发过程中。Tavily 是一家知名的、有声誉的公司,拥有真正的产品,而不是匿名的注册列表。
## 核心 CRUD 工作流之外的额外功能
- `get_destination_weather` 从工具内部调用免费的 Open-Meteo API(地理编码 + 当前天气,无需密钥)。
- `backend/integrations/fastapimcp_integration.py` 从一个 FastAPI ASGI 应用程序提供 REST (`/health`, `/journeys`) 和 MCP (`/mcp`)。
- `add_stop` 通过 `ctx.info(...)` 记录,在 MCP Inspector 的通知面板中实时可见。
- 浏览器控制台 (`backend/api/app.py` + `frontend/`) — 参见下文。
## 控制台 UI(奖励)
一个小型 React 控制台,用于驱动代理从浏览器而不是 CLI:选择 12 个测试问题之一(或输入自己的问题),实时查看每个工具调用及其结果流,工具工具地运行。
`backend/api/app.py` 将 `backend/client/langgraphagent.py` 中的同一个 LangGraph 代理包装在一个长寿命的 FastAPI 进程中。它在启动时连接到所有三个 MCP 服务器,然后将每个问题的逐步跟踪流式传输回浏览器,以换行分隔的 JSON 格式。
三个进程,三个终端:
```
uv run backend/mcpserver/TripPlannerServer.py # :8000 — MCP 服务器
uv run python -m backend.api.app # :8010 — 流式控制台 API
cd frontend && npm install && npm run dev # :5173 — UI
```
打开打印的 `localhost` URL。左侧边栏显示来自 `/api/servers` 的实时服务器/工具计数;问题列表是与 [`DocsAndDemo/question.md`](DocsAndDemo/question.md) 相同的 12 个问题的集合。
## 如何运行
1. 安装依赖项(使用 `uv`):
```
uv sync
```
2. 将 `.env.example` 复制到 `.env` 并填写:
```
OPENAI_API_KEY=sk-...
TAVILY_API_KEY=tvly-... # 在 https://tavily.com 上免费获取密钥
```
3. 启动服务器(终端 1):
```
uv run backend/mcpserver/TripPlannerServer.py
```
在 `http://127.0.0.1:8000/mcp` 处实时运行(注意所需的 `/mcp` 后缀)。
可选 — 运行组合的 REST+MCP 应用程序:
```
uv run python -m backend.integrations.fastapimcp_integration
```
4. 运行代理(终端 2):
```
uv run backend/client/langgraphagent.py "在旅程 1 中添加一个京都的 3 晚停留,并告诉我那里的当地时间"
```
5. 可选 — 在运行代理之前直接在 MCP Inspector 中测试服务器:
```
npx @modelcontextprotocol/inspector
```
传输:Streamable HTTP,网址:`http://127.0.0.1:8000/mcp`。
需要安装 `uv`(Python/uvx)和 Node.js (`npx`, 用于 Inspector)。
## 示例运行
完整的工具调用跟踪记录在 [`screenshots/demo.txt`](screenshots/demo.txt) 中。摘要:
1. **仅写入** — *"创建一个名为 'Bali Getaway' 的新行程,从孟买出发,预算 1500,日期从 2026-09-01 到 2026-09-10。然后在行程中添加一个印度尼西亚巴厘岛的 5 晚停留。"* → 在我们的服务器上创建了行程 #3 和停留 #4。
2. **写入 + 公共 `time`** — *"在行程 3 中添加一个冰岛雷克雅未克的 4 晚停留,并告诉我当前的当地时间。"* → 添加了停留 #5,并调用 `get_current_time`(两个工具调用在同一回合中并行触发)→ 11:03,星期六,Atlantic/Reykjavik(UTC+0)。
3. **公共 `tavily` 研究** — *"在雷克雅未克目前有哪些顶级景点?冰岛有哪些当前的旅行建议?"* → 实时网络搜索返回 8 个排名景点和 3 个当前政府旅行建议,带有实时来源。
## 为什么使用 MCP 服务器而不是普通的 Python 函数?
将工具放入 MCP 服务器中,可以将工具**能做什么**与**哪个代理使用它**解耦。相同的 `trip_planner` 服务器可以被这个 LangGraph 代理调用,在 MCP Inspector 中手动测试,或以后插入一个完全不同的代理或客户端,而无需触及工具代码 — 因为服务器通过标准协议公开了一个稳定、自我描述的接口(直接从函数签名和文档字符串生成的模式),而不是被绑定到一个代理的 Python 进程中。这也意味着服务器可以独立于代理运行、扩展和失败:如果行程规划逻辑需要不同的主机、不同的语言或自己的部署计划,那是基础设施变更,而不是重写。将相同的逻辑编写为内联 Python 函数,对于单个一次性脚本来说很好,但它锁定了工具到那个代理,并放弃了所有重用、隔离和可发现性,而没有任何好处。
## 视频讲解
[`DocsAndDemo/12.07.2026_22.51.20_REC.mp4`](DocsAndDemo/12.07.2026_22.51.20_REC.mp4) — 演示、架构讲解、MCP 连接配置以及跨本地和公共服务器的实时工具调用。