@leju-gym/gym-mcp 0.0.2 → 0.0.4
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/package.json +1 -1
- package/src/services/mcp/ToolService.js +280 -10
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @ModuleName: ToolService
|
|
3
3
|
* @ModuleMethod: src/services/mcp
|
|
4
4
|
* @ModuleUsage: MCP 工具注册与调用服务
|
|
5
|
-
* @ModuleDesc: 通过 spawn gym CLI
|
|
5
|
+
* @ModuleDesc: 通过 spawn gym CLI 调用本地 gym 命令,
|
|
6
6
|
* 逻辑零重复,认证完全复用 gym config login
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -17,6 +17,9 @@ function gymBin() {
|
|
|
17
17
|
return process.env.GYM_BIN || 'gym';
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* 执行 gym CLI 命令,返回文本输出(不再要求 --json,直接返回原始输出)
|
|
22
|
+
*/
|
|
20
23
|
async function runGym(args = []) {
|
|
21
24
|
const bin = gymBin();
|
|
22
25
|
appHelper.info('runGym', bin, args.join(' '));
|
|
@@ -26,20 +29,33 @@ async function runGym(args = []) {
|
|
|
26
29
|
maxBuffer: 1024 * 1024,
|
|
27
30
|
});
|
|
28
31
|
if (stderr) appHelper.warn('runGym stderr', stderr);
|
|
29
|
-
|
|
32
|
+
// 清理 ANSI 转义序列,返回纯文本
|
|
33
|
+
return cleanAnsi(stdout || '');
|
|
30
34
|
} catch (e) {
|
|
31
35
|
if (e.code === 'ENOENT') {
|
|
32
|
-
return
|
|
36
|
+
return 'gym CLI 未安装或不在 PATH 中。请执行: npm i -g @leju-gym/gym-cli && gym config login';
|
|
33
37
|
}
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
return
|
|
38
|
+
// 输出错误信息
|
|
39
|
+
const out = cleanAnsi(e.stdout || '') || e.stderr || e.message;
|
|
40
|
+
return out;
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
/**
|
|
45
|
+
* 清理 ANSI 转义序列(颜色码、光标移动等),保留可读文本
|
|
46
|
+
*/
|
|
47
|
+
function cleanAnsi(str) {
|
|
48
|
+
return str
|
|
49
|
+
.replace(/\x1b\[[0-9;]*m/g, '') // 颜色/样式
|
|
50
|
+
.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '') // 光标移动等
|
|
51
|
+
.replace(/\x1b\]8;.*?\x1b\\/g, '') // 超链接
|
|
52
|
+
.trim();
|
|
53
|
+
}
|
|
54
|
+
|
|
40
55
|
// ─── 工具定义 ──────────────────────────────────────────────────
|
|
41
56
|
|
|
42
57
|
const toolDefinitions = [
|
|
58
|
+
// ── 连通性测试 ──
|
|
43
59
|
{
|
|
44
60
|
name: 'echo',
|
|
45
61
|
description: '回显输入内容,用于测试连通性',
|
|
@@ -51,6 +67,8 @@ const toolDefinitions = [
|
|
|
51
67
|
required: ['message'],
|
|
52
68
|
},
|
|
53
69
|
},
|
|
70
|
+
|
|
71
|
+
// ── 项目管理 ──
|
|
54
72
|
{
|
|
55
73
|
name: 'gym_project_list',
|
|
56
74
|
description:
|
|
@@ -63,6 +81,151 @@ const toolDefinitions = [
|
|
|
63
81
|
},
|
|
64
82
|
},
|
|
65
83
|
},
|
|
84
|
+
|
|
85
|
+
// ── 任务管理 ──
|
|
86
|
+
{
|
|
87
|
+
name: 'gym_task_list',
|
|
88
|
+
description:
|
|
89
|
+
'查询任务列表。支持按状态、任务名称、任务编码、项目、场景、标签等筛选,支持分页。',
|
|
90
|
+
inputSchema: {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: {
|
|
93
|
+
page: { type: 'number', description: '页码,默认 1' },
|
|
94
|
+
limit: { type: 'number', description: '每页数量,默认 10' },
|
|
95
|
+
status: { type: 'string', description: '按状态筛选: 0=待处理 1=进行中 2=已完成;支持逗号分隔多个' },
|
|
96
|
+
taskId: { type: 'string', description: '按任务ID(精确匹配)筛选' },
|
|
97
|
+
taskName: { type: 'string', description: '按任务名称(包含匹配)筛选' },
|
|
98
|
+
taskCode: { type: 'string', description: '按任务编码(精确匹配)筛选' },
|
|
99
|
+
projectId: { type: 'string', description: '【必填】项目ID' },
|
|
100
|
+
scene1: { type: 'string', description: '按一级场景ID过滤' },
|
|
101
|
+
scene2: { type: 'string', description: '按二级场景ID过滤' },
|
|
102
|
+
scene3: { type: 'string', description: '按三级场景ID过滤' },
|
|
103
|
+
startDate: { type: 'string', description: '创建开始日期 (YYYY-MM-DD HH:MM:SS 或 YYYY-MM-DD)' },
|
|
104
|
+
endDate: { type: 'string', description: '创建结束日期 (YYYY-MM-DD HH:MM:SS 或 YYYY-MM-DD)' },
|
|
105
|
+
tag: { type: 'string', description: '按任务标签名过滤(支持多个,逗号分隔)' },
|
|
106
|
+
},
|
|
107
|
+
required: ['projectId'],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'gym_task_detail',
|
|
112
|
+
description: '查看单个任务详情,包括采集/标注/审核进度等信息',
|
|
113
|
+
inputSchema: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
taskId: { type: 'string', description: '任务ID' },
|
|
117
|
+
},
|
|
118
|
+
required: ['taskId'],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'gym_task_list_bag',
|
|
123
|
+
description: '按任务ID列出对应的 BAG 数据信息,支持分页和状态筛选',
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: 'object',
|
|
126
|
+
properties: {
|
|
127
|
+
taskId: { type: 'string', description: '【必填】任务ID' },
|
|
128
|
+
page: { type: 'number', description: '页码,默认 1' },
|
|
129
|
+
limit: { type: 'number', description: '每页数量,默认 10' },
|
|
130
|
+
status: { type: 'number', description: '按状态筛选: 0=待标注 1=待审核 2=审核失败 3=审核成功 4=待同步 5=同步失败' },
|
|
131
|
+
},
|
|
132
|
+
required: ['taskId'],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
// ── 数据管理 ──
|
|
137
|
+
{
|
|
138
|
+
name: 'gym_data_list',
|
|
139
|
+
description:
|
|
140
|
+
'查询数据管理列表。支持按状态、任务、项目、场景、标签、设备、采集员等筛选,支持分页。',
|
|
141
|
+
inputSchema: {
|
|
142
|
+
type: 'object',
|
|
143
|
+
properties: {
|
|
144
|
+
page: { type: 'number', description: '页码,默认 1' },
|
|
145
|
+
limit: { type: 'number', description: '每页数量,默认 10' },
|
|
146
|
+
status: { type: 'string', description: '状态过滤: 0=待标注 1=待审核 2=审核失败 3=审核成功 4=待同步 5=同步失败 6=已标注 7=待验收 8=验收失败 9=验收成功 10=标注中' },
|
|
147
|
+
taskId: { type: 'string', description: '按任务ID(精确匹配)筛选' },
|
|
148
|
+
dataName: { type: 'string', description: '按数据文件名(包含匹配)筛选' },
|
|
149
|
+
taskName: { type: 'string', description: '按任务名(包含匹配)筛选' },
|
|
150
|
+
taskCode: { type: 'string', description: '按任务编码(精确匹配)筛选' },
|
|
151
|
+
projectId: { type: 'string', description: '按项目过滤' },
|
|
152
|
+
scene1: { type: 'string', description: '按一级场景过滤' },
|
|
153
|
+
scene2: { type: 'string', description: '按二级场景过滤' },
|
|
154
|
+
scene3: { type: 'string', description: '按三级场景过滤' },
|
|
155
|
+
startDate: { type: 'string', description: '上传开始日期 (YYYY-MM-DD HH:MM:SS 或 YYYY-MM-DD)' },
|
|
156
|
+
endDate: { type: 'string', description: '上传结束日期 (YYYY-MM-DD HH:MM:SS 或 YYYY-MM-DD)' },
|
|
157
|
+
tag: { type: 'string', description: '按数据标签名过滤' },
|
|
158
|
+
deviceSn: { type: 'string', description: '按设备序列号(包含匹配)筛选' },
|
|
159
|
+
collector: { type: 'string', description: '按采集员(包含匹配)筛选' },
|
|
160
|
+
dataType: { type: 'string', description: '按数据类型筛选: success/failure' },
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'gym_data_detail',
|
|
166
|
+
description: '查看单条数据详情,包括设备、采集员、标注员、审核员等信息',
|
|
167
|
+
inputSchema: {
|
|
168
|
+
type: 'object',
|
|
169
|
+
properties: {
|
|
170
|
+
dataId: { type: 'string', description: '数据ID' },
|
|
171
|
+
},
|
|
172
|
+
required: ['dataId'],
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
// ── 设备管理 ──
|
|
177
|
+
{
|
|
178
|
+
name: 'gym_device_list',
|
|
179
|
+
description: '分页查询设备列表',
|
|
180
|
+
inputSchema: {
|
|
181
|
+
type: 'object',
|
|
182
|
+
properties: {
|
|
183
|
+
page: { type: 'number', description: '页码,默认 1' },
|
|
184
|
+
limit: { type: 'number', description: '每页数量,默认 10' },
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
// ── 场景管理 ──
|
|
190
|
+
{
|
|
191
|
+
name: 'gym_scene_list',
|
|
192
|
+
description: '查询场景列表。可选一级/二级场景ID来查询子场景。',
|
|
193
|
+
inputSchema: {
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: {
|
|
196
|
+
projectId: { type: 'string', description: '【必填】项目ID' },
|
|
197
|
+
scene1: { type: 'string', description: '一级场景ID(可选),传入后列出该一级场景下的二级场景' },
|
|
198
|
+
scene2: { type: 'string', description: '二级场景ID(可选),需配合 scene1 使用,列出该二级场景下的三级场景' },
|
|
199
|
+
},
|
|
200
|
+
required: ['projectId'],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// ── 交付任务管理 ──
|
|
205
|
+
{
|
|
206
|
+
name: 'gym_delivery_list',
|
|
207
|
+
description: '查询交付任务列表(支持分页)',
|
|
208
|
+
inputSchema: {
|
|
209
|
+
type: 'object',
|
|
210
|
+
properties: {
|
|
211
|
+
page: { type: 'number', description: '页码,默认 1' },
|
|
212
|
+
limit: { type: 'number', description: '每页数量,默认 10' },
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
// ── 文件记录管理 ──
|
|
218
|
+
{
|
|
219
|
+
name: 'gym_record_list',
|
|
220
|
+
description: '分页查询文件记录列表',
|
|
221
|
+
inputSchema: {
|
|
222
|
+
type: 'object',
|
|
223
|
+
properties: {
|
|
224
|
+
page: { type: 'number', description: '页码,默认 1' },
|
|
225
|
+
limit: { type: 'number', description: '每页数量,默认 10' },
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
66
229
|
];
|
|
67
230
|
|
|
68
231
|
// ─── ToolService ────────────────────────────────────────────────
|
|
@@ -89,6 +252,7 @@ class ToolService {
|
|
|
89
252
|
case 'echo':
|
|
90
253
|
return { ok: true, data: { echo: args.message || '' } };
|
|
91
254
|
|
|
255
|
+
// ── 项目管理 ──
|
|
92
256
|
case 'gym_project_list': {
|
|
93
257
|
const page = args.page || 1;
|
|
94
258
|
const limit = args.limit || 10;
|
|
@@ -96,11 +260,117 @@ class ToolService {
|
|
|
96
260
|
'project', 'list',
|
|
97
261
|
'--page', String(page),
|
|
98
262
|
'--limit', String(limit),
|
|
99
|
-
'--json',
|
|
100
263
|
]);
|
|
101
|
-
return result
|
|
102
|
-
|
|
103
|
-
|
|
264
|
+
return { ok: true, data: result };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ── 任务管理 ──
|
|
268
|
+
case 'gym_task_list': {
|
|
269
|
+
const cliArgs = ['task', 'list'];
|
|
270
|
+
if (args.page) cliArgs.push('--page', String(args.page));
|
|
271
|
+
if (args.limit) cliArgs.push('--limit', String(args.limit));
|
|
272
|
+
if (args.status) cliArgs.push('--status', String(args.status));
|
|
273
|
+
if (args.taskId) cliArgs.push('--task-id', String(args.taskId));
|
|
274
|
+
if (args.taskName) cliArgs.push('--task-name', String(args.taskName));
|
|
275
|
+
if (args.taskCode) cliArgs.push('--task-code', String(args.taskCode));
|
|
276
|
+
if (args.startDate) cliArgs.push('--start-date', String(args.startDate));
|
|
277
|
+
if (args.endDate) cliArgs.push('--end-date', String(args.endDate));
|
|
278
|
+
if (args.tag) cliArgs.push('--tag', String(args.tag));
|
|
279
|
+
// projectId 和 scene 是必填/可选参数
|
|
280
|
+
cliArgs.push('--project-id', String(args.projectId));
|
|
281
|
+
if (args.scene1) cliArgs.push('--scene1', String(args.scene1));
|
|
282
|
+
if (args.scene2) cliArgs.push('--scene2', String(args.scene2));
|
|
283
|
+
if (args.scene3) cliArgs.push('--scene3', String(args.scene3));
|
|
284
|
+
const result = await runGym(cliArgs);
|
|
285
|
+
return { ok: true, data: result };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
case 'gym_task_detail': {
|
|
289
|
+
const result = await runGym(['task', 'detail', String(args.taskId)]);
|
|
290
|
+
return { ok: true, data: result };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
case 'gym_task_list_bag': {
|
|
294
|
+
const cliArgs = ['task', 'list-bag', String(args.taskId)];
|
|
295
|
+
if (args.page) cliArgs.push('--page', String(args.page));
|
|
296
|
+
if (args.limit) cliArgs.push('--limit', String(args.limit));
|
|
297
|
+
if (args.status !== undefined) cliArgs.push('--status', String(args.status));
|
|
298
|
+
const result = await runGym(cliArgs);
|
|
299
|
+
return { ok: true, data: result };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ── 数据管理 ──
|
|
303
|
+
case 'gym_data_list': {
|
|
304
|
+
const cliArgs = ['data', 'list'];
|
|
305
|
+
if (args.page) cliArgs.push('--page', String(args.page));
|
|
306
|
+
if (args.limit) cliArgs.push('--limit', String(args.limit));
|
|
307
|
+
if (args.status) cliArgs.push('--status', String(args.status));
|
|
308
|
+
if (args.taskId) cliArgs.push('--task-id', String(args.taskId));
|
|
309
|
+
if (args.dataName) cliArgs.push('--data-name', String(args.dataName));
|
|
310
|
+
if (args.taskName) cliArgs.push('--task-name', String(args.taskName));
|
|
311
|
+
if (args.taskCode) cliArgs.push('--task-code', String(args.taskCode));
|
|
312
|
+
if (args.startDate) cliArgs.push('--start-date', String(args.startDate));
|
|
313
|
+
if (args.endDate) cliArgs.push('--end-date', String(args.endDate));
|
|
314
|
+
if (args.tag) cliArgs.push('--tag', String(args.tag));
|
|
315
|
+
if (args.deviceSn) cliArgs.push('--device-sn', String(args.deviceSn));
|
|
316
|
+
if (args.collector) cliArgs.push('--collector', String(args.collector));
|
|
317
|
+
if (args.dataType) cliArgs.push('--data-type', String(args.dataType));
|
|
318
|
+
if (args.projectId) cliArgs.push('--project-id', String(args.projectId));
|
|
319
|
+
if (args.scene1) cliArgs.push('--scene1', String(args.scene1));
|
|
320
|
+
if (args.scene2) cliArgs.push('--scene2', String(args.scene2));
|
|
321
|
+
if (args.scene3) cliArgs.push('--scene3', String(args.scene3));
|
|
322
|
+
const result = await runGym(cliArgs);
|
|
323
|
+
return { ok: true, data: result };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
case 'gym_data_detail': {
|
|
327
|
+
const result = await runGym(['data', 'detail', String(args.dataId)]);
|
|
328
|
+
return { ok: true, data: result };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// ── 设备管理 ──
|
|
332
|
+
case 'gym_device_list': {
|
|
333
|
+
const page = args.page || 1;
|
|
334
|
+
const limit = args.limit || 10;
|
|
335
|
+
const result = await runGym([
|
|
336
|
+
'device', 'list',
|
|
337
|
+
'--page', String(page),
|
|
338
|
+
'--limit', String(limit),
|
|
339
|
+
]);
|
|
340
|
+
return { ok: true, data: result };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ── 场景管理 ──
|
|
344
|
+
case 'gym_scene_list': {
|
|
345
|
+
const cliArgs = ['scene', 'list', '--project', String(args.projectId)];
|
|
346
|
+
if (args.scene1) cliArgs.push('--scene1', String(args.scene1));
|
|
347
|
+
if (args.scene2) cliArgs.push('--scene2', String(args.scene2));
|
|
348
|
+
const result = await runGym(cliArgs);
|
|
349
|
+
return { ok: true, data: result };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ── 交付任务管理 ──
|
|
353
|
+
case 'gym_delivery_list': {
|
|
354
|
+
const page = args.page || 1;
|
|
355
|
+
const limit = args.limit || 10;
|
|
356
|
+
const result = await runGym([
|
|
357
|
+
'delivery', 'list',
|
|
358
|
+
'--page', String(page),
|
|
359
|
+
'--limit', String(limit),
|
|
360
|
+
]);
|
|
361
|
+
return { ok: true, data: result };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ── 文件记录管理 ──
|
|
365
|
+
case 'gym_record_list': {
|
|
366
|
+
const page = args.page || 1;
|
|
367
|
+
const limit = args.limit || 10;
|
|
368
|
+
const result = await runGym([
|
|
369
|
+
'record', 'list',
|
|
370
|
+
'--page', String(page),
|
|
371
|
+
'--limit', String(limit),
|
|
372
|
+
]);
|
|
373
|
+
return { ok: true, data: result };
|
|
104
374
|
}
|
|
105
375
|
|
|
106
376
|
default:
|