qingflow-mcp 0.3.16 → 0.3.18
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 +45 -3
- package/dist/qingflow-client.js +42 -0
- package/dist/server.js +632 -59
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
This MCP server wraps Qingflow OpenAPI for:
|
|
4
4
|
|
|
5
5
|
- `qf_apps_list`
|
|
6
|
+
- `qf_departments_list`
|
|
7
|
+
- `qf_department_users_list`
|
|
8
|
+
- `qf_users_list`
|
|
9
|
+
- `qf_user_get`
|
|
6
10
|
- `qf_form_get`
|
|
7
11
|
- `qf_field_resolve`
|
|
8
12
|
- `qf_query_plan`
|
|
@@ -108,7 +112,7 @@ npm i -g git+https://github.com/853046310/qingflow-mcp.git
|
|
|
108
112
|
Install from npm (pinned version):
|
|
109
113
|
|
|
110
114
|
```bash
|
|
111
|
-
npm i -g qingflow-mcp@0.3.
|
|
115
|
+
npm i -g qingflow-mcp@0.3.18
|
|
112
116
|
```
|
|
113
117
|
|
|
114
118
|
Or one-click installer:
|
|
@@ -148,6 +152,13 @@ MCP client config example:
|
|
|
148
152
|
3. `qf_record_create` or `qf_record_update`.
|
|
149
153
|
4. If create/update returns only `request_id`, call `qf_operation_get` to resolve async result.
|
|
150
154
|
|
|
155
|
+
Directory / org flow:
|
|
156
|
+
|
|
157
|
+
1. `qf_departments_list` to inspect department tree.
|
|
158
|
+
2. `qf_department_users_list` to inspect one department's members.
|
|
159
|
+
3. `qf_users_list` for workspace-wide pagination.
|
|
160
|
+
4. `qf_user_get` for one exact user.
|
|
161
|
+
|
|
151
162
|
Full calling contract (Chinese):
|
|
152
163
|
|
|
153
164
|
- [MCP 调用规范](./docs/MCP_CALLING_SPEC.md)
|
|
@@ -165,14 +176,45 @@ Full calling contract (Chinese):
|
|
|
165
176
|
4. In `list` mode, `select_columns` is required.
|
|
166
177
|
5. In `list` mode, row cap defaults to 200 when `max_rows` and `max_items` are omitted.
|
|
167
178
|
6. In `record` mode, `select_columns` is required.
|
|
168
|
-
7. In `summary` mode, `select_columns` is
|
|
179
|
+
7. In `summary` mode, `select_columns` is optional and can be auto-derived from `amount_column` / `time_range` (`max_rows` defaults to 200 when omitted).
|
|
169
180
|
|
|
170
181
|
Summary mode output:
|
|
171
182
|
|
|
172
183
|
1. `summary`: aggregated stats (`total_count`, `total_amount`, `by_day`, `missing_count`).
|
|
173
|
-
2. `rows`: strict column rows (
|
|
184
|
+
2. `rows`: strict column rows (requested `select_columns`, or auto-derived preview columns when omitted).
|
|
174
185
|
3. `meta`: field mapping, filter scope, stat policy, execution limits (`output_profile=verbose` only).
|
|
175
186
|
|
|
187
|
+
## Directory / Org Tools
|
|
188
|
+
|
|
189
|
+
These tools expose department and member APIs without routing through `qf_query`:
|
|
190
|
+
|
|
191
|
+
1. `qf_departments_list`
|
|
192
|
+
- optional `dept_id`
|
|
193
|
+
- local `keyword`, `limit`, `offset`
|
|
194
|
+
- aliases: `deptId`, `department_id`, `departmentId`
|
|
195
|
+
2. `qf_department_users_list`
|
|
196
|
+
- required `dept_id`, `fetch_child`
|
|
197
|
+
- local `keyword`, `limit`, `offset`
|
|
198
|
+
- aliases: `deptId`, `department_id`, `departmentId`, `fetchChild`
|
|
199
|
+
3. `qf_users_list`
|
|
200
|
+
- required `page_num`, `page_size`
|
|
201
|
+
- aliases: `pageNum`, `pageSize`
|
|
202
|
+
4. `qf_user_get`
|
|
203
|
+
- required `user_id`
|
|
204
|
+
- alias: `userId`
|
|
205
|
+
|
|
206
|
+
CLI examples:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
qingflow-mcp cli call qf_departments_list --args '{"keyword":"销售","limit":20}'
|
|
210
|
+
|
|
211
|
+
qingflow-mcp cli call qf_department_users_list --args '{"deptId":111,"fetchChild":true}'
|
|
212
|
+
|
|
213
|
+
qingflow-mcp cli call qf_users_list --args '{"pageNum":1,"pageSize":100}'
|
|
214
|
+
|
|
215
|
+
qingflow-mcp cli call qf_user_get --args '{"userId":"u_123"}'
|
|
216
|
+
```
|
|
217
|
+
|
|
176
218
|
Return shape:
|
|
177
219
|
|
|
178
220
|
1. success: structured payload `{ "ok": true, "data": ... }` (`meta` only in `output_profile=verbose`)
|
package/dist/qingflow-client.js
CHANGED
|
@@ -33,6 +33,48 @@ export class QingflowClient {
|
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
+
listDepartments(options = {}) {
|
|
37
|
+
return this.request({
|
|
38
|
+
method: "GET",
|
|
39
|
+
path: "/department",
|
|
40
|
+
options: {
|
|
41
|
+
query: {
|
|
42
|
+
deptId: options.deptId !== undefined && options.deptId !== null
|
|
43
|
+
? String(options.deptId)
|
|
44
|
+
: undefined
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
listDepartmentUsers(deptId, options) {
|
|
50
|
+
return this.request({
|
|
51
|
+
method: "GET",
|
|
52
|
+
path: `/department/${encodeURIComponent(deptId)}/user`,
|
|
53
|
+
options: {
|
|
54
|
+
query: {
|
|
55
|
+
fetchChild: options.fetchChild
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
listUsers(options) {
|
|
61
|
+
return this.request({
|
|
62
|
+
method: "GET",
|
|
63
|
+
path: "/user",
|
|
64
|
+
options: {
|
|
65
|
+
query: {
|
|
66
|
+
pageNum: options.pageNum,
|
|
67
|
+
pageSize: options.pageSize
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
getUser(userId) {
|
|
73
|
+
return this.request({
|
|
74
|
+
method: "GET",
|
|
75
|
+
path: `/user/${encodeURIComponent(userId)}`
|
|
76
|
+
});
|
|
77
|
+
}
|
|
36
78
|
getForm(appKey, options = {}) {
|
|
37
79
|
return this.request({
|
|
38
80
|
method: "GET",
|
package/dist/server.js
CHANGED
|
@@ -65,7 +65,7 @@ const REQUEST_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_REQUEST_TIMEOUT_MS
|
|
|
65
65
|
const EXECUTION_BUDGET_MS = toPositiveInt(process.env.QINGFLOW_EXECUTION_BUDGET_MS) ?? 20000;
|
|
66
66
|
const WAIT_RESULT_DEFAULT_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_WAIT_RESULT_TIMEOUT_MS) ?? 5000;
|
|
67
67
|
const WAIT_RESULT_POLL_INTERVAL_MS = toPositiveInt(process.env.QINGFLOW_WAIT_RESULT_POLL_INTERVAL_MS) ?? 500;
|
|
68
|
-
const SERVER_VERSION = "0.3.
|
|
68
|
+
const SERVER_VERSION = "0.3.18";
|
|
69
69
|
const accessToken = process.env.QINGFLOW_ACCESS_TOKEN;
|
|
70
70
|
const baseUrl = process.env.QINGFLOW_BASE_URL;
|
|
71
71
|
if (!accessToken) {
|
|
@@ -215,6 +215,125 @@ const appsSuccessOutputSchema = z.object({
|
|
|
215
215
|
meta: apiMetaSchema
|
|
216
216
|
});
|
|
217
217
|
const appsOutputSchema = appsSuccessOutputSchema;
|
|
218
|
+
const publicDirectorySelectorSchema = z.union([z.string().min(1), z.number().int()]);
|
|
219
|
+
const departmentOutputSchema = z.object({
|
|
220
|
+
dept_id: z.number().int().nullable(),
|
|
221
|
+
name: z.string().nullable(),
|
|
222
|
+
parent_id: z.number().int().nullable(),
|
|
223
|
+
ordinal: z.number().int().nullable(),
|
|
224
|
+
dept_leader_ids: z.array(z.string())
|
|
225
|
+
});
|
|
226
|
+
const directoryUserSchema = z.object({
|
|
227
|
+
user_id: z.string().nullable(),
|
|
228
|
+
name: z.string().nullable(),
|
|
229
|
+
area_code: z.string().nullable(),
|
|
230
|
+
mobile_num: z.string().nullable(),
|
|
231
|
+
email: z.string().nullable(),
|
|
232
|
+
head_img: z.string().nullable(),
|
|
233
|
+
department_ids: z.array(z.string()),
|
|
234
|
+
role_ids: z.array(z.string()),
|
|
235
|
+
custom_role_ids: z.array(z.string()),
|
|
236
|
+
custom_department_ids: z.array(z.string()),
|
|
237
|
+
being_disabled: z.boolean().nullable(),
|
|
238
|
+
being_active: z.boolean().nullable(),
|
|
239
|
+
superior_id: z.string().nullable()
|
|
240
|
+
});
|
|
241
|
+
const departmentListInputPublicSchema = z.object({
|
|
242
|
+
dept_id: publicDirectorySelectorSchema.optional(),
|
|
243
|
+
deptId: publicDirectorySelectorSchema.optional(),
|
|
244
|
+
department_id: publicDirectorySelectorSchema.optional(),
|
|
245
|
+
departmentId: publicDirectorySelectorSchema.optional(),
|
|
246
|
+
keyword: z.string().min(1).optional(),
|
|
247
|
+
limit: z.number().int().positive().max(500).optional(),
|
|
248
|
+
offset: z.number().int().nonnegative().optional()
|
|
249
|
+
});
|
|
250
|
+
const departmentListInputSchema = z.preprocess(normalizeDepartmentListInput, z.object({
|
|
251
|
+
dept_id: z.union([z.string().min(1), z.number().int()]).optional(),
|
|
252
|
+
keyword: z.string().min(1).optional(),
|
|
253
|
+
limit: z.number().int().positive().max(500).optional(),
|
|
254
|
+
offset: z.number().int().nonnegative().optional()
|
|
255
|
+
}));
|
|
256
|
+
const departmentListOutputSchema = z.object({
|
|
257
|
+
ok: z.literal(true),
|
|
258
|
+
data: z.object({
|
|
259
|
+
total_departments: z.number().int().nonnegative(),
|
|
260
|
+
returned_departments: z.number().int().nonnegative(),
|
|
261
|
+
limit: z.number().int().positive(),
|
|
262
|
+
offset: z.number().int().nonnegative(),
|
|
263
|
+
dept_id_filter: z.union([z.string(), z.number(), z.null()]),
|
|
264
|
+
departments: z.array(departmentOutputSchema)
|
|
265
|
+
}),
|
|
266
|
+
meta: apiMetaSchema
|
|
267
|
+
});
|
|
268
|
+
const departmentUsersInputPublicSchema = z.object({
|
|
269
|
+
dept_id: publicDirectorySelectorSchema.optional(),
|
|
270
|
+
deptId: publicDirectorySelectorSchema.optional(),
|
|
271
|
+
department_id: publicDirectorySelectorSchema.optional(),
|
|
272
|
+
departmentId: publicDirectorySelectorSchema.optional(),
|
|
273
|
+
fetch_child: z.boolean().optional(),
|
|
274
|
+
fetchChild: z.boolean().optional(),
|
|
275
|
+
keyword: z.string().min(1).optional(),
|
|
276
|
+
limit: z.number().int().positive().max(500).optional(),
|
|
277
|
+
offset: z.number().int().nonnegative().optional()
|
|
278
|
+
});
|
|
279
|
+
const departmentUsersInputSchema = z.preprocess(normalizeDepartmentUsersInput, z.object({
|
|
280
|
+
dept_id: z.union([z.string().min(1), z.number().int()]).optional(),
|
|
281
|
+
fetch_child: z.boolean().optional(),
|
|
282
|
+
keyword: z.string().min(1).optional(),
|
|
283
|
+
limit: z.number().int().positive().max(500).optional(),
|
|
284
|
+
offset: z.number().int().nonnegative().optional()
|
|
285
|
+
}));
|
|
286
|
+
const departmentUsersOutputSchema = z.object({
|
|
287
|
+
ok: z.literal(true),
|
|
288
|
+
data: z.object({
|
|
289
|
+
dept_id: z.string(),
|
|
290
|
+
fetch_child: z.boolean(),
|
|
291
|
+
leader_ids: z.array(z.string()),
|
|
292
|
+
total_users: z.number().int().nonnegative(),
|
|
293
|
+
returned_users: z.number().int().nonnegative(),
|
|
294
|
+
limit: z.number().int().positive(),
|
|
295
|
+
offset: z.number().int().nonnegative(),
|
|
296
|
+
users: z.array(directoryUserSchema)
|
|
297
|
+
}),
|
|
298
|
+
meta: apiMetaSchema
|
|
299
|
+
});
|
|
300
|
+
const usersListInputPublicSchema = z.object({
|
|
301
|
+
page_num: z.number().int().positive().optional(),
|
|
302
|
+
pageNum: z.number().int().positive().optional(),
|
|
303
|
+
page_size: z.number().int().positive().optional(),
|
|
304
|
+
pageSize: z.number().int().positive().optional()
|
|
305
|
+
});
|
|
306
|
+
const usersListInputSchema = z.preprocess(normalizeUsersListInput, z.object({
|
|
307
|
+
page_num: z.number().int().positive().optional(),
|
|
308
|
+
page_size: z.number().int().positive().optional()
|
|
309
|
+
}));
|
|
310
|
+
const usersListOutputSchema = z.object({
|
|
311
|
+
ok: z.literal(true),
|
|
312
|
+
data: z.object({
|
|
313
|
+
pagination: z.object({
|
|
314
|
+
page_num: z.number().int().positive(),
|
|
315
|
+
page_size: z.number().int().positive(),
|
|
316
|
+
page_amount: z.number().int().nonnegative(),
|
|
317
|
+
result_amount: z.number().int().nonnegative()
|
|
318
|
+
}),
|
|
319
|
+
users: z.array(directoryUserSchema)
|
|
320
|
+
}),
|
|
321
|
+
meta: apiMetaSchema
|
|
322
|
+
});
|
|
323
|
+
const userGetInputPublicSchema = z.object({
|
|
324
|
+
user_id: z.string().min(1).optional(),
|
|
325
|
+
userId: z.string().min(1).optional()
|
|
326
|
+
});
|
|
327
|
+
const userGetInputSchema = z.preprocess(normalizeUserGetInput, z.object({
|
|
328
|
+
user_id: z.string().min(1).optional()
|
|
329
|
+
}));
|
|
330
|
+
const userGetOutputSchema = z.object({
|
|
331
|
+
ok: z.literal(true),
|
|
332
|
+
data: z.object({
|
|
333
|
+
user: directoryUserSchema
|
|
334
|
+
}),
|
|
335
|
+
meta: apiMetaSchema
|
|
336
|
+
});
|
|
218
337
|
const formInputSchema = z.object({
|
|
219
338
|
app_key: z.string().min(1),
|
|
220
339
|
user_id: z.string().min(1).optional(),
|
|
@@ -517,12 +636,23 @@ const createInputSchema = z
|
|
|
517
636
|
const createSuccessOutputSchema = z.object({
|
|
518
637
|
ok: z.literal(true),
|
|
519
638
|
data: z.object({
|
|
639
|
+
status: z.enum(["completed", "pending", "timeout", "failed"]),
|
|
520
640
|
request_id: z.string().nullable(),
|
|
521
641
|
apply_id: z.union([z.string(), z.number(), z.null()]),
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
642
|
+
resource: z
|
|
643
|
+
.object({
|
|
644
|
+
type: z.literal("record"),
|
|
645
|
+
apply_id: z.union([z.string(), z.number()])
|
|
646
|
+
})
|
|
647
|
+
.nullable(),
|
|
648
|
+
next_action: z
|
|
649
|
+
.object({
|
|
650
|
+
tool: z.string(),
|
|
651
|
+
arguments: z.record(z.unknown()),
|
|
652
|
+
reason: z.string().optional()
|
|
653
|
+
})
|
|
654
|
+
.nullable(),
|
|
655
|
+
raw: z.object({ operation_result: z.unknown() }).nullable()
|
|
526
656
|
}),
|
|
527
657
|
meta: apiMetaSchema
|
|
528
658
|
});
|
|
@@ -555,12 +685,23 @@ const updateInputSchema = z
|
|
|
555
685
|
const updateSuccessOutputSchema = z.object({
|
|
556
686
|
ok: z.literal(true),
|
|
557
687
|
data: z.object({
|
|
688
|
+
status: z.enum(["completed", "pending", "timeout", "failed"]),
|
|
558
689
|
request_id: z.string().nullable(),
|
|
559
|
-
apply_id: z.union([z.string(), z.number(), z.null()])
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
690
|
+
apply_id: z.union([z.string(), z.number(), z.null()]),
|
|
691
|
+
resource: z
|
|
692
|
+
.object({
|
|
693
|
+
type: z.literal("record"),
|
|
694
|
+
apply_id: z.union([z.string(), z.number()])
|
|
695
|
+
})
|
|
696
|
+
.nullable(),
|
|
697
|
+
next_action: z
|
|
698
|
+
.object({
|
|
699
|
+
tool: z.string(),
|
|
700
|
+
arguments: z.record(z.unknown()),
|
|
701
|
+
reason: z.string().optional()
|
|
702
|
+
})
|
|
703
|
+
.nullable(),
|
|
704
|
+
raw: z.object({ operation_result: z.unknown() }).nullable()
|
|
564
705
|
}),
|
|
565
706
|
meta: apiMetaSchema
|
|
566
707
|
});
|
|
@@ -1277,6 +1418,219 @@ server.registerTool("qf_apps_list", {
|
|
|
1277
1418
|
return errorResult(error);
|
|
1278
1419
|
}
|
|
1279
1420
|
});
|
|
1421
|
+
server.registerTool("qf_departments_list", {
|
|
1422
|
+
title: "Qingflow Departments List",
|
|
1423
|
+
description: "List departments with optional dept_id filter and local keyword slicing.",
|
|
1424
|
+
inputSchema: departmentListInputPublicSchema,
|
|
1425
|
+
outputSchema: departmentListOutputSchema,
|
|
1426
|
+
annotations: {
|
|
1427
|
+
readOnlyHint: true,
|
|
1428
|
+
idempotentHint: true
|
|
1429
|
+
}
|
|
1430
|
+
}, async (args) => {
|
|
1431
|
+
try {
|
|
1432
|
+
const parsedArgs = departmentListInputSchema.parse(args);
|
|
1433
|
+
let response;
|
|
1434
|
+
try {
|
|
1435
|
+
response = await client.listDepartments(parsedArgs.dept_id !== undefined ? { deptId: parsedArgs.dept_id } : {});
|
|
1436
|
+
}
|
|
1437
|
+
catch (error) {
|
|
1438
|
+
if (parsedArgs.dept_id !== undefined) {
|
|
1439
|
+
throw translateDirectoryApiError(error, {
|
|
1440
|
+
tool: "qf_departments_list",
|
|
1441
|
+
entity: "department",
|
|
1442
|
+
deptId: parsedArgs.dept_id
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
throw error;
|
|
1446
|
+
}
|
|
1447
|
+
const keyword = parsedArgs.keyword?.trim().toLowerCase() ?? null;
|
|
1448
|
+
const limit = parsedArgs.limit ?? 50;
|
|
1449
|
+
const offset = parsedArgs.offset ?? 0;
|
|
1450
|
+
const departments = asArray(asObject(response.result)?.department).map((item) => normalizeDepartment(item));
|
|
1451
|
+
const filtered = keyword
|
|
1452
|
+
? departments.filter((item) => (item.name ?? "").toLowerCase().includes(keyword) ||
|
|
1453
|
+
String(item.dept_id ?? "").toLowerCase().includes(keyword))
|
|
1454
|
+
: departments;
|
|
1455
|
+
const sliced = filtered.slice(offset, offset + limit);
|
|
1456
|
+
return okResult({
|
|
1457
|
+
ok: true,
|
|
1458
|
+
data: {
|
|
1459
|
+
total_departments: filtered.length,
|
|
1460
|
+
returned_departments: sliced.length,
|
|
1461
|
+
limit,
|
|
1462
|
+
offset,
|
|
1463
|
+
dept_id_filter: parsedArgs.dept_id ?? null,
|
|
1464
|
+
departments: sliced
|
|
1465
|
+
},
|
|
1466
|
+
meta: buildMeta(response)
|
|
1467
|
+
}, `Returned ${sliced.length}/${filtered.length} departments`);
|
|
1468
|
+
}
|
|
1469
|
+
catch (error) {
|
|
1470
|
+
return errorResult(error);
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1473
|
+
server.registerTool("qf_department_users_list", {
|
|
1474
|
+
title: "Qingflow Department Users List",
|
|
1475
|
+
description: "List department members with optional child recursion and local keyword slicing.",
|
|
1476
|
+
inputSchema: departmentUsersInputPublicSchema,
|
|
1477
|
+
outputSchema: departmentUsersOutputSchema,
|
|
1478
|
+
annotations: {
|
|
1479
|
+
readOnlyHint: true,
|
|
1480
|
+
idempotentHint: true
|
|
1481
|
+
}
|
|
1482
|
+
}, async (args) => {
|
|
1483
|
+
try {
|
|
1484
|
+
const parsedArgs = departmentUsersInputSchema.parse(args);
|
|
1485
|
+
if (parsedArgs.dept_id === undefined) {
|
|
1486
|
+
throw missingRequiredFieldError({
|
|
1487
|
+
field: "dept_id",
|
|
1488
|
+
tool: "qf_department_users_list",
|
|
1489
|
+
fixHint: "Provide dept_id (or deptId), for example: {\"dept_id\":111,\"fetch_child\":false}."
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
if (parsedArgs.fetch_child === undefined) {
|
|
1493
|
+
throw missingRequiredFieldError({
|
|
1494
|
+
field: "fetch_child",
|
|
1495
|
+
tool: "qf_department_users_list",
|
|
1496
|
+
fixHint: "Provide fetch_child as a native JSON boolean, for example: {\"dept_id\":111,\"fetch_child\":true}."
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
let response;
|
|
1500
|
+
try {
|
|
1501
|
+
response = await client.listDepartmentUsers(String(parsedArgs.dept_id), {
|
|
1502
|
+
fetchChild: parsedArgs.fetch_child
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
catch (error) {
|
|
1506
|
+
throw translateDirectoryApiError(error, {
|
|
1507
|
+
tool: "qf_department_users_list",
|
|
1508
|
+
entity: "department",
|
|
1509
|
+
deptId: parsedArgs.dept_id
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
const keyword = parsedArgs.keyword?.trim().toLowerCase() ?? null;
|
|
1513
|
+
const limit = parsedArgs.limit ?? 200;
|
|
1514
|
+
const offset = parsedArgs.offset ?? 0;
|
|
1515
|
+
const result = asObject(response.result);
|
|
1516
|
+
const users = asArray(result?.userList).map((item) => normalizeUser(item));
|
|
1517
|
+
const filtered = keyword
|
|
1518
|
+
? users.filter((item) => [item.user_id, item.name, item.email, item.mobile_num]
|
|
1519
|
+
.map((value) => (value ?? "").toLowerCase())
|
|
1520
|
+
.some((value) => value.includes(keyword)))
|
|
1521
|
+
: users;
|
|
1522
|
+
const sliced = filtered.slice(offset, offset + limit);
|
|
1523
|
+
return okResult({
|
|
1524
|
+
ok: true,
|
|
1525
|
+
data: {
|
|
1526
|
+
dept_id: String(parsedArgs.dept_id),
|
|
1527
|
+
fetch_child: parsedArgs.fetch_child,
|
|
1528
|
+
leader_ids: normalizeStringArray(result?.leaderIds),
|
|
1529
|
+
total_users: filtered.length,
|
|
1530
|
+
returned_users: sliced.length,
|
|
1531
|
+
limit,
|
|
1532
|
+
offset,
|
|
1533
|
+
users: sliced
|
|
1534
|
+
},
|
|
1535
|
+
meta: buildMeta(response)
|
|
1536
|
+
}, `Returned ${sliced.length}/${filtered.length} department users`);
|
|
1537
|
+
}
|
|
1538
|
+
catch (error) {
|
|
1539
|
+
return errorResult(error);
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
server.registerTool("qf_users_list", {
|
|
1543
|
+
title: "Qingflow Users List",
|
|
1544
|
+
description: "List workspace users with explicit pagination.",
|
|
1545
|
+
inputSchema: usersListInputPublicSchema,
|
|
1546
|
+
outputSchema: usersListOutputSchema,
|
|
1547
|
+
annotations: {
|
|
1548
|
+
readOnlyHint: true,
|
|
1549
|
+
idempotentHint: true
|
|
1550
|
+
}
|
|
1551
|
+
}, async (args) => {
|
|
1552
|
+
try {
|
|
1553
|
+
const parsedArgs = usersListInputSchema.parse(args);
|
|
1554
|
+
if (parsedArgs.page_num === undefined) {
|
|
1555
|
+
throw missingRequiredFieldError({
|
|
1556
|
+
field: "page_num",
|
|
1557
|
+
tool: "qf_users_list",
|
|
1558
|
+
fixHint: "Provide page_num (or pageNum), for example: {\"page_num\":1,\"page_size\":100}."
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
if (parsedArgs.page_size === undefined) {
|
|
1562
|
+
throw missingRequiredFieldError({
|
|
1563
|
+
field: "page_size",
|
|
1564
|
+
tool: "qf_users_list",
|
|
1565
|
+
fixHint: "Provide page_size (or pageSize), for example: {\"page_num\":1,\"page_size\":100}."
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
const response = await client.listUsers({
|
|
1569
|
+
pageNum: parsedArgs.page_num,
|
|
1570
|
+
pageSize: parsedArgs.page_size
|
|
1571
|
+
});
|
|
1572
|
+
const result = asObject(response.result);
|
|
1573
|
+
const users = asArray(result?.result).map((item) => normalizeUser(item));
|
|
1574
|
+
return okResult({
|
|
1575
|
+
ok: true,
|
|
1576
|
+
data: {
|
|
1577
|
+
pagination: {
|
|
1578
|
+
page_num: toPositiveInt(result?.pageNum) ?? parsedArgs.page_num,
|
|
1579
|
+
page_size: toPositiveInt(result?.pageSize) ?? parsedArgs.page_size,
|
|
1580
|
+
page_amount: toNonNegativeInt(result?.pageAmount) ?? 0,
|
|
1581
|
+
result_amount: toNonNegativeInt(result?.resultAmount) ?? users.length
|
|
1582
|
+
},
|
|
1583
|
+
users
|
|
1584
|
+
},
|
|
1585
|
+
meta: buildMeta(response)
|
|
1586
|
+
}, `Returned ${users.length} users`);
|
|
1587
|
+
}
|
|
1588
|
+
catch (error) {
|
|
1589
|
+
return errorResult(error);
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
server.registerTool("qf_user_get", {
|
|
1593
|
+
title: "Qingflow User Get",
|
|
1594
|
+
description: "Get one workspace user by user_id.",
|
|
1595
|
+
inputSchema: userGetInputPublicSchema,
|
|
1596
|
+
outputSchema: userGetOutputSchema,
|
|
1597
|
+
annotations: {
|
|
1598
|
+
readOnlyHint: true,
|
|
1599
|
+
idempotentHint: true
|
|
1600
|
+
}
|
|
1601
|
+
}, async (args) => {
|
|
1602
|
+
try {
|
|
1603
|
+
const parsedArgs = userGetInputSchema.parse(args);
|
|
1604
|
+
if (!parsedArgs.user_id) {
|
|
1605
|
+
throw missingRequiredFieldError({
|
|
1606
|
+
field: "user_id",
|
|
1607
|
+
tool: "qf_user_get",
|
|
1608
|
+
fixHint: "Provide user_id (or userId), for example: {\"user_id\":\"u_123\"}."
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
let response;
|
|
1612
|
+
try {
|
|
1613
|
+
response = await client.getUser(parsedArgs.user_id);
|
|
1614
|
+
}
|
|
1615
|
+
catch (error) {
|
|
1616
|
+
throw translateDirectoryApiError(error, {
|
|
1617
|
+
tool: "qf_user_get",
|
|
1618
|
+
entity: "user",
|
|
1619
|
+
userId: parsedArgs.user_id
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
return okResult({
|
|
1623
|
+
ok: true,
|
|
1624
|
+
data: {
|
|
1625
|
+
user: normalizeUser(response.result)
|
|
1626
|
+
},
|
|
1627
|
+
meta: buildMeta(response)
|
|
1628
|
+
}, `Fetched user ${parsedArgs.user_id}`);
|
|
1629
|
+
}
|
|
1630
|
+
catch (error) {
|
|
1631
|
+
return errorResult(error);
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1280
1634
|
server.registerTool("qf_form_get", {
|
|
1281
1635
|
title: "Qingflow Form Get",
|
|
1282
1636
|
description: "Get form metadata and compact field summaries for one app.",
|
|
@@ -1578,30 +1932,36 @@ server.registerTool("qf_record_create", {
|
|
|
1578
1932
|
const immediateApplyId = result?.applyId ?? null;
|
|
1579
1933
|
const shouldWaitForResult = (parsedArgs.wait_result ?? false) && requestId !== null && immediateApplyId === null;
|
|
1580
1934
|
let finalApplyId = immediateApplyId;
|
|
1581
|
-
let
|
|
1582
|
-
let
|
|
1583
|
-
let operationResult = null;
|
|
1935
|
+
let waitStatus = immediateApplyId !== null ? "completed" : "pending";
|
|
1936
|
+
let rawOperationResult = null;
|
|
1584
1937
|
if (shouldWaitForResult) {
|
|
1585
1938
|
const waited = await waitForOperationResolution({
|
|
1586
1939
|
requestId: requestId,
|
|
1587
1940
|
timeoutMs: parsedArgs.wait_timeout_ms ?? WAIT_RESULT_DEFAULT_TIMEOUT_MS
|
|
1588
1941
|
});
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
operationResult = waited.operationResult;
|
|
1942
|
+
waitStatus = waited.status;
|
|
1943
|
+
rawOperationResult = waited.operationResult;
|
|
1592
1944
|
finalApplyId = waited.applyId;
|
|
1593
1945
|
}
|
|
1946
|
+
const createResource = finalApplyId !== null ? { type: "record", apply_id: finalApplyId } : null;
|
|
1947
|
+
const createNextAction = waitStatus === "pending" || waitStatus === "timeout"
|
|
1948
|
+
? {
|
|
1949
|
+
tool: "qf_operation_get",
|
|
1950
|
+
arguments: { request_id: requestId },
|
|
1951
|
+
reason: waitStatus === "timeout"
|
|
1952
|
+
? "Operation timed out; poll again to check completion."
|
|
1953
|
+
: "Operation is async; poll to check completion."
|
|
1954
|
+
}
|
|
1955
|
+
: null;
|
|
1594
1956
|
return okResult({
|
|
1595
1957
|
ok: true,
|
|
1596
1958
|
data: {
|
|
1959
|
+
status: waitStatus,
|
|
1597
1960
|
request_id: requestId,
|
|
1598
1961
|
apply_id: finalApplyId,
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
async_hint: isResolved
|
|
1603
|
-
? "Record created and resolved."
|
|
1604
|
-
: "Use qf_operation_get with request_id to fetch the result."
|
|
1962
|
+
resource: createResource,
|
|
1963
|
+
next_action: createNextAction,
|
|
1964
|
+
raw: rawOperationResult !== null ? { operation_result: rawOperationResult } : null
|
|
1605
1965
|
},
|
|
1606
1966
|
meta: buildMeta(response)
|
|
1607
1967
|
}, `Create request sent for app ${parsedArgs.app_key}`);
|
|
@@ -1624,7 +1984,11 @@ server.registerTool("qf_record_update", {
|
|
|
1624
1984
|
const parsedArgs = updateInputSchema.parse(args);
|
|
1625
1985
|
const requiresForm = needsFormResolution(parsedArgs.fields);
|
|
1626
1986
|
if (requiresForm && !parsedArgs.app_key) {
|
|
1627
|
-
throw
|
|
1987
|
+
throw missingRequiredFieldError({
|
|
1988
|
+
field: "app_key",
|
|
1989
|
+
tool: "qf_record_update",
|
|
1990
|
+
fixHint: "Provide app_key when fields uses title-based keys, or switch fields to numeric que_id."
|
|
1991
|
+
});
|
|
1628
1992
|
}
|
|
1629
1993
|
const form = requiresForm && parsedArgs.app_key
|
|
1630
1994
|
? await getFormCached(parsedArgs.app_key, parsedArgs.user_id, Boolean(parsedArgs.force_refresh_form))
|
|
@@ -1639,31 +2003,45 @@ server.registerTool("qf_record_update", {
|
|
|
1639
2003
|
const result = asObject(response.result);
|
|
1640
2004
|
const updateRequestId = asNullableString(result?.requestId);
|
|
1641
2005
|
const shouldWaitForUpdate = (parsedArgs.wait_result ?? false) && updateRequestId !== null;
|
|
1642
|
-
let
|
|
1643
|
-
let
|
|
1644
|
-
let
|
|
1645
|
-
let updateApplyId = null;
|
|
2006
|
+
let updateStatus = "pending";
|
|
2007
|
+
let updateRawOperationResult = null;
|
|
2008
|
+
let updateApplyId = parsedArgs.apply_id;
|
|
1646
2009
|
if (shouldWaitForUpdate) {
|
|
1647
2010
|
const waited = await waitForOperationResolution({
|
|
1648
2011
|
requestId: updateRequestId,
|
|
1649
2012
|
timeoutMs: parsedArgs.wait_timeout_ms ?? WAIT_RESULT_DEFAULT_TIMEOUT_MS
|
|
1650
2013
|
});
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
2014
|
+
updateStatus = waited.status;
|
|
2015
|
+
updateRawOperationResult = waited.operationResult;
|
|
2016
|
+
// For updates, the apply_id is already known from input; keep it unless operation returned a different one
|
|
2017
|
+
if (waited.applyId !== null) {
|
|
2018
|
+
updateApplyId = waited.applyId;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
else if (updateRequestId === null) {
|
|
2022
|
+
// No async operation — synchronous completion
|
|
2023
|
+
updateStatus = "completed";
|
|
1655
2024
|
}
|
|
2025
|
+
// else: wait_result=false but has requestId — submitted, not polled, stays "pending"
|
|
2026
|
+
const updateResource = updateApplyId !== null ? { type: "record", apply_id: updateApplyId } : null;
|
|
2027
|
+
const updateNextAction = updateStatus === "pending" || updateStatus === "timeout"
|
|
2028
|
+
? {
|
|
2029
|
+
tool: "qf_operation_get",
|
|
2030
|
+
arguments: { request_id: updateRequestId },
|
|
2031
|
+
reason: updateStatus === "timeout"
|
|
2032
|
+
? "Operation timed out; poll again to check completion."
|
|
2033
|
+
: "Operation is async; poll to check completion."
|
|
2034
|
+
}
|
|
2035
|
+
: null;
|
|
1656
2036
|
return okResult({
|
|
1657
2037
|
ok: true,
|
|
1658
2038
|
data: {
|
|
2039
|
+
status: updateStatus,
|
|
1659
2040
|
request_id: updateRequestId,
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
async_hint: updateIsResolved
|
|
1665
|
-
? "Record updated and resolved."
|
|
1666
|
-
: "Use qf_operation_get with request_id to fetch the update result."
|
|
2041
|
+
apply_id: updateApplyId,
|
|
2042
|
+
resource: updateResource,
|
|
2043
|
+
next_action: updateNextAction,
|
|
2044
|
+
raw: updateRawOperationResult !== null ? { operation_result: updateRawOperationResult } : null
|
|
1667
2045
|
},
|
|
1668
2046
|
meta: buildMeta(response)
|
|
1669
2047
|
}, `Update request sent for apply ${String(parsedArgs.apply_id)}`);
|
|
@@ -1980,6 +2358,65 @@ function buildMeta(response) {
|
|
|
1980
2358
|
base_url: baseUrl
|
|
1981
2359
|
};
|
|
1982
2360
|
}
|
|
2361
|
+
function normalizeStringArray(value) {
|
|
2362
|
+
return uniqueStringList(asArray(value)
|
|
2363
|
+
.map((item) => asNullableString(item)?.trim() ?? "")
|
|
2364
|
+
.filter((item) => item.length > 0));
|
|
2365
|
+
}
|
|
2366
|
+
function normalizeDepartment(raw) {
|
|
2367
|
+
const obj = asObject(raw) ?? {};
|
|
2368
|
+
return {
|
|
2369
|
+
dept_id: toNonNegativeInt(obj.deptId),
|
|
2370
|
+
name: asNullableString(obj.name),
|
|
2371
|
+
parent_id: toNonNegativeInt(obj.parentId),
|
|
2372
|
+
ordinal: toNonNegativeInt(obj.ordinal),
|
|
2373
|
+
dept_leader_ids: normalizeStringArray(obj.deptLeader)
|
|
2374
|
+
};
|
|
2375
|
+
}
|
|
2376
|
+
function normalizeUser(raw) {
|
|
2377
|
+
const obj = asObject(raw) ?? {};
|
|
2378
|
+
return {
|
|
2379
|
+
user_id: asNullableString(obj.userId),
|
|
2380
|
+
name: asNullableString(obj.name),
|
|
2381
|
+
area_code: asNullableString(obj.areaCode),
|
|
2382
|
+
mobile_num: asNullableString(obj.mobileNum),
|
|
2383
|
+
email: asNullableString(obj.email),
|
|
2384
|
+
head_img: asNullableString(obj.headImg),
|
|
2385
|
+
department_ids: normalizeStringArray(obj.department),
|
|
2386
|
+
role_ids: normalizeStringArray(obj.role),
|
|
2387
|
+
custom_role_ids: normalizeStringArray(obj.customRole),
|
|
2388
|
+
custom_department_ids: normalizeStringArray(obj.customDepartment),
|
|
2389
|
+
being_disabled: typeof obj.beingDisabled === "boolean" ? obj.beingDisabled : null,
|
|
2390
|
+
being_active: typeof obj.beingActive === "boolean" ? obj.beingActive : null,
|
|
2391
|
+
superior_id: asNullableString(obj.superiorId)
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
function translateDirectoryApiError(error, params) {
|
|
2395
|
+
if (error instanceof QingflowApiError &&
|
|
2396
|
+
(error.httpStatus === 404 || error.errCode === 404)) {
|
|
2397
|
+
if (params.entity === "department") {
|
|
2398
|
+
return new InputValidationError({
|
|
2399
|
+
message: `Department \"${String(params.deptId)}\" not found`,
|
|
2400
|
+
errorCode: "DEPARTMENT_NOT_FOUND",
|
|
2401
|
+
fixHint: "Call qf_departments_list first to confirm the exact dept_id.",
|
|
2402
|
+
details: {
|
|
2403
|
+
tool: params.tool,
|
|
2404
|
+
dept_id: params.deptId
|
|
2405
|
+
}
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
return new InputValidationError({
|
|
2409
|
+
message: `User \"${params.userId}\" not found`,
|
|
2410
|
+
errorCode: "USER_NOT_FOUND",
|
|
2411
|
+
fixHint: "Call qf_users_list or qf_department_users_list first to obtain a valid user_id.",
|
|
2412
|
+
details: {
|
|
2413
|
+
tool: params.tool,
|
|
2414
|
+
user_id: params.userId
|
|
2415
|
+
}
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
2419
|
+
}
|
|
1983
2420
|
function resolveOutputProfile(value) {
|
|
1984
2421
|
return value === "verbose" ? "verbose" : DEFAULT_OUTPUT_PROFILE;
|
|
1985
2422
|
}
|
|
@@ -2075,6 +2512,76 @@ function normalizeToolSpecInput(raw) {
|
|
|
2075
2512
|
include_all: coerceBooleanLike(normalizedObj.include_all)
|
|
2076
2513
|
};
|
|
2077
2514
|
}
|
|
2515
|
+
function normalizeDepartmentListInput(raw) {
|
|
2516
|
+
const parsedRoot = parseJsonLikeDeep(raw);
|
|
2517
|
+
const obj = asObject(parsedRoot);
|
|
2518
|
+
if (!obj) {
|
|
2519
|
+
return parsedRoot;
|
|
2520
|
+
}
|
|
2521
|
+
const normalizedObj = applyAliases(obj, {
|
|
2522
|
+
deptId: "dept_id",
|
|
2523
|
+
department_id: "dept_id",
|
|
2524
|
+
departmentId: "dept_id"
|
|
2525
|
+
});
|
|
2526
|
+
return {
|
|
2527
|
+
...normalizedObj,
|
|
2528
|
+
dept_id: coerceNumberLike(normalizeSelectorInputValue(normalizedObj.dept_id)),
|
|
2529
|
+
keyword: coerceStringLike(normalizedObj.keyword),
|
|
2530
|
+
limit: coerceNumberLike(normalizedObj.limit),
|
|
2531
|
+
offset: coerceNumberLike(normalizedObj.offset)
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
function normalizeDepartmentUsersInput(raw) {
|
|
2535
|
+
const parsedRoot = parseJsonLikeDeep(raw);
|
|
2536
|
+
const obj = asObject(parsedRoot);
|
|
2537
|
+
if (!obj) {
|
|
2538
|
+
return parsedRoot;
|
|
2539
|
+
}
|
|
2540
|
+
const normalizedObj = applyAliases(obj, {
|
|
2541
|
+
deptId: "dept_id",
|
|
2542
|
+
department_id: "dept_id",
|
|
2543
|
+
departmentId: "dept_id",
|
|
2544
|
+
fetchChild: "fetch_child"
|
|
2545
|
+
});
|
|
2546
|
+
return {
|
|
2547
|
+
...normalizedObj,
|
|
2548
|
+
dept_id: coerceNumberLike(normalizeSelectorInputValue(normalizedObj.dept_id)),
|
|
2549
|
+
fetch_child: coerceBooleanLike(normalizedObj.fetch_child),
|
|
2550
|
+
keyword: coerceStringLike(normalizedObj.keyword),
|
|
2551
|
+
limit: coerceNumberLike(normalizedObj.limit),
|
|
2552
|
+
offset: coerceNumberLike(normalizedObj.offset)
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
function normalizeUsersListInput(raw) {
|
|
2556
|
+
const parsedRoot = parseJsonLikeDeep(raw);
|
|
2557
|
+
const obj = asObject(parsedRoot);
|
|
2558
|
+
if (!obj) {
|
|
2559
|
+
return parsedRoot;
|
|
2560
|
+
}
|
|
2561
|
+
const normalizedObj = applyAliases(obj, {
|
|
2562
|
+
pageNum: "page_num",
|
|
2563
|
+
pageSize: "page_size"
|
|
2564
|
+
});
|
|
2565
|
+
return {
|
|
2566
|
+
...normalizedObj,
|
|
2567
|
+
page_num: coerceNumberLike(normalizedObj.page_num),
|
|
2568
|
+
page_size: coerceNumberLike(normalizedObj.page_size)
|
|
2569
|
+
};
|
|
2570
|
+
}
|
|
2571
|
+
function normalizeUserGetInput(raw) {
|
|
2572
|
+
const parsedRoot = parseJsonLikeDeep(raw);
|
|
2573
|
+
const obj = asObject(parsedRoot);
|
|
2574
|
+
if (!obj) {
|
|
2575
|
+
return parsedRoot;
|
|
2576
|
+
}
|
|
2577
|
+
const normalizedObj = applyAliases(obj, {
|
|
2578
|
+
userId: "user_id"
|
|
2579
|
+
});
|
|
2580
|
+
return {
|
|
2581
|
+
...normalizedObj,
|
|
2582
|
+
user_id: coerceStringLike(normalizedObj.user_id)
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2078
2585
|
function buildToolSpecCatalog() {
|
|
2079
2586
|
return [
|
|
2080
2587
|
{
|
|
@@ -2104,6 +2611,63 @@ function buildToolSpecCatalog() {
|
|
|
2104
2611
|
limit: 20
|
|
2105
2612
|
}
|
|
2106
2613
|
},
|
|
2614
|
+
{
|
|
2615
|
+
tool: "qf_departments_list",
|
|
2616
|
+
required: [],
|
|
2617
|
+
limits: {
|
|
2618
|
+
limit_max: 500,
|
|
2619
|
+
offset_min: 0,
|
|
2620
|
+
input_contract: "strict JSON only; dept_id must be a native JSON string or number when provided"
|
|
2621
|
+
},
|
|
2622
|
+
aliases: collectAliasHints(["dept_id"], {
|
|
2623
|
+
dept_id: ["deptId", "department_id", "departmentId"]
|
|
2624
|
+
}),
|
|
2625
|
+
minimal_example: {
|
|
2626
|
+
dept_id: 111,
|
|
2627
|
+
limit: 20
|
|
2628
|
+
}
|
|
2629
|
+
},
|
|
2630
|
+
{
|
|
2631
|
+
tool: "qf_department_users_list",
|
|
2632
|
+
required: ["dept_id", "fetch_child"],
|
|
2633
|
+
limits: {
|
|
2634
|
+
limit_max: 500,
|
|
2635
|
+
offset_min: 0,
|
|
2636
|
+
input_contract: "strict JSON only; fetch_child must be a native JSON boolean"
|
|
2637
|
+
},
|
|
2638
|
+
aliases: collectAliasHints(["dept_id", "fetch_child"], {
|
|
2639
|
+
dept_id: ["deptId", "department_id", "departmentId"],
|
|
2640
|
+
fetch_child: ["fetchChild"]
|
|
2641
|
+
}),
|
|
2642
|
+
minimal_example: {
|
|
2643
|
+
dept_id: 111,
|
|
2644
|
+
fetch_child: true,
|
|
2645
|
+
limit: 50
|
|
2646
|
+
}
|
|
2647
|
+
},
|
|
2648
|
+
{
|
|
2649
|
+
tool: "qf_users_list",
|
|
2650
|
+
required: ["page_num", "page_size"],
|
|
2651
|
+
limits: {
|
|
2652
|
+
input_contract: "strict JSON only; page_num/page_size must be native JSON numbers"
|
|
2653
|
+
},
|
|
2654
|
+
aliases: collectAliasHints(["page_num", "page_size"], {}),
|
|
2655
|
+
minimal_example: {
|
|
2656
|
+
page_num: 1,
|
|
2657
|
+
page_size: 100
|
|
2658
|
+
}
|
|
2659
|
+
},
|
|
2660
|
+
{
|
|
2661
|
+
tool: "qf_user_get",
|
|
2662
|
+
required: ["user_id"],
|
|
2663
|
+
limits: {
|
|
2664
|
+
input_contract: "strict JSON only; user_id must be a native JSON string"
|
|
2665
|
+
},
|
|
2666
|
+
aliases: collectAliasHints(["user_id"], {}),
|
|
2667
|
+
minimal_example: {
|
|
2668
|
+
user_id: "u_123"
|
|
2669
|
+
}
|
|
2670
|
+
},
|
|
2107
2671
|
{
|
|
2108
2672
|
tool: "qf_form_get",
|
|
2109
2673
|
required: ["app_key"],
|
|
@@ -3362,7 +3926,7 @@ function inferPlanMissingRequired(tool, args) {
|
|
|
3362
3926
|
missing.push("select_columns");
|
|
3363
3927
|
}
|
|
3364
3928
|
}
|
|
3365
|
-
else {
|
|
3929
|
+
else if (queryMode === "list") {
|
|
3366
3930
|
if (!hasAppKey) {
|
|
3367
3931
|
missing.push("app_key");
|
|
3368
3932
|
}
|
|
@@ -3370,6 +3934,9 @@ function inferPlanMissingRequired(tool, args) {
|
|
|
3370
3934
|
missing.push("select_columns");
|
|
3371
3935
|
}
|
|
3372
3936
|
}
|
|
3937
|
+
else if (!hasAppKey) {
|
|
3938
|
+
missing.push("app_key");
|
|
3939
|
+
}
|
|
3373
3940
|
}
|
|
3374
3941
|
return missing;
|
|
3375
3942
|
}
|
|
@@ -4516,12 +5083,12 @@ async function executeRecordsExport(format, args) {
|
|
|
4516
5083
|
app_key: args.app_key,
|
|
4517
5084
|
selected_columns: selectedColumnsForRows,
|
|
4518
5085
|
filters: echoFilters(effectiveFilters),
|
|
4519
|
-
time_range:
|
|
5086
|
+
time_range: timeRangeResolution.mapping
|
|
4520
5087
|
? {
|
|
4521
|
-
column: String(args.time_range.
|
|
4522
|
-
from:
|
|
4523
|
-
to:
|
|
4524
|
-
timezone:
|
|
5088
|
+
column: String(args.time_range?.column ?? timeRangeResolution.mapping.requested ?? ""),
|
|
5089
|
+
from: timeRangeResolution.time_range?.from ?? null,
|
|
5090
|
+
to: timeRangeResolution.time_range?.to ?? null,
|
|
5091
|
+
timezone: timeRangeResolution.time_range?.timezone ?? null
|
|
4525
5092
|
}
|
|
4526
5093
|
: null
|
|
4527
5094
|
}, sourcePages);
|
|
@@ -4547,7 +5114,6 @@ async function executeRecordsExport(format, args) {
|
|
|
4547
5114
|
? {
|
|
4548
5115
|
completeness,
|
|
4549
5116
|
evidence,
|
|
4550
|
-
resolved_mappings: resolvedMappings,
|
|
4551
5117
|
execution: {
|
|
4552
5118
|
scanned_pages: fetchedPages,
|
|
4553
5119
|
requested_pages: requestedPages,
|
|
@@ -4562,6 +5128,7 @@ async function executeRecordsExport(format, args) {
|
|
|
4562
5128
|
? {
|
|
4563
5129
|
completeness,
|
|
4564
5130
|
evidence,
|
|
5131
|
+
resolved_mappings: resolvedMappings,
|
|
4565
5132
|
error_code: null,
|
|
4566
5133
|
fix_hint: null
|
|
4567
5134
|
}
|
|
@@ -6441,6 +7008,13 @@ function isPendingOperationStatus(status) {
|
|
|
6441
7008
|
return ["PENDING", "PROCESSING", "RUNNING", "IN_PROGRESS", "QUEUED"].includes(status);
|
|
6442
7009
|
}
|
|
6443
7010
|
function extractOperationApplyId(operationResult) {
|
|
7011
|
+
// Handle case where operationResult itself is a numeric string or number (the apply_id directly)
|
|
7012
|
+
if (typeof operationResult === "string" && /^\d+$/.test(operationResult.trim())) {
|
|
7013
|
+
return operationResult.trim();
|
|
7014
|
+
}
|
|
7015
|
+
if (typeof operationResult === "number" && Number.isFinite(operationResult)) {
|
|
7016
|
+
return operationResult;
|
|
7017
|
+
}
|
|
6444
7018
|
const obj = asObject(operationResult);
|
|
6445
7019
|
return obj?.applyId ?? obj?.apply_id ?? null;
|
|
6446
7020
|
}
|
|
@@ -6450,15 +7024,14 @@ async function waitForOperationResolution(params) {
|
|
|
6450
7024
|
while (Date.now() <= deadline) {
|
|
6451
7025
|
const response = await client.getOperation(params.requestId);
|
|
6452
7026
|
lastResult = response.result;
|
|
6453
|
-
const
|
|
7027
|
+
const opStatus = extractOperationStatus(lastResult);
|
|
6454
7028
|
const applyId = extractOperationApplyId(lastResult);
|
|
6455
|
-
if (
|
|
6456
|
-
return {
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
};
|
|
7029
|
+
if (applyId !== null) {
|
|
7030
|
+
return { status: "completed", operationResult: lastResult, applyId };
|
|
7031
|
+
}
|
|
7032
|
+
if (opStatus && !isPendingOperationStatus(opStatus)) {
|
|
7033
|
+
// Non-pending status but no apply_id — treat as failed
|
|
7034
|
+
return { status: "failed", operationResult: lastResult, applyId: null };
|
|
6462
7035
|
}
|
|
6463
7036
|
const remaining = deadline - Date.now();
|
|
6464
7037
|
if (remaining <= 0) {
|
|
@@ -6466,12 +7039,12 @@ async function waitForOperationResolution(params) {
|
|
|
6466
7039
|
}
|
|
6467
7040
|
await delay(Math.min(WAIT_RESULT_POLL_INTERVAL_MS, remaining));
|
|
6468
7041
|
}
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
operationResult: lastResult,
|
|
6473
|
-
|
|
6474
|
-
};
|
|
7042
|
+
// Timed out — check if last result has apply_id anyway (edge case)
|
|
7043
|
+
const finalApplyId = extractOperationApplyId(lastResult);
|
|
7044
|
+
if (finalApplyId !== null) {
|
|
7045
|
+
return { status: "completed", operationResult: lastResult, applyId: finalApplyId };
|
|
7046
|
+
}
|
|
7047
|
+
return { status: "timeout", operationResult: lastResult, applyId: null };
|
|
6475
7048
|
}
|
|
6476
7049
|
function resolveListItemLimit(params) {
|
|
6477
7050
|
if (params.total <= 0) {
|