qingflow-mcp 0.3.3 → 0.3.4

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.
Files changed (2) hide show
  1. package/dist/server.js +240 -61
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -64,7 +64,7 @@ const client = new QingflowClient({
64
64
  });
65
65
  const server = new McpServer({
66
66
  name: "qingflow-mcp",
67
- version: "0.3.3"
67
+ version: "0.3.4"
68
68
  });
69
69
  const jsonPrimitiveSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
70
70
  const answerValueSchema = z.union([
@@ -1228,29 +1228,70 @@ function missingRequiredFieldError(params) {
1228
1228
  }
1229
1229
  });
1230
1230
  }
1231
+ const COMMON_INPUT_ALIASES = {
1232
+ appKey: "app_key",
1233
+ userId: "user_id",
1234
+ pageNum: "page_num",
1235
+ pageSize: "page_size",
1236
+ pageToken: "page_token",
1237
+ requestedPages: "requested_pages",
1238
+ scanMaxPages: "scan_max_pages",
1239
+ queryMode: "query_mode",
1240
+ queryLogic: "query_logic",
1241
+ applyId: "apply_id",
1242
+ applyIds: "apply_ids",
1243
+ maxRows: "max_rows",
1244
+ maxItems: "max_items",
1245
+ maxColumns: "max_columns",
1246
+ selectColumns: "select_columns",
1247
+ keepColumns: "keep_columns",
1248
+ keep_columns: "select_columns",
1249
+ includeAnswers: "include_answers",
1250
+ amountColumn: "amount_column",
1251
+ amountColumns: "amount_column",
1252
+ amount_columns: "amount_column",
1253
+ timeRange: "time_range",
1254
+ statPolicy: "stat_policy",
1255
+ groupBy: "group_by",
1256
+ strictFull: "strict_full",
1257
+ forceRefresh: "force_refresh",
1258
+ forceRefreshForm: "force_refresh_form",
1259
+ applyUser: "apply_user"
1260
+ };
1261
+ function applyAliases(obj, aliases) {
1262
+ const out = { ...obj };
1263
+ for (const [alias, canonical] of Object.entries(aliases)) {
1264
+ if (out[canonical] === undefined && out[alias] !== undefined) {
1265
+ out[canonical] = out[alias];
1266
+ }
1267
+ }
1268
+ return out;
1269
+ }
1231
1270
  function normalizeListInput(raw) {
1232
1271
  const parsedRoot = parseJsonLikeDeep(raw);
1233
1272
  const obj = asObject(parsedRoot);
1234
1273
  if (!obj) {
1235
1274
  return parsedRoot;
1236
1275
  }
1276
+ const normalizedObj = applyAliases(obj, COMMON_INPUT_ALIASES);
1277
+ const selectColumns = normalizedObj.select_columns ?? normalizedObj.keep_columns;
1237
1278
  return {
1238
- ...obj,
1239
- page_num: coerceNumberLike(obj.page_num),
1240
- page_size: coerceNumberLike(obj.page_size),
1241
- requested_pages: coerceNumberLike(obj.requested_pages),
1242
- scan_max_pages: coerceNumberLike(obj.scan_max_pages),
1243
- type: coerceNumberLike(obj.type),
1244
- max_rows: coerceNumberLike(obj.max_rows),
1245
- max_items: coerceNumberLike(obj.max_items),
1246
- max_columns: coerceNumberLike(obj.max_columns),
1247
- strict_full: coerceBooleanLike(obj.strict_full),
1248
- include_answers: coerceBooleanLike(obj.include_answers),
1249
- apply_ids: normalizeIdArrayInput(obj.apply_ids),
1250
- sort: normalizeSortInput(obj.sort),
1251
- filters: normalizeFiltersInput(obj.filters),
1252
- select_columns: normalizeSelectorListInput(obj.select_columns),
1253
- time_range: normalizeTimeRangeInput(obj.time_range)
1279
+ ...normalizedObj,
1280
+ page_num: coerceNumberLike(normalizedObj.page_num),
1281
+ page_size: coerceNumberLike(normalizedObj.page_size),
1282
+ requested_pages: coerceNumberLike(normalizedObj.requested_pages),
1283
+ scan_max_pages: coerceNumberLike(normalizedObj.scan_max_pages),
1284
+ type: coerceNumberLike(normalizedObj.type),
1285
+ max_rows: coerceNumberLike(normalizedObj.max_rows),
1286
+ max_items: coerceNumberLike(normalizedObj.max_items),
1287
+ max_columns: coerceNumberLike(normalizedObj.max_columns),
1288
+ strict_full: coerceBooleanLike(normalizedObj.strict_full),
1289
+ include_answers: coerceBooleanLike(normalizedObj.include_answers),
1290
+ apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
1291
+ sort: normalizeSortInput(normalizedObj.sort),
1292
+ filters: normalizeFiltersInput(normalizedObj.filters),
1293
+ select_columns: normalizeSelectorListInput(selectColumns),
1294
+ time_range: normalizeTimeRangeInput(normalizedObj.time_range)
1254
1295
  };
1255
1296
  }
1256
1297
  function normalizeRecordGetInput(raw) {
@@ -1259,11 +1300,13 @@ function normalizeRecordGetInput(raw) {
1259
1300
  if (!obj) {
1260
1301
  return parsedRoot;
1261
1302
  }
1303
+ const normalizedObj = applyAliases(obj, COMMON_INPUT_ALIASES);
1304
+ const selectColumns = normalizedObj.select_columns ?? normalizedObj.keep_columns;
1262
1305
  return {
1263
- ...obj,
1264
- apply_id: coerceNumberLike(obj.apply_id),
1265
- max_columns: coerceNumberLike(obj.max_columns),
1266
- select_columns: normalizeSelectorListInput(obj.select_columns)
1306
+ ...normalizedObj,
1307
+ apply_id: coerceNumberLike(normalizedObj.apply_id),
1308
+ max_columns: coerceNumberLike(normalizedObj.max_columns),
1309
+ select_columns: normalizeSelectorListInput(selectColumns)
1267
1310
  };
1268
1311
  }
1269
1312
  function normalizeQueryInput(raw) {
@@ -1272,24 +1315,28 @@ function normalizeQueryInput(raw) {
1272
1315
  if (!obj) {
1273
1316
  return parsedRoot;
1274
1317
  }
1318
+ const normalizedObj = applyAliases(obj, COMMON_INPUT_ALIASES);
1319
+ const selectColumns = normalizedObj.select_columns ?? normalizedObj.keep_columns;
1275
1320
  return {
1276
- ...obj,
1277
- page_num: coerceNumberLike(obj.page_num),
1278
- page_size: coerceNumberLike(obj.page_size),
1279
- requested_pages: coerceNumberLike(obj.requested_pages),
1280
- scan_max_pages: coerceNumberLike(obj.scan_max_pages),
1281
- type: coerceNumberLike(obj.type),
1282
- max_rows: coerceNumberLike(obj.max_rows),
1283
- max_items: coerceNumberLike(obj.max_items),
1284
- max_columns: coerceNumberLike(obj.max_columns),
1285
- apply_id: coerceNumberLike(obj.apply_id),
1286
- strict_full: coerceBooleanLike(obj.strict_full),
1287
- include_answers: coerceBooleanLike(obj.include_answers),
1288
- apply_ids: normalizeIdArrayInput(obj.apply_ids),
1289
- sort: normalizeSortInput(obj.sort),
1290
- filters: normalizeFiltersInput(obj.filters),
1291
- select_columns: normalizeSelectorListInput(obj.select_columns),
1292
- time_range: normalizeTimeRangeInput(obj.time_range)
1321
+ ...normalizedObj,
1322
+ page_num: coerceNumberLike(normalizedObj.page_num),
1323
+ page_size: coerceNumberLike(normalizedObj.page_size),
1324
+ requested_pages: coerceNumberLike(normalizedObj.requested_pages),
1325
+ scan_max_pages: coerceNumberLike(normalizedObj.scan_max_pages),
1326
+ type: coerceNumberLike(normalizedObj.type),
1327
+ max_rows: coerceNumberLike(normalizedObj.max_rows),
1328
+ max_items: coerceNumberLike(normalizedObj.max_items),
1329
+ max_columns: coerceNumberLike(normalizedObj.max_columns),
1330
+ apply_id: coerceNumberLike(normalizedObj.apply_id),
1331
+ strict_full: coerceBooleanLike(normalizedObj.strict_full),
1332
+ include_answers: coerceBooleanLike(normalizedObj.include_answers),
1333
+ amount_column: coerceNumberLike(normalizedObj.amount_column),
1334
+ apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
1335
+ sort: normalizeSortInput(normalizedObj.sort),
1336
+ filters: normalizeFiltersInput(normalizedObj.filters),
1337
+ select_columns: normalizeSelectorListInput(selectColumns),
1338
+ time_range: normalizeTimeRangeInput(normalizedObj.time_range),
1339
+ stat_policy: normalizeStatPolicyInput(normalizedObj.stat_policy)
1293
1340
  };
1294
1341
  }
1295
1342
  function normalizeAggregateInput(raw) {
@@ -1298,21 +1345,23 @@ function normalizeAggregateInput(raw) {
1298
1345
  if (!obj) {
1299
1346
  return parsedRoot;
1300
1347
  }
1348
+ const normalizedObj = applyAliases(obj, COMMON_INPUT_ALIASES);
1301
1349
  return {
1302
- ...obj,
1303
- page_num: coerceNumberLike(obj.page_num),
1304
- page_size: coerceNumberLike(obj.page_size),
1305
- requested_pages: coerceNumberLike(obj.requested_pages),
1306
- scan_max_pages: coerceNumberLike(obj.scan_max_pages),
1307
- type: coerceNumberLike(obj.type),
1308
- max_groups: coerceNumberLike(obj.max_groups),
1309
- strict_full: coerceBooleanLike(obj.strict_full),
1310
- group_by: normalizeSelectorListInput(obj.group_by),
1311
- amount_column: coerceNumberLike(obj.amount_column),
1312
- apply_ids: normalizeIdArrayInput(obj.apply_ids),
1313
- sort: normalizeSortInput(obj.sort),
1314
- filters: normalizeFiltersInput(obj.filters),
1315
- time_range: normalizeTimeRangeInput(obj.time_range)
1350
+ ...normalizedObj,
1351
+ page_num: coerceNumberLike(normalizedObj.page_num),
1352
+ page_size: coerceNumberLike(normalizedObj.page_size),
1353
+ requested_pages: coerceNumberLike(normalizedObj.requested_pages),
1354
+ scan_max_pages: coerceNumberLike(normalizedObj.scan_max_pages),
1355
+ type: coerceNumberLike(normalizedObj.type),
1356
+ max_groups: coerceNumberLike(normalizedObj.max_groups),
1357
+ strict_full: coerceBooleanLike(normalizedObj.strict_full),
1358
+ group_by: normalizeSelectorListInput(normalizedObj.group_by),
1359
+ amount_column: coerceNumberLike(normalizedObj.amount_column),
1360
+ apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
1361
+ sort: normalizeSortInput(normalizedObj.sort),
1362
+ filters: normalizeFiltersInput(normalizedObj.filters),
1363
+ time_range: normalizeTimeRangeInput(normalizedObj.time_range),
1364
+ stat_policy: normalizeStatPolicyInput(normalizedObj.stat_policy)
1316
1365
  };
1317
1366
  }
1318
1367
  function coerceNumberLike(value) {
@@ -1426,10 +1475,11 @@ function normalizeSortInput(value) {
1426
1475
  if (!obj) {
1427
1476
  return item;
1428
1477
  }
1478
+ const normalizedObj = applyAliases(obj, { queId: "que_id", isAscend: "ascend" });
1429
1479
  return {
1430
- ...obj,
1431
- que_id: coerceNumberLike(obj.que_id),
1432
- ascend: coerceBooleanLike(obj.ascend)
1480
+ ...normalizedObj,
1481
+ que_id: coerceNumberLike(normalizedObj.que_id),
1482
+ ascend: coerceBooleanLike(normalizedObj.ascend)
1433
1483
  };
1434
1484
  });
1435
1485
  }
@@ -1444,11 +1494,20 @@ function normalizeFiltersInput(value) {
1444
1494
  if (!obj) {
1445
1495
  return item;
1446
1496
  }
1497
+ const normalizedObj = applyAliases(obj, {
1498
+ queId: "que_id",
1499
+ searchKey: "search_key",
1500
+ searchKeys: "search_keys",
1501
+ minValue: "min_value",
1502
+ maxValue: "max_value",
1503
+ searchOptions: "search_options",
1504
+ searchUserIds: "search_user_ids"
1505
+ });
1447
1506
  return {
1448
- ...obj,
1449
- que_id: coerceNumberLike(obj.que_id),
1450
- scope: coerceNumberLike(obj.scope),
1451
- search_options: normalizeIdArrayInput(obj.search_options)
1507
+ ...normalizedObj,
1508
+ que_id: coerceNumberLike(normalizedObj.que_id),
1509
+ scope: coerceNumberLike(normalizedObj.scope),
1510
+ search_options: normalizeIdArrayInput(normalizedObj.search_options)
1452
1511
  };
1453
1512
  });
1454
1513
  }
@@ -1458,9 +1517,30 @@ function normalizeTimeRangeInput(value) {
1458
1517
  if (!obj) {
1459
1518
  return parsed;
1460
1519
  }
1520
+ const normalizedObj = applyAliases(obj, {
1521
+ queId: "column",
1522
+ que_id: "column",
1523
+ timeZone: "timezone"
1524
+ });
1461
1525
  return {
1462
- ...obj,
1463
- column: coerceNumberLike(obj.column)
1526
+ ...normalizedObj,
1527
+ column: coerceNumberLike(normalizedObj.column)
1528
+ };
1529
+ }
1530
+ function normalizeStatPolicyInput(value) {
1531
+ const parsed = parseJsonLikeDeep(value);
1532
+ const obj = asObject(parsed);
1533
+ if (!obj) {
1534
+ return parsed;
1535
+ }
1536
+ const normalizedObj = applyAliases(obj, {
1537
+ includeNegative: "include_negative",
1538
+ includeNull: "include_null"
1539
+ });
1540
+ return {
1541
+ ...normalizedObj,
1542
+ include_negative: coerceBooleanLike(normalizedObj.include_negative),
1543
+ include_null: coerceBooleanLike(normalizedObj.include_null)
1464
1544
  };
1465
1545
  }
1466
1546
  function resolveStartPage(pageNum, pageToken, appKey) {
@@ -1606,6 +1686,98 @@ function appendTimeRangeFilter(inputFilters, timeRange) {
1606
1686
  }
1607
1687
  return filters.length > 0 ? filters : undefined;
1608
1688
  }
1689
+ function isLikelyDateLiteral(value) {
1690
+ if (!value) {
1691
+ return false;
1692
+ }
1693
+ const trimmed = value.trim();
1694
+ if (!trimmed) {
1695
+ return false;
1696
+ }
1697
+ return /^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}:\d{2})?$/.test(trimmed);
1698
+ }
1699
+ function isDateLikeQueType(queType) {
1700
+ if (typeof queType === "number" && Number.isInteger(queType)) {
1701
+ return queType === 4;
1702
+ }
1703
+ if (typeof queType === "string") {
1704
+ const trimmed = queType.trim().toLowerCase();
1705
+ if (trimmed === "date" || trimmed === "datetime") {
1706
+ return true;
1707
+ }
1708
+ if (/^\d+$/.test(trimmed)) {
1709
+ return Number(trimmed) === 4;
1710
+ }
1711
+ }
1712
+ return false;
1713
+ }
1714
+ function pickDateFieldSuggestion(index) {
1715
+ for (const field of index.byId.values()) {
1716
+ if (!isDateLikeQueType(field.queType) || field.queId === undefined || field.queId === null) {
1717
+ continue;
1718
+ }
1719
+ const normalizedQueId = normalizeQueId(field.queId);
1720
+ if (typeof normalizedQueId === "number" && normalizedQueId <= 0) {
1721
+ continue;
1722
+ }
1723
+ return {
1724
+ que_id: normalizedQueId,
1725
+ que_title: asNullableString(field.queTitle)
1726
+ };
1727
+ }
1728
+ return null;
1729
+ }
1730
+ function hasDateLikeRangeFilters(filters) {
1731
+ return (filters ?? []).some((item) => item.que_id !== undefined &&
1732
+ (isLikelyDateLiteral(item.min_value) || isLikelyDateLiteral(item.max_value)));
1733
+ }
1734
+ function validateDateRangeFilters(filters, index, tool) {
1735
+ for (const filter of filters ?? []) {
1736
+ if (filter.que_id === undefined ||
1737
+ (!isLikelyDateLiteral(filter.min_value) && !isLikelyDateLiteral(filter.max_value))) {
1738
+ continue;
1739
+ }
1740
+ let resolved;
1741
+ try {
1742
+ resolved = resolveFieldByKey(String(filter.que_id), index);
1743
+ }
1744
+ catch (error) {
1745
+ throw new InputValidationError({
1746
+ message: `Cannot resolve filter field "${String(filter.que_id)}"`,
1747
+ errorCode: "INVALID_FILTER_FIELD",
1748
+ fixHint: "Use qf_form_get to confirm exact que_id/que_title before passing filters.",
1749
+ details: {
1750
+ tool,
1751
+ filter,
1752
+ reason: error instanceof Error ? error.message : String(error)
1753
+ }
1754
+ });
1755
+ }
1756
+ if (!resolved || resolved.queType === undefined || resolved.queType === null) {
1757
+ continue;
1758
+ }
1759
+ if (isDateLikeQueType(resolved.queType)) {
1760
+ continue;
1761
+ }
1762
+ const suggestion = pickDateFieldSuggestion(index);
1763
+ throw new InputValidationError({
1764
+ message: `Date-like filter range targets non-date field "${String(filter.que_id)}"`,
1765
+ errorCode: "FILTER_FIELD_TYPE_MISMATCH",
1766
+ fixHint: suggestion
1767
+ ? `Use a date field for date range filters, e.g. que_id=${String(suggestion.que_id)} (${suggestion.que_title ?? "date field"}).`
1768
+ : "Use a date field (queType=4) for date range filters.",
1769
+ details: {
1770
+ tool,
1771
+ filter,
1772
+ resolved_field: {
1773
+ que_id: resolved.queId ?? null,
1774
+ que_title: asNullableString(resolved.queTitle),
1775
+ que_type: resolved.queType
1776
+ }
1777
+ }
1778
+ });
1779
+ }
1780
+ }
1609
1781
  function buildRecordGetArgsFromQuery(args) {
1610
1782
  if (args.apply_id === undefined) {
1611
1783
  throw missingRequiredFieldError({
@@ -1648,6 +1820,11 @@ async function executeRecordsList(args) {
1648
1820
  const requestedPages = args.requested_pages ?? 1;
1649
1821
  const scanMaxPages = args.scan_max_pages ?? requestedPages;
1650
1822
  const effectiveFilters = appendTimeRangeFilter(args.filters, args.time_range);
1823
+ if (hasDateLikeRangeFilters(effectiveFilters)) {
1824
+ const form = await getFormCached(args.app_key, args.user_id, false);
1825
+ const index = buildFieldIndex(form.result);
1826
+ validateDateRangeFilters(effectiveFilters, index, "qf_records_list");
1827
+ }
1651
1828
  const normalizedSort = await normalizeListSort(args.sort, args.app_key, args.user_id);
1652
1829
  const includeAnswers = true;
1653
1830
  const startedAt = Date.now();
@@ -1933,6 +2110,7 @@ async function executeRecordsSummary(args) {
1933
2110
  ...(args.time_range.to ? { max_value: args.time_range.to } : {})
1934
2111
  });
1935
2112
  }
2113
+ validateDateRangeFilters(summaryFilters, index, "qf_query(summary)");
1936
2114
  const listState = {
1937
2115
  query_id: queryId,
1938
2116
  app_key: args.app_key,
@@ -2173,6 +2351,7 @@ async function executeRecordsAggregate(args) {
2173
2351
  ...(args.time_range.to ? { max_value: args.time_range.to } : {})
2174
2352
  });
2175
2353
  }
2354
+ validateDateRangeFilters(aggregateFilters, index, "qf_records_aggregate");
2176
2355
  const listState = {
2177
2356
  query_id: queryId,
2178
2357
  app_key: args.app_key,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qingflow-mcp",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",