qingflow-mcp 0.2.2 → 0.2.3
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 +18 -2
- package/dist/server.js +117 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,8 +107,24 @@ 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
|
+
```
|
|
112
128
|
|
|
113
129
|
Optional env vars:
|
|
114
130
|
|
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,7 +210,15 @@ 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
|
});
|
|
@@ -365,6 +380,7 @@ server.registerTool("qf_records_list", {
|
|
|
365
380
|
const pageNum = args.page_num ?? 1;
|
|
366
381
|
const pageSize = args.page_size ?? DEFAULT_PAGE_SIZE;
|
|
367
382
|
const normalizedSort = await normalizeListSort(args.sort, args.app_key, args.user_id);
|
|
383
|
+
const includeAnswers = Boolean(args.include_answers || (args.select_columns && args.select_columns.length > 0));
|
|
368
384
|
const payload = buildListPayload({
|
|
369
385
|
pageNum,
|
|
370
386
|
pageSize,
|
|
@@ -379,20 +395,26 @@ server.registerTool("qf_records_list", {
|
|
|
379
395
|
const response = await client.listRecords(args.app_key, payload, { userId: args.user_id });
|
|
380
396
|
const result = asObject(response.result);
|
|
381
397
|
const rawItems = asArray(result?.result);
|
|
382
|
-
const includeAnswers = Boolean(args.include_answers);
|
|
383
398
|
const listLimit = resolveListItemLimit({
|
|
384
399
|
total: rawItems.length,
|
|
400
|
+
requestedMaxRows: args.max_rows,
|
|
385
401
|
requestedMaxItems: args.max_items,
|
|
386
402
|
includeAnswers
|
|
387
403
|
});
|
|
388
404
|
const items = rawItems
|
|
389
405
|
.slice(0, listLimit.limit)
|
|
390
406
|
.map((raw) => normalizeRecordItem(raw, includeAnswers));
|
|
391
|
-
const
|
|
407
|
+
const columnProjection = projectRecordItemsColumns({
|
|
392
408
|
items,
|
|
409
|
+
includeAnswers,
|
|
410
|
+
maxColumns: args.max_columns,
|
|
411
|
+
selectColumns: args.select_columns
|
|
412
|
+
});
|
|
413
|
+
const fitted = fitListItemsWithinSize({
|
|
414
|
+
items: columnProjection.items,
|
|
393
415
|
limitBytes: MAX_LIST_ITEMS_BYTES
|
|
394
416
|
});
|
|
395
|
-
const truncationReason = mergeTruncationReasons(listLimit.reason, fitted.reason);
|
|
417
|
+
const truncationReason = mergeTruncationReasons(listLimit.reason, columnProjection.reason, fitted.reason);
|
|
396
418
|
return okResult({
|
|
397
419
|
ok: true,
|
|
398
420
|
data: {
|
|
@@ -403,7 +425,13 @@ server.registerTool("qf_records_list", {
|
|
|
403
425
|
page_amount: toNonNegativeInt(result?.pageAmount),
|
|
404
426
|
result_amount: toNonNegativeInt(result?.resultAmount) ?? fitted.items.length
|
|
405
427
|
},
|
|
406
|
-
items: fitted.items
|
|
428
|
+
items: fitted.items,
|
|
429
|
+
applied_limits: {
|
|
430
|
+
include_answers: includeAnswers,
|
|
431
|
+
row_cap: listLimit.limit,
|
|
432
|
+
column_cap: args.max_columns ?? null,
|
|
433
|
+
selected_columns: columnProjection.selectedColumns
|
|
434
|
+
}
|
|
407
435
|
},
|
|
408
436
|
meta: buildMeta(response)
|
|
409
437
|
}, buildRecordsListMessage({
|
|
@@ -841,12 +869,21 @@ function resolveListItemLimit(params) {
|
|
|
841
869
|
if (params.total <= 0) {
|
|
842
870
|
return { limit: 0, reason: null };
|
|
843
871
|
}
|
|
872
|
+
const explicitLimits = [];
|
|
873
|
+
if (params.requestedMaxRows !== undefined) {
|
|
874
|
+
explicitLimits.push({ name: "max_rows", value: params.requestedMaxRows });
|
|
875
|
+
}
|
|
844
876
|
if (params.requestedMaxItems !== undefined) {
|
|
845
|
-
|
|
877
|
+
explicitLimits.push({ name: "max_items", value: params.requestedMaxItems });
|
|
878
|
+
}
|
|
879
|
+
if (explicitLimits.length > 0) {
|
|
880
|
+
const limit = Math.min(params.total, ...explicitLimits.map((item) => item.value));
|
|
846
881
|
if (limit < params.total) {
|
|
847
882
|
return {
|
|
848
883
|
limit,
|
|
849
|
-
reason: `limited by
|
|
884
|
+
reason: `limited by ${explicitLimits
|
|
885
|
+
.map((item) => `${item.name}=${item.value}`)
|
|
886
|
+
.join(", ")} (effective=${limit})`
|
|
850
887
|
};
|
|
851
888
|
}
|
|
852
889
|
return { limit, reason: null };
|
|
@@ -859,6 +896,41 @@ function resolveListItemLimit(params) {
|
|
|
859
896
|
}
|
|
860
897
|
return { limit: params.total, reason: null };
|
|
861
898
|
}
|
|
899
|
+
function projectRecordItemsColumns(params) {
|
|
900
|
+
if (!params.includeAnswers) {
|
|
901
|
+
return {
|
|
902
|
+
items: params.items,
|
|
903
|
+
reason: null,
|
|
904
|
+
selectedColumns: null
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
const normalizedSelectors = normalizeColumnSelectors(params.selectColumns);
|
|
908
|
+
const selectorSet = new Set(normalizedSelectors.map((item) => normalizeColumnSelector(item)));
|
|
909
|
+
let columnCapped = false;
|
|
910
|
+
const projectedItems = params.items.map((item) => {
|
|
911
|
+
const answers = asArray(item.answers);
|
|
912
|
+
let projected = answers;
|
|
913
|
+
if (selectorSet.size > 0) {
|
|
914
|
+
projected = answers.filter((answer) => answerMatchesAnySelector(answer, selectorSet));
|
|
915
|
+
}
|
|
916
|
+
if (params.maxColumns !== undefined && projected.length > params.maxColumns) {
|
|
917
|
+
projected = projected.slice(0, params.maxColumns);
|
|
918
|
+
columnCapped = true;
|
|
919
|
+
}
|
|
920
|
+
return {
|
|
921
|
+
...item,
|
|
922
|
+
answers: projected
|
|
923
|
+
};
|
|
924
|
+
});
|
|
925
|
+
const reason = mergeTruncationReasons(selectorSet.size > 0 ? `selected columns=${normalizedSelectors.length}` : null, columnCapped && params.maxColumns !== undefined
|
|
926
|
+
? `limited to max_columns=${params.maxColumns}`
|
|
927
|
+
: null);
|
|
928
|
+
return {
|
|
929
|
+
items: projectedItems,
|
|
930
|
+
reason,
|
|
931
|
+
selectedColumns: normalizedSelectors.length > 0 ? normalizedSelectors : null
|
|
932
|
+
};
|
|
933
|
+
}
|
|
862
934
|
function fitListItemsWithinSize(params) {
|
|
863
935
|
let candidate = params.items;
|
|
864
936
|
let size = jsonSizeBytes(candidate);
|
|
@@ -887,6 +959,44 @@ function buildRecordsListMessage(params) {
|
|
|
887
959
|
}
|
|
888
960
|
return `Fetched ${params.returned}/${params.total} records (${params.truncationReason})`;
|
|
889
961
|
}
|
|
962
|
+
function normalizeColumnSelectors(selectColumns) {
|
|
963
|
+
if (!selectColumns?.length) {
|
|
964
|
+
return [];
|
|
965
|
+
}
|
|
966
|
+
const deduped = new Set();
|
|
967
|
+
for (const value of selectColumns) {
|
|
968
|
+
const normalized = String(value).trim();
|
|
969
|
+
if (!normalized) {
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
deduped.add(normalized);
|
|
973
|
+
}
|
|
974
|
+
return Array.from(deduped);
|
|
975
|
+
}
|
|
976
|
+
function normalizeColumnSelector(value) {
|
|
977
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
978
|
+
return `id:${Math.trunc(value)}`;
|
|
979
|
+
}
|
|
980
|
+
const normalized = String(value).trim();
|
|
981
|
+
if (!normalized) {
|
|
982
|
+
return "title:";
|
|
983
|
+
}
|
|
984
|
+
if (isNumericKey(normalized)) {
|
|
985
|
+
return `id:${Number(normalized)}`;
|
|
986
|
+
}
|
|
987
|
+
return `title:${normalized.toLowerCase()}`;
|
|
988
|
+
}
|
|
989
|
+
function answerMatchesAnySelector(answer, selectorSet) {
|
|
990
|
+
const obj = asObject(answer);
|
|
991
|
+
if (!obj) {
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
const candidates = [
|
|
995
|
+
normalizeColumnSelector(asNullableString(obj.queId) ?? ""),
|
|
996
|
+
normalizeColumnSelector(asNullableString(obj.queTitle) ?? "")
|
|
997
|
+
];
|
|
998
|
+
return candidates.some((candidate) => selectorSet.has(candidate));
|
|
999
|
+
}
|
|
890
1000
|
function normalizeQueId(queId) {
|
|
891
1001
|
if (typeof queId === "number" && Number.isInteger(queId)) {
|
|
892
1002
|
return queId;
|