🚀 从零打造AI航班助手:OpenClaw + 飞常准MCP全攻略

摘要:本文记录了我从0到1开发一个完整的OpenClaw Skill的全过程,涵盖MCP协议实战、YAML front matter踩坑、API密钥安全管理等核心技术点。如果你也想开发AI插件,这篇避坑指南值得收藏!


📋 目录

  1. 项目背景:为什么要做这个Skill?
  2. 技术选型:OpenClaw + MCP是什么?
  3. Step 1:项目架构设计
  4. Step 2:SKILL.md的YAML坑(重点!)
  5. Step 3:MCP服务器生命周期管理
  6. Step 4:API密钥安全最佳实践
  7. Step 5:数据解析与格式化输出
  8. 完整代码与发布
  9. 总结与展望

一、项目背景:为什么要做这个Skill?

最近AI应用爆发,但大多数AI工具都是"孤岛"——无法直接获取实时数据。作为经常出差的开发者,我需要一个能查航班、看天气、追踪飞机的AI助手。

核心需求

  • 🔍 实时航班查询(按航线/航班号)
  • 😊 航班舒适度评估(准点率、机型评分)
  • 🌤️ 机场天气预报
  • 📍 飞机实时位置追踪

技术路线:OpenClaw(AI技能平台)+ 飞常准MCP(航班数据源)


二、技术选型:OpenClaw + MCP是什么?

2.1 OpenClaw

OpenClaw是AI技能(Skill)托管平台,类似"AI应用商店"。开发者发布Skill,用户通过自然语言调用。

2.2 MCP(Model Context Protocol)

MCP是Anthropic推出的开放协议,让AI模型能安全地访问外部数据源。简单说:AI的"USB接口"

┌─────────────┐     MCP协议      ┌─────────────┐
│   AI助手    │ ◄──────────────► │  飞常准API  │
│  (Claude等) │   stdio/SSE     │  航班数据   │
└─────────────┘                  └─────────────┘

为什么选择MCP?

  • ✅ 标准化接口,无需重复开发API适配层
  • ✅ 支持stdio/SSE多种传输模式
  • ✅ 内置安全机制(权限控制、密钥隔离)

三、Step 1:项目架构设计

3.1 目录结构

variflight-aviation-skill/
├── SKILL.md                    # 技能定义文件(元数据+文档)
├── package.json                # npm配置
├── config.json                 # 默认配置(无敏感信息)
├── src/
│   ├── lib/
│   │   ├── variflight-client.js    # MCP客户端封装
│   │   ├── mcp-server-manager.js   # 服务器生命周期管理
│   │   └── config-loader.js        # 配置加载器
│   └── commands/
│       ├── search.js               # 航线搜索
│       ├── info.js                 # 航班详情
│       ├── comfort.js              # 舒适度指数
│       ├── weather.js              # 机场天气
│       ├── transfer.js             # 中转方案
│       └── track.js                # 飞机追踪
└── bin/
    └── variflight                  # CLI入口

3.2 核心设计:按需启动架构

关键决策:MCP服务器不作为守护进程,而是每次调用时启动、用完即走。

// 伪代码
async function searchFlights(dep, arr, date) {
  const client = new VariflightClient();
  try {
    await client.connect();      // 1. 启动MCP服务器
    const result = await client.callTool('searchFlightsByDepArr', {dep, arr, date});
    return result;
  } finally {
    await client.disconnect();   // 3. 关闭MCP服务器(关键!)
  }
}

为什么不用守护进程?

  • ❌ MCP stdio模式需要保持stdin/stdout连接,守护进程会退出(exit code 0/78)
  • ✅ 按需启动避免资源浪费
  • ✅ 错误隔离,单次失败不影响其他调用

四、Step 2:SKILL.md的YAML坑(重点!)

这是最容易踩坑的地方。SKILL.md采用YAML front matter + Markdown混合格式。

4.1 基础结构

---
name: variflight-aviation
description: 飞常准航班信息查询
version: 1.0.0
metadata:
  openclaw:
    emoji: "✈️"
    category: "travel"
    requirements: |     # ← 多行字符串开始
      ## 系统要求
      - Node.js >= 18.0.0
---
## 功能概述      # ← Markdown正文开始
...

4.2 致命坑点:requirements中的代码块

错误写法(会导致Markdown解析错乱):

    requirements: |
      ## 配置方法
      
      ```bash          # ← 嵌套代码块!
      export KEY="xxx"
      ```
      
      ```json          # ← 又一个嵌套代码块!
      {"key": "xxx"}
      ```

错误现象

  • 功能概述的一部分在代码框内,一部分在外面
  • 命令显示错乱

正确写法(三种方案):

方案1:简化requirements,代码示例放正文

    requirements: |
      系统要求:Node.js >= 18.0.0
      配置:设置X_VARIFLIGHT_KEY环境变量
---
## 配置说明

```bash
export X_VARIFLIGHT_KEY="your_key"

**方案2:使用4空格缩进代替反引号**

```yaml
    requirements: |
      设置环境变量:
      
          export X_VARIFLIGHT_KEY="your_key"

方案3:使用>-折叠修饰符

    requirements: >-
      系统要求:Node.js >= 18.0.0

4.3 环境变量命名陷阱

飞常准MCP不使用常见的VARIFLIGHT_API_KEY,而是 X_VARIFLIGHT_KEY

// 错误 ❌
env: { VARIFLIGHT_API_KEY: "xxx" }  // 返回401 Unauthorized

// 正确 ✅
env: { X_VARIFLIGHT_KEY: "xxx" }    // 官方要求

兼容写法(支持两种变量名):

const apiKey = process.env.X_VARIFLIGHT_KEY || process.env.VARIFLIGHT_API_KEY;

五、Step 3:MCP服务器生命周期管理

5.1 核心类:VariflightClient

const { Client } = require('@modelcontextprotocol/sdk/client/index.js');
const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio.js');

class VariflightClient {
  async connect() {
    // 关键:使用绝对路径,避免nvm路径问题
    const transport = new StdioClientTransport({
      command: '/Users/lixiao/.nvm/versions/node/v22.14.0/bin/npx',
      args: ['-y', '@variflight-ai/variflight-mcp'],
      env: {
        X_VARIFLIGHT_KEY: this.apiKey,
        PATH: '/Users/lixiao/.nvm/versions/node/v22.14.0/bin:/usr/local/bin:/usr/bin:/bin'
      }
    });

    this.client = new Client({ name: 'variflight-skill', version: '1.0.0' });
    await this.client.connect(transport);
  }

  async callTool(name, args) {
    const result = await this.client.callTool({ name, arguments: args });
    
    // 解析MCP返回的content数组
    if (result?.content?.[0]?.type === 'text') {
      try {
        return JSON.parse(result.content[0].text);  // 尝试解析JSON
      } catch (e) {
        return result.content[0].text;              // 返回原始文本
      }
    }
    return result;
  }
}

5.2 为什么不能用launchd守护进程?

我最初尝试用macOS launchd管理MCP服务器:

launchctl list | grep variflight
-    0    com.variflight.mcp    # ← exit code 0表示正常退出

失败原因

  • MCP stdio模式需要与客户端保持stdin/stdout连接
  • 没有客户端连接时,服务器自动退出(这是设计特性,不是bug)
  • 守护进程模式适合SSE(HTTP)传输,不适合stdio

六、Step 4:API密钥安全最佳实践

6.1 绝不提交密钥到Git

# .gitignore
config.local.json      # 本地配置文件(含密钥)
.env
.env.local
*.key
*.pem

6.2 配置优先级(从高到低)

// config-loader.js
function loadConfig() {
  // 1. 环境变量(最高优先级,生产环境用)
  if (process.env.X_VARIFLIGHT_KEY) {
    return { apiKey: process.env.X_VARIFLIGHT_KEY };
  }
  
  // 2. 本地配置文件(开发环境用)
  if (fs.existsSync('config.local.json')) {
    return JSON.parse(fs.readFileSync('config.local.json'));
  }
  
  // 3. 默认配置(无密钥,仅模板)
  return JSON.parse(fs.readFileSync('config.json'));
}

6.3 SKILL.md中的安全声明

metadata:
  openclaw:
    env:
      - X_VARIFLIGHT_KEY    # 只声明变量名,不提供值!
    requirements: |
      ## 配置方法
      1. 环境变量:export X_VARIFLIGHT_KEY="your_key"
      2. 本地文件:创建config.local.json(已加入.gitignore)
      
      ⚠️ 注意:请勿将API Key提交到Git!

七、Step 5:数据解析与格式化输出

7.1 飞常准API返回格式

{
  "code": 200,
  "message": "Success",
  "data": [
    {
      "FlightNo": "HU7601",
      "FlightCompany": "海南航空股份有限公司",
      "FlightDeptimePlanDate": "2026-02-20 07:25:00",
      "FlightState": "计划",
      "OntimeRate": "93.33%",
      "ftype": "78A"
    }
  ]
}

注意:字段名是首字母大写的驼峰命名!

7.2 智能字段提取

// 处理各种可能的字段名
const flightNo = flight.FlightNo || flight.flightNo || flight.flightNum || '未知';
const airline = flight.FlightCompany || flight.airline || '未知航司';
const depTime = (flight.FlightDeptimePlanDate || '').split(' ')[1]?.substring(0, 5) || '待定';

7.3 终端输出格式化

console.log(`${index + 1}. ${flightNo} | ${airline}`);
console.log(`   🛫 ${depTime} ${depAirport}${depTerminal ? ' T' + depTerminal : ''}`);
console.log(`   🛬 ${arrTime} ${arrAirport}${arrTerminal ? ' T' + arrTerminal : ''}`);
console.log(`   ✈️  ${aircraft} | 状态: ${status}${ontimeRate ? ' | 准点率: ' + ontimeRate : ''}`);

输出效果

1. HU7601 | 海南航空股份有限公司
   🛫 07:25 PEK T2
   🛬 09:35 SHA T2
   ✈️  78A | 状态: 计划 | 准点率: 93.33%
   🎫 值机柜台: F,G
   📏 距离: 1178公里

八、完整代码与发布

8.1 GitHub仓库结构

https://github.com/your-username/variflight-aviation-skill
├── SKILL.md
├── README.md
├── package.json
├── .gitignore
└── src/
    ├── lib/
    │   ├── variflight-client.js
    │   ├── mcp-server-manager.js
    │   └── config-loader.js
    └── commands/
        ├── search.js
        ├── info.js
        ├── comfort.js
        ├── weather.js
        ├── transfer.js
        └── track.js

8.2 发布到ClawHub

# 安装CLI
npm install -g clawhub

# 登录(需要GitHub账号,注册满一周)
clawhub login

# 发布
cd variflight-aviation-skill
clawhub publish . \
  --slug variflight-aviation \
  --name "Variflight Aviation" \
  --version 1.0.0

# 验证
clawhub install variflight-aviation

8.3 用户使用

安装后,用户可以在OpenClaw中使用:

@variflight-aviation search PEK SHA 2026-02-20
@variflight-aviation info HU7601 2026-02-20
@variflight-aviation weather PEK

九、总结与展望

核心收获

技术点 关键决策
MCP架构 按需启动,避免守护进程
YAML格式 requirements简化,代码示例放正文
API密钥 X_VARIFLIGHT_KEY环境变量
数据解析 首字母大写字段名兼容
错误处理 try-finally确保资源释放

十、安装即可用

  • clawhub skill名:variflight-aviation

  • openclaw安装

    clawhub install variflight-aviation
    
  • 现在可以会话

    查询3月13日下午上海虹桥到昆明的直达航班。

  • 回复

    ✈️ 2026年3月13日 上海虹桥 → 昆明长水 下午航班信息

    以下是12:00以后出发的直飞航班(不含经停):

    航班号 航司 起飞 到达 航站楼 机型 准点率 飞行时长
    MU5802 东航 12:10 15:45 T2 B737 96.7% ~3h35m
    8L9888 祥鹏航空 13:25 16:55 T2 A330 100% ~3h30m
    MU5806 东航 15:05 18:50 T2 B737 96.7% ~3h45m
    HO1099 吉祥航空 15:55 19:35 T2 A320 93.3% ~2h53m
    MU5808 东航 16:20 19:50 T2 B737 100% ~3h30m
    MU5810 东航 17:20 21:10 T2 B737 93.3% ~3h50m
    MU5804 东航 20:05 23:55 T2 B737 96.7% ~3h50m
    MU5812 东航 21:15 00:15+1 T2 B737 100% ~3h45m

    ⚠️ 注意

    • 9C8829 (春秋 12:25出发) - 经停常德,不是直飞
    • MU5323/9 (09:25出发) - 经停长沙,飞行时间较长

    📋 推荐下午航班

    1. MU5806 (15:05出发) - 东航,准点率96.7%
    2. HO1099 (15:55出发) - 吉祥航空,用时较短
    3. MU5808 (16:20出发) - 东航,准点率100%

    📞 联系方式

    • 东航:95530
    • 吉祥航空:95520
    • 祥鹏航空:95326
    • 上海虹桥:021-22344553
    • 昆明长水:0871-67092570

    航线距离:约2042公里

    需要查询具体某个航班的票价或舒适度评估吗?

踩坑清单

  • ❌ launchd守护进程失败 → ✅ 按需启动架构
  • ❌ VARIFLIGHT_API_KEY 401错误 → ✅ X_VARIFLIGHT_KEY
  • ❌ YAML嵌套代码块解析错乱 → ✅ 简化requirements
  • ❌ npx路径找不到 → ✅ 使用绝对路径

未来优化

  1. 缓存机制:航班数据缓存5分钟,减少API调用
  2. 自然语言:支持"明天北京飞上海"而非强制YYYY-MM-DD
  3. 订阅推送:航班状态变更主动通知

📚 参考资源


作者简介:全栈开发者,专注AI应用与DevOps。热爱用技术解决实际问题,欢迎Star我的GitHub项目,一起交流!

GitHub: https://github.com/Lancenas/variflight-aviation-skill
CSDN: https://blog.csdn.net/qq_38637122

如果本文对你有帮助,请点赞👍、收藏⭐、评论💬三连支持!有问题欢迎在评论区讨论。


#OpenClaw #MCP #AI开发 #航班查询 #飞常准 #YAML #Node.js #技能开发

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐