qingflow-mcp 0.3.20 → 0.3.21

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
@@ -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.21
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.21";
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,
@@ -5640,6 +5703,7 @@ async function executeRecordsBatchGet(args) {
5640
5703
  missingApplyIds.push(applyId);
5641
5704
  continue;
5642
5705
  }
5706
+ rememberApplyAppKey(record.applyId ?? applyId, args.app_key);
5643
5707
  rows.push(buildFlatRowFromAnswers({
5644
5708
  applyId: record.applyId ?? applyId,
5645
5709
  answers: asArray(record.answers),
@@ -6063,6 +6127,9 @@ async function executeRecordsList(args) {
6063
6127
  const items = collectedRawItems
6064
6128
  .slice(0, listLimit.limit)
6065
6129
  .map((raw) => normalizeRecordItem(raw, includeAnswers));
6130
+ for (const item of items) {
6131
+ rememberApplyAppKey(item.apply_id, args.app_key);
6132
+ }
6066
6133
  const sourceItemsForRows = items.slice();
6067
6134
  const requestedSelectColumns = selectResolution.columns.map((item) => item.requested);
6068
6135
  const columnProjection = projectRecordItemsColumns({
@@ -6219,11 +6286,12 @@ async function executeRecordGet(args) {
6219
6286
  if (!shouldFallback) {
6220
6287
  throw error;
6221
6288
  }
6222
- if (!args.app_key) {
6289
+ const fallbackAppKey = args.app_key ?? getCachedApplyAppKey(args.apply_id);
6290
+ if (!fallbackAppKey) {
6223
6291
  throw new InputValidationError({
6224
6292
  message: `qf_record_get could not read apply_id \"${String(args.apply_id)}\" through the direct record endpoint`,
6225
6293
  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.",
6294
+ 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
6295
  details: {
6228
6296
  apply_id: String(args.apply_id),
6229
6297
  provider_err_code: providerError?.errCode ?? null,
@@ -6232,22 +6300,23 @@ async function executeRecordGet(args) {
6232
6300
  });
6233
6301
  }
6234
6302
  const fallback = await fetchRecordsByApplyIds({
6235
- appKey: args.app_key,
6303
+ appKey: fallbackAppKey,
6236
6304
  applyIds: [String(args.apply_id)]
6237
6305
  });
6238
6306
  response = fallback.response;
6239
6307
  record = fallback.records.find((item) => String(item.applyId ?? "") === String(args.apply_id)) ?? null;
6240
6308
  if (!record) {
6241
6309
  throw new InputValidationError({
6242
- message: `Record \"${String(args.apply_id)}\" not found in app \"${args.app_key}\"`,
6310
+ message: `Record \"${String(args.apply_id)}\" not found in app \"${fallbackAppKey}\"`,
6243
6311
  errorCode: "RECORD_NOT_FOUND",
6244
6312
  fixHint: "Confirm apply_id and app_key, or fetch the row via qf_records_list/qf_query first.",
6245
6313
  details: {
6246
6314
  apply_id: String(args.apply_id),
6247
- app_key: args.app_key
6315
+ app_key: fallbackAppKey
6248
6316
  }
6249
6317
  });
6250
6318
  }
6319
+ rememberApplyAppKey(record.applyId ?? args.apply_id, fallbackAppKey);
6251
6320
  }
6252
6321
  const projection = projectAnswersForOutput({
6253
6322
  answers: asArray(record?.answers),
@@ -6280,6 +6349,7 @@ async function executeRecordGet(args) {
6280
6349
  apply_id: String(args.apply_id),
6281
6350
  selected_columns: selectedColumnsForRow
6282
6351
  };
6352
+ rememberApplyAppKey(record?.applyId ?? args.apply_id, args.app_key ?? getCachedApplyAppKey(args.apply_id));
6283
6353
  return {
6284
6354
  payload: {
6285
6355
  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.21",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",