qingflow-mcp 0.3.17 → 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 +519 -1
- 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(),
|
|
@@ -1299,6 +1418,219 @@ server.registerTool("qf_apps_list", {
|
|
|
1299
1418
|
return errorResult(error);
|
|
1300
1419
|
}
|
|
1301
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
|
+
});
|
|
1302
1634
|
server.registerTool("qf_form_get", {
|
|
1303
1635
|
title: "Qingflow Form Get",
|
|
1304
1636
|
description: "Get form metadata and compact field summaries for one app.",
|
|
@@ -2026,6 +2358,65 @@ function buildMeta(response) {
|
|
|
2026
2358
|
base_url: baseUrl
|
|
2027
2359
|
};
|
|
2028
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
|
+
}
|
|
2029
2420
|
function resolveOutputProfile(value) {
|
|
2030
2421
|
return value === "verbose" ? "verbose" : DEFAULT_OUTPUT_PROFILE;
|
|
2031
2422
|
}
|
|
@@ -2121,6 +2512,76 @@ function normalizeToolSpecInput(raw) {
|
|
|
2121
2512
|
include_all: coerceBooleanLike(normalizedObj.include_all)
|
|
2122
2513
|
};
|
|
2123
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
|
+
}
|
|
2124
2585
|
function buildToolSpecCatalog() {
|
|
2125
2586
|
return [
|
|
2126
2587
|
{
|
|
@@ -2150,6 +2611,63 @@ function buildToolSpecCatalog() {
|
|
|
2150
2611
|
limit: 20
|
|
2151
2612
|
}
|
|
2152
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
|
+
},
|
|
2153
2671
|
{
|
|
2154
2672
|
tool: "qf_form_get",
|
|
2155
2673
|
required: ["app_key"],
|