Skip to main content
Glama
Relyonyou

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) | 换机开发交接 |