flight-deals-mcp
by Relyonyou
README.md
# flight-deals-mcp
> A local MCP server for discovering and re-verifying domestic flight options in mainland China.
**中文:** 本地运行的 Python MCP Server。面向支持 MCP 的 AI 客户端,用于比较中国大陆境内低价航班方案,并在购买前重新核验价格与可售状态。
仓库:<https://github.com/Relyonyou/flight-deals-mcp>
> [!WARNING]
> 本项目仍是 MVP,**尚未通过生产验收**。报价、库存、乘客适用性和票规必须在购买前重新核验。正式 Key 下已完成 24/24 路线矩阵的基准搜索 + 购买前核验 + HTTPS 短链可达(baseline-only);登录后购买页与扩展策略的人工验收仍待执行。详见 [验收矩阵](docs/acceptance/manual-route-matrix.md)。
导航:[MVP 范围](#mvp-范围) · [快速开始](#快速开始) · [如何获取正式 API Key](#如何获取正式-api-key) · [MCP 客户端配置](#接入-mcp-客户端) · [使用方式](#推荐使用方式) · [测试与构建](#测试与构建) · [安全与隐私](#安全与隐私) · [贡献与许可证](#贡献与许可证)
---
## MVP 范围
当前固定搜索意图:
| 维度 | 约束 |
|------|------|
| 航线 | 中国大陆境内民航 |
| 行程 | 单程 |
| 乘客 | 1 名成人(项目意图;见下) |
| 舱位 | 经济舱 |
| 城市 | 随包静态表约 **31** 个主要城市,见 `src/flight_deals_mcp/data/airports.json` |
不在静态表内的城市不会生成扩展候选。这是有意收窄,不等于覆盖全部大陆民航城市。
服务会向上游发送出发地、目的地、日期,以及经济舱、单程等上游支持的约束。官方 `@fly-ai/flyai-cli` **1.0.16** 的 `search_flight` schema **不提供乘客人数参数**。因此「1 名成人」是本项目固定意图,实际报价依赖上游默认语义,**购买页必须再次确认**该价格适用于 1 名成人。项目不会向上游编造 `adult_count` 等不存在的字段。
本项目**不会**创建订单、收款、支付、出票、退改签,也不代替航空公司或售票平台的最终页面。即使首次搜索刚完成,购买前也必须调用 `verify_flight_option`;只有外部售票页当时显示的价格、库存和规则才是最终依据。
---
## 功能概览
仅暴露两个 MCP 工具:
| 工具 | 作用 |
|------|------|
| `find_flight_options` | 查询直飞/官方联程基准,并在约束内尝试扩展策略 |
| `verify_flight_option` | 购买前按 `search_id` + `option_id` 重新查询,核对价格、可售状态、航段与链接 |
默认数据路径:`official_cli`(官方 FlyAI CLI)。另有实验性 `direct_mcp`(Bearer 直连,未作正式验收)。
---
## 策略说明
- **直飞 / 官方联程**:低风险基准,以数据源返回的完整报价为准。
- **日期浮动**:可搜索原日期前后各 1 天(`date_flex_days` 仅允许 `0` 或 `1`)。
- **同城机场**:比较同一城市不同机场;注意地面交通时间与费用。
- **自拼中转**:两张彼此独立的机票,可能更便宜,但无联程保护;前序延误、行李再托运、误机损失通常由旅客自行承担。
- **隐藏城市**:仅在 `risk_preference=exploratory` 且不托运行李时可能出现,并固定标为高风险。航变可能绕过真实目的地,弃乘可能影响后续票联,航司可能重新计价,行李可能被运到票面终点。不是默认推荐,也不保证更便宜或可实际使用。
扩展策略只是候选。`coverage.completed` / `failed` / `skipped` 说明实际完成、失败与跳过的搜索;**部分结果 ≠ 覆盖整个市场**。搜索总预算默认 **60 秒**(`search_timeout_seconds`);超时不等于确认无航班。
---
## 快速开始
需要:
- Python **3.12**(项目约束 `>=3.12,<3.13`)
- [uv](https://docs.astral.sh/uv/)
- Node.js + npm(`official_cli` 模式)
- 官方 CLI:`@fly-ai/flyai-cli@1.0.16`
```powershell
git clone https://github.com/Relyonyou/flight-deals-mcp.git
Set-Location flight-deals-mcp
uv sync
npm install --global '@fly-ai/flyai-cli@1.0.16' --registry https://registry.npmjs.org/
# 正式 Key 申请与配置见下文「如何获取正式 API Key」
flyai config set FLYAI_API_KEY 你的正式Key
# Windows 若遇执行策略问题,可用:flyai.cmd config set FLYAI_API_KEY 你的正式Key
uv run flight-deals-mcp
```
`flight-deals-mcp` 是 **stdio** Server:启动后安静等待 MCP 客户端属正常现象。不要在同一终端输入聊天文字;`Ctrl+C` 停止。
查询本机 `uv` 绝对路径(写入客户端配置时用):
```powershell
(Get-Command uv).Source
```
Windows 上建议用 **`flyai.cmd`**(PowerShell 可能拦截 `flyai.ps1`)。确认:
```powershell
where.exe node
where.exe flyai.cmd
flyai.cmd --help
```
---
## 如何获取正式 API Key
正式 Key 由 **飞猪 AI 开放平台** 发放,不由本仓库生成。官方说明见:
- 开放平台首页:<https://flyai.open.fliggy.com/>
- 快速开始(含「前往控制台获取正式 API Key」):<https://flyai.open.fliggy.com/docs/quickstart>
- 推广者入驻(申请加入 / 实名 / 协议):<https://flyai.open.fliggy.com/docs/partner>
建议步骤(以官网当前流程为准,页面文案可能调整):
1. 使用**淘宝账号**打开并登录 [飞猪 AI 开放平台](https://flyai.open.fliggy.com/)。
2. 如需推广者能力,按官网「立即申请加入」完成实名认证并签署推广协议(详见 [入驻指南](https://flyai.open.fliggy.com/docs/partner))。
3. 在平台**控制台**获取正式 **API Key**(官网快速开始写明:安装后前往控制台领取)。
4. 仅在本机配置,**不要**写入 Git、Issue、PR 或聊天记录:
```powershell
flyai config set FLYAI_API_KEY 你的正式Key
# Windows 若遇执行策略问题:
flyai.cmd config set FLYAI_API_KEY 你的正式Key
```
5. 配置后**完全重启** MCP 客户端(Codex / Claude Desktop 等),再调用本项目的搜索工具。
说明:
- 未配置 Key 时,官方 CLI 仍可能进入**体验调用**;额度与稳定性有限,正式使用请配置 Key。
- 本项目默认 `official_cli`:Key 交给官方 CLI 本地配置即可,**不必**写进 Codex `config.toml`。
- 控制台入口、领取按钮名称以飞猪官网为准;若与上文不一致,以 [快速开始](https://flyai.open.fliggy.com/docs/quickstart) 为准。
---
## 安装与数据源
### 推荐:`official_cli` + 正式 Key
先按上一节在控制台拿到 Key,再执行:
```powershell
$env:FLIGHT_PROVIDER_MODE = 'official_cli' # 默认,可省略
flyai config set FLYAI_API_KEY 你的正式Key
```
Key 由官方 CLI 本地配置管理;**不要**写进仓库、客户端明文配置或对话历史。配置后重启 MCP 客户端,并确保客户端子进程 `PATH` 能解析到 `node` 与 `flyai.cmd`。
### 无 Key 体验路径
安装官方 CLI 后,不设 Key 也可能获得有限体验调用。额度、稳定性、返回范围均可能受限,**不是**官方稳定服务承诺,仅适合本地试用。
Windows 上 CLI 有时会先输出业务成功 JSON(`status: 0`),随后 Node 退出码为 1。本项目接受该成功业务载荷,并把非零退出保留为警告;业务 `status` 非 0 仍按失败处理。
### 实验性:`direct_mcp`
`direct_mcp` 是实验性、未实测的标准 Bearer-Key 直连路径,不是当前推荐或已验收的生产路径。
```powershell
$env:FLIGHT_PROVIDER_MODE = 'direct_mcp'
$env:FLYAI_API_KEY = '你的正式Key'
$env:FLYAI_MCP_URL = 'https://flyai.open.fliggy.com/mcp'
```
无正式 Key 时该模式拒绝启动。优先使用官方 CLI + 正式 Key。不要从 CLI 提取私有签名或未公开请求头。
---
## 接入 MCP 客户端
下文用 `<MCP存放路径>`、`<uv绝对路径>` 表示本机路径,请自行替换。
### Codex(推荐关注)
Codex 使用 `[mcp_servers.<id>]`(`config.toml`)或 `codex mcp add`,**不使用** Claude Desktop 风格的 `mcpServers` JSON。
#### CLI 注册
```powershell
codex mcp add flight-deals --env FLIGHT_PROVIDER_MODE=official_cli -- '<uv绝对路径>' --directory '<MCP存放路径>' run flight-deals-mcp
```
#### `~/.codex/config.toml`(Windows 建议写法)
Codex **默认不把完整用户环境交给 stdio MCP**。在 Windows 上若只配 `FLIGHT_PROVIDER_MODE`,子进程经常找不到 `node` / `flyai.cmd`,或家庭目录/系统变量缺失,表现为「**官方数据源在 60 秒预算内超时**」(本机直接跑 CLI 往往数秒就有结果)。请显式配置 `Path`、`USERPROFILE`、`SystemRoot` 等,并将客户端 `tool_timeout_sec` 设为大于 Server 的 60 秒总预算(建议 90~120)。
```toml
[mcp_servers.flight-deals]
command = '<uv绝对路径>'
args = ["--directory", "<MCP存放路径>", "run", "flight-deals-mcp"]
cwd = "<MCP存放路径>"
startup_timeout_sec = 90
tool_timeout_sec = 120
env_vars = [
"USERPROFILE", "HOME", "APPDATA", "LOCALAPPDATA",
"TEMP", "TMP", "SystemRoot", "SYSTEMROOT", "ComSpec",
"USERNAME", "USERDOMAIN", "PATHEXT", "NUMBER_OF_PROCESSORS",
]
[mcp_servers.flight-deals.env]
FLIGHT_PROVIDER_MODE = "official_cli"
USERPROFILE = '<你的用户目录>'
HOME = '<你的用户目录>'
APPDATA = '<你的用户目录>\\AppData\\Roaming'
LOCALAPPDATA = '<你的用户目录>\\AppData\\Local'
TEMP = '<你的用户目录>\\AppData\\Local\\Temp'
TMP = '<你的用户目录>\\AppData\\Local\\Temp'
SystemRoot = 'C:\\Windows'
SYSTEMROOT = 'C:\\Windows'
ComSpec = 'C:\\Windows\\System32\\cmd.exe'
PATHEXT = '.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC'
# 必须能解析到 uv、node.exe、flyai.cmd
Path = '<uv所在目录>;<Node安装目录>;<npm全局目录>;C:\\Windows\\System32;C:\\Windows'
```
保存后**完全退出并重启 Codex**。应只发现 `find_flight_options` 与 `verify_flight_option`。
超时专项排查(含本机对照命令与决策树):
[docs/codex-timeout-troubleshooting.md](docs/codex-timeout-troubleshooting.md)
### Claude Desktop / 通用 JSON stdio
```json
{
"mcpServers": {
"flight-deals": {
"command": "<uv绝对路径>",
"args": [
"--directory",
"<MCP存放路径>",
"run",
"flight-deals-mcp"
],
"env": {
"FLIGHT_PROVIDER_MODE": "official_cli"
}
}
}
}
```
JSON 中反斜杠需写成 `\\`。Codex 不使用上述 JSON;Codex 请用上一节的 CLI 或 TOML。其他客户端的具体配置文件位置由客户端决定。
---
## 推荐使用方式
### 自然语言搜索示例
可直接复制到已接入本 MCP 的 AI 客户端:
**固定日期(建议首次先用这个,降低超时与风控概率):**
> 用 flight-deals 搜索 2026-08-12 北京到杭州,日期不要浮动,不托运行李,最长 10 小时,风险偏好 balanced。
**日期前后浮动 1 天:**
> 搜索 2026-08-12 北京到杭州,前后可浮动 1 天,不托运行李,最长 10 小时,风险偏好 balanced。
**需要托运行李(会排除不兼容方案,含隐藏城市):**
> 搜索 2026-08-12 上海到成都,不浮动日期,需要托运行李,最长 12 小时,风险偏好 conservative。
**风险偏好场景:**
| 偏好 | 适用 |
|------|------|
| `conservative` | 默认更稳妥;偏直飞/官方联程 |
| `balanced` | 可接受有限扩展(如日期浮动、自拼等,仍受规则约束) |
| `exploratory` | 才可能看到隐藏城市等高风险候选;须保留完整风险提示 |
### 购买前核验示例
选出方案后:
> 购买前用刚才的 `search_id` 和该方案的 `option_id` 调用 verify_flight_option 重新核验。
核验会重新访问数据源,**不会**把缓存旧价冒充实时价。核验后仍须自行打开最新 HTTPS 购买链接,确认:1 名成人适用、航班、日期、机场、经济舱、行李、退改规则与总价。上游无乘客人数入参,此步不可省略。
### MCP 工具参数示例
以下仅为参数对象示意,不是完整 JSON-RPC 信封;价格/ID 亦为示意。
**`find_flight_options`:**
```json
{
"origin_city": "北京",
"destination_city": "杭州",
"departure_date": "2026-08-12",
"date_flex_days": 0,
"checked_baggage": false,
"max_duration_hours": 10,
"risk_preference": "balanced"
}
```
**`verify_flight_option`:**
```json
{
"search_id": "00000000-0000-4000-8000-000000000000",
"option_id": "example-option-id"
}
```
以上两个值是示意 ID,实际调用必须使用 `find_flight_options` 返回的值。
示例不代表当前价格、库存或链接有效。
### 结果解读
- `search_id`:本次搜索快照标识,核验时必填。
- `option_id`:候选方案标识。
- `baseline`:基准报价(通常来自直飞/官方联程筛选)。
- `options`:可比较候选列表(含风险字段)。
- `coverage`:哪些策略完成 / 失败 / 跳过。
- `warnings`:超时、部分失败等提示。
**部分策略超时或失败 ≠ 整个搜索失败,更 ≠ 市场上无航班。** 隐藏城市必须保留完整风险提示,且不适用于托运行李场景。
---
## 数据与缓存
| 内容 | 位置 |
|------|------|
| 机场 / 城市静态表 | `src/flight_deals_mcp/data/airports.json` |
| 隐藏城市后续目的地表 | `src/flight_deals_mcp/data/onward_destinations.json` |
| 搜索快照 SQLite | 进程工作目录下 `.local/flight-deals.db`(默认 TTL 5 分钟) |
通过客户端 `cwd` 或 `uv --directory <MCP存放路径>` 固定工作目录后,缓存落在 `<MCP存放路径>/.local/flight-deals.db`。过期快照仅用于核验时定位旧方案并比较变化;**当前报价仍会重新查询**。
缓存含路线与报价,可能反映出行意图。**不要**提交、上传或随意分享 `.local/`。该目录已在 `.gitignore` 中忽略。
---
## 验收与项目进度
### 当前结论(2026-07-28)
| 项 | 状态 |
|----|------|
| MVP 实现(双工具 MCP) | 完成 |
| 最终审查 Important / Minor 修复 | 完成 |
| 正式 Key + `official_cli`:24/24 矩阵 baseline 搜索 | 完成 |
| 同矩阵:购买前核验 + HTTPS 短链可达 | 完成 |
| 登录后购买页人工核对 / 单成人适用性 | **未完成** |
| 日期浮动 / 自拼 / 隐藏城市等扩展策略矩阵 | **未覆盖** |
| **生产验收** | **未通过** |
明细与复现:[docs/acceptance/manual-route-matrix.md](docs/acceptance/manual-route-matrix.md)
本地批量烟雾(注意上游风控,勿把 Key 与原始结果入库):
```powershell
uv run python scripts/smoke_matrix_batch.py
$env:SMOKE_MATRIX_IDS = '1,2,3'
uv run python scripts/smoke_matrix_batch.py
```
### 本阶段相关文档
- 设计 / 计划:`docs/superpowers/specs/`、`docs/superpowers/plans/`
- 数据源决策:`docs/data-access-decision.md`
- Codex 超时排查:`docs/codex-timeout-troubleshooting.md`
- 换机交接(开发用,含本机路径):`HANDOFF.md`
---
## 常见问题
### 正式 API Key 从哪里获取?
见上文 [如何获取正式 API Key](#如何获取正式-api-key)。入口是 [飞猪 AI 开放平台](https://flyai.open.fliggy.com/),在控制台领取后执行 `flyai config set FLYAI_API_KEY ...`。本仓库不发放、不代理 Key。
### 客户端找不到 `uv`
```powershell
(Get-Command uv).Source
```
把绝对路径写入 TOML/JSON 的 `command`,保存后彻底重启客户端。
### `flyai CLI is not installed`
```powershell
npm install --global '@fly-ai/flyai-cli@1.0.16' --registry https://registry.npmjs.org/
where.exe flyai.cmd
```
客户端仍找不到时:检查 MCP 子进程 `Path` 是否包含 npm 全局目录与 Node 安装目录,然后完全重启客户端。
### `FLYAI_API_KEY is required`
当前为实验性 `direct_mcp`。改回 `official_cli` 并用 `flyai.cmd config set` 配置 Key。
### Codex / 客户端报「60 秒预算超时」「未获得航班结果」
优先阅读:[docs/codex-timeout-troubleshooting.md](docs/codex-timeout-troubleshooting.md)。
要点:
1. 文案中的 60 秒多为 **Server 搜索总预算**,不是「确认无航班」。
2. 先在本机跑通 `flyai.cmd search-flight`;若本机秒级成功、仅客户端超时,重点查 MCP 环境变量与 Windows 沙箱出网。
3. 首次搜索建议 `date_flex_days=0`;通了再加浮动。
### 没有结果或只有部分策略
查看 `coverage` 与 `warnings`。体验额度、风控、上游空结果均可能发生。可缩小浮动、稍后重试,或为 CLI 配置正式 Key。
### 修改配置后仍用旧环境
MCP 客户端通常只在启动 Server 时读取 `env`。改 TOML/JSON 后须**完全退出客户端再打开**,不要只关聊天窗口。
### SQLite 被占用或缓存异常
停止所有本项目 MCP 进程后,备份并删除 `<MCP存放路径>/.local/flight-deals.db`;下次启动会重建。
---
## 测试与构建
```powershell
uv run pytest -v
uv run ruff check .
uv build
```
仅跑产品验收层断言:
```powershell
uv run pytest tests/test_acceptance.py -v
```
自动化测试通过 **不等于** 24 条路线已完成人工购买页核对,也不等于生产验收通过。
---
## 安全与隐私
- Key 只通过环境变量或官方 CLI 本地配置注入;不写入源码、测试、日志或 MCP 业务输出。
- 含真实 Key 的客户端配置视为秘密,勿提交 Git、勿发 Issue/PR。
- 不收集乘机人身份,不接收支付信息,无下单能力。
- 购买链接经 HTTPS 校验,但 HTTPS ≠ 页面一定可靠;仍需核对域名与页面内容。
- 不绕过验证码、登录、限流或平台访问控制。
- 默认仅本地 `stdio`,不监听公网端口。
- 分享日志或缓存前,检查是否暴露行程意图或凭据。
安全问题请优先通过 GitHub Security Advisory 私密报告(若仓库已启用)。
---
## 贡献与许可证
欢迎通过 Issue 和 Pull Request 改进项目。提交前请阅读[贡献指南](CONTRIBUTING.md);
安全问题请按[安全政策](SECURITY.md)私下报告,并遵守
[社区行为准则](CODE_OF_CONDUCT.md)。
本项目采用 [MIT License](LICENSE)。
贡献前请勿提交:`.local/`、真实 Key、未脱敏行程日志、个人绝对路径配置。Issue / PR 中同样不要粘贴密钥。
## 文档索引
| 文档 | 说明 |
|------|------|
| [docs/acceptance/manual-route-matrix.md](docs/acceptance/manual-route-matrix.md) | 24 行人工核对矩阵与执行状态 |
| [docs/codex-timeout-troubleshooting.md](docs/codex-timeout-troubleshooting.md) | Codex 60 秒超时排查 |
| [docs/data-access-decision.md](docs/data-access-decision.md) | 数据访问路径决策 |
| [docs/superpowers/specs/2026-07-24-domestic-flight-deals-mcp-design.md](docs/superpowers/specs/2026-07-24-domestic-flight-deals-mcp-design.md) | 产品设计规格 |
| [HANDOFF.md](HANDOFF.md) | 换机开发交接 |
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues