Skip to main content
Glama
mr-cn
by mr-cn
README.md
# FlyAI MCP Gateway

运行在 Cloudflare Workers 的 ChatGPT ↔ 飞猪 FlyAI 中间层。用户在 OAuth 授权页输入自己的 FlyAI API Key,无需注册 Gateway 账号。ChatGPT 仅持有 Gateway OAuth Token,服务端使用对应用户的 Key 查询飞猪。

```text
ChatGPT → OAuth + MCP → Cloudflare Worker → FlyAI /mcp
                           ↓
                     Workers KV
                 OAuth 授权与加密凭证
```

## 已实现

- Authorization Code + PKCE S256、动态客户端注册(DCR)、OAuth/资源发现、`resource` audience 绑定。
- 1 小时 access token、30 天 refresh token、刷新轮换、降权、撤销。
- 带 CSRF、HttpOnly/SameSite Cookie、来源校验、明确同意与取消按钮的中文授权页。
- 验证 Key 后才授权;用 HMAC 指纹作为用户标识,凭证通过 Cloudflare OAuth 库加密保存在授权 props 中。
- 请求级用户隔离、请求/响应体大小限制、45 秒上游超时、JSON/SSE 解析、敏感错误脱敏。
- 8 个明确限定的只读查询工具,不提供下单、支付或任意上游调用。

| MCP 工具 | FlyAI 工具 | 功能 |
| --- | --- | --- |
| `search_flights` | `search_flight` | 国内/国际航班 |
| `search_trains` | `search_domestic_train` | 国内火车票 |
| `search_hotels` | `search_hotels` | 酒店 |
| `search_poi` | `search_poi` | 景点 |
| `keyword_search` | `fliggy_fast_search` | 旅行商品关键词搜索 |
| `ai_search` | `fliggy_ai_search` | 自然语言旅行搜索 |
| `search_marriott_hotels` | `search_marriott_hotels` | 万豪酒店 |
| `search_marriott_packages` | `search_marriott_packages` | 万豪套餐 |

工具参数来自官方 `@fly-ai/flyai-cli@1.0.16`,如航班使用 `origin`、`destination`、`depDate`,酒店使用 `destName`、`checkInDate`、`checkOutDate`。酒店、航班、火车查询沿用官方 CLI 的 10 条候选限制,不代表完整库存。

## 本地运行

需要 Node.js 22 或更新版本。

```powershell
npm ci
npm run setup:local
npm run check
npm test
npm run dev
```

`setup:local` 生成已被 Git 忽略的 `.dev.vars`,包含两个独立随机服务密钥;已有文件不会被覆盖。打开 `http://localhost:8787`。本地 KV 与线上 KV 独立。

`npm test` 会先构建最新 Worker,再执行单元测试和真实 workerd/Miniflare 运行时集成测试,飞猪响应由测试桩提供,不使用真实 Key、不消耗额度。

```powershell
# 只验证发现信息,不创建授权
npm run test:oauth -- http://localhost:8787
# 完整浏览器授权测试:在打印的网页地址中录入 Key
npm run test:oauth -- http://localhost:8787 --authorize
```

完整测试会使用一次“杭州·西湖”景点查询验证 Key,可能消耗额度。测试结束撤销测试授权;Token 不输出、不写入文件。浏览器应与测试终端运行在同一台机器,回调绑定本机回环地址。

## 部署到 Cloudflare

1. 登录并创建独立 KV:

   ```powershell
   node scripts/wrangler.mjs login
   node scripts/wrangler.mjs kv namespace create OAUTH_KV
   ```

2. 修改 `wrangler.jsonc`:把 KV 返回的 namespace ID 填入 `kv_namespaces[0].id`;把 `MCP_RESOURCE_URL` 改成实际地址,如 `https://feizhu-mcp-gateway.<你的子域>.workers.dev/mcp`。如果使用自定义域名,也要以该域名的 `/mcp` 为唯一 canonical resource,并在 Cloudflare 配置对应域名。

3. 设置两个独立随机 secret(至少 32 字符),不要使用示例占位符,也不要把 secret 写入 `wrangler.jsonc`。可以使用密码管理器生成:

   ```powershell
   node scripts/wrangler.mjs secret put AUTH_COOKIE_SECRET
   node scripts/wrangler.mjs secret put USER_ID_SECRET
   ```

4. 构建和部署:

   ```powershell
   npm run check
   npm test
   npm run deploy
   npm run test:oauth -- https://feizhu-mcp-gateway.<你的子域>.workers.dev
   ```

`AUTH_COOKIE_SECRET` 用于授权表单防伪,`USER_ID_SECRET` 用于 Key 的不可逆 HMAC 指纹。FlyAI API Key 由用户在授权页面提交,不作为全局 Worker secret 配置。变更域名会改变 OAuth audience,旧客户端需重新连接;更换指纹密钥会改变新授权的用户标识。

### 飞猪签名兼容配置

固定上游是 `https://flyai.open.fliggy.com/mcp`。默认使用用户自己的 Bearer API Key;本项目没有复制官方 CLI 内置的共享 API Key 或签名密钥,也不依赖本机运行 CLI。

官方 CLI 还支持 v7 HMAC 请求签名和 AES-256-GCM `x-ff-ctx`。如飞猪为你的接入渠道要求签名,需要向飞猪获取适用于服务端的签名 profile,然后设置:

```powershell
node scripts/wrangler.mjs secret put FLYAI_SIGN_SECRET
node scripts/wrangler.mjs secret put FLYAI_TTID
```

签名算法已按 CLI 格式实现,并有签名/解密测试;环境信息如实标记为 Cloudflare Workers,未伪装个人设备。签名 secret 不能随意自生成,必须与飞猪服务端约定一致。

**目前尚未使用真实 FlyAI Key 验证成功查询。** 已验证无效 Key 会被官方入口拒绝。仅凭此结果无法确定有效 Key 是否还需要渠道签名或服务端风控许可;部署前请运行 `test:oauth -- ... --authorize` 验证。若验证失败,检查 Key、额度以及飞猪提供的签名要求。

## 在 ChatGPT 中连接

在支持自定义 MCP 的 ChatGPT 账户中打开开发者模式,创建远程 MCP 连接:

- MCP URL:`https://你的域名/mcp`
- Authentication:OAuth
- 使用动态客户端注册(DCR),不手填 Client ID/Secret。

连接时,确认授权页展示的客户端及回调地址,输入自己的 Key,勾选同意后连接。服务依据注册元数据严格匹配回调 URI,不能把任意网页地址替换为回调。OpenAI 当前回调格式可能因连接模式不同而变化,以 ChatGPT 管理页为准。

连接后可尝试:“查询 2026-10-01 杭州到北京的机票,按价格升序排列。”未来使用时请换成实际出行日期。

## 数据、安全和运行边界

- Key 只发给本 Gateway 和固定 FlyAI 上游。不会出现在工具参数、OAuth 元数据或 Token 响应中。服务运营者在处理请求时可以访问 Key。
- KV 保存 OAuth 客户端、授权和令牌状态,Key 保存在库管理的加密 props 中,`user_id` 为带服务端 secret 的 HMAC 指纹。授权 metadata 不保存 Key。
- `POST /oauth/token` 同时提供 RFC 7009 撤销:提交 `client_id`、`token`、可选 `token_type_hint`,不带 `grant_type`。撤销 refresh token 会撤销整份授权。ChatGPT 断开连接是否请求撤销由客户端决定;在飞猪控制台撤销 Key 能阻止后续飞猪查询。
- 测试覆盖顺序授权码重放、资源绑定、降权和用户隔离。OAuth 状态由 Workers KV 保存,KV 的全球最终一致性不提供跨地域并发消费的强一致保证;高并发公网多租户服务需进一步审计这一边界,不能将当前顺序重放测试视为并发安全证明。
- 默认关闭 Workers Observability,代码不记录 Key、Token 或上游原始错误。不要在 Cloudflare 自定义日志或代理层采集授权表单和 Authorization 请求头。
- 未自动重试上游请求;验证 Key 与工具查询都可能消耗飞猪额度。当前没有应用层用户限流;公开给大量用户前应为注册、授权及 MCP 路由配置 Cloudflare 限流,并核对 KV/Workers 用量。
- 本实现为查询网关,不保证飞猪完整库存、价格或库存时效;最终信息以飞猪为准。

## 验证状态与依据

已完成类型检查、Worker 打包、单元测试及 Workers 运行时 OAuth/MCP 集成测试。尚未创建 Cloudflare 线上资源、部署线上地址或完成真实 ChatGPT ↔ FlyAI 联调。

- [OpenAI MCP/OAuth 官方要求](https://developers.openai.com/plugins/build/auth)
- [Cloudflare OAuth Provider](https://github.com/cloudflare/workers-oauth-provider)
- [FlyAI 官方平台](https://flyai.open.fliggy.com/)
- [FlyAI 官方技能仓库](https://github.com/alibaba-flyai/flyai-skill)
- [FlyAI CLI 1.0.16](https://www.npmjs.com/package/@fly-ai/flyai-cli/v/1.0.16)