qingflow-mcp 0.2.2 → 0.2.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.
- package/README.md +28 -2
- package/dist/server.js +161 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,8 +107,34 @@ MCP client config example:
|
|
|
107
107
|
|
|
108
108
|
1. For `qf_records_list.sort[].que_id`, use a real field `que_id` (numeric) or exact field title from `qf_form_get`.
|
|
109
109
|
2. Avoid aliases like `create_time`; Qingflow often rejects them.
|
|
110
|
-
3.
|
|
111
|
-
4.
|
|
110
|
+
3. Use `max_rows` (or `max_items`) to cap returned rows.
|
|
111
|
+
4. Use `max_columns` to cap answers per row when `include_answers=true`.
|
|
112
|
+
5. Use `select_columns` to return only specific columns (supports `que_id` or exact field title).
|
|
113
|
+
6. When `include_answers=true`, the server still auto-limits by response size to protect MCP context.
|
|
114
|
+
|
|
115
|
+
Example:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"app_key": "your_app_key",
|
|
120
|
+
"mode": "all",
|
|
121
|
+
"page_size": 50,
|
|
122
|
+
"include_answers": true,
|
|
123
|
+
"max_rows": 10,
|
|
124
|
+
"max_columns": 5,
|
|
125
|
+
"select_columns": [1, "客户名称", "1003"]
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
For single record details (`qf_record_get`), the same column controls are supported:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"apply_id": "497600278750478338",
|
|
134
|
+
"max_columns": 5,
|
|
135
|
+
"select_columns": [1, "客户名称"]
|
|
136
|
+
}
|
|
137
|
+
```
|
|
112
138
|
|
|
113
139
|
Optional env vars:
|
|
114
140
|
|
package/dist/server.js
CHANGED
|
@@ -190,7 +190,14 @@ const listInputSchema = z.object({
|
|
|
190
190
|
search_user_ids: z.array(z.string()).optional()
|
|
191
191
|
}))
|
|
192
192
|
.optional(),
|
|
193
|
+
max_rows: z.number().int().positive().max(200).optional(),
|
|
193
194
|
max_items: z.number().int().positive().max(200).optional(),
|
|
195
|
+
max_columns: z.number().int().positive().max(200).optional(),
|
|
196
|
+
select_columns: z
|
|
197
|
+
.array(z.union([z.string().min(1), z.number().int()]))
|
|
198
|
+
.min(1)
|
|
199
|
+
.max(200)
|
|
200
|
+
.optional(),
|
|
194
201
|
include_answers: z.boolean().optional()
|
|
195
202
|
});
|
|
196
203
|
const listOutputSchema = z.object({
|
|
@@ -203,19 +210,39 @@ const listOutputSchema = z.object({
|
|
|
203
210
|
page_amount: z.number().int().nonnegative().nullable(),
|
|
204
211
|
result_amount: z.number().int().nonnegative()
|
|
205
212
|
}),
|
|
206
|
-
items: z.array(recordItemSchema)
|
|
213
|
+
items: z.array(recordItemSchema),
|
|
214
|
+
applied_limits: z
|
|
215
|
+
.object({
|
|
216
|
+
include_answers: z.boolean(),
|
|
217
|
+
row_cap: z.number().int().nonnegative(),
|
|
218
|
+
column_cap: z.number().int().positive().nullable(),
|
|
219
|
+
selected_columns: z.array(z.string()).nullable()
|
|
220
|
+
})
|
|
221
|
+
.optional()
|
|
207
222
|
}),
|
|
208
223
|
meta: apiMetaSchema
|
|
209
224
|
});
|
|
210
225
|
const recordGetInputSchema = z.object({
|
|
211
|
-
apply_id: z.union([z.string().min(1), z.number().int()])
|
|
226
|
+
apply_id: z.union([z.string().min(1), z.number().int()]),
|
|
227
|
+
max_columns: z.number().int().positive().max(200).optional(),
|
|
228
|
+
select_columns: z
|
|
229
|
+
.array(z.union([z.string().min(1), z.number().int()]))
|
|
230
|
+
.min(1)
|
|
231
|
+
.max(200)
|
|
232
|
+
.optional()
|
|
212
233
|
});
|
|
213
234
|
const recordGetOutputSchema = z.object({
|
|
214
235
|
ok: z.literal(true),
|
|
215
236
|
data: z.object({
|
|
216
237
|
apply_id: z.union([z.string(), z.number(), z.null()]),
|
|
217
238
|
answer_count: z.number().int().nonnegative(),
|
|
218
|
-
record: z.unknown()
|
|
239
|
+
record: z.unknown(),
|
|
240
|
+
applied_limits: z
|
|
241
|
+
.object({
|
|
242
|
+
column_cap: z.number().int().positive().nullable(),
|
|
243
|
+
selected_columns: z.array(z.string()).nullable()
|
|
244
|
+
})
|
|
245
|
+
.optional()
|
|
219
246
|
}),
|
|
220
247
|
meta: apiMetaSchema
|
|
221
248
|
});
|
|
@@ -365,6 +392,7 @@ server.registerTool("qf_records_list", {
|
|
|
365
392
|
const pageNum = args.page_num ?? 1;
|
|
366
393
|
const pageSize = args.page_size ?? DEFAULT_PAGE_SIZE;
|
|
367
394
|
const normalizedSort = await normalizeListSort(args.sort, args.app_key, args.user_id);
|
|
395
|
+
const includeAnswers = Boolean(args.include_answers || (args.select_columns && args.select_columns.length > 0));
|
|
368
396
|
const payload = buildListPayload({
|
|
369
397
|
pageNum,
|
|
370
398
|
pageSize,
|
|
@@ -379,20 +407,26 @@ server.registerTool("qf_records_list", {
|
|
|
379
407
|
const response = await client.listRecords(args.app_key, payload, { userId: args.user_id });
|
|
380
408
|
const result = asObject(response.result);
|
|
381
409
|
const rawItems = asArray(result?.result);
|
|
382
|
-
const includeAnswers = Boolean(args.include_answers);
|
|
383
410
|
const listLimit = resolveListItemLimit({
|
|
384
411
|
total: rawItems.length,
|
|
412
|
+
requestedMaxRows: args.max_rows,
|
|
385
413
|
requestedMaxItems: args.max_items,
|
|
386
414
|
includeAnswers
|
|
387
415
|
});
|
|
388
416
|
const items = rawItems
|
|
389
417
|
.slice(0, listLimit.limit)
|
|
390
418
|
.map((raw) => normalizeRecordItem(raw, includeAnswers));
|
|
391
|
-
const
|
|
419
|
+
const columnProjection = projectRecordItemsColumns({
|
|
392
420
|
items,
|
|
421
|
+
includeAnswers,
|
|
422
|
+
maxColumns: args.max_columns,
|
|
423
|
+
selectColumns: args.select_columns
|
|
424
|
+
});
|
|
425
|
+
const fitted = fitListItemsWithinSize({
|
|
426
|
+
items: columnProjection.items,
|
|
393
427
|
limitBytes: MAX_LIST_ITEMS_BYTES
|
|
394
428
|
});
|
|
395
|
-
const truncationReason = mergeTruncationReasons(listLimit.reason, fitted.reason);
|
|
429
|
+
const truncationReason = mergeTruncationReasons(listLimit.reason, columnProjection.reason, fitted.reason);
|
|
396
430
|
return okResult({
|
|
397
431
|
ok: true,
|
|
398
432
|
data: {
|
|
@@ -403,7 +437,13 @@ server.registerTool("qf_records_list", {
|
|
|
403
437
|
page_amount: toNonNegativeInt(result?.pageAmount),
|
|
404
438
|
result_amount: toNonNegativeInt(result?.resultAmount) ?? fitted.items.length
|
|
405
439
|
},
|
|
406
|
-
items: fitted.items
|
|
440
|
+
items: fitted.items,
|
|
441
|
+
applied_limits: {
|
|
442
|
+
include_answers: includeAnswers,
|
|
443
|
+
row_cap: listLimit.limit,
|
|
444
|
+
column_cap: args.max_columns ?? null,
|
|
445
|
+
selected_columns: columnProjection.selectedColumns
|
|
446
|
+
}
|
|
407
447
|
},
|
|
408
448
|
meta: buildMeta(response)
|
|
409
449
|
}, buildRecordsListMessage({
|
|
@@ -429,13 +469,26 @@ server.registerTool("qf_record_get", {
|
|
|
429
469
|
try {
|
|
430
470
|
const response = await client.getRecord(String(args.apply_id));
|
|
431
471
|
const record = asObject(response.result) ?? {};
|
|
432
|
-
const
|
|
472
|
+
const projection = projectAnswersForOutput({
|
|
473
|
+
answers: asArray(record.answers),
|
|
474
|
+
maxColumns: args.max_columns,
|
|
475
|
+
selectColumns: args.select_columns
|
|
476
|
+
});
|
|
477
|
+
const projectedRecord = {
|
|
478
|
+
...record,
|
|
479
|
+
answers: projection.answers
|
|
480
|
+
};
|
|
481
|
+
const answerCount = projection.answers.length;
|
|
433
482
|
return okResult({
|
|
434
483
|
ok: true,
|
|
435
484
|
data: {
|
|
436
485
|
apply_id: record.applyId ?? null,
|
|
437
486
|
answer_count: answerCount,
|
|
438
|
-
record:
|
|
487
|
+
record: projectedRecord,
|
|
488
|
+
applied_limits: {
|
|
489
|
+
column_cap: args.max_columns ?? null,
|
|
490
|
+
selected_columns: projection.selectedColumns
|
|
491
|
+
}
|
|
439
492
|
},
|
|
440
493
|
meta: buildMeta(response)
|
|
441
494
|
}, `Fetched record ${String(args.apply_id)}`);
|
|
@@ -841,12 +894,21 @@ function resolveListItemLimit(params) {
|
|
|
841
894
|
if (params.total <= 0) {
|
|
842
895
|
return { limit: 0, reason: null };
|
|
843
896
|
}
|
|
897
|
+
const explicitLimits = [];
|
|
898
|
+
if (params.requestedMaxRows !== undefined) {
|
|
899
|
+
explicitLimits.push({ name: "max_rows", value: params.requestedMaxRows });
|
|
900
|
+
}
|
|
844
901
|
if (params.requestedMaxItems !== undefined) {
|
|
845
|
-
|
|
902
|
+
explicitLimits.push({ name: "max_items", value: params.requestedMaxItems });
|
|
903
|
+
}
|
|
904
|
+
if (explicitLimits.length > 0) {
|
|
905
|
+
const limit = Math.min(params.total, ...explicitLimits.map((item) => item.value));
|
|
846
906
|
if (limit < params.total) {
|
|
847
907
|
return {
|
|
848
908
|
limit,
|
|
849
|
-
reason: `limited by
|
|
909
|
+
reason: `limited by ${explicitLimits
|
|
910
|
+
.map((item) => `${item.name}=${item.value}`)
|
|
911
|
+
.join(", ")} (effective=${limit})`
|
|
850
912
|
};
|
|
851
913
|
}
|
|
852
914
|
return { limit, reason: null };
|
|
@@ -859,6 +921,56 @@ function resolveListItemLimit(params) {
|
|
|
859
921
|
}
|
|
860
922
|
return { limit: params.total, reason: null };
|
|
861
923
|
}
|
|
924
|
+
function projectRecordItemsColumns(params) {
|
|
925
|
+
if (!params.includeAnswers) {
|
|
926
|
+
return {
|
|
927
|
+
items: params.items,
|
|
928
|
+
reason: null,
|
|
929
|
+
selectedColumns: null
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
const normalizedSelectors = normalizeColumnSelectors(params.selectColumns);
|
|
933
|
+
const selectorSet = new Set(normalizedSelectors.map((item) => normalizeColumnSelector(item)));
|
|
934
|
+
let columnCapped = false;
|
|
935
|
+
const projectedItems = params.items.map((item) => {
|
|
936
|
+
const answers = asArray(item.answers);
|
|
937
|
+
let projected = answers;
|
|
938
|
+
if (selectorSet.size > 0) {
|
|
939
|
+
projected = answers.filter((answer) => answerMatchesAnySelector(answer, selectorSet));
|
|
940
|
+
}
|
|
941
|
+
if (params.maxColumns !== undefined && projected.length > params.maxColumns) {
|
|
942
|
+
projected = projected.slice(0, params.maxColumns);
|
|
943
|
+
columnCapped = true;
|
|
944
|
+
}
|
|
945
|
+
return {
|
|
946
|
+
...item,
|
|
947
|
+
answers: projected
|
|
948
|
+
};
|
|
949
|
+
});
|
|
950
|
+
const reason = mergeTruncationReasons(selectorSet.size > 0 ? `selected columns=${normalizedSelectors.length}` : null, columnCapped && params.maxColumns !== undefined
|
|
951
|
+
? `limited to max_columns=${params.maxColumns}`
|
|
952
|
+
: null);
|
|
953
|
+
return {
|
|
954
|
+
items: projectedItems,
|
|
955
|
+
reason,
|
|
956
|
+
selectedColumns: normalizedSelectors.length > 0 ? normalizedSelectors : null
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
function projectAnswersForOutput(params) {
|
|
960
|
+
const normalizedSelectors = normalizeColumnSelectors(params.selectColumns);
|
|
961
|
+
const selectorSet = new Set(normalizedSelectors.map((item) => normalizeColumnSelector(item)));
|
|
962
|
+
let projected = params.answers;
|
|
963
|
+
if (selectorSet.size > 0) {
|
|
964
|
+
projected = projected.filter((answer) => answerMatchesAnySelector(answer, selectorSet));
|
|
965
|
+
}
|
|
966
|
+
if (params.maxColumns !== undefined && projected.length > params.maxColumns) {
|
|
967
|
+
projected = projected.slice(0, params.maxColumns);
|
|
968
|
+
}
|
|
969
|
+
return {
|
|
970
|
+
answers: projected,
|
|
971
|
+
selectedColumns: normalizedSelectors.length > 0 ? normalizedSelectors : null
|
|
972
|
+
};
|
|
973
|
+
}
|
|
862
974
|
function fitListItemsWithinSize(params) {
|
|
863
975
|
let candidate = params.items;
|
|
864
976
|
let size = jsonSizeBytes(candidate);
|
|
@@ -887,6 +999,44 @@ function buildRecordsListMessage(params) {
|
|
|
887
999
|
}
|
|
888
1000
|
return `Fetched ${params.returned}/${params.total} records (${params.truncationReason})`;
|
|
889
1001
|
}
|
|
1002
|
+
function normalizeColumnSelectors(selectColumns) {
|
|
1003
|
+
if (!selectColumns?.length) {
|
|
1004
|
+
return [];
|
|
1005
|
+
}
|
|
1006
|
+
const deduped = new Set();
|
|
1007
|
+
for (const value of selectColumns) {
|
|
1008
|
+
const normalized = String(value).trim();
|
|
1009
|
+
if (!normalized) {
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
deduped.add(normalized);
|
|
1013
|
+
}
|
|
1014
|
+
return Array.from(deduped);
|
|
1015
|
+
}
|
|
1016
|
+
function normalizeColumnSelector(value) {
|
|
1017
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1018
|
+
return `id:${Math.trunc(value)}`;
|
|
1019
|
+
}
|
|
1020
|
+
const normalized = String(value).trim();
|
|
1021
|
+
if (!normalized) {
|
|
1022
|
+
return "title:";
|
|
1023
|
+
}
|
|
1024
|
+
if (isNumericKey(normalized)) {
|
|
1025
|
+
return `id:${Number(normalized)}`;
|
|
1026
|
+
}
|
|
1027
|
+
return `title:${normalized.toLowerCase()}`;
|
|
1028
|
+
}
|
|
1029
|
+
function answerMatchesAnySelector(answer, selectorSet) {
|
|
1030
|
+
const obj = asObject(answer);
|
|
1031
|
+
if (!obj) {
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
1034
|
+
const candidates = [
|
|
1035
|
+
normalizeColumnSelector(asNullableString(obj.queId) ?? ""),
|
|
1036
|
+
normalizeColumnSelector(asNullableString(obj.queTitle) ?? "")
|
|
1037
|
+
];
|
|
1038
|
+
return candidates.some((candidate) => selectorSet.has(candidate));
|
|
1039
|
+
}
|
|
890
1040
|
function normalizeQueId(queId) {
|
|
891
1041
|
if (typeof queId === "number" && Number.isInteger(queId)) {
|
|
892
1042
|
return queId;
|