@flightmaster/dast-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -0
- package/bin/dast +3 -0
- package/dist/client/gateway.d.ts +16 -0
- package/dist/client/gateway.js +83 -0
- package/dist/commands/call.d.ts +2 -0
- package/dist/commands/call.js +58 -0
- package/dist/commands/info.d.ts +2 -0
- package/dist/commands/info.js +49 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.js +36 -0
- package/dist/commands/schema.d.ts +2 -0
- package/dist/commands/schema.js +23 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +46 -0
- package/dist/scripts/auto-install-skill.d.ts +1 -0
- package/dist/scripts/auto-install-skill.js +58 -0
- package/dist/types/index.d.ts +37 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/errors.d.ts +37 -0
- package/dist/utils/errors.js +169 -0
- package/dist/utils/formatter.d.ts +2 -0
- package/dist/utils/formatter.js +20 -0
- package/package.json +60 -0
- package/skills/SKILL.md +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# @flightmaster/dast-cli
|
|
2
|
+
|
|
3
|
+
DAST MCP 命令行客户端。固定对接单一 MCP Server(`dast_mcp`),将工具发现与调用封装为简洁命令,方便 AI Agent 集成与本地调试。
|
|
4
|
+
|
|
5
|
+
默认上游:`https://fly.huoli.com/mcp/dast_mcp`
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @flightmaster/dast-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
本地开发:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install
|
|
17
|
+
npm run build
|
|
18
|
+
npm link
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 配置
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
export DAST_API_KEY=sk_phone_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`dast-cli` 使用 MCP 网关的 **PhoneKey** 无状态鉴权(与 huoli-cli 等其它凭证体系不同):
|
|
28
|
+
|
|
29
|
+
- 格式:`sk_phone_` + 32 位小写字母或数字
|
|
30
|
+
- 每个请求自动带 `Authorization: Bearer <PhoneKey>`(无需、也不会依赖 `MCP-Session-Id`)
|
|
31
|
+
- Key 可从 [https://dast.133.cn](https://dast.133.cn) 获取;创建时完整串只显示一次
|
|
32
|
+
|
|
33
|
+
| 变量 | 说明 | 默认值 |
|
|
34
|
+
| :--- | :--- | :--- |
|
|
35
|
+
| `DAST_API_KEY` | **必填**。PhoneKey(`sk_phone_…`) | - |
|
|
36
|
+
| `DAST_MCP_URL` | 自定义 MCP 服务地址 | `https://fly.huoli.com/mcp/dast_mcp` |
|
|
37
|
+
| `DAST_TIMEOUT` | 全局请求超时(毫秒) | `20000` |
|
|
38
|
+
|
|
39
|
+
### 请求超时
|
|
40
|
+
|
|
41
|
+
超时指 CLI 等待 MCP 网关 HTTP 响应的最长时间(毫秒),适用于 `list` / `schema` / `info` / `call`。
|
|
42
|
+
|
|
43
|
+
| 方式 | 作用范围 | 示例 |
|
|
44
|
+
| :--- | :--- | :--- |
|
|
45
|
+
| 默认 | 未配置时 | `20000` |
|
|
46
|
+
| `DAST_TIMEOUT` | 当前 shell / 进程环境 | `export DAST_TIMEOUT=60000` |
|
|
47
|
+
| `-t` / `--timeout` | **仅本次命令** | `dast call <tool> -a '...' -t 60000` |
|
|
48
|
+
|
|
49
|
+
优先级:命令行 `-t` > 环境变量 `DAST_TIMEOUT` > 默认 `20000`。个别慢工具建议用 `-t` 临时加长,不必改全局环境变量。
|
|
50
|
+
|
|
51
|
+
## 快速入门
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 列出全部工具
|
|
55
|
+
dast list
|
|
56
|
+
|
|
57
|
+
# 导出 Schema(供 Agent 初始化)
|
|
58
|
+
dast schema
|
|
59
|
+
|
|
60
|
+
# 查看某个工具的参数说明
|
|
61
|
+
dast info dast_flight_dynamic
|
|
62
|
+
|
|
63
|
+
# 调用工具
|
|
64
|
+
dast call dast_flight_dynamic -a '{"fnum":"CA1831","date":"2026-09-09"}'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 命令速查
|
|
68
|
+
|
|
69
|
+
| 命令 | 说明 |
|
|
70
|
+
| :--- | :--- |
|
|
71
|
+
| `dast list` | 列出 dast_mcp 下全部工具 |
|
|
72
|
+
| `dast schema` | 导出工具 Schema(JSON) |
|
|
73
|
+
| `dast info <tool>` | 查看工具描述与参数 |
|
|
74
|
+
| `dast call <tool> -a '<json>'` | 调用工具;可用 `-t` / `--timeout` 设置超时 |
|
|
75
|
+
|
|
76
|
+
输出格式:`list` / `call` 支持 `-o json` 或 `-o table`(`call` 默认 json)。
|
|
77
|
+
|
|
78
|
+
## 退出码
|
|
79
|
+
|
|
80
|
+
| Exit Code | 名称 | 说明 |
|
|
81
|
+
| :--- | :--- | :--- |
|
|
82
|
+
| `0` | SUCCESS | 成功 |
|
|
83
|
+
| `101` | GATEWAY_ERROR | 网关/网络错误 |
|
|
84
|
+
| `102` | NOT_FOUND | 工具不存在 |
|
|
85
|
+
| `103` | INVALID_ARGS | 参数非法(含 JSON 解析失败) |
|
|
86
|
+
| `104` | UNAUTHORIZED | PhoneKey 认证失败(含网关 JSON-RPC `-32003` AUTH_FAILED) |
|
|
87
|
+
| `105` | TIMEOUT | 超时 |
|
|
88
|
+
| `106` | INTERNAL_ERROR | CLI 内部错误 |
|
|
89
|
+
|
|
90
|
+
## Agent 集成
|
|
91
|
+
|
|
92
|
+
1. 启动时执行 `dast schema`,将工具定义注入模型。
|
|
93
|
+
2. 按用户意图选择 tool,用 `dast info <tool>` 核对参数(或直接读 schema)。
|
|
94
|
+
3. 执行 `dast call <tool> -a '<json>'`。
|
|
95
|
+
4. 按 Exit Code 决定重试或修正参数。
|
|
96
|
+
|
|
97
|
+
安装包时会通过 `postinstall` 尝试把 `skills/SKILL.md` 复制到 Cursor / Claude 等 Agent 的 skills 目录。
|
|
98
|
+
|
|
99
|
+
## 与 aviation-dast-mcp 的关系
|
|
100
|
+
|
|
101
|
+
| 包 | 形态 | 场景 |
|
|
102
|
+
| :--- | :--- | :--- |
|
|
103
|
+
| `@flightmaster/aviation-dast-mcp` | stdio MCP bridge | Cursor / Claude 原生 MCP |
|
|
104
|
+
| `@flightmaster/dast-cli` | Shell CLI | Agent Skill、脚本、本地调试 |
|
|
105
|
+
|
|
106
|
+
二者共用同一上游 `dast_mcp`。
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
ISC
|
package/bin/dast
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { JsonRpcResponse, ToolsListResult, ToolCallResult } from '../types';
|
|
2
|
+
export declare class GatewayClient {
|
|
3
|
+
private client;
|
|
4
|
+
private apiKey;
|
|
5
|
+
private pendingEnvTimeout;
|
|
6
|
+
constructor();
|
|
7
|
+
setTimeout(timeout: number): void;
|
|
8
|
+
private applyEnvTimeoutIfNeeded;
|
|
9
|
+
private ensureApiKey;
|
|
10
|
+
private rpc;
|
|
11
|
+
/** 列出固定 dast_mcp 服务下的全部工具 */
|
|
12
|
+
getTools(): Promise<JsonRpcResponse<ToolsListResult>>;
|
|
13
|
+
/** 调用指定工具;若 MCP 返回 isError,抛出 CliError */
|
|
14
|
+
callTool(tool: string, args: Record<string, unknown>): Promise<JsonRpcResponse<ToolCallResult>>;
|
|
15
|
+
}
|
|
16
|
+
export declare const gatewayClient: GatewayClient;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.gatewayClient = exports.GatewayClient = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const errors_1 = require("../utils/errors");
|
|
9
|
+
const DEFAULT_MCP_URL = 'https://fly.huoli.com/mcp/dast_mcp';
|
|
10
|
+
const DEFAULT_TIMEOUT_MS = 20000;
|
|
11
|
+
class GatewayClient {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.apiKey = (0, errors_1.normalizePhoneKey)(process.env.DAST_API_KEY);
|
|
14
|
+
this.pendingEnvTimeout = process.env.DAST_TIMEOUT;
|
|
15
|
+
this.client = axios_1.default.create({
|
|
16
|
+
baseURL: process.env.DAST_MCP_URL || DEFAULT_MCP_URL,
|
|
17
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
18
|
+
headers: {
|
|
19
|
+
'Content-Type': 'application/json',
|
|
20
|
+
Accept: 'application/json, text/event-stream',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
setTimeout(timeout) {
|
|
25
|
+
this.pendingEnvTimeout = undefined;
|
|
26
|
+
this.client.defaults.timeout = timeout;
|
|
27
|
+
}
|
|
28
|
+
applyEnvTimeoutIfNeeded() {
|
|
29
|
+
if (this.pendingEnvTimeout === undefined) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const raw = this.pendingEnvTimeout;
|
|
33
|
+
this.pendingEnvTimeout = undefined;
|
|
34
|
+
this.client.defaults.timeout = (0, errors_1.parseTimeoutMs)(raw, 'DAST_TIMEOUT');
|
|
35
|
+
}
|
|
36
|
+
ensureApiKey() {
|
|
37
|
+
if (!this.apiKey) {
|
|
38
|
+
throw new errors_1.CliError('未配置 DAST_API_KEY 环境变量(PhoneKey,格式 sk_phone_ + 32 位小写字母或数字)', errors_1.ExitCodes.UNAUTHORIZED);
|
|
39
|
+
}
|
|
40
|
+
(0, errors_1.assertPhoneKeyFormat)(this.apiKey);
|
|
41
|
+
}
|
|
42
|
+
async rpc(method, params = {}) {
|
|
43
|
+
this.ensureApiKey();
|
|
44
|
+
this.applyEnvTimeoutIfNeeded();
|
|
45
|
+
const { data: response } = await this.client.post('', {
|
|
46
|
+
jsonrpc: '2.0',
|
|
47
|
+
id: Date.now(),
|
|
48
|
+
method,
|
|
49
|
+
params,
|
|
50
|
+
}, {
|
|
51
|
+
headers: {
|
|
52
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
if (response?.error) {
|
|
56
|
+
const message = response.error.message || `JSON-RPC error (${response.error.code})`;
|
|
57
|
+
throw (0, errors_1.mapJsonRpcError)(response.error.code, message);
|
|
58
|
+
}
|
|
59
|
+
return response;
|
|
60
|
+
}
|
|
61
|
+
/** 列出固定 dast_mcp 服务下的全部工具 */
|
|
62
|
+
async getTools() {
|
|
63
|
+
return this.rpc('tools/list', {});
|
|
64
|
+
}
|
|
65
|
+
/** 调用指定工具;若 MCP 返回 isError,抛出 CliError */
|
|
66
|
+
async callTool(tool, args) {
|
|
67
|
+
const response = await this.rpc('tools/call', {
|
|
68
|
+
name: tool,
|
|
69
|
+
arguments: args,
|
|
70
|
+
});
|
|
71
|
+
const result = response.result;
|
|
72
|
+
if (result?.isError) {
|
|
73
|
+
const text = result.content
|
|
74
|
+
?.map((item) => (typeof item.text === 'string' ? item.text : ''))
|
|
75
|
+
.filter(Boolean)
|
|
76
|
+
.join('\n');
|
|
77
|
+
throw new errors_1.CliError(text || '工具调用返回错误 (isError=true)', errors_1.ExitCodes.GATEWAY_ERROR);
|
|
78
|
+
}
|
|
79
|
+
return response;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.GatewayClient = GatewayClient;
|
|
83
|
+
exports.gatewayClient = new GatewayClient();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerCallCommand = registerCallCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const gateway_1 = require("../client/gateway");
|
|
9
|
+
const errors_1 = require("../utils/errors");
|
|
10
|
+
function parseArgsObject(raw) {
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = JSON.parse(raw);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new errors_1.CliError('参数格式错误,必须是合法的 JSON 字符串', errors_1.ExitCodes.INVALID_ARGS);
|
|
17
|
+
}
|
|
18
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
19
|
+
throw new errors_1.CliError('参数必须是 JSON 对象(例如 {"fnum":"CA1831"})', errors_1.ExitCodes.INVALID_ARGS);
|
|
20
|
+
}
|
|
21
|
+
return parsed;
|
|
22
|
+
}
|
|
23
|
+
function registerCallCommand(program) {
|
|
24
|
+
program
|
|
25
|
+
.command('call <tool>')
|
|
26
|
+
.description('调用 dast_mcp 上的指定工具')
|
|
27
|
+
.requiredOption('-a, --args <json>', 'JSON 格式的参数字符串')
|
|
28
|
+
.option('-o, --output <format>', '输出格式: json/table', 'json')
|
|
29
|
+
.action(async (tool, options) => {
|
|
30
|
+
let parsedArgs;
|
|
31
|
+
try {
|
|
32
|
+
parsedArgs = parseArgsObject(options.args);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
(0, errors_1.handleError)(error, errors_1.ExitCodes.INVALID_ARGS);
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const response = await gateway_1.gatewayClient.callTool(tool, parsedArgs);
|
|
39
|
+
let outputData = response;
|
|
40
|
+
if (response?.result) {
|
|
41
|
+
outputData = response.result;
|
|
42
|
+
}
|
|
43
|
+
else if (response?.data) {
|
|
44
|
+
outputData = response.data;
|
|
45
|
+
}
|
|
46
|
+
if (options.output === 'json') {
|
|
47
|
+
console.log(JSON.stringify(outputData, null, 2));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.log(chalk_1.default.green('\n✅ 调用成功\n'));
|
|
51
|
+
console.log(outputData);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
(0, errors_1.handleError)(error, errors_1.ExitCodes.GATEWAY_ERROR);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerInfoCommand = registerInfoCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const gateway_1 = require("../client/gateway");
|
|
9
|
+
const errors_1 = require("../utils/errors");
|
|
10
|
+
function registerInfoCommand(program) {
|
|
11
|
+
program
|
|
12
|
+
.command('info <tool>')
|
|
13
|
+
.alias('desc')
|
|
14
|
+
.description('查看指定工具的功能说明与参数详情')
|
|
15
|
+
.action(async (tool) => {
|
|
16
|
+
try {
|
|
17
|
+
const response = await gateway_1.gatewayClient.getTools();
|
|
18
|
+
const tools = response?.result?.tools ?? [];
|
|
19
|
+
if (tools.length === 0) {
|
|
20
|
+
throw new errors_1.CliError('未找到可用工具', errors_1.ExitCodes.NOT_FOUND);
|
|
21
|
+
}
|
|
22
|
+
const targetTool = tools.find((t) => t.name === tool);
|
|
23
|
+
if (!targetTool) {
|
|
24
|
+
throw new errors_1.CliError(`未找到工具 '${tool}'`, errors_1.ExitCodes.NOT_FOUND);
|
|
25
|
+
}
|
|
26
|
+
console.log(`\n${chalk_1.default.bold.green(targetTool.name)}`);
|
|
27
|
+
console.log(`${chalk_1.default.bold('描述:')} ${targetTool.description || '无描述'}`);
|
|
28
|
+
if (targetTool.inputSchema?.properties) {
|
|
29
|
+
console.log(`\n${chalk_1.default.bold('参数:')}`);
|
|
30
|
+
const props = targetTool.inputSchema.properties;
|
|
31
|
+
const required = targetTool.inputSchema.required || [];
|
|
32
|
+
for (const [key, value] of Object.entries(props)) {
|
|
33
|
+
const isRequired = required.includes(key);
|
|
34
|
+
const reqStr = isRequired ? chalk_1.default.red('(必填)') : chalk_1.default.gray('(可选)');
|
|
35
|
+
const typeStr = value.type || 'any';
|
|
36
|
+
const descStr = value.description || '';
|
|
37
|
+
console.log(` ${chalk_1.default.cyan(key)} ${reqStr}: ${chalk_1.default.yellow(typeStr)} - ${descStr}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.log(`\n${chalk_1.default.bold('参数:')} 无参数`);
|
|
42
|
+
}
|
|
43
|
+
console.log();
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
(0, errors_1.handleError)(error, errors_1.ExitCodes.GATEWAY_ERROR);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerListCommand = registerListCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const gateway_1 = require("../client/gateway");
|
|
9
|
+
const formatter_1 = require("../utils/formatter");
|
|
10
|
+
const errors_1 = require("../utils/errors");
|
|
11
|
+
function registerListCommand(program) {
|
|
12
|
+
program
|
|
13
|
+
.command('list')
|
|
14
|
+
.description('列出 dast_mcp 服务下的全部可用工具')
|
|
15
|
+
.option('-o, --output <format>', '输出格式: json/table', 'table')
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
try {
|
|
18
|
+
const response = await gateway_1.gatewayClient.getTools();
|
|
19
|
+
const tools = response?.result?.tools ?? [];
|
|
20
|
+
if (tools.length === 0) {
|
|
21
|
+
throw new errors_1.CliError('未找到可用工具', errors_1.ExitCodes.NOT_FOUND);
|
|
22
|
+
}
|
|
23
|
+
if (options.output === 'json') {
|
|
24
|
+
console.log(JSON.stringify(tools, null, 2));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.log(chalk_1.default.bold('\n🛠️ DAST MCP 工具列表:\n'));
|
|
28
|
+
console.log((0, formatter_1.formatToolsTable)(tools));
|
|
29
|
+
console.log(`\n💡 提示: 运行 ${chalk_1.default.cyan("dast call <tool_name> -a '{}'")} 调用工具。\n`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
(0, errors_1.handleError)(error, errors_1.ExitCodes.GATEWAY_ERROR);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerSchemaCommand = registerSchemaCommand;
|
|
4
|
+
const gateway_1 = require("../client/gateway");
|
|
5
|
+
const errors_1 = require("../utils/errors");
|
|
6
|
+
function registerSchemaCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('schema')
|
|
9
|
+
.description('导出全部工具的 Schema 定义,供 Agent 或自动化脚本初始化时使用')
|
|
10
|
+
.action(async () => {
|
|
11
|
+
try {
|
|
12
|
+
const response = await gateway_1.gatewayClient.getTools();
|
|
13
|
+
const tools = response?.result?.tools ?? [];
|
|
14
|
+
if (tools.length === 0) {
|
|
15
|
+
throw new errors_1.CliError('未找到可用工具', errors_1.ExitCodes.NOT_FOUND);
|
|
16
|
+
}
|
|
17
|
+
console.log(JSON.stringify({ tools }, null, 2));
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
(0, errors_1.handleError)(error, errors_1.ExitCodes.GATEWAY_ERROR);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const commander_1 = require("commander");
|
|
4
|
+
const list_1 = require("./commands/list");
|
|
5
|
+
const schema_1 = require("./commands/schema");
|
|
6
|
+
const call_1 = require("./commands/call");
|
|
7
|
+
const info_1 = require("./commands/info");
|
|
8
|
+
const gateway_1 = require("./client/gateway");
|
|
9
|
+
const errors_1 = require("./utils/errors");
|
|
10
|
+
const program = new commander_1.Command();
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
12
|
+
const pkg = require('../package.json');
|
|
13
|
+
program
|
|
14
|
+
.name('dast')
|
|
15
|
+
.description('DAST MCP CLI — Flight Master aviation data (fixed dast_mcp server)')
|
|
16
|
+
.version(pkg.version)
|
|
17
|
+
.option('-t, --timeout <ms>', '设置全局请求超时时间(毫秒)')
|
|
18
|
+
.addHelpText('after', `
|
|
19
|
+
环境变量:
|
|
20
|
+
DAST_API_KEY PhoneKey 鉴权凭证 (必填,格式 sk_phone_ + 32 位小写字母或数字)
|
|
21
|
+
DAST_MCP_URL 自定义 MCP 服务地址 (默认: https://fly.huoli.com/mcp/dast_mcp)
|
|
22
|
+
DAST_TIMEOUT 全局请求超时时间,单位毫秒 (默认: 20000)
|
|
23
|
+
|
|
24
|
+
示例:
|
|
25
|
+
$ dast list
|
|
26
|
+
$ dast schema
|
|
27
|
+
$ dast info dast_flight_dynamic
|
|
28
|
+
$ dast call dast_flight_dynamic -a '{"fnum":"CA1831","date":"2026-09-09"}'
|
|
29
|
+
$ dast call dast_flight_dynamic -a '{"fnum":"CA1831","date":"2026-09-09"}' -t 30000
|
|
30
|
+
`);
|
|
31
|
+
program.hook('preAction', (thisCommand) => {
|
|
32
|
+
const opts = thisCommand.optsWithGlobals();
|
|
33
|
+
if (opts.timeout) {
|
|
34
|
+
try {
|
|
35
|
+
gateway_1.gatewayClient.setTimeout((0, errors_1.parseTimeoutMs)(opts.timeout, '-t/--timeout'));
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
(0, errors_1.handleError)(error);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
(0, list_1.registerListCommand)(program);
|
|
43
|
+
(0, schema_1.registerSchemaCommand)(program);
|
|
44
|
+
(0, call_1.registerCallCommand)(program);
|
|
45
|
+
(0, info_1.registerInfoCommand)(program);
|
|
46
|
+
program.parse();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const AGENT_DIRECTORIES = [
|
|
10
|
+
path_1.default.join(os_1.default.homedir(), '.cursor', 'skills'),
|
|
11
|
+
path_1.default.join(os_1.default.homedir(), '.trae', 'skills'),
|
|
12
|
+
path_1.default.join(os_1.default.homedir(), '.claude', 'skills'),
|
|
13
|
+
path_1.default.join(os_1.default.homedir(), '.agents', 'skills'),
|
|
14
|
+
];
|
|
15
|
+
const SKILL_NAME = 'dast-mcp';
|
|
16
|
+
const SOURCE_SKILL_PATH = path_1.default.join(__dirname, '..', '..', 'skills', 'SKILL.md');
|
|
17
|
+
function installSkill() {
|
|
18
|
+
console.log('📦 正在为支持的 AI Agent 自动安装 dast-mcp 技能...');
|
|
19
|
+
if (!fs_1.default.existsSync(SOURCE_SKILL_PATH)) {
|
|
20
|
+
console.warn(`⚠️ 警告: 找不到源技能文件: ${SOURCE_SKILL_PATH}`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let installedCount = 0;
|
|
24
|
+
let attemptedCount = 0;
|
|
25
|
+
AGENT_DIRECTORIES.forEach((agentDir) => {
|
|
26
|
+
const baseAgentDir = path_1.default.dirname(agentDir);
|
|
27
|
+
if (fs_1.default.existsSync(baseAgentDir)) {
|
|
28
|
+
attemptedCount++;
|
|
29
|
+
try {
|
|
30
|
+
if (!fs_1.default.existsSync(agentDir)) {
|
|
31
|
+
fs_1.default.mkdirSync(agentDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
const targetSkillDir = path_1.default.join(agentDir, SKILL_NAME);
|
|
34
|
+
if (!fs_1.default.existsSync(targetSkillDir)) {
|
|
35
|
+
fs_1.default.mkdirSync(targetSkillDir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
const targetSkillPath = path_1.default.join(targetSkillDir, 'SKILL.md');
|
|
38
|
+
fs_1.default.copyFileSync(SOURCE_SKILL_PATH, targetSkillPath);
|
|
39
|
+
console.log(`✅ 成功将技能安装到: ${targetSkillPath}`);
|
|
40
|
+
installedCount++;
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
44
|
+
console.error(`❌ 安装技能到 ${agentDir} 失败 (可能是权限不足): ${message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
if (attemptedCount === 0) {
|
|
49
|
+
console.log('ℹ️ 未检测到支持的 AI Agent 目录,跳过自动安装。');
|
|
50
|
+
}
|
|
51
|
+
else if (installedCount === 0) {
|
|
52
|
+
console.log('⚠️ 已检测到 Agent 目录,但技能安装均失败,请检查权限后手动复制 skills/SKILL.md。');
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.log('🎉 技能安装完成!你的 AI Agent 现在可以直接使用自然语言调用 DAST MCP 服务了。');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
installSkill();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface McpTool {
|
|
2
|
+
name: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
inputSchema?: {
|
|
5
|
+
type?: string;
|
|
6
|
+
properties?: Record<string, {
|
|
7
|
+
type?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}>;
|
|
11
|
+
required?: string[];
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface JsonRpcResponse<T = unknown> {
|
|
16
|
+
jsonrpc?: string;
|
|
17
|
+
id?: number | string;
|
|
18
|
+
result?: T;
|
|
19
|
+
error?: {
|
|
20
|
+
code: number;
|
|
21
|
+
message: string;
|
|
22
|
+
data?: unknown;
|
|
23
|
+
};
|
|
24
|
+
data?: unknown;
|
|
25
|
+
}
|
|
26
|
+
export interface ToolsListResult {
|
|
27
|
+
tools: McpTool[];
|
|
28
|
+
}
|
|
29
|
+
export interface ToolCallResult {
|
|
30
|
+
content?: Array<{
|
|
31
|
+
type: string;
|
|
32
|
+
text?: string;
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
}>;
|
|
35
|
+
isError?: boolean;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const ExitCodes: {
|
|
2
|
+
readonly SUCCESS: 0;
|
|
3
|
+
readonly GATEWAY_ERROR: 101;
|
|
4
|
+
readonly NOT_FOUND: 102;
|
|
5
|
+
readonly INVALID_ARGS: 103;
|
|
6
|
+
readonly UNAUTHORIZED: 104;
|
|
7
|
+
readonly TIMEOUT: 105;
|
|
8
|
+
readonly INTERNAL_ERROR: 106;
|
|
9
|
+
};
|
|
10
|
+
export type ExitCode = (typeof ExitCodes)[keyof typeof ExitCodes];
|
|
11
|
+
/** PhoneKey: sk_phone_ + 32 lowercase letters or digits (mcp-gateway PhoneKey). */
|
|
12
|
+
export declare const PHONE_KEY_PATTERN: RegExp;
|
|
13
|
+
export declare class CliError extends Error {
|
|
14
|
+
readonly exitCode: ExitCode;
|
|
15
|
+
constructor(message: string, exitCode?: ExitCode);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Trim whitespace and strip a redundant `Bearer ` prefix if the user pasted the full header value.
|
|
19
|
+
*/
|
|
20
|
+
export declare function normalizePhoneKey(raw: string | undefined): string | undefined;
|
|
21
|
+
export declare function assertPhoneKeyFormat(key: string): void;
|
|
22
|
+
export declare function parseTimeoutMs(value: string, label: string): number;
|
|
23
|
+
/**
|
|
24
|
+
* Map JSON-RPC errors from the PhoneKey MCP gateway.
|
|
25
|
+
* PhoneKey auth failures are typically HTTP 200 + jsonrpc error code -32003 (AUTH_FAILED).
|
|
26
|
+
*/
|
|
27
|
+
export declare function mapJsonRpcError(code: number | undefined, message: string): CliError;
|
|
28
|
+
export declare function resolveError(error: unknown): {
|
|
29
|
+
message: string;
|
|
30
|
+
exitCode: ExitCode;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Print error and exit.
|
|
34
|
+
* Prefer CliError / axios-derived exit codes. Optional fallbackExitCode
|
|
35
|
+
* only applies to generic Error instances without a mapped code.
|
|
36
|
+
*/
|
|
37
|
+
export declare function handleError(error: unknown, fallbackExitCode?: ExitCode): never;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CliError = exports.PHONE_KEY_PATTERN = exports.ExitCodes = void 0;
|
|
7
|
+
exports.normalizePhoneKey = normalizePhoneKey;
|
|
8
|
+
exports.assertPhoneKeyFormat = assertPhoneKeyFormat;
|
|
9
|
+
exports.parseTimeoutMs = parseTimeoutMs;
|
|
10
|
+
exports.mapJsonRpcError = mapJsonRpcError;
|
|
11
|
+
exports.resolveError = resolveError;
|
|
12
|
+
exports.handleError = handleError;
|
|
13
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
14
|
+
const axios_1 = __importDefault(require("axios"));
|
|
15
|
+
exports.ExitCodes = {
|
|
16
|
+
SUCCESS: 0,
|
|
17
|
+
GATEWAY_ERROR: 101,
|
|
18
|
+
NOT_FOUND: 102,
|
|
19
|
+
INVALID_ARGS: 103,
|
|
20
|
+
UNAUTHORIZED: 104,
|
|
21
|
+
TIMEOUT: 105,
|
|
22
|
+
INTERNAL_ERROR: 106,
|
|
23
|
+
};
|
|
24
|
+
/** PhoneKey: sk_phone_ + 32 lowercase letters or digits (mcp-gateway PhoneKey). */
|
|
25
|
+
exports.PHONE_KEY_PATTERN = /^sk_phone_[a-z0-9]{32}$/;
|
|
26
|
+
class CliError extends Error {
|
|
27
|
+
constructor(message, exitCode = exports.ExitCodes.INTERNAL_ERROR) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = 'CliError';
|
|
30
|
+
this.exitCode = exitCode;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.CliError = CliError;
|
|
34
|
+
/**
|
|
35
|
+
* Trim whitespace and strip a redundant `Bearer ` prefix if the user pasted the full header value.
|
|
36
|
+
*/
|
|
37
|
+
function normalizePhoneKey(raw) {
|
|
38
|
+
if (raw === undefined) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
let key = raw.trim();
|
|
42
|
+
if (!key) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
if (/^bearer\s+/i.test(key)) {
|
|
46
|
+
key = key.replace(/^bearer\s+/i, '').trim();
|
|
47
|
+
}
|
|
48
|
+
return key || undefined;
|
|
49
|
+
}
|
|
50
|
+
function assertPhoneKeyFormat(key) {
|
|
51
|
+
if (!exports.PHONE_KEY_PATTERN.test(key)) {
|
|
52
|
+
throw new CliError('DAST_API_KEY 须为 PhoneKey:sk_phone_ + 32 位小写字母或数字(不要加 Bearer 前缀)', exports.ExitCodes.INVALID_ARGS);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function parseTimeoutMs(value, label) {
|
|
56
|
+
const asNumber = Number(value);
|
|
57
|
+
if (!Number.isFinite(asNumber) || !Number.isInteger(asNumber) || asNumber <= 0) {
|
|
58
|
+
throw new CliError(`${label} 无效,必须是正整数毫秒数`, exports.ExitCodes.INVALID_ARGS);
|
|
59
|
+
}
|
|
60
|
+
return asNumber;
|
|
61
|
+
}
|
|
62
|
+
function extractAxiosMessage(error) {
|
|
63
|
+
if (!axios_1.default.isAxiosError(error)) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
const data = error.response?.data;
|
|
67
|
+
if (typeof data === 'string' && data.trim()) {
|
|
68
|
+
return data.trim();
|
|
69
|
+
}
|
|
70
|
+
if (data && typeof data === 'object') {
|
|
71
|
+
const record = data;
|
|
72
|
+
const rpcError = record.error;
|
|
73
|
+
if (rpcError && typeof rpcError === 'object') {
|
|
74
|
+
const message = rpcError.message;
|
|
75
|
+
if (typeof message === 'string' && message) {
|
|
76
|
+
return message;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (typeof record.message === 'string' && record.message) {
|
|
80
|
+
return record.message;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return error.message;
|
|
84
|
+
}
|
|
85
|
+
function isRateLimitMessage(message) {
|
|
86
|
+
return /rate.?limit|限流|过于频繁|too many requests|qps/i.test(message);
|
|
87
|
+
}
|
|
88
|
+
function isPhoneKeyAuthMessage(message) {
|
|
89
|
+
return /phone\s*key|phonekey|sk_phone_|api key|unauthorized|认证|鉴权|未授权|invalid.*key|missing.*key|key.*无效|无效.*或不存在/i.test(message);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Map JSON-RPC errors from the PhoneKey MCP gateway.
|
|
93
|
+
* PhoneKey auth failures are typically HTTP 200 + jsonrpc error code -32003 (AUTH_FAILED).
|
|
94
|
+
*/
|
|
95
|
+
function mapJsonRpcError(code, message) {
|
|
96
|
+
const lower = message.toLowerCase();
|
|
97
|
+
if (isRateLimitMessage(message)) {
|
|
98
|
+
return new CliError(message, exports.ExitCodes.GATEWAY_ERROR);
|
|
99
|
+
}
|
|
100
|
+
// Guide: PhoneKey errors (48001–48009) surface as JSON-RPC -32003 AUTH_FAILED
|
|
101
|
+
if (code === -32003 || isPhoneKeyAuthMessage(message)) {
|
|
102
|
+
return new CliError(message, exports.ExitCodes.UNAUTHORIZED);
|
|
103
|
+
}
|
|
104
|
+
if (code === -32602 || /invalid params|参数非法|参数错误/i.test(lower)) {
|
|
105
|
+
return new CliError(message, exports.ExitCodes.INVALID_ARGS);
|
|
106
|
+
}
|
|
107
|
+
if (/not found|未找到|unknown tool|tool .* not/i.test(lower)) {
|
|
108
|
+
return new CliError(message, exports.ExitCodes.NOT_FOUND);
|
|
109
|
+
}
|
|
110
|
+
if (/timeout|超时/i.test(lower)) {
|
|
111
|
+
return new CliError(message, exports.ExitCodes.TIMEOUT);
|
|
112
|
+
}
|
|
113
|
+
return new CliError(message, exports.ExitCodes.GATEWAY_ERROR);
|
|
114
|
+
}
|
|
115
|
+
function resolveError(error) {
|
|
116
|
+
if (error instanceof CliError) {
|
|
117
|
+
return { message: error.message, exitCode: error.exitCode };
|
|
118
|
+
}
|
|
119
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
120
|
+
if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT' || error.code === 'ERR_CANCELED') {
|
|
121
|
+
return {
|
|
122
|
+
message: `请求超时: ${error.message}`,
|
|
123
|
+
exitCode: exports.ExitCodes.TIMEOUT,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const status = error.response?.status;
|
|
127
|
+
const detail = extractAxiosMessage(error);
|
|
128
|
+
if (status === 401 || status === 403) {
|
|
129
|
+
return {
|
|
130
|
+
message: detail || 'PhoneKey 认证失败,请检查 DAST_API_KEY(sk_phone_…)是否正确',
|
|
131
|
+
exitCode: exports.ExitCodes.UNAUTHORIZED,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (detail && isRateLimitMessage(detail)) {
|
|
135
|
+
return { message: detail, exitCode: exports.ExitCodes.GATEWAY_ERROR };
|
|
136
|
+
}
|
|
137
|
+
if (detail && isPhoneKeyAuthMessage(detail)) {
|
|
138
|
+
return { message: detail, exitCode: exports.ExitCodes.UNAUTHORIZED };
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
message: detail || error.message || '网关请求失败',
|
|
142
|
+
exitCode: exports.ExitCodes.GATEWAY_ERROR,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (error instanceof Error && error.message) {
|
|
146
|
+
if (/DAST_API_KEY|PhoneKey/i.test(error.message)) {
|
|
147
|
+
return { message: error.message, exitCode: exports.ExitCodes.UNAUTHORIZED };
|
|
148
|
+
}
|
|
149
|
+
return { message: error.message, exitCode: exports.ExitCodes.INTERNAL_ERROR };
|
|
150
|
+
}
|
|
151
|
+
return { message: 'An unknown error occurred.', exitCode: exports.ExitCodes.INTERNAL_ERROR };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Print error and exit.
|
|
155
|
+
* Prefer CliError / axios-derived exit codes. Optional fallbackExitCode
|
|
156
|
+
* only applies to generic Error instances without a mapped code.
|
|
157
|
+
*/
|
|
158
|
+
function handleError(error, fallbackExitCode) {
|
|
159
|
+
const resolved = resolveError(error);
|
|
160
|
+
const useResolved = error instanceof CliError ||
|
|
161
|
+
axios_1.default.isAxiosError(error) ||
|
|
162
|
+
(error instanceof Error && /DAST_API_KEY|PhoneKey/i.test(error.message));
|
|
163
|
+
const exitCode = useResolved ? resolved.exitCode : (fallbackExitCode ?? resolved.exitCode);
|
|
164
|
+
console.error(chalk_1.default.red(`Error: ${resolved.message}`));
|
|
165
|
+
if (!(error instanceof Error) && error !== undefined && error !== null) {
|
|
166
|
+
console.error(error);
|
|
167
|
+
}
|
|
168
|
+
process.exit(exitCode);
|
|
169
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.formatToolsTable = formatToolsTable;
|
|
7
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
function formatToolsTable(tools) {
|
|
10
|
+
const table = new cli_table3_1.default({
|
|
11
|
+
head: [chalk_1.default.cyan('Tool Name'), chalk_1.default.cyan('Description')],
|
|
12
|
+
style: { head: [], border: [] },
|
|
13
|
+
colWidths: [30, 70],
|
|
14
|
+
wordWrap: true,
|
|
15
|
+
});
|
|
16
|
+
tools.forEach((tool) => {
|
|
17
|
+
table.push([tool.name, tool.description || '-']);
|
|
18
|
+
});
|
|
19
|
+
return table.toString();
|
|
20
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flightmaster/dast-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"mcpName": "io.github.flight-master-dast/dast-cli",
|
|
5
|
+
"description": "DAST MCP CLI — fixed single-server client for Flight Master aviation data",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dast": "./bin/dast"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"bin",
|
|
13
|
+
"skills",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc && chmod 755 bin/dast",
|
|
18
|
+
"start": "ts-node src/index.ts",
|
|
19
|
+
"dev": "ts-node src/index.ts",
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"postinstall": "node -e \"try{require('./dist/scripts/auto-install-skill.js')}catch(e){if(e.code!=='MODULE_NOT_FOUND')throw e}\""
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"cli",
|
|
27
|
+
"command-line",
|
|
28
|
+
"dast",
|
|
29
|
+
"flight",
|
|
30
|
+
"flights",
|
|
31
|
+
"aviation",
|
|
32
|
+
"airport",
|
|
33
|
+
"weather",
|
|
34
|
+
"delay",
|
|
35
|
+
"travel",
|
|
36
|
+
"flightmaster",
|
|
37
|
+
"agent",
|
|
38
|
+
"gateway"
|
|
39
|
+
],
|
|
40
|
+
"author": "DAST",
|
|
41
|
+
"license": "ISC",
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public",
|
|
44
|
+
"registry": "https://registry.npmjs.org/"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"axios": "^1.14.0",
|
|
51
|
+
"chalk": "^4.1.2",
|
|
52
|
+
"cli-table3": "^0.6.5",
|
|
53
|
+
"commander": "^14.0.3"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^20.11.17",
|
|
57
|
+
"ts-node": "^10.9.2",
|
|
58
|
+
"typescript": "^5.3.3"
|
|
59
|
+
}
|
|
60
|
+
}
|
package/skills/SKILL.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dast-mcp
|
|
3
|
+
description: DAST 航空数据助手 - 教 AI 通过 dast CLI 发现并调用固定的 dast_mcp 服务(航班动态、航线、舒适度、延误概率、天气、飞行轨迹等)。
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
metadata: {"openclaw": {"emoji": "✈️", "category": "travel", "tags": ["DAST", "航班", "航空数据", "MCP"], "requires": {"bins": ["dast"]}, "env": {"DAST_API_KEY": {"type": "string", "description": "DAST PhoneKey(sk_phone_ + 32 位小写字母或数字)", "required": true}}}}
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# DAST 航空数据助手 (dast MCP Agent)
|
|
9
|
+
|
|
10
|
+
当用户向你询问任何与 **航班动态、航线班次、舒适度、延误概率、机场天气、飞行轨迹** 等相关的需求时,你**必须**使用本地的 `dast` 命令行工具来获取数据并帮助用户完成任务。
|
|
11
|
+
|
|
12
|
+
本 CLI 固定对接单一 MCP Server(`dast_mcp`),使用 **PhoneKey** 无状态鉴权(`Authorization: Bearer sk_phone_…`),无需先选择 server,也不依赖 session。
|
|
13
|
+
|
|
14
|
+
## 核心工作流(严格遵守):
|
|
15
|
+
|
|
16
|
+
### 第一步:查阅工具说明书 (Schema)
|
|
17
|
+
你不知道当前有哪些工具以及参数规则,必须先执行:
|
|
18
|
+
```bash
|
|
19
|
+
dast schema
|
|
20
|
+
```
|
|
21
|
+
仔细阅读返回的 JSON,特别是每个工具的 `inputSchema`(`type`、`description`、`required`)。
|
|
22
|
+
|
|
23
|
+
如只需确认某一个工具,可执行:
|
|
24
|
+
```bash
|
|
25
|
+
dast info <toolName>
|
|
26
|
+
```
|
|
27
|
+
(例如: `dast info dast_flight_dynamic`)
|
|
28
|
+
|
|
29
|
+
### 第二步:调用工具并返回结果 (Call)
|
|
30
|
+
根据 schema 构造 JSON 参数,执行:
|
|
31
|
+
```bash
|
|
32
|
+
dast call <toolName> -a '<json_string>'
|
|
33
|
+
```
|
|
34
|
+
(例如: `dast call dast_flight_dynamic -a '{"fnum":"CA1831","date":"2026-09-09"}'`)
|
|
35
|
+
|
|
36
|
+
提取返回的核心业务数据,整理成易读自然语言展示给用户。
|
|
37
|
+
|
|
38
|
+
可选:`dast list -o json` 可快速查看工具名称与描述(不含完整 schema)。
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## ⚠️ 重要注意事项:
|
|
43
|
+
|
|
44
|
+
1. **绝不瞎猜参数**:调用前必须通过 `schema` 或 `info` 确认参数名。若返回参数错误,重新查阅 schema 后修正。
|
|
45
|
+
2. **JSON 转义**:在 Linux/Mac 终端中,务必用单引号 `' '` 包裹 `-a` 后的整个 JSON 字符串。
|
|
46
|
+
3. **认证前置**:执行任何命令前,确保存在环境变量 `DAST_API_KEY`,且值为 PhoneKey(`sk_phone_` + 32 位小写字母或数字,不要带 `Bearer ` 前缀);若没有或格式不对,请要求用户从 dast 平台重新提供。
|
|
47
|
+
4. **日期与三字码**:多数工具需要 `YYYY-MM-DD` 日期和机场三字码(如 PEK、CAN);城市名需先转换为机场码。
|
|
48
|
+
5. **退出码**:鉴权失败为 `104`;参数非法为 `103`;网关/限流等为 `101`。按退出码决定是换 Key、改参数还是重试。
|