qingflow-mcp 0.3.18 → 0.3.20

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.20
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.20";
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()
@@ -565,12 +745,14 @@ const listOutputSchema = listSuccessOutputSchema;
565
745
  const recordGetInputPublicSchema = z
566
746
  .object({
567
747
  apply_id: publicFieldSelectorSchema,
748
+ app_key: publicStringSchema.optional(),
568
749
  max_columns: z.number().int().positive().max(MAX_COLUMN_LIMIT).optional(),
569
750
  select_columns: z.array(publicFieldSelectorSchema).min(1).max(MAX_COLUMN_LIMIT),
570
751
  output_profile: outputProfileSchema.optional()
571
752
  });
572
753
  const recordGetInputSchema = z.preprocess(normalizeRecordGetInput, z.object({
573
754
  apply_id: z.union([z.string().min(1), z.number().int()]),
755
+ app_key: z.string().min(1).optional(),
574
756
  max_columns: z.number().int().positive().max(MAX_COLUMN_LIMIT).optional(),
575
757
  select_columns: z
576
758
  .array(z.union([z.string().min(1), z.number().int()]))
@@ -1418,6 +1600,300 @@ server.registerTool("qf_apps_list", {
1418
1600
  return errorResult(error);
1419
1601
  }
1420
1602
  });
1603
+ server.registerTool("qf_apps_info_list", {
1604
+ title: "Qingflow Apps Info List",
1605
+ description: "List app admin info with explicit pagination. Requires admin-level visibility.",
1606
+ inputSchema: appInfoListInputPublicSchema,
1607
+ outputSchema: appInfoListOutputSchema,
1608
+ annotations: {
1609
+ readOnlyHint: true,
1610
+ idempotentHint: true
1611
+ }
1612
+ }, async (args) => {
1613
+ try {
1614
+ const parsedArgs = appInfoListInputSchema.parse(args);
1615
+ if (parsedArgs.page_num === undefined) {
1616
+ throw missingRequiredFieldError({
1617
+ field: "page_num",
1618
+ tool: "qf_apps_info_list",
1619
+ fixHint: "Provide page_num (or pageNum), for example: {\"page_num\":1,\"page_size\":50}."
1620
+ });
1621
+ }
1622
+ if (parsedArgs.page_size === undefined) {
1623
+ throw missingRequiredFieldError({
1624
+ field: "page_size",
1625
+ tool: "qf_apps_info_list",
1626
+ fixHint: "Provide page_size (or pageSize), for example: {\"page_num\":1,\"page_size\":50}."
1627
+ });
1628
+ }
1629
+ let response;
1630
+ try {
1631
+ response = await client.listAppsInfo({
1632
+ appKey: parsedArgs.app_key,
1633
+ pageNum: parsedArgs.page_num,
1634
+ pageSize: parsedArgs.page_size
1635
+ });
1636
+ }
1637
+ catch (error) {
1638
+ if (parsedArgs.app_key) {
1639
+ throw translateAdminApiError(error, {
1640
+ tool: "qf_apps_info_list",
1641
+ entity: "app",
1642
+ appKey: parsedArgs.app_key
1643
+ });
1644
+ }
1645
+ throw error;
1646
+ }
1647
+ const result = asObject(response.result);
1648
+ const apps = asArray(result?.apps).map((item) => normalizeAppInfo(item));
1649
+ return okResult({
1650
+ ok: true,
1651
+ data: {
1652
+ pagination: {
1653
+ page_num: toPositiveInt(result?.pageNum) ?? parsedArgs.page_num,
1654
+ page_size: toPositiveInt(result?.pageSize) ?? parsedArgs.page_size,
1655
+ page_amount: toNonNegativeInt(result?.pageAmount) ?? 0,
1656
+ result_amount: toNonNegativeInt(result?.resultAmount) ?? apps.length
1657
+ },
1658
+ apps
1659
+ },
1660
+ meta: buildMeta(response)
1661
+ }, `Returned ${apps.length} app info rows`);
1662
+ }
1663
+ catch (error) {
1664
+ return errorResult(error);
1665
+ }
1666
+ });
1667
+ server.registerTool("qf_app_info_get", {
1668
+ title: "Qingflow App Info Get",
1669
+ description: "Get one app's admin info by app_key. Requires admin-level visibility.",
1670
+ inputSchema: appInfoGetInputPublicSchema,
1671
+ outputSchema: appInfoGetOutputSchema,
1672
+ annotations: {
1673
+ readOnlyHint: true,
1674
+ idempotentHint: true
1675
+ }
1676
+ }, async (args) => {
1677
+ try {
1678
+ const parsedArgs = appInfoGetInputSchema.parse(args);
1679
+ if (!parsedArgs.app_key) {
1680
+ throw missingRequiredFieldError({
1681
+ field: "app_key",
1682
+ tool: "qf_app_info_get",
1683
+ fixHint: "Provide app_key (or appKey), for example: {\"app_key\":\"21b3d559\"}."
1684
+ });
1685
+ }
1686
+ let response;
1687
+ try {
1688
+ response = await client.listAppsInfo({
1689
+ appKey: parsedArgs.app_key,
1690
+ pageNum: 1,
1691
+ pageSize: 1
1692
+ });
1693
+ }
1694
+ catch (error) {
1695
+ throw translateAdminApiError(error, {
1696
+ tool: "qf_app_info_get",
1697
+ entity: "app",
1698
+ appKey: parsedArgs.app_key
1699
+ });
1700
+ }
1701
+ const result = asObject(response.result);
1702
+ const app = asArray(result?.apps)
1703
+ .map((item) => normalizeAppInfo(item))
1704
+ .find((item) => item.app_key === parsedArgs.app_key);
1705
+ if (!app) {
1706
+ throw new InputValidationError({
1707
+ message: `App \"${parsedArgs.app_key}\" not found`,
1708
+ errorCode: "APP_NOT_FOUND",
1709
+ fixHint: "Call qf_apps_list or qf_apps_info_list first to confirm the exact app_key.",
1710
+ details: {
1711
+ tool: "qf_app_info_get",
1712
+ app_key: parsedArgs.app_key
1713
+ }
1714
+ });
1715
+ }
1716
+ return okResult({
1717
+ ok: true,
1718
+ data: {
1719
+ app
1720
+ },
1721
+ meta: buildMeta(response)
1722
+ }, `Fetched app ${parsedArgs.app_key}`);
1723
+ }
1724
+ catch (error) {
1725
+ return errorResult(error);
1726
+ }
1727
+ });
1728
+ server.registerTool("qf_app_packages_list", {
1729
+ title: "Qingflow App Packages List",
1730
+ description: "List app packages visible to one user, with optional local tag/keyword slicing.",
1731
+ inputSchema: appPackageListInputPublicSchema,
1732
+ outputSchema: appPackageListOutputSchema,
1733
+ annotations: {
1734
+ readOnlyHint: true,
1735
+ idempotentHint: true
1736
+ }
1737
+ }, async (args) => {
1738
+ try {
1739
+ const parsedArgs = appPackageListInputSchema.parse(args);
1740
+ if (!parsedArgs.user_id) {
1741
+ throw missingRequiredFieldError({
1742
+ field: "user_id",
1743
+ tool: "qf_app_packages_list",
1744
+ fixHint: "Provide user_id (or userId), for example: {\"user_id\":\"u_123\"}."
1745
+ });
1746
+ }
1747
+ const response = await client.listAppPackages({
1748
+ userId: parsedArgs.user_id
1749
+ });
1750
+ const keyword = parsedArgs.keyword?.trim().toLowerCase() ?? null;
1751
+ const limit = parsedArgs.limit ?? 50;
1752
+ const offset = parsedArgs.offset ?? 0;
1753
+ let packages = asArray(asObject(response.result)?.tagList).map((item) => normalizeAppPackage(item));
1754
+ if (parsedArgs.tag_id !== undefined) {
1755
+ packages = packages.filter((item) => String(item.tag_id ?? "") === String(parsedArgs.tag_id));
1756
+ if (packages.length === 0) {
1757
+ throw new InputValidationError({
1758
+ message: `App package \"${String(parsedArgs.tag_id)}\" not found`,
1759
+ errorCode: "APP_PACKAGE_NOT_FOUND",
1760
+ fixHint: "Call qf_app_packages_list without tag_id first to confirm the exact package id.",
1761
+ details: {
1762
+ tool: "qf_app_packages_list",
1763
+ tag_id: parsedArgs.tag_id,
1764
+ user_id: parsedArgs.user_id
1765
+ }
1766
+ });
1767
+ }
1768
+ }
1769
+ const filtered = keyword
1770
+ ? packages.filter((item) => {
1771
+ const values = [
1772
+ item.tag_name ?? "",
1773
+ ...item.apps.map((app) => app.app_name ?? ""),
1774
+ ...item.dashboards.map((dash) => dash.dash_name ?? "")
1775
+ ];
1776
+ return values.some((value) => value.toLowerCase().includes(keyword));
1777
+ })
1778
+ : packages;
1779
+ const sliced = filtered.slice(offset, offset + limit);
1780
+ return okResult({
1781
+ ok: true,
1782
+ data: {
1783
+ user_id: parsedArgs.user_id,
1784
+ tag_id_filter: parsedArgs.tag_id ?? null,
1785
+ total_packages: filtered.length,
1786
+ returned_packages: sliced.length,
1787
+ limit,
1788
+ offset,
1789
+ packages: sliced
1790
+ },
1791
+ meta: buildMeta(response)
1792
+ }, `Returned ${sliced.length}/${filtered.length} app packages`);
1793
+ }
1794
+ catch (error) {
1795
+ return errorResult(error);
1796
+ }
1797
+ });
1798
+ server.registerTool("qf_apply_audit_records_list", {
1799
+ title: "Qingflow Apply Audit Records List",
1800
+ description: "List one record's workflow audit log and current pending nodes.",
1801
+ inputSchema: applyAuditRecordsInputPublicSchema,
1802
+ outputSchema: applyAuditRecordsOutputSchema,
1803
+ annotations: {
1804
+ readOnlyHint: true,
1805
+ idempotentHint: true
1806
+ }
1807
+ }, async (args) => {
1808
+ try {
1809
+ const parsedArgs = applyAuditRecordsInputSchema.parse(args);
1810
+ if (!parsedArgs.apply_id) {
1811
+ throw missingRequiredFieldError({
1812
+ field: "apply_id",
1813
+ tool: "qf_apply_audit_records_list",
1814
+ fixHint: "Provide apply_id (or applyId), for example: {\"apply_id\":\"50001234\"}."
1815
+ });
1816
+ }
1817
+ let response;
1818
+ try {
1819
+ response = await client.listApplyAuditRecords(parsedArgs.apply_id);
1820
+ }
1821
+ catch (error) {
1822
+ throw translateAdminApiError(error, {
1823
+ tool: "qf_apply_audit_records_list",
1824
+ entity: "apply",
1825
+ applyId: parsedArgs.apply_id
1826
+ });
1827
+ }
1828
+ const result = asObject(response.result);
1829
+ return okResult({
1830
+ ok: true,
1831
+ data: {
1832
+ apply_id: parsedArgs.apply_id,
1833
+ apply_status: result?.applyStatus ?? null,
1834
+ audit_records: asArray(result?.auditRecords).map((item) => normalizeAuditRecordSummary(item)),
1835
+ current_nodes: asArray(result?.currentNodes).map((item) => normalizeAuditRecordSummary(item))
1836
+ },
1837
+ meta: buildMeta(response)
1838
+ }, `Fetched audit records for apply ${parsedArgs.apply_id}`);
1839
+ }
1840
+ catch (error) {
1841
+ return errorResult(error);
1842
+ }
1843
+ });
1844
+ server.registerTool("qf_apply_audit_record_get", {
1845
+ title: "Qingflow Apply Audit Record Get",
1846
+ description: "Get one workflow audit record detail by apply_id and audit_rcd_id.",
1847
+ inputSchema: applyAuditRecordGetInputPublicSchema,
1848
+ outputSchema: applyAuditRecordGetOutputSchema,
1849
+ annotations: {
1850
+ readOnlyHint: true,
1851
+ idempotentHint: true
1852
+ }
1853
+ }, async (args) => {
1854
+ try {
1855
+ const parsedArgs = applyAuditRecordGetInputSchema.parse(args);
1856
+ if (!parsedArgs.apply_id) {
1857
+ throw missingRequiredFieldError({
1858
+ field: "apply_id",
1859
+ tool: "qf_apply_audit_record_get",
1860
+ fixHint: "Provide apply_id (or applyId), for example: {\"apply_id\":\"50001234\",\"audit_rcd_id\":\"1111\"}."
1861
+ });
1862
+ }
1863
+ if (!parsedArgs.audit_rcd_id) {
1864
+ throw missingRequiredFieldError({
1865
+ field: "audit_rcd_id",
1866
+ tool: "qf_apply_audit_record_get",
1867
+ fixHint: "Provide audit_rcd_id (or auditRcdId), for example: {\"apply_id\":\"50001234\",\"audit_rcd_id\":\"1111\"}."
1868
+ });
1869
+ }
1870
+ let response;
1871
+ try {
1872
+ response = await client.getApplyAuditRecord(parsedArgs.apply_id, parsedArgs.audit_rcd_id);
1873
+ }
1874
+ catch (error) {
1875
+ throw translateAdminApiError(error, {
1876
+ tool: "qf_apply_audit_record_get",
1877
+ entity: "audit_record",
1878
+ applyId: parsedArgs.apply_id,
1879
+ auditRcdId: parsedArgs.audit_rcd_id
1880
+ });
1881
+ }
1882
+ const result = asObject(response.result);
1883
+ return okResult({
1884
+ ok: true,
1885
+ data: {
1886
+ apply_id: parsedArgs.apply_id,
1887
+ audit_rcd_id: result?.auditRcdId ?? parsedArgs.audit_rcd_id,
1888
+ modifies: normalizeAuditModifies(result?.auditModifies)
1889
+ },
1890
+ meta: buildMeta(response)
1891
+ }, `Fetched audit record ${parsedArgs.audit_rcd_id} for apply ${parsedArgs.apply_id}`);
1892
+ }
1893
+ catch (error) {
1894
+ return errorResult(error);
1895
+ }
1896
+ });
1421
1897
  server.registerTool("qf_departments_list", {
1422
1898
  title: "Qingflow Departments List",
1423
1899
  description: "List departments with optional dept_id filter and local keyword slicing.",
@@ -2358,6 +2834,17 @@ function buildMeta(response) {
2358
2834
  base_url: baseUrl
2359
2835
  };
2360
2836
  }
2837
+ async function fetchRecordsByApplyIds(params) {
2838
+ const response = await client.listRecords(params.appKey, buildListPayload({
2839
+ pageNum: 1,
2840
+ pageSize: Math.min(Math.max(params.applyIds.length, 1), 200),
2841
+ applyIds: params.applyIds
2842
+ }), { userId: params.userId });
2843
+ const records = asArray(asObject(response.result)?.result)
2844
+ .map((item) => asObject(item))
2845
+ .filter((item) => Boolean(item));
2846
+ return { response, records };
2847
+ }
2361
2848
  function normalizeStringArray(value) {
2362
2849
  return uniqueStringList(asArray(value)
2363
2850
  .map((item) => asNullableString(item)?.trim() ?? "")
@@ -2391,6 +2878,174 @@ function normalizeUser(raw) {
2391
2878
  superior_id: asNullableString(obj.superiorId)
2392
2879
  };
2393
2880
  }
2881
+ function normalizeAppAuthMembers(raw) {
2882
+ const obj = asObject(raw) ?? {};
2883
+ return {
2884
+ users: asArray(obj.users).map((item) => {
2885
+ const entry = asObject(item) ?? {};
2886
+ return {
2887
+ user_id: asNullableString(entry.userId),
2888
+ user_name: asNullableString(entry.userName)
2889
+ };
2890
+ }),
2891
+ departments: asArray(obj.depts).map((item) => {
2892
+ const entry = asObject(item) ?? {};
2893
+ return {
2894
+ dept_id: toNonNegativeInt(entry.deptId),
2895
+ dept_name: asNullableString(entry.deptName)
2896
+ };
2897
+ }),
2898
+ roles: asArray(obj.roles).map((item) => {
2899
+ const entry = asObject(item) ?? {};
2900
+ return {
2901
+ role_id: toNonNegativeInt(entry.roleId),
2902
+ role_name: asNullableString(entry.roleName)
2903
+ };
2904
+ })
2905
+ };
2906
+ }
2907
+ function normalizeAppInfo(raw) {
2908
+ const obj = asObject(raw) ?? {};
2909
+ const creator = asObject(obj.creator) ?? {};
2910
+ return {
2911
+ app_key: asNullableString(obj.appKey),
2912
+ app_name: asNullableString(obj.appName),
2913
+ app_auth: toNonNegativeInt(obj.appAuth),
2914
+ app_icon: asNullableString(obj.appIcon),
2915
+ auth_members: normalizeAppAuthMembers(obj.authmembers ?? obj.authMembers),
2916
+ creator: {
2917
+ user_id: asNullableString(creator.userId),
2918
+ nick_name: asNullableString(creator.nickName),
2919
+ head_img: asNullableString(creator.headImg)
2920
+ },
2921
+ create_time: asNullableString(obj.createTime),
2922
+ tags: asArray(obj.tags).map((item) => {
2923
+ const entry = asObject(item) ?? {};
2924
+ return {
2925
+ tag_id: toNonNegativeInt(entry.tagId),
2926
+ tag_name: asNullableString(entry.tagName)
2927
+ };
2928
+ }),
2929
+ app_publish_status: toNonNegativeInt(obj.appPublishStatus)
2930
+ };
2931
+ }
2932
+ function normalizeAppPackage(raw) {
2933
+ const obj = asObject(raw) ?? {};
2934
+ return {
2935
+ tag_id: toNonNegativeInt(obj.tagId),
2936
+ tag_name: asNullableString(obj.tagName),
2937
+ tag_icon: asNullableString(obj.tagIcon),
2938
+ apps: asArray(obj.appList).map((item) => {
2939
+ const entry = asObject(item) ?? {};
2940
+ return {
2941
+ app_key: asNullableString(entry.appKey),
2942
+ app_name: asNullableString(entry.appName)
2943
+ };
2944
+ }),
2945
+ dashboards: asArray(obj.dashList).map((item) => {
2946
+ const entry = asObject(item) ?? {};
2947
+ return {
2948
+ dash_key: asNullableString(entry.dashKey),
2949
+ dash_name: asNullableString(entry.dashName)
2950
+ };
2951
+ })
2952
+ };
2953
+ }
2954
+ function normalizeAuditUserInfo(raw) {
2955
+ const obj = asObject(raw) ?? {};
2956
+ return {
2957
+ user_id: asNullableString(obj.userId),
2958
+ user_name: asNullableString(obj.userName),
2959
+ nick_name: asNullableString(obj.nickName),
2960
+ head_img: asNullableString(obj.headImg)
2961
+ };
2962
+ }
2963
+ function toNullableSpecialId(value) {
2964
+ if (typeof value === "number" && Number.isFinite(value)) {
2965
+ return Math.trunc(value);
2966
+ }
2967
+ if (typeof value === "string" && value.trim()) {
2968
+ const normalized = value.trim();
2969
+ if (/^\d+$/.test(normalized)) {
2970
+ return Number(normalized);
2971
+ }
2972
+ return normalized;
2973
+ }
2974
+ const obj = asObject(value);
2975
+ if (!obj) {
2976
+ return null;
2977
+ }
2978
+ const candidate = obj.queId ?? obj.id ?? obj.value ?? obj.dataValue ?? null;
2979
+ return candidate === null ? null : toNullableSpecialId(candidate);
2980
+ }
2981
+ function normalizeAuditRecordSummary(raw) {
2982
+ const obj = asObject(raw) ?? {};
2983
+ return {
2984
+ audit_rcd_id: toNonNegativeInt(obj.auditRcdId),
2985
+ audit_node_id: toNonNegativeInt(obj.auditNodeId),
2986
+ audit_node_name: asNullableString(obj.auditNodeName),
2987
+ audit_time_ms: toNonNegativeInt(obj.auditTime),
2988
+ audit_result: obj.auditResult ?? null,
2989
+ audit_feedback: asNullableString(obj.auditFeedback),
2990
+ audit_user: obj.auditUser ? normalizeAuditUserInfo(obj.auditUser) : null,
2991
+ wait_audit_users: asArray(obj.waitAuditUserList).map((item) => normalizeAuditUserInfo(item))
2992
+ };
2993
+ }
2994
+ function normalizeAuditModifies(raw) {
2995
+ const source = Array.isArray(raw)
2996
+ ? raw
2997
+ : asObject(raw)
2998
+ ? Object.values(raw)
2999
+ : [];
3000
+ return source.map((item) => {
3001
+ const entry = asObject(item) ?? {};
3002
+ return {
3003
+ que_id: toNullableSpecialId(entry.queId),
3004
+ que_title: asNullableString(entry.queTitle),
3005
+ que_type: entry.queType ?? null,
3006
+ before_answer: asArray(entry.beforeAnswer),
3007
+ after_answer: asArray(entry.afterAnswer)
3008
+ };
3009
+ });
3010
+ }
3011
+ function translateAdminApiError(error, params) {
3012
+ if (error instanceof QingflowApiError &&
3013
+ (error.httpStatus === 404 || error.errCode === 404)) {
3014
+ if (params.entity === "app") {
3015
+ return new InputValidationError({
3016
+ message: `App \"${params.appKey}\" not found`,
3017
+ errorCode: "APP_NOT_FOUND",
3018
+ fixHint: "Call qf_apps_list or qf_apps_info_list first to confirm the exact app_key.",
3019
+ details: {
3020
+ tool: params.tool,
3021
+ app_key: params.appKey
3022
+ }
3023
+ });
3024
+ }
3025
+ if (params.entity === "apply") {
3026
+ return new InputValidationError({
3027
+ message: `Apply \"${params.applyId}\" not found`,
3028
+ errorCode: "APPLY_NOT_FOUND",
3029
+ fixHint: "Use qf_record_get or your record query tools first to confirm the exact apply_id.",
3030
+ details: {
3031
+ tool: params.tool,
3032
+ apply_id: params.applyId
3033
+ }
3034
+ });
3035
+ }
3036
+ return new InputValidationError({
3037
+ message: `Audit record \"${params.auditRcdId}\" for apply \"${params.applyId}\" not found`,
3038
+ errorCode: "AUDIT_RECORD_NOT_FOUND",
3039
+ fixHint: "Call qf_apply_audit_records_list first to confirm the exact audit_rcd_id.",
3040
+ details: {
3041
+ tool: params.tool,
3042
+ apply_id: params.applyId,
3043
+ audit_rcd_id: params.auditRcdId
3044
+ }
3045
+ });
3046
+ }
3047
+ return error instanceof Error ? error : new Error(String(error));
3048
+ }
2394
3049
  function translateDirectoryApiError(error, params) {
2395
3050
  if (error instanceof QingflowApiError &&
2396
3051
  (error.httpStatus === 404 || error.errCode === 404)) {
@@ -2437,6 +3092,7 @@ function missingRequiredFieldError(params) {
2437
3092
  const COMMON_INPUT_ALIASES = {
2438
3093
  appKey: "app_key",
2439
3094
  userId: "user_id",
3095
+ tagId: "tag_id",
2440
3096
  pageNum: "page_num",
2441
3097
  pageSize: "page_size",
2442
3098
  pageToken: "page_token",
@@ -2448,6 +3104,7 @@ const COMMON_INPUT_ALIASES = {
2448
3104
  queryMode: "query_mode",
2449
3105
  queryLogic: "query_logic",
2450
3106
  applyId: "apply_id",
3107
+ auditRcdId: "audit_rcd_id",
2451
3108
  applyIds: "apply_ids",
2452
3109
  maxRows: "max_rows",
2453
3110
  rowLimit: "max_rows",
@@ -2582,6 +3239,87 @@ function normalizeUserGetInput(raw) {
2582
3239
  user_id: coerceStringLike(normalizedObj.user_id)
2583
3240
  };
2584
3241
  }
3242
+ function normalizeAppInfoListInput(raw) {
3243
+ const parsedRoot = parseJsonLikeDeep(raw);
3244
+ const obj = asObject(parsedRoot);
3245
+ if (!obj) {
3246
+ return parsedRoot;
3247
+ }
3248
+ const normalizedObj = applyAliases(obj, {
3249
+ appKey: "app_key",
3250
+ pageNum: "page_num",
3251
+ pageSize: "page_size"
3252
+ });
3253
+ return {
3254
+ ...normalizedObj,
3255
+ app_key: coerceStringLike(normalizedObj.app_key),
3256
+ page_num: coerceNumberLike(normalizedObj.page_num),
3257
+ page_size: coerceNumberLike(normalizedObj.page_size)
3258
+ };
3259
+ }
3260
+ function normalizeAppInfoGetInput(raw) {
3261
+ const parsedRoot = parseJsonLikeDeep(raw);
3262
+ const obj = asObject(parsedRoot);
3263
+ if (!obj) {
3264
+ return parsedRoot;
3265
+ }
3266
+ const normalizedObj = applyAliases(obj, {
3267
+ appKey: "app_key"
3268
+ });
3269
+ return {
3270
+ ...normalizedObj,
3271
+ app_key: coerceStringLike(normalizedObj.app_key)
3272
+ };
3273
+ }
3274
+ function normalizeAppPackageListInput(raw) {
3275
+ const parsedRoot = parseJsonLikeDeep(raw);
3276
+ const obj = asObject(parsedRoot);
3277
+ if (!obj) {
3278
+ return parsedRoot;
3279
+ }
3280
+ const normalizedObj = applyAliases(obj, {
3281
+ userId: "user_id",
3282
+ tagId: "tag_id"
3283
+ });
3284
+ return {
3285
+ ...normalizedObj,
3286
+ user_id: coerceStringLike(normalizedObj.user_id),
3287
+ tag_id: coerceNumberLike(normalizeSelectorInputValue(normalizedObj.tag_id)),
3288
+ keyword: coerceStringLike(normalizedObj.keyword),
3289
+ limit: coerceNumberLike(normalizedObj.limit),
3290
+ offset: coerceNumberLike(normalizedObj.offset)
3291
+ };
3292
+ }
3293
+ function normalizeApplyAuditRecordsInput(raw) {
3294
+ const parsedRoot = parseJsonLikeDeep(raw);
3295
+ const obj = asObject(parsedRoot);
3296
+ if (!obj) {
3297
+ return parsedRoot;
3298
+ }
3299
+ const normalizedObj = applyAliases(obj, {
3300
+ applyId: "apply_id"
3301
+ });
3302
+ return {
3303
+ ...normalizedObj,
3304
+ apply_id: coerceStringLike(normalizedObj.apply_id)
3305
+ };
3306
+ }
3307
+ function normalizeApplyAuditRecordGetInput(raw) {
3308
+ const parsedRoot = parseJsonLikeDeep(raw);
3309
+ const obj = asObject(parsedRoot);
3310
+ if (!obj) {
3311
+ return parsedRoot;
3312
+ }
3313
+ const normalizedObj = applyAliases(obj, {
3314
+ applyId: "apply_id",
3315
+ auditRcdId: "audit_rcd_id"
3316
+ });
3317
+ return {
3318
+ ...normalizedObj,
3319
+ apply_id: coerceStringLike(normalizedObj.apply_id),
3320
+ audit_rcd_id: coerceStringLike(normalizedObj.audit_rcd_id)
3321
+ };
3322
+ }
2585
3323
  function buildToolSpecCatalog() {
2586
3324
  return [
2587
3325
  {
@@ -2611,6 +3349,67 @@ function buildToolSpecCatalog() {
2611
3349
  limit: 20
2612
3350
  }
2613
3351
  },
3352
+ {
3353
+ tool: "qf_apps_info_list",
3354
+ required: ["page_num", "page_size"],
3355
+ limits: {
3356
+ input_contract: "strict JSON only; page_num/page_size must be native JSON numbers"
3357
+ },
3358
+ aliases: collectAliasHints(["page_num", "page_size", "app_key"], {}),
3359
+ minimal_example: {
3360
+ page_num: 1,
3361
+ page_size: 50,
3362
+ app_key: "21b3d559"
3363
+ }
3364
+ },
3365
+ {
3366
+ tool: "qf_app_info_get",
3367
+ required: ["app_key"],
3368
+ limits: {
3369
+ input_contract: "strict JSON only; app_key must be a native JSON string"
3370
+ },
3371
+ aliases: collectAliasHints(["app_key"], {}),
3372
+ minimal_example: {
3373
+ app_key: "21b3d559"
3374
+ }
3375
+ },
3376
+ {
3377
+ tool: "qf_app_packages_list",
3378
+ required: ["user_id"],
3379
+ limits: {
3380
+ limit_max: 500,
3381
+ offset_min: 0,
3382
+ input_contract: "strict JSON only; user_id must be a native JSON string when provided"
3383
+ },
3384
+ aliases: collectAliasHints(["user_id", "tag_id"], {}),
3385
+ minimal_example: {
3386
+ user_id: "u_123",
3387
+ limit: 20
3388
+ }
3389
+ },
3390
+ {
3391
+ tool: "qf_apply_audit_records_list",
3392
+ required: ["apply_id"],
3393
+ limits: {
3394
+ input_contract: "strict JSON only; apply_id must be a native JSON string"
3395
+ },
3396
+ aliases: collectAliasHints(["apply_id"], {}),
3397
+ minimal_example: {
3398
+ apply_id: "50001234"
3399
+ }
3400
+ },
3401
+ {
3402
+ tool: "qf_apply_audit_record_get",
3403
+ required: ["apply_id", "audit_rcd_id"],
3404
+ limits: {
3405
+ input_contract: "strict JSON only; apply_id/audit_rcd_id must be native JSON strings"
3406
+ },
3407
+ aliases: collectAliasHints(["apply_id", "audit_rcd_id"], {}),
3408
+ minimal_example: {
3409
+ apply_id: "50001234",
3410
+ audit_rcd_id: "1111"
3411
+ }
3412
+ },
2614
3413
  {
2615
3414
  tool: "qf_departments_list",
2616
3415
  required: [],
@@ -2997,6 +3796,7 @@ function normalizeRecordGetInput(raw) {
2997
3796
  return {
2998
3797
  ...normalizedObj,
2999
3798
  apply_id: coerceNumberLike(normalizedObj.apply_id),
3799
+ app_key: coerceStringLike(normalizedObj.app_key),
3000
3800
  max_columns: coerceNumberLike(normalizedObj.max_columns),
3001
3801
  select_columns: normalizeSelectorListInput(selectColumns),
3002
3802
  output_profile: normalizeOutputProfileInput(normalizedObj.output_profile)
@@ -3117,6 +3917,7 @@ function normalizeBatchGetInput(raw) {
3117
3917
  const selectColumns = normalizedObj.select_columns ?? normalizedObj.keep_columns;
3118
3918
  return {
3119
3919
  ...normalizedObj,
3920
+ app_key: coerceStringLike(normalizedObj.app_key),
3120
3921
  apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
3121
3922
  max_columns: coerceNumberLike(normalizedObj.max_columns),
3122
3923
  select_columns: normalizeSelectorListInput(selectColumns),
@@ -4717,6 +5518,7 @@ function buildRecordGetArgsFromQuery(args) {
4717
5518
  }
4718
5519
  return recordGetInputSchema.parse({
4719
5520
  apply_id: args.apply_id,
5521
+ app_key: args.app_key,
4720
5522
  max_columns: args.max_columns,
4721
5523
  select_columns: args.select_columns,
4722
5524
  output_profile: args.output_profile
@@ -4802,6 +5604,13 @@ async function executeQueryPlan(args) {
4802
5604
  };
4803
5605
  }
4804
5606
  async function executeRecordsBatchGet(args) {
5607
+ if (!args.app_key) {
5608
+ throw missingRequiredFieldError({
5609
+ field: "app_key",
5610
+ tool: "qf_records_batch_get",
5611
+ fixHint: "Provide app_key, for example: {\"app_key\":\"21b3d559\",\"apply_ids\":[\"...\"],\"select_columns\":[0]}."
5612
+ });
5613
+ }
4805
5614
  if (!args.select_columns?.length) {
4806
5615
  throw missingRequiredFieldError({
4807
5616
  field: "select_columns",
@@ -4819,25 +5628,23 @@ async function executeRecordsBatchGet(args) {
4819
5628
  const rows = [];
4820
5629
  const missingApplyIds = [];
4821
5630
  let metaResponse = null;
5631
+ const { response, records } = await fetchRecordsByApplyIds({
5632
+ appKey: args.app_key,
5633
+ applyIds: requestedApplyIds
5634
+ });
5635
+ metaResponse = buildMeta(response);
5636
+ const byApplyId = new Map(records.map((record) => [String(record.applyId ?? ""), record]));
4822
5637
  for (const applyId of requestedApplyIds) {
4823
- try {
4824
- const response = await client.getRecord(applyId);
4825
- metaResponse = metaResponse ?? buildMeta(response);
4826
- const record = asObject(response.result) ?? {};
4827
- rows.push(buildFlatRowFromAnswers({
4828
- applyId: record.applyId ?? applyId,
4829
- answers: asArray(record.answers),
4830
- selectedColumns: selectedColumnsForRow
4831
- }));
4832
- }
4833
- catch (error) {
4834
- if (error instanceof QingflowApiError &&
4835
- (error.httpStatus === 404 || error.errCode === 404)) {
4836
- missingApplyIds.push(applyId);
4837
- continue;
4838
- }
4839
- throw error;
5638
+ const record = byApplyId.get(String(applyId));
5639
+ if (!record) {
5640
+ missingApplyIds.push(applyId);
5641
+ continue;
4840
5642
  }
5643
+ rows.push(buildFlatRowFromAnswers({
5644
+ applyId: record.applyId ?? applyId,
5645
+ answers: asArray(record.answers),
5646
+ selectedColumns: selectedColumnsForRow
5647
+ }));
4841
5648
  }
4842
5649
  const completeness = {
4843
5650
  result_amount: requestedApplyIds.length,
@@ -5400,10 +6207,50 @@ async function executeRecordGet(args) {
5400
6207
  }
5401
6208
  const outputProfile = resolveOutputProfile(args.output_profile);
5402
6209
  const queryId = randomUUID();
5403
- const response = await client.getRecord(String(args.apply_id));
5404
- const record = asObject(response.result) ?? {};
6210
+ let response;
6211
+ let record = null;
6212
+ try {
6213
+ response = await client.getRecord(String(args.apply_id));
6214
+ record = asObject(response.result) ?? {};
6215
+ }
6216
+ catch (error) {
6217
+ const providerError = error instanceof QingflowApiError ? error : null;
6218
+ const shouldFallback = providerError && (providerError.httpStatus === 404 || providerError.errCode === 404 || providerError.errCode === 49304);
6219
+ if (!shouldFallback) {
6220
+ throw error;
6221
+ }
6222
+ if (!args.app_key) {
6223
+ throw new InputValidationError({
6224
+ message: `qf_record_get could not read apply_id \"${String(args.apply_id)}\" through the direct record endpoint`,
6225
+ errorCode: "APP_KEY_REQUIRED_FOR_RECORD_GET",
6226
+ fixHint: "Retry qf_record_get with app_key, or use qf_query/qf_records_batch_get with explicit app_key for this record.",
6227
+ details: {
6228
+ apply_id: String(args.apply_id),
6229
+ provider_err_code: providerError?.errCode ?? null,
6230
+ provider_err_msg: providerError?.errMsg ?? null
6231
+ }
6232
+ });
6233
+ }
6234
+ const fallback = await fetchRecordsByApplyIds({
6235
+ appKey: args.app_key,
6236
+ applyIds: [String(args.apply_id)]
6237
+ });
6238
+ response = fallback.response;
6239
+ record = fallback.records.find((item) => String(item.applyId ?? "") === String(args.apply_id)) ?? null;
6240
+ if (!record) {
6241
+ throw new InputValidationError({
6242
+ message: `Record \"${String(args.apply_id)}\" not found in app \"${args.app_key}\"`,
6243
+ errorCode: "RECORD_NOT_FOUND",
6244
+ fixHint: "Confirm apply_id and app_key, or fetch the row via qf_records_list/qf_query first.",
6245
+ details: {
6246
+ apply_id: String(args.apply_id),
6247
+ app_key: args.app_key
6248
+ }
6249
+ });
6250
+ }
6251
+ }
5405
6252
  const projection = projectAnswersForOutput({
5406
- answers: asArray(record.answers),
6253
+ answers: asArray(record?.answers),
5407
6254
  maxColumns: args.max_columns,
5408
6255
  selectColumns: args.select_columns
5409
6256
  });
@@ -5411,8 +6258,8 @@ async function executeRecordGet(args) {
5411
6258
  ? (projection.selectedColumns ?? []).slice(0, args.max_columns)
5412
6259
  : projection.selectedColumns ?? [];
5413
6260
  const row = buildFlatRowFromAnswers({
5414
- applyId: record.applyId ?? null,
5415
- answers: asArray(record.answers),
6261
+ applyId: record?.applyId ?? null,
6262
+ answers: asArray(record?.answers),
5416
6263
  selectedColumns: selectedColumnsForRow
5417
6264
  });
5418
6265
  const completeness = {
@@ -5437,7 +6284,7 @@ async function executeRecordGet(args) {
5437
6284
  payload: {
5438
6285
  ok: true,
5439
6286
  data: {
5440
- apply_id: record.applyId ?? null,
6287
+ apply_id: record?.applyId ?? null,
5441
6288
  row,
5442
6289
  applied_limits: {
5443
6290
  column_cap: args.max_columns ?? null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qingflow-mcp",
3
- "version": "0.3.18",
3
+ "version": "0.3.20",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",