qingflow-mcp 0.3.20 → 0.3.22

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/server.js +111 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -117,7 +117,7 @@ npm i -g git+https://github.com/853046310/qingflow-mcp.git
117
117
  Install from npm (pinned version):
118
118
 
119
119
  ```bash
120
- npm i -g qingflow-mcp@0.3.20
120
+ npm i -g qingflow-mcp@0.3.22
121
121
  ```
122
122
 
123
123
  Or one-click installer:
package/dist/server.js CHANGED
@@ -46,6 +46,9 @@ class InputValidationError extends Error {
46
46
  }
47
47
  const FORM_CACHE_TTL_MS = Number(process.env.QINGFLOW_FORM_CACHE_TTL_MS ?? "300000");
48
48
  const formCache = new Map();
49
+ const APPLY_APP_KEY_CACHE_TTL_MS = Number(process.env.QINGFLOW_APPLY_APP_KEY_CACHE_TTL_MS ?? "1800000");
50
+ const applyAppKeyCache = new Map();
51
+ const requestAppKeyCache = new Map();
49
52
  const CONTINUATION_CACHE_TTL_MS = Number(process.env.QINGFLOW_CONTINUATION_CACHE_TTL_MS ?? "900000");
50
53
  const continuationCache = new Map();
51
54
  const DEFAULT_PAGE_SIZE = 50;
@@ -65,7 +68,7 @@ const REQUEST_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_REQUEST_TIMEOUT_MS
65
68
  const EXECUTION_BUDGET_MS = toPositiveInt(process.env.QINGFLOW_EXECUTION_BUDGET_MS) ?? 20000;
66
69
  const WAIT_RESULT_DEFAULT_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_WAIT_RESULT_TIMEOUT_MS) ?? 5000;
67
70
  const WAIT_RESULT_POLL_INTERVAL_MS = toPositiveInt(process.env.QINGFLOW_WAIT_RESULT_POLL_INTERVAL_MS) ?? 500;
68
- const SERVER_VERSION = "0.3.20";
71
+ const SERVER_VERSION = "0.3.22";
69
72
  const accessToken = process.env.QINGFLOW_ACCESS_TOKEN;
70
73
  const baseUrl = process.env.QINGFLOW_BASE_URL;
71
74
  if (!accessToken) {
@@ -2406,6 +2409,8 @@ server.registerTool("qf_record_create", {
2406
2409
  const result = asObject(response.result);
2407
2410
  const requestId = asNullableString(result?.requestId);
2408
2411
  const immediateApplyId = result?.applyId ?? null;
2412
+ rememberRequestAppKey(requestId, parsedArgs.app_key);
2413
+ rememberApplyAppKey(immediateApplyId, parsedArgs.app_key);
2409
2414
  const shouldWaitForResult = (parsedArgs.wait_result ?? false) && requestId !== null && immediateApplyId === null;
2410
2415
  let finalApplyId = immediateApplyId;
2411
2416
  let waitStatus = immediateApplyId !== null ? "completed" : "pending";
@@ -2418,6 +2423,7 @@ server.registerTool("qf_record_create", {
2418
2423
  waitStatus = waited.status;
2419
2424
  rawOperationResult = waited.operationResult;
2420
2425
  finalApplyId = waited.applyId;
2426
+ rememberApplyAppKey(waited.applyId, parsedArgs.app_key);
2421
2427
  }
2422
2428
  const createResource = finalApplyId !== null ? { type: "record", apply_id: finalApplyId } : null;
2423
2429
  const createNextAction = waitStatus === "pending" || waitStatus === "timeout"
@@ -2478,6 +2484,8 @@ server.registerTool("qf_record_update", {
2478
2484
  const response = await client.updateRecord(String(parsedArgs.apply_id), { answers: normalizedAnswers }, { userId: parsedArgs.user_id });
2479
2485
  const result = asObject(response.result);
2480
2486
  const updateRequestId = asNullableString(result?.requestId);
2487
+ rememberRequestAppKey(updateRequestId, parsedArgs.app_key ?? null);
2488
+ rememberApplyAppKey(parsedArgs.apply_id, parsedArgs.app_key ?? null);
2481
2489
  const shouldWaitForUpdate = (parsedArgs.wait_result ?? false) && updateRequestId !== null;
2482
2490
  let updateStatus = "pending";
2483
2491
  let updateRawOperationResult = null;
@@ -2493,6 +2501,7 @@ server.registerTool("qf_record_update", {
2493
2501
  if (waited.applyId !== null) {
2494
2502
  updateApplyId = waited.applyId;
2495
2503
  }
2504
+ rememberApplyAppKey(waited.applyId, parsedArgs.app_key ?? null);
2496
2505
  }
2497
2506
  else if (updateRequestId === null) {
2498
2507
  // No async operation — synchronous completion
@@ -2538,6 +2547,8 @@ server.registerTool("qf_operation_get", {
2538
2547
  }, async (args) => {
2539
2548
  try {
2540
2549
  const response = await client.getOperation(args.request_id);
2550
+ const cachedAppKey = getCachedRequestAppKey(args.request_id);
2551
+ rememberApplyAppKey(extractOperationApplyId(response.result), cachedAppKey);
2541
2552
  return okResult({
2542
2553
  ok: true,
2543
2554
  data: {
@@ -2834,6 +2845,58 @@ function buildMeta(response) {
2834
2845
  base_url: baseUrl
2835
2846
  };
2836
2847
  }
2848
+ function rememberApplyAppKey(applyId, appKey) {
2849
+ const normalizedApplyId = asNullableString(applyId)?.trim();
2850
+ const normalizedAppKey = asNullableString(appKey)?.trim();
2851
+ if (!normalizedApplyId || !normalizedAppKey) {
2852
+ return;
2853
+ }
2854
+ applyAppKeyCache.set(normalizedApplyId, {
2855
+ appKey: normalizedAppKey,
2856
+ expiresAt: Date.now() + APPLY_APP_KEY_CACHE_TTL_MS
2857
+ });
2858
+ }
2859
+ function rememberRequestAppKey(requestId, appKey) {
2860
+ const normalizedRequestId = asNullableString(requestId)?.trim();
2861
+ const normalizedAppKey = asNullableString(appKey)?.trim();
2862
+ if (!normalizedRequestId || !normalizedAppKey) {
2863
+ return;
2864
+ }
2865
+ requestAppKeyCache.set(normalizedRequestId, {
2866
+ appKey: normalizedAppKey,
2867
+ expiresAt: Date.now() + APPLY_APP_KEY_CACHE_TTL_MS
2868
+ });
2869
+ }
2870
+ function getCachedApplyAppKey(applyId) {
2871
+ const normalizedApplyId = asNullableString(applyId)?.trim();
2872
+ if (!normalizedApplyId) {
2873
+ return null;
2874
+ }
2875
+ const hit = applyAppKeyCache.get(normalizedApplyId);
2876
+ if (!hit) {
2877
+ return null;
2878
+ }
2879
+ if (hit.expiresAt <= Date.now()) {
2880
+ applyAppKeyCache.delete(normalizedApplyId);
2881
+ return null;
2882
+ }
2883
+ return hit.appKey;
2884
+ }
2885
+ function getCachedRequestAppKey(requestId) {
2886
+ const normalizedRequestId = asNullableString(requestId)?.trim();
2887
+ if (!normalizedRequestId) {
2888
+ return null;
2889
+ }
2890
+ const hit = requestAppKeyCache.get(normalizedRequestId);
2891
+ if (!hit) {
2892
+ return null;
2893
+ }
2894
+ if (hit.expiresAt <= Date.now()) {
2895
+ requestAppKeyCache.delete(normalizedRequestId);
2896
+ return null;
2897
+ }
2898
+ return hit.appKey;
2899
+ }
2837
2900
  async function fetchRecordsByApplyIds(params) {
2838
2901
  const response = await client.listRecords(params.appKey, buildListPayload({
2839
2902
  pageNum: 1,
@@ -3778,7 +3841,7 @@ function normalizeListInput(raw) {
3778
3841
  strict_full: coerceBooleanLike(normalizedObj.strict_full),
3779
3842
  include_answers: coerceBooleanLike(normalizedObj.include_answers),
3780
3843
  output_profile: normalizeOutputProfileInput(normalizedObj.output_profile),
3781
- apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
3844
+ apply_ids: normalizeOpaqueIdArrayInput(normalizedObj.apply_ids),
3782
3845
  sort: normalizeSortInput(normalizedObj.sort),
3783
3846
  filters: normalizeFiltersInput(normalizedObj.filters),
3784
3847
  select_columns: normalizeSelectorListInput(selectColumns),
@@ -3795,7 +3858,7 @@ function normalizeRecordGetInput(raw) {
3795
3858
  const selectColumns = normalizedObj.select_columns ?? normalizedObj.keep_columns;
3796
3859
  return {
3797
3860
  ...normalizedObj,
3798
- apply_id: coerceNumberLike(normalizedObj.apply_id),
3861
+ apply_id: normalizeOpaqueIdInput(normalizedObj.apply_id),
3799
3862
  app_key: coerceStringLike(normalizedObj.app_key),
3800
3863
  max_columns: coerceNumberLike(normalizedObj.max_columns),
3801
3864
  select_columns: normalizeSelectorListInput(selectColumns),
@@ -3821,12 +3884,12 @@ function normalizeQueryInput(raw) {
3821
3884
  max_rows: coerceNumberLike(normalizedObj.max_rows),
3822
3885
  max_items: coerceNumberLike(normalizedObj.max_items),
3823
3886
  max_columns: coerceNumberLike(normalizedObj.max_columns),
3824
- apply_id: coerceNumberLike(normalizedObj.apply_id),
3887
+ apply_id: normalizeOpaqueIdInput(normalizedObj.apply_id),
3825
3888
  strict_full: coerceBooleanLike(normalizedObj.strict_full),
3826
3889
  include_answers: coerceBooleanLike(normalizedObj.include_answers),
3827
3890
  output_profile: normalizeOutputProfileInput(normalizedObj.output_profile),
3828
3891
  amount_column: normalizeAmountColumnInput(normalizedObj.amount_column),
3829
- apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
3892
+ apply_ids: normalizeOpaqueIdArrayInput(normalizedObj.apply_ids),
3830
3893
  sort: normalizeSortInput(normalizedObj.sort),
3831
3894
  filters: normalizeFiltersInput(normalizedObj.filters),
3832
3895
  select_columns: normalizeSelectorListInput(selectColumns),
@@ -3858,7 +3921,7 @@ function normalizeAggregateInput(raw) {
3858
3921
  amount_column: normalizeAmountColumnInput(amountColumns),
3859
3922
  metrics: normalizeMetricsInput(normalizedObj.metrics),
3860
3923
  time_bucket: normalizeTimeBucketInput(normalizedObj.time_bucket),
3861
- apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
3924
+ apply_ids: normalizeOpaqueIdArrayInput(normalizedObj.apply_ids),
3862
3925
  sort: normalizeSortInput(normalizedObj.sort),
3863
3926
  filters: normalizeFiltersInput(normalizedObj.filters),
3864
3927
  time_range: timeRange,
@@ -3918,7 +3981,7 @@ function normalizeBatchGetInput(raw) {
3918
3981
  return {
3919
3982
  ...normalizedObj,
3920
3983
  app_key: coerceStringLike(normalizedObj.app_key),
3921
- apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
3984
+ apply_ids: normalizeOpaqueIdArrayInput(normalizedObj.apply_ids),
3922
3985
  max_columns: coerceNumberLike(normalizedObj.max_columns),
3923
3986
  select_columns: normalizeSelectorListInput(selectColumns),
3924
3987
  output_profile: normalizeOutputProfileInput(normalizedObj.output_profile)
@@ -3944,7 +4007,7 @@ function normalizeExportInput(raw) {
3944
4007
  max_columns: coerceNumberLike(normalizedObj.max_columns),
3945
4008
  strict_full: coerceBooleanLike(normalizedObj.strict_full),
3946
4009
  output_profile: normalizeOutputProfileInput(normalizedObj.output_profile),
3947
- apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
4010
+ apply_ids: normalizeOpaqueIdArrayInput(normalizedObj.apply_ids),
3948
4011
  sort: normalizeSortInput(normalizedObj.sort),
3949
4012
  filters: normalizeFiltersInput(normalizedObj.filters),
3950
4013
  select_columns: normalizeSelectorListInput(selectColumns),
@@ -4071,6 +4134,34 @@ function normalizeIdArrayInput(value) {
4071
4134
  }
4072
4135
  return parsed;
4073
4136
  }
4137
+ function normalizeOpaqueIdInput(value) {
4138
+ const parsed = parseJsonLikeDeep(value);
4139
+ if (parsed === undefined || parsed === null) {
4140
+ return parsed;
4141
+ }
4142
+ if (typeof parsed === "string") {
4143
+ const trimmed = parsed.trim();
4144
+ return trimmed ? trimmed : parsed;
4145
+ }
4146
+ if (typeof parsed === "number" && Number.isFinite(parsed)) {
4147
+ return String(Math.trunc(parsed));
4148
+ }
4149
+ return parsed;
4150
+ }
4151
+ function normalizeOpaqueIdArrayInput(value) {
4152
+ const parsed = parseJsonLikeDeep(value);
4153
+ if (Array.isArray(parsed)) {
4154
+ return parsed.map((item) => normalizeOpaqueIdInput(item));
4155
+ }
4156
+ if (typeof parsed === "string" && parsed.includes(",")) {
4157
+ return parsed
4158
+ .split(",")
4159
+ .map((item) => item.trim())
4160
+ .filter((item) => item.length > 0)
4161
+ .map((item) => normalizeOpaqueIdInput(item));
4162
+ }
4163
+ return parsed;
4164
+ }
4074
4165
  function normalizeSortInput(value) {
4075
4166
  const parsed = parseJsonLikeDeep(value);
4076
4167
  if (!Array.isArray(parsed)) {
@@ -5640,6 +5731,7 @@ async function executeRecordsBatchGet(args) {
5640
5731
  missingApplyIds.push(applyId);
5641
5732
  continue;
5642
5733
  }
5734
+ rememberApplyAppKey(record.applyId ?? applyId, args.app_key);
5643
5735
  rows.push(buildFlatRowFromAnswers({
5644
5736
  applyId: record.applyId ?? applyId,
5645
5737
  answers: asArray(record.answers),
@@ -6063,6 +6155,9 @@ async function executeRecordsList(args) {
6063
6155
  const items = collectedRawItems
6064
6156
  .slice(0, listLimit.limit)
6065
6157
  .map((raw) => normalizeRecordItem(raw, includeAnswers));
6158
+ for (const item of items) {
6159
+ rememberApplyAppKey(item.apply_id, args.app_key);
6160
+ }
6066
6161
  const sourceItemsForRows = items.slice();
6067
6162
  const requestedSelectColumns = selectResolution.columns.map((item) => item.requested);
6068
6163
  const columnProjection = projectRecordItemsColumns({
@@ -6219,11 +6314,12 @@ async function executeRecordGet(args) {
6219
6314
  if (!shouldFallback) {
6220
6315
  throw error;
6221
6316
  }
6222
- if (!args.app_key) {
6317
+ const fallbackAppKey = args.app_key ?? getCachedApplyAppKey(args.apply_id);
6318
+ if (!fallbackAppKey) {
6223
6319
  throw new InputValidationError({
6224
6320
  message: `qf_record_get could not read apply_id \"${String(args.apply_id)}\" through the direct record endpoint`,
6225
6321
  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.",
6322
+ fixHint: "Retry qf_record_get with app_key, or call qf_records_batch_get/qf_records_list/qf_record_update first so MCP can infer the app_key for this apply_id.",
6227
6323
  details: {
6228
6324
  apply_id: String(args.apply_id),
6229
6325
  provider_err_code: providerError?.errCode ?? null,
@@ -6232,22 +6328,23 @@ async function executeRecordGet(args) {
6232
6328
  });
6233
6329
  }
6234
6330
  const fallback = await fetchRecordsByApplyIds({
6235
- appKey: args.app_key,
6331
+ appKey: fallbackAppKey,
6236
6332
  applyIds: [String(args.apply_id)]
6237
6333
  });
6238
6334
  response = fallback.response;
6239
6335
  record = fallback.records.find((item) => String(item.applyId ?? "") === String(args.apply_id)) ?? null;
6240
6336
  if (!record) {
6241
6337
  throw new InputValidationError({
6242
- message: `Record \"${String(args.apply_id)}\" not found in app \"${args.app_key}\"`,
6338
+ message: `Record \"${String(args.apply_id)}\" not found in app \"${fallbackAppKey}\"`,
6243
6339
  errorCode: "RECORD_NOT_FOUND",
6244
6340
  fixHint: "Confirm apply_id and app_key, or fetch the row via qf_records_list/qf_query first.",
6245
6341
  details: {
6246
6342
  apply_id: String(args.apply_id),
6247
- app_key: args.app_key
6343
+ app_key: fallbackAppKey
6248
6344
  }
6249
6345
  });
6250
6346
  }
6347
+ rememberApplyAppKey(record.applyId ?? args.apply_id, fallbackAppKey);
6251
6348
  }
6252
6349
  const projection = projectAnswersForOutput({
6253
6350
  answers: asArray(record?.answers),
@@ -6280,6 +6377,7 @@ async function executeRecordGet(args) {
6280
6377
  apply_id: String(args.apply_id),
6281
6378
  selected_columns: selectedColumnsForRow
6282
6379
  };
6380
+ rememberApplyAppKey(record?.applyId ?? args.apply_id, args.app_key ?? getCachedApplyAppKey(args.apply_id));
6283
6381
  return {
6284
6382
  payload: {
6285
6383
  ok: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qingflow-mcp",
3
- "version": "0.3.20",
3
+ "version": "0.3.22",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",