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 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.14
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 required (`max_rows` defaults to 200 when omitted).
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 (only requested `select_columns`).
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`)
@@ -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.17";
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"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qingflow-mcp",
3
- "version": "0.3.17",
3
+ "version": "0.3.18",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",