qingflow-mcp 0.3.18 → 0.3.19

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,9 @@
3
3
  This MCP server wraps Qingflow OpenAPI for:
4
4
 
5
5
  - `qf_apps_list`
6
+ - `qf_apps_info_list`
7
+ - `qf_app_info_get`
8
+ - `qf_app_packages_list`
6
9
  - `qf_departments_list`
7
10
  - `qf_department_users_list`
8
11
  - `qf_users_list`
@@ -17,6 +20,8 @@ This MCP server wraps Qingflow OpenAPI for:
17
20
  - `qf_export_json`
18
21
  - `qf_query` (unified read entry: list / record / summary)
19
22
  - `qf_records_aggregate` (deterministic grouped metrics)
23
+ - `qf_apply_audit_records_list`
24
+ - `qf_apply_audit_record_get`
20
25
  - `qf_record_create`
21
26
  - `qf_record_update`
22
27
  - `qf_operation_get`
@@ -112,7 +117,7 @@ npm i -g git+https://github.com/853046310/qingflow-mcp.git
112
117
  Install from npm (pinned version):
113
118
 
114
119
  ```bash
115
- npm i -g qingflow-mcp@0.3.18
120
+ npm i -g qingflow-mcp@0.3.19
116
121
  ```
117
122
 
118
123
  Or one-click installer:
@@ -159,6 +164,18 @@ Directory / org flow:
159
164
  3. `qf_users_list` for workspace-wide pagination.
160
165
  4. `qf_user_get` for one exact user.
161
166
 
167
+ Admin app flow:
168
+
169
+ 1. `qf_apps_list` for lightweight visible app listing.
170
+ 2. `qf_apps_info_list` for admin-level app detail listing.
171
+ 3. `qf_app_info_get` for one exact app.
172
+ 4. `qf_app_packages_list` for user-visible app packages.
173
+
174
+ Audit flow:
175
+
176
+ 1. `qf_apply_audit_records_list` to inspect one record's workflow history.
177
+ 2. `qf_apply_audit_record_get` to inspect one audit record's field modifications.
178
+
162
179
  Full calling contract (Chinese):
163
180
 
164
181
  - [MCP 调用规范](./docs/MCP_CALLING_SPEC.md)
@@ -215,6 +232,51 @@ qingflow-mcp cli call qf_users_list --args '{"pageNum":1,"pageSize":100}'
215
232
  qingflow-mcp cli call qf_user_get --args '{"userId":"u_123"}'
216
233
  ```
217
234
 
235
+ ## Admin App Tools
236
+
237
+ These tools expose admin-facing app and package metadata without routing through `qf_query`:
238
+
239
+ 1. `qf_apps_info_list`
240
+ - required `page_num`, `page_size`
241
+ - optional `app_key`
242
+ - aliases: `pageNum`, `pageSize`, `appKey`
243
+ 2. `qf_app_info_get`
244
+ - required `app_key`
245
+ - alias: `appKey`
246
+ 3. `qf_app_packages_list`
247
+ - required `user_id`
248
+ - optional `tag_id`, `keyword`, `limit`, `offset`
249
+ - aliases: `userId`, `tagId`
250
+
251
+ CLI examples:
252
+
253
+ ```bash
254
+ qingflow-mcp cli call qf_apps_info_list --args '{"pageNum":1,"pageSize":50}'
255
+
256
+ qingflow-mcp cli call qf_app_info_get --args '{"appKey":"app_demo"}'
257
+
258
+ qingflow-mcp cli call qf_app_packages_list --args '{"userId":"u_123","tagId":1001}'
259
+ ```
260
+
261
+ ## Audit Tools
262
+
263
+ These tools expose workflow log history as read-only MCP tools:
264
+
265
+ 1. `qf_apply_audit_records_list`
266
+ - required `apply_id`
267
+ - alias: `applyId`
268
+ 2. `qf_apply_audit_record_get`
269
+ - required `apply_id`, `audit_rcd_id`
270
+ - aliases: `applyId`, `auditRcdId`
271
+
272
+ CLI examples:
273
+
274
+ ```bash
275
+ qingflow-mcp cli call qf_apply_audit_records_list --args '{"applyId":"50001234"}'
276
+
277
+ qingflow-mcp cli call qf_apply_audit_record_get --args '{"applyId":"50001234","auditRcdId":"1111"}'
278
+ ```
279
+
218
280
  Return shape:
219
281
 
220
282
  1. success: structured payload `{ "ok": true, "data": ... }` (`meta` only in `output_profile=verbose`)
@@ -33,6 +33,42 @@ export class QingflowClient {
33
33
  }
34
34
  });
35
35
  }
36
+ listAppsInfo(options) {
37
+ return this.request({
38
+ method: "GET",
39
+ path: "/apps",
40
+ options: {
41
+ query: {
42
+ appKey: options.appKey,
43
+ pageNum: options.pageNum,
44
+ pageSize: options.pageSize
45
+ }
46
+ }
47
+ });
48
+ }
49
+ listAppPackages(options) {
50
+ return this.request({
51
+ method: "GET",
52
+ path: "/tags",
53
+ options: {
54
+ query: {
55
+ userId: options.userId
56
+ }
57
+ }
58
+ });
59
+ }
60
+ listApplyAuditRecords(applyId) {
61
+ return this.request({
62
+ method: "GET",
63
+ path: `/apply/${encodeURIComponent(applyId)}/auditRecord`
64
+ });
65
+ }
66
+ getApplyAuditRecord(applyId, auditRcdId) {
67
+ return this.request({
68
+ method: "GET",
69
+ path: `/apply/${encodeURIComponent(applyId)}/auditRecord/${encodeURIComponent(auditRcdId)}`
70
+ });
71
+ }
36
72
  listDepartments(options = {}) {
37
73
  return this.request({
38
74
  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.18";
68
+ const SERVER_VERSION = "0.3.19";
69
69
  const accessToken = process.env.QINGFLOW_ACCESS_TOKEN;
70
70
  const baseUrl = process.env.QINGFLOW_BASE_URL;
71
71
  if (!accessToken) {
@@ -353,6 +353,186 @@ const formSuccessOutputSchema = z.object({
353
353
  const formOutputSchema = formSuccessOutputSchema;
354
354
  const publicStringSchema = z.string().min(1);
355
355
  const publicFieldSelectorSchema = z.union([publicStringSchema, z.number().int()]);
356
+ const appAuthUserSchema = z.object({
357
+ user_id: z.string().nullable(),
358
+ user_name: z.string().nullable()
359
+ });
360
+ const appAuthDepartmentSchema = z.object({
361
+ dept_id: z.number().int().nullable(),
362
+ dept_name: z.string().nullable()
363
+ });
364
+ const appAuthRoleSchema = z.object({
365
+ role_id: z.number().int().nullable(),
366
+ role_name: z.string().nullable()
367
+ });
368
+ const appAuthMembersSchema = z.object({
369
+ users: z.array(appAuthUserSchema),
370
+ departments: z.array(appAuthDepartmentSchema),
371
+ roles: z.array(appAuthRoleSchema)
372
+ });
373
+ const appCreatorSchema = z.object({
374
+ user_id: z.string().nullable(),
375
+ nick_name: z.string().nullable(),
376
+ head_img: z.string().nullable()
377
+ });
378
+ const appTagRefSchema = z.object({
379
+ tag_id: z.number().int().nullable(),
380
+ tag_name: z.string().nullable()
381
+ });
382
+ const appInfoSchema = z.object({
383
+ app_key: z.string().nullable(),
384
+ app_name: z.string().nullable(),
385
+ app_auth: z.number().int().nullable(),
386
+ app_icon: z.string().nullable(),
387
+ auth_members: appAuthMembersSchema,
388
+ creator: appCreatorSchema,
389
+ create_time: z.string().nullable(),
390
+ tags: z.array(appTagRefSchema),
391
+ app_publish_status: z.number().int().nullable()
392
+ });
393
+ const appInfoListInputPublicSchema = z.object({
394
+ page_num: z.number().int().positive().optional(),
395
+ pageNum: z.number().int().positive().optional(),
396
+ page_size: z.number().int().positive().optional(),
397
+ pageSize: z.number().int().positive().optional(),
398
+ app_key: publicStringSchema.optional(),
399
+ appKey: publicStringSchema.optional()
400
+ });
401
+ const appInfoListInputSchema = z.preprocess(normalizeAppInfoListInput, z.object({
402
+ page_num: z.number().int().positive().optional(),
403
+ page_size: z.number().int().positive().optional(),
404
+ app_key: z.string().min(1).optional()
405
+ }));
406
+ const appInfoListOutputSchema = z.object({
407
+ ok: z.literal(true),
408
+ data: z.object({
409
+ pagination: z.object({
410
+ page_num: z.number().int().positive(),
411
+ page_size: z.number().int().positive(),
412
+ page_amount: z.number().int().nonnegative(),
413
+ result_amount: z.number().int().nonnegative()
414
+ }),
415
+ apps: z.array(appInfoSchema)
416
+ }),
417
+ meta: apiMetaSchema
418
+ });
419
+ const appInfoGetInputPublicSchema = z.object({
420
+ app_key: publicStringSchema.optional(),
421
+ appKey: publicStringSchema.optional()
422
+ });
423
+ const appInfoGetInputSchema = z.preprocess(normalizeAppInfoGetInput, z.object({
424
+ app_key: z.string().min(1).optional()
425
+ }));
426
+ const appInfoGetOutputSchema = z.object({
427
+ ok: z.literal(true),
428
+ data: z.object({
429
+ app: appInfoSchema
430
+ }),
431
+ meta: apiMetaSchema
432
+ });
433
+ const appPackageAppSchema = z.object({
434
+ app_key: z.string().nullable(),
435
+ app_name: z.string().nullable()
436
+ });
437
+ const dashboardRefSchema = z.object({
438
+ dash_key: z.string().nullable(),
439
+ dash_name: z.string().nullable()
440
+ });
441
+ const appPackageSchema = z.object({
442
+ tag_id: z.number().int().nullable(),
443
+ tag_name: z.string().nullable(),
444
+ tag_icon: z.string().nullable(),
445
+ apps: z.array(appPackageAppSchema),
446
+ dashboards: z.array(dashboardRefSchema)
447
+ });
448
+ const appPackageListInputPublicSchema = z.object({
449
+ user_id: publicStringSchema.optional(),
450
+ userId: publicStringSchema.optional(),
451
+ tag_id: publicDirectorySelectorSchema.optional(),
452
+ tagId: publicDirectorySelectorSchema.optional(),
453
+ keyword: publicStringSchema.optional(),
454
+ limit: z.number().int().positive().max(500).optional(),
455
+ offset: z.number().int().nonnegative().optional()
456
+ });
457
+ const appPackageListInputSchema = z.preprocess(normalizeAppPackageListInput, z.object({
458
+ user_id: z.string().min(1).optional(),
459
+ tag_id: z.union([z.string().min(1), z.number().int()]).optional(),
460
+ keyword: z.string().min(1).optional(),
461
+ limit: z.number().int().positive().max(500).optional(),
462
+ offset: z.number().int().nonnegative().optional()
463
+ }));
464
+ const appPackageListOutputSchema = z.object({
465
+ ok: z.literal(true),
466
+ data: z.object({
467
+ user_id: z.string(),
468
+ tag_id_filter: z.union([z.string(), z.number(), z.null()]),
469
+ total_packages: z.number().int().nonnegative(),
470
+ returned_packages: z.number().int().nonnegative(),
471
+ limit: z.number().int().positive(),
472
+ offset: z.number().int().nonnegative(),
473
+ packages: z.array(appPackageSchema)
474
+ }),
475
+ meta: apiMetaSchema
476
+ });
477
+ const auditUserInfoSchema = z.object({
478
+ user_id: z.string().nullable(),
479
+ user_name: z.string().nullable(),
480
+ nick_name: z.string().nullable(),
481
+ head_img: z.string().nullable()
482
+ });
483
+ const auditRecordSummarySchema = z.object({
484
+ audit_rcd_id: z.number().int().nullable(),
485
+ audit_node_id: z.number().int().nullable(),
486
+ audit_node_name: z.string().nullable(),
487
+ audit_time_ms: z.number().int().nullable(),
488
+ audit_result: z.unknown().nullable(),
489
+ audit_feedback: z.string().nullable(),
490
+ audit_user: auditUserInfoSchema.nullable(),
491
+ wait_audit_users: z.array(auditUserInfoSchema)
492
+ });
493
+ const applyAuditRecordsInputPublicSchema = z.object({
494
+ apply_id: publicStringSchema.optional(),
495
+ applyId: publicStringSchema.optional()
496
+ });
497
+ const applyAuditRecordsInputSchema = z.preprocess(normalizeApplyAuditRecordsInput, z.object({
498
+ apply_id: z.string().min(1).optional()
499
+ }));
500
+ const applyAuditRecordsOutputSchema = z.object({
501
+ ok: z.literal(true),
502
+ data: z.object({
503
+ apply_id: z.string(),
504
+ apply_status: z.unknown().nullable(),
505
+ audit_records: z.array(auditRecordSummarySchema),
506
+ current_nodes: z.array(auditRecordSummarySchema)
507
+ }),
508
+ meta: apiMetaSchema
509
+ });
510
+ const auditModifySchema = z.object({
511
+ que_id: z.union([z.number(), z.string(), z.null()]),
512
+ que_title: z.string().nullable(),
513
+ que_type: z.unknown().nullable(),
514
+ before_answer: z.array(z.unknown()),
515
+ after_answer: z.array(z.unknown())
516
+ });
517
+ const applyAuditRecordGetInputPublicSchema = z.object({
518
+ apply_id: publicStringSchema.optional(),
519
+ applyId: publicStringSchema.optional(),
520
+ audit_rcd_id: publicStringSchema.optional(),
521
+ auditRcdId: publicStringSchema.optional()
522
+ });
523
+ const applyAuditRecordGetInputSchema = z.preprocess(normalizeApplyAuditRecordGetInput, z.object({
524
+ apply_id: z.string().min(1).optional(),
525
+ audit_rcd_id: z.string().min(1).optional()
526
+ }));
527
+ const applyAuditRecordGetOutputSchema = z.object({
528
+ ok: z.literal(true),
529
+ data: z.object({
530
+ apply_id: z.string(),
531
+ audit_rcd_id: z.union([z.string(), z.number()]),
532
+ modifies: z.array(auditModifySchema)
533
+ }),
534
+ meta: apiMetaSchema
535
+ });
356
536
  const publicSortItemSchema = z.object({
357
537
  que_id: publicFieldSelectorSchema,
358
538
  ascend: z.boolean().optional()
@@ -1418,6 +1598,300 @@ server.registerTool("qf_apps_list", {
1418
1598
  return errorResult(error);
1419
1599
  }
1420
1600
  });
1601
+ server.registerTool("qf_apps_info_list", {
1602
+ title: "Qingflow Apps Info List",
1603
+ description: "List app admin info with explicit pagination. Requires admin-level visibility.",
1604
+ inputSchema: appInfoListInputPublicSchema,
1605
+ outputSchema: appInfoListOutputSchema,
1606
+ annotations: {
1607
+ readOnlyHint: true,
1608
+ idempotentHint: true
1609
+ }
1610
+ }, async (args) => {
1611
+ try {
1612
+ const parsedArgs = appInfoListInputSchema.parse(args);
1613
+ if (parsedArgs.page_num === undefined) {
1614
+ throw missingRequiredFieldError({
1615
+ field: "page_num",
1616
+ tool: "qf_apps_info_list",
1617
+ fixHint: "Provide page_num (or pageNum), for example: {\"page_num\":1,\"page_size\":50}."
1618
+ });
1619
+ }
1620
+ if (parsedArgs.page_size === undefined) {
1621
+ throw missingRequiredFieldError({
1622
+ field: "page_size",
1623
+ tool: "qf_apps_info_list",
1624
+ fixHint: "Provide page_size (or pageSize), for example: {\"page_num\":1,\"page_size\":50}."
1625
+ });
1626
+ }
1627
+ let response;
1628
+ try {
1629
+ response = await client.listAppsInfo({
1630
+ appKey: parsedArgs.app_key,
1631
+ pageNum: parsedArgs.page_num,
1632
+ pageSize: parsedArgs.page_size
1633
+ });
1634
+ }
1635
+ catch (error) {
1636
+ if (parsedArgs.app_key) {
1637
+ throw translateAdminApiError(error, {
1638
+ tool: "qf_apps_info_list",
1639
+ entity: "app",
1640
+ appKey: parsedArgs.app_key
1641
+ });
1642
+ }
1643
+ throw error;
1644
+ }
1645
+ const result = asObject(response.result);
1646
+ const apps = asArray(result?.apps).map((item) => normalizeAppInfo(item));
1647
+ return okResult({
1648
+ ok: true,
1649
+ data: {
1650
+ pagination: {
1651
+ page_num: toPositiveInt(result?.pageNum) ?? parsedArgs.page_num,
1652
+ page_size: toPositiveInt(result?.pageSize) ?? parsedArgs.page_size,
1653
+ page_amount: toNonNegativeInt(result?.pageAmount) ?? 0,
1654
+ result_amount: toNonNegativeInt(result?.resultAmount) ?? apps.length
1655
+ },
1656
+ apps
1657
+ },
1658
+ meta: buildMeta(response)
1659
+ }, `Returned ${apps.length} app info rows`);
1660
+ }
1661
+ catch (error) {
1662
+ return errorResult(error);
1663
+ }
1664
+ });
1665
+ server.registerTool("qf_app_info_get", {
1666
+ title: "Qingflow App Info Get",
1667
+ description: "Get one app's admin info by app_key. Requires admin-level visibility.",
1668
+ inputSchema: appInfoGetInputPublicSchema,
1669
+ outputSchema: appInfoGetOutputSchema,
1670
+ annotations: {
1671
+ readOnlyHint: true,
1672
+ idempotentHint: true
1673
+ }
1674
+ }, async (args) => {
1675
+ try {
1676
+ const parsedArgs = appInfoGetInputSchema.parse(args);
1677
+ if (!parsedArgs.app_key) {
1678
+ throw missingRequiredFieldError({
1679
+ field: "app_key",
1680
+ tool: "qf_app_info_get",
1681
+ fixHint: "Provide app_key (or appKey), for example: {\"app_key\":\"21b3d559\"}."
1682
+ });
1683
+ }
1684
+ let response;
1685
+ try {
1686
+ response = await client.listAppsInfo({
1687
+ appKey: parsedArgs.app_key,
1688
+ pageNum: 1,
1689
+ pageSize: 1
1690
+ });
1691
+ }
1692
+ catch (error) {
1693
+ throw translateAdminApiError(error, {
1694
+ tool: "qf_app_info_get",
1695
+ entity: "app",
1696
+ appKey: parsedArgs.app_key
1697
+ });
1698
+ }
1699
+ const result = asObject(response.result);
1700
+ const app = asArray(result?.apps)
1701
+ .map((item) => normalizeAppInfo(item))
1702
+ .find((item) => item.app_key === parsedArgs.app_key);
1703
+ if (!app) {
1704
+ throw new InputValidationError({
1705
+ message: `App \"${parsedArgs.app_key}\" not found`,
1706
+ errorCode: "APP_NOT_FOUND",
1707
+ fixHint: "Call qf_apps_list or qf_apps_info_list first to confirm the exact app_key.",
1708
+ details: {
1709
+ tool: "qf_app_info_get",
1710
+ app_key: parsedArgs.app_key
1711
+ }
1712
+ });
1713
+ }
1714
+ return okResult({
1715
+ ok: true,
1716
+ data: {
1717
+ app
1718
+ },
1719
+ meta: buildMeta(response)
1720
+ }, `Fetched app ${parsedArgs.app_key}`);
1721
+ }
1722
+ catch (error) {
1723
+ return errorResult(error);
1724
+ }
1725
+ });
1726
+ server.registerTool("qf_app_packages_list", {
1727
+ title: "Qingflow App Packages List",
1728
+ description: "List app packages visible to one user, with optional local tag/keyword slicing.",
1729
+ inputSchema: appPackageListInputPublicSchema,
1730
+ outputSchema: appPackageListOutputSchema,
1731
+ annotations: {
1732
+ readOnlyHint: true,
1733
+ idempotentHint: true
1734
+ }
1735
+ }, async (args) => {
1736
+ try {
1737
+ const parsedArgs = appPackageListInputSchema.parse(args);
1738
+ if (!parsedArgs.user_id) {
1739
+ throw missingRequiredFieldError({
1740
+ field: "user_id",
1741
+ tool: "qf_app_packages_list",
1742
+ fixHint: "Provide user_id (or userId), for example: {\"user_id\":\"u_123\"}."
1743
+ });
1744
+ }
1745
+ const response = await client.listAppPackages({
1746
+ userId: parsedArgs.user_id
1747
+ });
1748
+ const keyword = parsedArgs.keyword?.trim().toLowerCase() ?? null;
1749
+ const limit = parsedArgs.limit ?? 50;
1750
+ const offset = parsedArgs.offset ?? 0;
1751
+ let packages = asArray(asObject(response.result)?.tagList).map((item) => normalizeAppPackage(item));
1752
+ if (parsedArgs.tag_id !== undefined) {
1753
+ packages = packages.filter((item) => String(item.tag_id ?? "") === String(parsedArgs.tag_id));
1754
+ if (packages.length === 0) {
1755
+ throw new InputValidationError({
1756
+ message: `App package \"${String(parsedArgs.tag_id)}\" not found`,
1757
+ errorCode: "APP_PACKAGE_NOT_FOUND",
1758
+ fixHint: "Call qf_app_packages_list without tag_id first to confirm the exact package id.",
1759
+ details: {
1760
+ tool: "qf_app_packages_list",
1761
+ tag_id: parsedArgs.tag_id,
1762
+ user_id: parsedArgs.user_id
1763
+ }
1764
+ });
1765
+ }
1766
+ }
1767
+ const filtered = keyword
1768
+ ? packages.filter((item) => {
1769
+ const values = [
1770
+ item.tag_name ?? "",
1771
+ ...item.apps.map((app) => app.app_name ?? ""),
1772
+ ...item.dashboards.map((dash) => dash.dash_name ?? "")
1773
+ ];
1774
+ return values.some((value) => value.toLowerCase().includes(keyword));
1775
+ })
1776
+ : packages;
1777
+ const sliced = filtered.slice(offset, offset + limit);
1778
+ return okResult({
1779
+ ok: true,
1780
+ data: {
1781
+ user_id: parsedArgs.user_id,
1782
+ tag_id_filter: parsedArgs.tag_id ?? null,
1783
+ total_packages: filtered.length,
1784
+ returned_packages: sliced.length,
1785
+ limit,
1786
+ offset,
1787
+ packages: sliced
1788
+ },
1789
+ meta: buildMeta(response)
1790
+ }, `Returned ${sliced.length}/${filtered.length} app packages`);
1791
+ }
1792
+ catch (error) {
1793
+ return errorResult(error);
1794
+ }
1795
+ });
1796
+ server.registerTool("qf_apply_audit_records_list", {
1797
+ title: "Qingflow Apply Audit Records List",
1798
+ description: "List one record's workflow audit log and current pending nodes.",
1799
+ inputSchema: applyAuditRecordsInputPublicSchema,
1800
+ outputSchema: applyAuditRecordsOutputSchema,
1801
+ annotations: {
1802
+ readOnlyHint: true,
1803
+ idempotentHint: true
1804
+ }
1805
+ }, async (args) => {
1806
+ try {
1807
+ const parsedArgs = applyAuditRecordsInputSchema.parse(args);
1808
+ if (!parsedArgs.apply_id) {
1809
+ throw missingRequiredFieldError({
1810
+ field: "apply_id",
1811
+ tool: "qf_apply_audit_records_list",
1812
+ fixHint: "Provide apply_id (or applyId), for example: {\"apply_id\":\"50001234\"}."
1813
+ });
1814
+ }
1815
+ let response;
1816
+ try {
1817
+ response = await client.listApplyAuditRecords(parsedArgs.apply_id);
1818
+ }
1819
+ catch (error) {
1820
+ throw translateAdminApiError(error, {
1821
+ tool: "qf_apply_audit_records_list",
1822
+ entity: "apply",
1823
+ applyId: parsedArgs.apply_id
1824
+ });
1825
+ }
1826
+ const result = asObject(response.result);
1827
+ return okResult({
1828
+ ok: true,
1829
+ data: {
1830
+ apply_id: parsedArgs.apply_id,
1831
+ apply_status: result?.applyStatus ?? null,
1832
+ audit_records: asArray(result?.auditRecords).map((item) => normalizeAuditRecordSummary(item)),
1833
+ current_nodes: asArray(result?.currentNodes).map((item) => normalizeAuditRecordSummary(item))
1834
+ },
1835
+ meta: buildMeta(response)
1836
+ }, `Fetched audit records for apply ${parsedArgs.apply_id}`);
1837
+ }
1838
+ catch (error) {
1839
+ return errorResult(error);
1840
+ }
1841
+ });
1842
+ server.registerTool("qf_apply_audit_record_get", {
1843
+ title: "Qingflow Apply Audit Record Get",
1844
+ description: "Get one workflow audit record detail by apply_id and audit_rcd_id.",
1845
+ inputSchema: applyAuditRecordGetInputPublicSchema,
1846
+ outputSchema: applyAuditRecordGetOutputSchema,
1847
+ annotations: {
1848
+ readOnlyHint: true,
1849
+ idempotentHint: true
1850
+ }
1851
+ }, async (args) => {
1852
+ try {
1853
+ const parsedArgs = applyAuditRecordGetInputSchema.parse(args);
1854
+ if (!parsedArgs.apply_id) {
1855
+ throw missingRequiredFieldError({
1856
+ field: "apply_id",
1857
+ tool: "qf_apply_audit_record_get",
1858
+ fixHint: "Provide apply_id (or applyId), for example: {\"apply_id\":\"50001234\",\"audit_rcd_id\":\"1111\"}."
1859
+ });
1860
+ }
1861
+ if (!parsedArgs.audit_rcd_id) {
1862
+ throw missingRequiredFieldError({
1863
+ field: "audit_rcd_id",
1864
+ tool: "qf_apply_audit_record_get",
1865
+ fixHint: "Provide audit_rcd_id (or auditRcdId), for example: {\"apply_id\":\"50001234\",\"audit_rcd_id\":\"1111\"}."
1866
+ });
1867
+ }
1868
+ let response;
1869
+ try {
1870
+ response = await client.getApplyAuditRecord(parsedArgs.apply_id, parsedArgs.audit_rcd_id);
1871
+ }
1872
+ catch (error) {
1873
+ throw translateAdminApiError(error, {
1874
+ tool: "qf_apply_audit_record_get",
1875
+ entity: "audit_record",
1876
+ applyId: parsedArgs.apply_id,
1877
+ auditRcdId: parsedArgs.audit_rcd_id
1878
+ });
1879
+ }
1880
+ const result = asObject(response.result);
1881
+ return okResult({
1882
+ ok: true,
1883
+ data: {
1884
+ apply_id: parsedArgs.apply_id,
1885
+ audit_rcd_id: result?.auditRcdId ?? parsedArgs.audit_rcd_id,
1886
+ modifies: normalizeAuditModifies(result?.auditModifies)
1887
+ },
1888
+ meta: buildMeta(response)
1889
+ }, `Fetched audit record ${parsedArgs.audit_rcd_id} for apply ${parsedArgs.apply_id}`);
1890
+ }
1891
+ catch (error) {
1892
+ return errorResult(error);
1893
+ }
1894
+ });
1421
1895
  server.registerTool("qf_departments_list", {
1422
1896
  title: "Qingflow Departments List",
1423
1897
  description: "List departments with optional dept_id filter and local keyword slicing.",
@@ -2391,6 +2865,174 @@ function normalizeUser(raw) {
2391
2865
  superior_id: asNullableString(obj.superiorId)
2392
2866
  };
2393
2867
  }
2868
+ function normalizeAppAuthMembers(raw) {
2869
+ const obj = asObject(raw) ?? {};
2870
+ return {
2871
+ users: asArray(obj.users).map((item) => {
2872
+ const entry = asObject(item) ?? {};
2873
+ return {
2874
+ user_id: asNullableString(entry.userId),
2875
+ user_name: asNullableString(entry.userName)
2876
+ };
2877
+ }),
2878
+ departments: asArray(obj.depts).map((item) => {
2879
+ const entry = asObject(item) ?? {};
2880
+ return {
2881
+ dept_id: toNonNegativeInt(entry.deptId),
2882
+ dept_name: asNullableString(entry.deptName)
2883
+ };
2884
+ }),
2885
+ roles: asArray(obj.roles).map((item) => {
2886
+ const entry = asObject(item) ?? {};
2887
+ return {
2888
+ role_id: toNonNegativeInt(entry.roleId),
2889
+ role_name: asNullableString(entry.roleName)
2890
+ };
2891
+ })
2892
+ };
2893
+ }
2894
+ function normalizeAppInfo(raw) {
2895
+ const obj = asObject(raw) ?? {};
2896
+ const creator = asObject(obj.creator) ?? {};
2897
+ return {
2898
+ app_key: asNullableString(obj.appKey),
2899
+ app_name: asNullableString(obj.appName),
2900
+ app_auth: toNonNegativeInt(obj.appAuth),
2901
+ app_icon: asNullableString(obj.appIcon),
2902
+ auth_members: normalizeAppAuthMembers(obj.authmembers ?? obj.authMembers),
2903
+ creator: {
2904
+ user_id: asNullableString(creator.userId),
2905
+ nick_name: asNullableString(creator.nickName),
2906
+ head_img: asNullableString(creator.headImg)
2907
+ },
2908
+ create_time: asNullableString(obj.createTime),
2909
+ tags: asArray(obj.tags).map((item) => {
2910
+ const entry = asObject(item) ?? {};
2911
+ return {
2912
+ tag_id: toNonNegativeInt(entry.tagId),
2913
+ tag_name: asNullableString(entry.tagName)
2914
+ };
2915
+ }),
2916
+ app_publish_status: toNonNegativeInt(obj.appPublishStatus)
2917
+ };
2918
+ }
2919
+ function normalizeAppPackage(raw) {
2920
+ const obj = asObject(raw) ?? {};
2921
+ return {
2922
+ tag_id: toNonNegativeInt(obj.tagId),
2923
+ tag_name: asNullableString(obj.tagName),
2924
+ tag_icon: asNullableString(obj.tagIcon),
2925
+ apps: asArray(obj.appList).map((item) => {
2926
+ const entry = asObject(item) ?? {};
2927
+ return {
2928
+ app_key: asNullableString(entry.appKey),
2929
+ app_name: asNullableString(entry.appName)
2930
+ };
2931
+ }),
2932
+ dashboards: asArray(obj.dashList).map((item) => {
2933
+ const entry = asObject(item) ?? {};
2934
+ return {
2935
+ dash_key: asNullableString(entry.dashKey),
2936
+ dash_name: asNullableString(entry.dashName)
2937
+ };
2938
+ })
2939
+ };
2940
+ }
2941
+ function normalizeAuditUserInfo(raw) {
2942
+ const obj = asObject(raw) ?? {};
2943
+ return {
2944
+ user_id: asNullableString(obj.userId),
2945
+ user_name: asNullableString(obj.userName),
2946
+ nick_name: asNullableString(obj.nickName),
2947
+ head_img: asNullableString(obj.headImg)
2948
+ };
2949
+ }
2950
+ function toNullableSpecialId(value) {
2951
+ if (typeof value === "number" && Number.isFinite(value)) {
2952
+ return Math.trunc(value);
2953
+ }
2954
+ if (typeof value === "string" && value.trim()) {
2955
+ const normalized = value.trim();
2956
+ if (/^\d+$/.test(normalized)) {
2957
+ return Number(normalized);
2958
+ }
2959
+ return normalized;
2960
+ }
2961
+ const obj = asObject(value);
2962
+ if (!obj) {
2963
+ return null;
2964
+ }
2965
+ const candidate = obj.queId ?? obj.id ?? obj.value ?? obj.dataValue ?? null;
2966
+ return candidate === null ? null : toNullableSpecialId(candidate);
2967
+ }
2968
+ function normalizeAuditRecordSummary(raw) {
2969
+ const obj = asObject(raw) ?? {};
2970
+ return {
2971
+ audit_rcd_id: toNonNegativeInt(obj.auditRcdId),
2972
+ audit_node_id: toNonNegativeInt(obj.auditNodeId),
2973
+ audit_node_name: asNullableString(obj.auditNodeName),
2974
+ audit_time_ms: toNonNegativeInt(obj.auditTime),
2975
+ audit_result: obj.auditResult ?? null,
2976
+ audit_feedback: asNullableString(obj.auditFeedback),
2977
+ audit_user: obj.auditUser ? normalizeAuditUserInfo(obj.auditUser) : null,
2978
+ wait_audit_users: asArray(obj.waitAuditUserList).map((item) => normalizeAuditUserInfo(item))
2979
+ };
2980
+ }
2981
+ function normalizeAuditModifies(raw) {
2982
+ const source = Array.isArray(raw)
2983
+ ? raw
2984
+ : asObject(raw)
2985
+ ? Object.values(raw)
2986
+ : [];
2987
+ return source.map((item) => {
2988
+ const entry = asObject(item) ?? {};
2989
+ return {
2990
+ que_id: toNullableSpecialId(entry.queId),
2991
+ que_title: asNullableString(entry.queTitle),
2992
+ que_type: entry.queType ?? null,
2993
+ before_answer: asArray(entry.beforeAnswer),
2994
+ after_answer: asArray(entry.afterAnswer)
2995
+ };
2996
+ });
2997
+ }
2998
+ function translateAdminApiError(error, params) {
2999
+ if (error instanceof QingflowApiError &&
3000
+ (error.httpStatus === 404 || error.errCode === 404)) {
3001
+ if (params.entity === "app") {
3002
+ return new InputValidationError({
3003
+ message: `App \"${params.appKey}\" not found`,
3004
+ errorCode: "APP_NOT_FOUND",
3005
+ fixHint: "Call qf_apps_list or qf_apps_info_list first to confirm the exact app_key.",
3006
+ details: {
3007
+ tool: params.tool,
3008
+ app_key: params.appKey
3009
+ }
3010
+ });
3011
+ }
3012
+ if (params.entity === "apply") {
3013
+ return new InputValidationError({
3014
+ message: `Apply \"${params.applyId}\" not found`,
3015
+ errorCode: "APPLY_NOT_FOUND",
3016
+ fixHint: "Use qf_record_get or your record query tools first to confirm the exact apply_id.",
3017
+ details: {
3018
+ tool: params.tool,
3019
+ apply_id: params.applyId
3020
+ }
3021
+ });
3022
+ }
3023
+ return new InputValidationError({
3024
+ message: `Audit record \"${params.auditRcdId}\" for apply \"${params.applyId}\" not found`,
3025
+ errorCode: "AUDIT_RECORD_NOT_FOUND",
3026
+ fixHint: "Call qf_apply_audit_records_list first to confirm the exact audit_rcd_id.",
3027
+ details: {
3028
+ tool: params.tool,
3029
+ apply_id: params.applyId,
3030
+ audit_rcd_id: params.auditRcdId
3031
+ }
3032
+ });
3033
+ }
3034
+ return error instanceof Error ? error : new Error(String(error));
3035
+ }
2394
3036
  function translateDirectoryApiError(error, params) {
2395
3037
  if (error instanceof QingflowApiError &&
2396
3038
  (error.httpStatus === 404 || error.errCode === 404)) {
@@ -2437,6 +3079,7 @@ function missingRequiredFieldError(params) {
2437
3079
  const COMMON_INPUT_ALIASES = {
2438
3080
  appKey: "app_key",
2439
3081
  userId: "user_id",
3082
+ tagId: "tag_id",
2440
3083
  pageNum: "page_num",
2441
3084
  pageSize: "page_size",
2442
3085
  pageToken: "page_token",
@@ -2448,6 +3091,7 @@ const COMMON_INPUT_ALIASES = {
2448
3091
  queryMode: "query_mode",
2449
3092
  queryLogic: "query_logic",
2450
3093
  applyId: "apply_id",
3094
+ auditRcdId: "audit_rcd_id",
2451
3095
  applyIds: "apply_ids",
2452
3096
  maxRows: "max_rows",
2453
3097
  rowLimit: "max_rows",
@@ -2582,6 +3226,87 @@ function normalizeUserGetInput(raw) {
2582
3226
  user_id: coerceStringLike(normalizedObj.user_id)
2583
3227
  };
2584
3228
  }
3229
+ function normalizeAppInfoListInput(raw) {
3230
+ const parsedRoot = parseJsonLikeDeep(raw);
3231
+ const obj = asObject(parsedRoot);
3232
+ if (!obj) {
3233
+ return parsedRoot;
3234
+ }
3235
+ const normalizedObj = applyAliases(obj, {
3236
+ appKey: "app_key",
3237
+ pageNum: "page_num",
3238
+ pageSize: "page_size"
3239
+ });
3240
+ return {
3241
+ ...normalizedObj,
3242
+ app_key: coerceStringLike(normalizedObj.app_key),
3243
+ page_num: coerceNumberLike(normalizedObj.page_num),
3244
+ page_size: coerceNumberLike(normalizedObj.page_size)
3245
+ };
3246
+ }
3247
+ function normalizeAppInfoGetInput(raw) {
3248
+ const parsedRoot = parseJsonLikeDeep(raw);
3249
+ const obj = asObject(parsedRoot);
3250
+ if (!obj) {
3251
+ return parsedRoot;
3252
+ }
3253
+ const normalizedObj = applyAliases(obj, {
3254
+ appKey: "app_key"
3255
+ });
3256
+ return {
3257
+ ...normalizedObj,
3258
+ app_key: coerceStringLike(normalizedObj.app_key)
3259
+ };
3260
+ }
3261
+ function normalizeAppPackageListInput(raw) {
3262
+ const parsedRoot = parseJsonLikeDeep(raw);
3263
+ const obj = asObject(parsedRoot);
3264
+ if (!obj) {
3265
+ return parsedRoot;
3266
+ }
3267
+ const normalizedObj = applyAliases(obj, {
3268
+ userId: "user_id",
3269
+ tagId: "tag_id"
3270
+ });
3271
+ return {
3272
+ ...normalizedObj,
3273
+ user_id: coerceStringLike(normalizedObj.user_id),
3274
+ tag_id: coerceNumberLike(normalizeSelectorInputValue(normalizedObj.tag_id)),
3275
+ keyword: coerceStringLike(normalizedObj.keyword),
3276
+ limit: coerceNumberLike(normalizedObj.limit),
3277
+ offset: coerceNumberLike(normalizedObj.offset)
3278
+ };
3279
+ }
3280
+ function normalizeApplyAuditRecordsInput(raw) {
3281
+ const parsedRoot = parseJsonLikeDeep(raw);
3282
+ const obj = asObject(parsedRoot);
3283
+ if (!obj) {
3284
+ return parsedRoot;
3285
+ }
3286
+ const normalizedObj = applyAliases(obj, {
3287
+ applyId: "apply_id"
3288
+ });
3289
+ return {
3290
+ ...normalizedObj,
3291
+ apply_id: coerceStringLike(normalizedObj.apply_id)
3292
+ };
3293
+ }
3294
+ function normalizeApplyAuditRecordGetInput(raw) {
3295
+ const parsedRoot = parseJsonLikeDeep(raw);
3296
+ const obj = asObject(parsedRoot);
3297
+ if (!obj) {
3298
+ return parsedRoot;
3299
+ }
3300
+ const normalizedObj = applyAliases(obj, {
3301
+ applyId: "apply_id",
3302
+ auditRcdId: "audit_rcd_id"
3303
+ });
3304
+ return {
3305
+ ...normalizedObj,
3306
+ apply_id: coerceStringLike(normalizedObj.apply_id),
3307
+ audit_rcd_id: coerceStringLike(normalizedObj.audit_rcd_id)
3308
+ };
3309
+ }
2585
3310
  function buildToolSpecCatalog() {
2586
3311
  return [
2587
3312
  {
@@ -2611,6 +3336,67 @@ function buildToolSpecCatalog() {
2611
3336
  limit: 20
2612
3337
  }
2613
3338
  },
3339
+ {
3340
+ tool: "qf_apps_info_list",
3341
+ required: ["page_num", "page_size"],
3342
+ limits: {
3343
+ input_contract: "strict JSON only; page_num/page_size must be native JSON numbers"
3344
+ },
3345
+ aliases: collectAliasHints(["page_num", "page_size", "app_key"], {}),
3346
+ minimal_example: {
3347
+ page_num: 1,
3348
+ page_size: 50,
3349
+ app_key: "21b3d559"
3350
+ }
3351
+ },
3352
+ {
3353
+ tool: "qf_app_info_get",
3354
+ required: ["app_key"],
3355
+ limits: {
3356
+ input_contract: "strict JSON only; app_key must be a native JSON string"
3357
+ },
3358
+ aliases: collectAliasHints(["app_key"], {}),
3359
+ minimal_example: {
3360
+ app_key: "21b3d559"
3361
+ }
3362
+ },
3363
+ {
3364
+ tool: "qf_app_packages_list",
3365
+ required: ["user_id"],
3366
+ limits: {
3367
+ limit_max: 500,
3368
+ offset_min: 0,
3369
+ input_contract: "strict JSON only; user_id must be a native JSON string when provided"
3370
+ },
3371
+ aliases: collectAliasHints(["user_id", "tag_id"], {}),
3372
+ minimal_example: {
3373
+ user_id: "u_123",
3374
+ limit: 20
3375
+ }
3376
+ },
3377
+ {
3378
+ tool: "qf_apply_audit_records_list",
3379
+ required: ["apply_id"],
3380
+ limits: {
3381
+ input_contract: "strict JSON only; apply_id must be a native JSON string"
3382
+ },
3383
+ aliases: collectAliasHints(["apply_id"], {}),
3384
+ minimal_example: {
3385
+ apply_id: "50001234"
3386
+ }
3387
+ },
3388
+ {
3389
+ tool: "qf_apply_audit_record_get",
3390
+ required: ["apply_id", "audit_rcd_id"],
3391
+ limits: {
3392
+ input_contract: "strict JSON only; apply_id/audit_rcd_id must be native JSON strings"
3393
+ },
3394
+ aliases: collectAliasHints(["apply_id", "audit_rcd_id"], {}),
3395
+ minimal_example: {
3396
+ apply_id: "50001234",
3397
+ audit_rcd_id: "1111"
3398
+ }
3399
+ },
2614
3400
  {
2615
3401
  tool: "qf_departments_list",
2616
3402
  required: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qingflow-mcp",
3
- "version": "0.3.18",
3
+ "version": "0.3.19",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",