qingflow-mcp 0.3.19 → 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 +1 -1
- package/dist/server.js +155 -24
- 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.21";
|
|
69
72
|
const accessToken = process.env.QINGFLOW_ACCESS_TOKEN;
|
|
70
73
|
const baseUrl = process.env.QINGFLOW_BASE_URL;
|
|
71
74
|
if (!accessToken) {
|
|
@@ -745,12 +748,14 @@ const listOutputSchema = listSuccessOutputSchema;
|
|
|
745
748
|
const recordGetInputPublicSchema = z
|
|
746
749
|
.object({
|
|
747
750
|
apply_id: publicFieldSelectorSchema,
|
|
751
|
+
app_key: publicStringSchema.optional(),
|
|
748
752
|
max_columns: z.number().int().positive().max(MAX_COLUMN_LIMIT).optional(),
|
|
749
753
|
select_columns: z.array(publicFieldSelectorSchema).min(1).max(MAX_COLUMN_LIMIT),
|
|
750
754
|
output_profile: outputProfileSchema.optional()
|
|
751
755
|
});
|
|
752
756
|
const recordGetInputSchema = z.preprocess(normalizeRecordGetInput, z.object({
|
|
753
757
|
apply_id: z.union([z.string().min(1), z.number().int()]),
|
|
758
|
+
app_key: z.string().min(1).optional(),
|
|
754
759
|
max_columns: z.number().int().positive().max(MAX_COLUMN_LIMIT).optional(),
|
|
755
760
|
select_columns: z
|
|
756
761
|
.array(z.union([z.string().min(1), z.number().int()]))
|
|
@@ -2404,6 +2409,8 @@ server.registerTool("qf_record_create", {
|
|
|
2404
2409
|
const result = asObject(response.result);
|
|
2405
2410
|
const requestId = asNullableString(result?.requestId);
|
|
2406
2411
|
const immediateApplyId = result?.applyId ?? null;
|
|
2412
|
+
rememberRequestAppKey(requestId, parsedArgs.app_key);
|
|
2413
|
+
rememberApplyAppKey(immediateApplyId, parsedArgs.app_key);
|
|
2407
2414
|
const shouldWaitForResult = (parsedArgs.wait_result ?? false) && requestId !== null && immediateApplyId === null;
|
|
2408
2415
|
let finalApplyId = immediateApplyId;
|
|
2409
2416
|
let waitStatus = immediateApplyId !== null ? "completed" : "pending";
|
|
@@ -2416,6 +2423,7 @@ server.registerTool("qf_record_create", {
|
|
|
2416
2423
|
waitStatus = waited.status;
|
|
2417
2424
|
rawOperationResult = waited.operationResult;
|
|
2418
2425
|
finalApplyId = waited.applyId;
|
|
2426
|
+
rememberApplyAppKey(waited.applyId, parsedArgs.app_key);
|
|
2419
2427
|
}
|
|
2420
2428
|
const createResource = finalApplyId !== null ? { type: "record", apply_id: finalApplyId } : null;
|
|
2421
2429
|
const createNextAction = waitStatus === "pending" || waitStatus === "timeout"
|
|
@@ -2476,6 +2484,8 @@ server.registerTool("qf_record_update", {
|
|
|
2476
2484
|
const response = await client.updateRecord(String(parsedArgs.apply_id), { answers: normalizedAnswers }, { userId: parsedArgs.user_id });
|
|
2477
2485
|
const result = asObject(response.result);
|
|
2478
2486
|
const updateRequestId = asNullableString(result?.requestId);
|
|
2487
|
+
rememberRequestAppKey(updateRequestId, parsedArgs.app_key ?? null);
|
|
2488
|
+
rememberApplyAppKey(parsedArgs.apply_id, parsedArgs.app_key ?? null);
|
|
2479
2489
|
const shouldWaitForUpdate = (parsedArgs.wait_result ?? false) && updateRequestId !== null;
|
|
2480
2490
|
let updateStatus = "pending";
|
|
2481
2491
|
let updateRawOperationResult = null;
|
|
@@ -2491,6 +2501,7 @@ server.registerTool("qf_record_update", {
|
|
|
2491
2501
|
if (waited.applyId !== null) {
|
|
2492
2502
|
updateApplyId = waited.applyId;
|
|
2493
2503
|
}
|
|
2504
|
+
rememberApplyAppKey(waited.applyId, parsedArgs.app_key ?? null);
|
|
2494
2505
|
}
|
|
2495
2506
|
else if (updateRequestId === null) {
|
|
2496
2507
|
// No async operation — synchronous completion
|
|
@@ -2536,6 +2547,8 @@ server.registerTool("qf_operation_get", {
|
|
|
2536
2547
|
}, async (args) => {
|
|
2537
2548
|
try {
|
|
2538
2549
|
const response = await client.getOperation(args.request_id);
|
|
2550
|
+
const cachedAppKey = getCachedRequestAppKey(args.request_id);
|
|
2551
|
+
rememberApplyAppKey(extractOperationApplyId(response.result), cachedAppKey);
|
|
2539
2552
|
return okResult({
|
|
2540
2553
|
ok: true,
|
|
2541
2554
|
data: {
|
|
@@ -2832,6 +2845,69 @@ function buildMeta(response) {
|
|
|
2832
2845
|
base_url: baseUrl
|
|
2833
2846
|
};
|
|
2834
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
|
+
}
|
|
2900
|
+
async function fetchRecordsByApplyIds(params) {
|
|
2901
|
+
const response = await client.listRecords(params.appKey, buildListPayload({
|
|
2902
|
+
pageNum: 1,
|
|
2903
|
+
pageSize: Math.min(Math.max(params.applyIds.length, 1), 200),
|
|
2904
|
+
applyIds: params.applyIds
|
|
2905
|
+
}), { userId: params.userId });
|
|
2906
|
+
const records = asArray(asObject(response.result)?.result)
|
|
2907
|
+
.map((item) => asObject(item))
|
|
2908
|
+
.filter((item) => Boolean(item));
|
|
2909
|
+
return { response, records };
|
|
2910
|
+
}
|
|
2835
2911
|
function normalizeStringArray(value) {
|
|
2836
2912
|
return uniqueStringList(asArray(value)
|
|
2837
2913
|
.map((item) => asNullableString(item)?.trim() ?? "")
|
|
@@ -3783,6 +3859,7 @@ function normalizeRecordGetInput(raw) {
|
|
|
3783
3859
|
return {
|
|
3784
3860
|
...normalizedObj,
|
|
3785
3861
|
apply_id: coerceNumberLike(normalizedObj.apply_id),
|
|
3862
|
+
app_key: coerceStringLike(normalizedObj.app_key),
|
|
3786
3863
|
max_columns: coerceNumberLike(normalizedObj.max_columns),
|
|
3787
3864
|
select_columns: normalizeSelectorListInput(selectColumns),
|
|
3788
3865
|
output_profile: normalizeOutputProfileInput(normalizedObj.output_profile)
|
|
@@ -3903,6 +3980,7 @@ function normalizeBatchGetInput(raw) {
|
|
|
3903
3980
|
const selectColumns = normalizedObj.select_columns ?? normalizedObj.keep_columns;
|
|
3904
3981
|
return {
|
|
3905
3982
|
...normalizedObj,
|
|
3983
|
+
app_key: coerceStringLike(normalizedObj.app_key),
|
|
3906
3984
|
apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
|
|
3907
3985
|
max_columns: coerceNumberLike(normalizedObj.max_columns),
|
|
3908
3986
|
select_columns: normalizeSelectorListInput(selectColumns),
|
|
@@ -5503,6 +5581,7 @@ function buildRecordGetArgsFromQuery(args) {
|
|
|
5503
5581
|
}
|
|
5504
5582
|
return recordGetInputSchema.parse({
|
|
5505
5583
|
apply_id: args.apply_id,
|
|
5584
|
+
app_key: args.app_key,
|
|
5506
5585
|
max_columns: args.max_columns,
|
|
5507
5586
|
select_columns: args.select_columns,
|
|
5508
5587
|
output_profile: args.output_profile
|
|
@@ -5588,6 +5667,13 @@ async function executeQueryPlan(args) {
|
|
|
5588
5667
|
};
|
|
5589
5668
|
}
|
|
5590
5669
|
async function executeRecordsBatchGet(args) {
|
|
5670
|
+
if (!args.app_key) {
|
|
5671
|
+
throw missingRequiredFieldError({
|
|
5672
|
+
field: "app_key",
|
|
5673
|
+
tool: "qf_records_batch_get",
|
|
5674
|
+
fixHint: "Provide app_key, for example: {\"app_key\":\"21b3d559\",\"apply_ids\":[\"...\"],\"select_columns\":[0]}."
|
|
5675
|
+
});
|
|
5676
|
+
}
|
|
5591
5677
|
if (!args.select_columns?.length) {
|
|
5592
5678
|
throw missingRequiredFieldError({
|
|
5593
5679
|
field: "select_columns",
|
|
@@ -5605,25 +5691,24 @@ async function executeRecordsBatchGet(args) {
|
|
|
5605
5691
|
const rows = [];
|
|
5606
5692
|
const missingApplyIds = [];
|
|
5607
5693
|
let metaResponse = null;
|
|
5694
|
+
const { response, records } = await fetchRecordsByApplyIds({
|
|
5695
|
+
appKey: args.app_key,
|
|
5696
|
+
applyIds: requestedApplyIds
|
|
5697
|
+
});
|
|
5698
|
+
metaResponse = buildMeta(response);
|
|
5699
|
+
const byApplyId = new Map(records.map((record) => [String(record.applyId ?? ""), record]));
|
|
5608
5700
|
for (const applyId of requestedApplyIds) {
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
rows.push(buildFlatRowFromAnswers({
|
|
5614
|
-
applyId: record.applyId ?? applyId,
|
|
5615
|
-
answers: asArray(record.answers),
|
|
5616
|
-
selectedColumns: selectedColumnsForRow
|
|
5617
|
-
}));
|
|
5618
|
-
}
|
|
5619
|
-
catch (error) {
|
|
5620
|
-
if (error instanceof QingflowApiError &&
|
|
5621
|
-
(error.httpStatus === 404 || error.errCode === 404)) {
|
|
5622
|
-
missingApplyIds.push(applyId);
|
|
5623
|
-
continue;
|
|
5624
|
-
}
|
|
5625
|
-
throw error;
|
|
5701
|
+
const record = byApplyId.get(String(applyId));
|
|
5702
|
+
if (!record) {
|
|
5703
|
+
missingApplyIds.push(applyId);
|
|
5704
|
+
continue;
|
|
5626
5705
|
}
|
|
5706
|
+
rememberApplyAppKey(record.applyId ?? applyId, args.app_key);
|
|
5707
|
+
rows.push(buildFlatRowFromAnswers({
|
|
5708
|
+
applyId: record.applyId ?? applyId,
|
|
5709
|
+
answers: asArray(record.answers),
|
|
5710
|
+
selectedColumns: selectedColumnsForRow
|
|
5711
|
+
}));
|
|
5627
5712
|
}
|
|
5628
5713
|
const completeness = {
|
|
5629
5714
|
result_amount: requestedApplyIds.length,
|
|
@@ -6042,6 +6127,9 @@ async function executeRecordsList(args) {
|
|
|
6042
6127
|
const items = collectedRawItems
|
|
6043
6128
|
.slice(0, listLimit.limit)
|
|
6044
6129
|
.map((raw) => normalizeRecordItem(raw, includeAnswers));
|
|
6130
|
+
for (const item of items) {
|
|
6131
|
+
rememberApplyAppKey(item.apply_id, args.app_key);
|
|
6132
|
+
}
|
|
6045
6133
|
const sourceItemsForRows = items.slice();
|
|
6046
6134
|
const requestedSelectColumns = selectResolution.columns.map((item) => item.requested);
|
|
6047
6135
|
const columnProjection = projectRecordItemsColumns({
|
|
@@ -6186,10 +6274,52 @@ async function executeRecordGet(args) {
|
|
|
6186
6274
|
}
|
|
6187
6275
|
const outputProfile = resolveOutputProfile(args.output_profile);
|
|
6188
6276
|
const queryId = randomUUID();
|
|
6189
|
-
|
|
6190
|
-
|
|
6277
|
+
let response;
|
|
6278
|
+
let record = null;
|
|
6279
|
+
try {
|
|
6280
|
+
response = await client.getRecord(String(args.apply_id));
|
|
6281
|
+
record = asObject(response.result) ?? {};
|
|
6282
|
+
}
|
|
6283
|
+
catch (error) {
|
|
6284
|
+
const providerError = error instanceof QingflowApiError ? error : null;
|
|
6285
|
+
const shouldFallback = providerError && (providerError.httpStatus === 404 || providerError.errCode === 404 || providerError.errCode === 49304);
|
|
6286
|
+
if (!shouldFallback) {
|
|
6287
|
+
throw error;
|
|
6288
|
+
}
|
|
6289
|
+
const fallbackAppKey = args.app_key ?? getCachedApplyAppKey(args.apply_id);
|
|
6290
|
+
if (!fallbackAppKey) {
|
|
6291
|
+
throw new InputValidationError({
|
|
6292
|
+
message: `qf_record_get could not read apply_id \"${String(args.apply_id)}\" through the direct record endpoint`,
|
|
6293
|
+
errorCode: "APP_KEY_REQUIRED_FOR_RECORD_GET",
|
|
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.",
|
|
6295
|
+
details: {
|
|
6296
|
+
apply_id: String(args.apply_id),
|
|
6297
|
+
provider_err_code: providerError?.errCode ?? null,
|
|
6298
|
+
provider_err_msg: providerError?.errMsg ?? null
|
|
6299
|
+
}
|
|
6300
|
+
});
|
|
6301
|
+
}
|
|
6302
|
+
const fallback = await fetchRecordsByApplyIds({
|
|
6303
|
+
appKey: fallbackAppKey,
|
|
6304
|
+
applyIds: [String(args.apply_id)]
|
|
6305
|
+
});
|
|
6306
|
+
response = fallback.response;
|
|
6307
|
+
record = fallback.records.find((item) => String(item.applyId ?? "") === String(args.apply_id)) ?? null;
|
|
6308
|
+
if (!record) {
|
|
6309
|
+
throw new InputValidationError({
|
|
6310
|
+
message: `Record \"${String(args.apply_id)}\" not found in app \"${fallbackAppKey}\"`,
|
|
6311
|
+
errorCode: "RECORD_NOT_FOUND",
|
|
6312
|
+
fixHint: "Confirm apply_id and app_key, or fetch the row via qf_records_list/qf_query first.",
|
|
6313
|
+
details: {
|
|
6314
|
+
apply_id: String(args.apply_id),
|
|
6315
|
+
app_key: fallbackAppKey
|
|
6316
|
+
}
|
|
6317
|
+
});
|
|
6318
|
+
}
|
|
6319
|
+
rememberApplyAppKey(record.applyId ?? args.apply_id, fallbackAppKey);
|
|
6320
|
+
}
|
|
6191
6321
|
const projection = projectAnswersForOutput({
|
|
6192
|
-
answers: asArray(record
|
|
6322
|
+
answers: asArray(record?.answers),
|
|
6193
6323
|
maxColumns: args.max_columns,
|
|
6194
6324
|
selectColumns: args.select_columns
|
|
6195
6325
|
});
|
|
@@ -6197,8 +6327,8 @@ async function executeRecordGet(args) {
|
|
|
6197
6327
|
? (projection.selectedColumns ?? []).slice(0, args.max_columns)
|
|
6198
6328
|
: projection.selectedColumns ?? [];
|
|
6199
6329
|
const row = buildFlatRowFromAnswers({
|
|
6200
|
-
applyId: record
|
|
6201
|
-
answers: asArray(record
|
|
6330
|
+
applyId: record?.applyId ?? null,
|
|
6331
|
+
answers: asArray(record?.answers),
|
|
6202
6332
|
selectedColumns: selectedColumnsForRow
|
|
6203
6333
|
});
|
|
6204
6334
|
const completeness = {
|
|
@@ -6219,11 +6349,12 @@ async function executeRecordGet(args) {
|
|
|
6219
6349
|
apply_id: String(args.apply_id),
|
|
6220
6350
|
selected_columns: selectedColumnsForRow
|
|
6221
6351
|
};
|
|
6352
|
+
rememberApplyAppKey(record?.applyId ?? args.apply_id, args.app_key ?? getCachedApplyAppKey(args.apply_id));
|
|
6222
6353
|
return {
|
|
6223
6354
|
payload: {
|
|
6224
6355
|
ok: true,
|
|
6225
6356
|
data: {
|
|
6226
|
-
apply_id: record
|
|
6357
|
+
apply_id: record?.applyId ?? null,
|
|
6227
6358
|
row,
|
|
6228
6359
|
applied_limits: {
|
|
6229
6360
|
column_cap: args.max_columns ?? null,
|