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.
- package/README.md +1 -1
- package/dist/server.js +111 -13
- package/package.json +1 -1
package/README.md
CHANGED
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.
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 \"${
|
|
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:
|
|
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,
|