qingflow-mcp 0.3.0 → 0.3.2
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 -0
- package/dist/server.js +658 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,6 +60,31 @@ Run tests:
|
|
|
60
60
|
npm test
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
## Command Line Usage
|
|
64
|
+
|
|
65
|
+
`qingflow-mcp` still defaults to MCP stdio mode:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
qingflow-mcp
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Use CLI mode for quick local invocation:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# list all available tools
|
|
75
|
+
qingflow-mcp cli tools
|
|
76
|
+
|
|
77
|
+
# machine-readable tool list
|
|
78
|
+
qingflow-mcp cli tools --json
|
|
79
|
+
|
|
80
|
+
# call one tool with JSON args
|
|
81
|
+
qingflow-mcp cli call qf_apps_list --args '{"limit":5}'
|
|
82
|
+
|
|
83
|
+
# call from stdin
|
|
84
|
+
echo '{"app_key":"your_app_key","mode":"all","select_columns":[1001]}' \
|
|
85
|
+
| qingflow-mcp cli call qf_query
|
|
86
|
+
```
|
|
87
|
+
|
|
63
88
|
## CLI Install
|
|
64
89
|
|
|
65
90
|
Global install from GitHub:
|
|
@@ -164,6 +189,9 @@ Deterministic read protocol (list/summary/aggregate):
|
|
|
164
189
|
- `time_range`
|
|
165
190
|
- `source_pages`
|
|
166
191
|
3. `strict_full=true` makes incomplete results fail fast with `NEED_MORE_DATA`.
|
|
192
|
+
4. Success payloads also expose top-level `error_code=null`, `fix_hint=null`, `next_page_token`.
|
|
193
|
+
5. Error payloads expose `error_code` and `fix_hint` for actionable retries.
|
|
194
|
+
6. Parameter tolerance supports stringified JSON and numeric/boolean strings for key query fields.
|
|
167
195
|
|
|
168
196
|
## List Query Tips
|
|
169
197
|
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
5
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4
6
|
import { randomUUID } from "node:crypto";
|
|
5
7
|
import { z } from "zod";
|
|
6
8
|
import { QingflowApiError, QingflowClient } from "./qingflow-client.js";
|
|
@@ -27,6 +29,18 @@ class NeedMoreDataError extends Error {
|
|
|
27
29
|
this.details = details;
|
|
28
30
|
}
|
|
29
31
|
}
|
|
32
|
+
class InputValidationError extends Error {
|
|
33
|
+
errorCode;
|
|
34
|
+
fixHint;
|
|
35
|
+
details;
|
|
36
|
+
constructor(params) {
|
|
37
|
+
super(params.message);
|
|
38
|
+
this.name = "InputValidationError";
|
|
39
|
+
this.errorCode = params.errorCode;
|
|
40
|
+
this.fixHint = params.fixHint;
|
|
41
|
+
this.details = params.details ?? null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
30
44
|
const FORM_CACHE_TTL_MS = Number(process.env.QINGFLOW_FORM_CACHE_TTL_MS ?? "300000");
|
|
31
45
|
const formCache = new Map();
|
|
32
46
|
const DEFAULT_PAGE_SIZE = 50;
|
|
@@ -47,7 +61,7 @@ const client = new QingflowClient({
|
|
|
47
61
|
});
|
|
48
62
|
const server = new McpServer({
|
|
49
63
|
name: "qingflow-mcp",
|
|
50
|
-
version: "0.3.
|
|
64
|
+
version: "0.3.1"
|
|
51
65
|
});
|
|
52
66
|
const jsonPrimitiveSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
|
53
67
|
const answerValueSchema = z.union([
|
|
@@ -129,6 +143,13 @@ const evidenceSchema = z.object({
|
|
|
129
143
|
.nullable(),
|
|
130
144
|
source_pages: z.array(z.number().int().positive())
|
|
131
145
|
});
|
|
146
|
+
const queryContractFields = {
|
|
147
|
+
completeness: completenessSchema,
|
|
148
|
+
evidence: z.record(z.unknown()),
|
|
149
|
+
error_code: z.null(),
|
|
150
|
+
fix_hint: z.null(),
|
|
151
|
+
next_page_token: z.string().nullable()
|
|
152
|
+
};
|
|
132
153
|
const appSchema = z.object({
|
|
133
154
|
appKey: z.string(),
|
|
134
155
|
appName: z.string()
|
|
@@ -189,8 +210,8 @@ const formSuccessOutputSchema = z.object({
|
|
|
189
210
|
});
|
|
190
211
|
const formOutputSchema = formSuccessOutputSchema;
|
|
191
212
|
const listInputSchema = z
|
|
192
|
-
.object({
|
|
193
|
-
app_key: z.string().min(1),
|
|
213
|
+
.preprocess(normalizeListInput, z.object({
|
|
214
|
+
app_key: z.string().min(1).optional(),
|
|
194
215
|
user_id: z.string().min(1).optional(),
|
|
195
216
|
page_num: z.number().int().positive().optional(),
|
|
196
217
|
page_token: z.string().min(1).optional(),
|
|
@@ -247,10 +268,14 @@ const listInputSchema = z
|
|
|
247
268
|
max_items: z.number().int().positive().max(200).optional(),
|
|
248
269
|
max_columns: z.number().int().positive().max(10).optional(),
|
|
249
270
|
// Strict mode: callers must explicitly choose columns.
|
|
250
|
-
select_columns: z
|
|
271
|
+
select_columns: z
|
|
272
|
+
.array(z.union([z.string().min(1), z.number().int()]))
|
|
273
|
+
.min(1)
|
|
274
|
+
.max(10)
|
|
275
|
+
.optional(),
|
|
251
276
|
include_answers: z.boolean().optional(),
|
|
252
277
|
strict_full: z.boolean().optional()
|
|
253
|
-
})
|
|
278
|
+
}))
|
|
254
279
|
.refine((value) => value.include_answers !== false, {
|
|
255
280
|
message: "include_answers=false is not allowed in strict column mode"
|
|
256
281
|
})
|
|
@@ -279,17 +304,19 @@ const listSuccessOutputSchema = z.object({
|
|
|
279
304
|
completeness: completenessSchema,
|
|
280
305
|
evidence: evidenceSchema
|
|
281
306
|
}),
|
|
307
|
+
...queryContractFields,
|
|
282
308
|
meta: apiMetaSchema
|
|
283
309
|
});
|
|
284
310
|
const listOutputSchema = listSuccessOutputSchema;
|
|
285
|
-
const recordGetInputSchema = z.object({
|
|
311
|
+
const recordGetInputSchema = z.preprocess(normalizeRecordGetInput, z.object({
|
|
286
312
|
apply_id: z.union([z.string().min(1), z.number().int()]),
|
|
287
313
|
max_columns: z.number().int().positive().max(10).optional(),
|
|
288
314
|
select_columns: z
|
|
289
315
|
.array(z.union([z.string().min(1), z.number().int()]))
|
|
290
316
|
.min(1)
|
|
291
317
|
.max(10)
|
|
292
|
-
|
|
318
|
+
.optional()
|
|
319
|
+
}));
|
|
293
320
|
const recordGetSuccessOutputSchema = z.object({
|
|
294
321
|
ok: z.literal(true),
|
|
295
322
|
data: z.object({
|
|
@@ -309,6 +336,7 @@ const recordGetSuccessOutputSchema = z.object({
|
|
|
309
336
|
selected_columns: z.array(z.string())
|
|
310
337
|
})
|
|
311
338
|
}),
|
|
339
|
+
...queryContractFields,
|
|
312
340
|
meta: apiMetaSchema
|
|
313
341
|
});
|
|
314
342
|
const recordGetOutputSchema = recordGetSuccessOutputSchema;
|
|
@@ -371,7 +399,8 @@ const operationSuccessOutputSchema = z.object({
|
|
|
371
399
|
meta: apiMetaSchema
|
|
372
400
|
});
|
|
373
401
|
const operationOutputSchema = operationSuccessOutputSchema;
|
|
374
|
-
const queryInputSchema = z
|
|
402
|
+
const queryInputSchema = z
|
|
403
|
+
.preprocess(normalizeQueryInput, z.object({
|
|
375
404
|
query_mode: z.enum(["auto", "list", "record", "summary"]).optional(),
|
|
376
405
|
app_key: z.string().min(1).optional(),
|
|
377
406
|
apply_id: z.union([z.string().min(1), z.number().int()]).optional(),
|
|
@@ -444,7 +473,7 @@ const queryInputSchema = z.object({
|
|
|
444
473
|
.optional(),
|
|
445
474
|
scan_max_pages: z.number().int().positive().max(500).optional(),
|
|
446
475
|
strict_full: z.boolean().optional()
|
|
447
|
-
})
|
|
476
|
+
}))
|
|
448
477
|
.refine((value) => !(value.page_num !== undefined && value.page_token !== undefined), {
|
|
449
478
|
message: "page_num and page_token cannot be used together"
|
|
450
479
|
});
|
|
@@ -504,11 +533,12 @@ const querySuccessOutputSchema = z.object({
|
|
|
504
533
|
record: recordGetSuccessOutputSchema.shape.data.optional(),
|
|
505
534
|
summary: querySummaryOutputSchema.optional()
|
|
506
535
|
}),
|
|
536
|
+
...queryContractFields,
|
|
507
537
|
meta: apiMetaSchema
|
|
508
538
|
});
|
|
509
539
|
const queryOutputSchema = querySuccessOutputSchema;
|
|
510
540
|
const aggregateInputSchema = z
|
|
511
|
-
.object({
|
|
541
|
+
.preprocess(normalizeAggregateInput, z.object({
|
|
512
542
|
app_key: z.string().min(1),
|
|
513
543
|
user_id: z.string().min(1).optional(),
|
|
514
544
|
page_num: z.number().int().positive().optional(),
|
|
@@ -572,7 +602,7 @@ const aggregateInputSchema = z
|
|
|
572
602
|
.optional(),
|
|
573
603
|
max_groups: z.number().int().positive().max(2000).optional(),
|
|
574
604
|
strict_full: z.boolean().optional()
|
|
575
|
-
})
|
|
605
|
+
}))
|
|
576
606
|
.refine((value) => !(value.page_num !== undefined && value.page_token !== undefined), {
|
|
577
607
|
message: "page_num and page_token cannot be used together"
|
|
578
608
|
});
|
|
@@ -607,6 +637,7 @@ const aggregateOutputSchema = z.object({
|
|
|
607
637
|
})
|
|
608
638
|
})
|
|
609
639
|
}),
|
|
640
|
+
...queryContractFields,
|
|
610
641
|
meta: apiMetaSchema
|
|
611
642
|
});
|
|
612
643
|
server.registerTool("qf_apps_list", {
|
|
@@ -736,6 +767,8 @@ server.registerTool("qf_query", {
|
|
|
736
767
|
if (routedMode === "record") {
|
|
737
768
|
const recordArgs = buildRecordGetArgsFromQuery(args);
|
|
738
769
|
const executed = await executeRecordGet(recordArgs);
|
|
770
|
+
const completeness = executed.payload.completeness;
|
|
771
|
+
const evidence = executed.payload.evidence;
|
|
739
772
|
return okResult({
|
|
740
773
|
ok: true,
|
|
741
774
|
data: {
|
|
@@ -743,11 +776,18 @@ server.registerTool("qf_query", {
|
|
|
743
776
|
source_tool: "qf_record_get",
|
|
744
777
|
record: executed.payload.data
|
|
745
778
|
},
|
|
779
|
+
completeness,
|
|
780
|
+
evidence,
|
|
781
|
+
error_code: null,
|
|
782
|
+
fix_hint: null,
|
|
783
|
+
next_page_token: completeness.next_page_token,
|
|
746
784
|
meta: executed.payload.meta
|
|
747
785
|
}, executed.message);
|
|
748
786
|
}
|
|
749
787
|
if (routedMode === "summary") {
|
|
750
788
|
const executed = await executeRecordsSummary(args);
|
|
789
|
+
const completeness = executed.data.completeness;
|
|
790
|
+
const evidence = executed.data.evidence;
|
|
751
791
|
return okResult({
|
|
752
792
|
ok: true,
|
|
753
793
|
data: {
|
|
@@ -755,11 +795,18 @@ server.registerTool("qf_query", {
|
|
|
755
795
|
source_tool: "qf_records_summary",
|
|
756
796
|
summary: executed.data
|
|
757
797
|
},
|
|
798
|
+
completeness,
|
|
799
|
+
evidence,
|
|
800
|
+
error_code: null,
|
|
801
|
+
fix_hint: null,
|
|
802
|
+
next_page_token: completeness.next_page_token,
|
|
758
803
|
meta: executed.meta
|
|
759
804
|
}, executed.message);
|
|
760
805
|
}
|
|
761
806
|
const listArgs = buildListArgsFromQuery(args);
|
|
762
807
|
const executed = await executeRecordsList(listArgs);
|
|
808
|
+
const completeness = executed.payload.completeness;
|
|
809
|
+
const evidence = executed.payload.evidence;
|
|
763
810
|
return okResult({
|
|
764
811
|
ok: true,
|
|
765
812
|
data: {
|
|
@@ -767,6 +814,11 @@ server.registerTool("qf_query", {
|
|
|
767
814
|
source_tool: "qf_records_list",
|
|
768
815
|
list: executed.payload.data
|
|
769
816
|
},
|
|
817
|
+
completeness,
|
|
818
|
+
evidence,
|
|
819
|
+
error_code: null,
|
|
820
|
+
fix_hint: null,
|
|
821
|
+
next_page_token: completeness.next_page_token,
|
|
770
822
|
meta: executed.payload.meta
|
|
771
823
|
}, executed.message);
|
|
772
824
|
}
|
|
@@ -899,10 +951,259 @@ server.registerTool("qf_records_aggregate", {
|
|
|
899
951
|
}
|
|
900
952
|
});
|
|
901
953
|
async function main() {
|
|
954
|
+
const cliExitCode = await runCli(process.argv.slice(2));
|
|
955
|
+
if (cliExitCode !== null) {
|
|
956
|
+
process.exitCode = cliExitCode;
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
902
959
|
const transport = new StdioServerTransport();
|
|
903
960
|
await server.connect(transport);
|
|
904
961
|
}
|
|
905
|
-
void main()
|
|
962
|
+
void main().catch((error) => {
|
|
963
|
+
process.stderr.write(`${error instanceof Error ? error.message : "Unknown error"}\n`);
|
|
964
|
+
process.exitCode = 1;
|
|
965
|
+
});
|
|
966
|
+
async function runCli(argv) {
|
|
967
|
+
if (argv.length === 0 || argv[0] === "--stdio-server") {
|
|
968
|
+
return null;
|
|
969
|
+
}
|
|
970
|
+
const [command, ...rest] = argv;
|
|
971
|
+
if (command === "--help" || command === "-h") {
|
|
972
|
+
printCliHelp();
|
|
973
|
+
return 0;
|
|
974
|
+
}
|
|
975
|
+
if (command !== "cli") {
|
|
976
|
+
printCliHelp();
|
|
977
|
+
return 2;
|
|
978
|
+
}
|
|
979
|
+
const [subcommand, ...subArgs] = rest;
|
|
980
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
981
|
+
printCliHelp();
|
|
982
|
+
return 0;
|
|
983
|
+
}
|
|
984
|
+
if (subcommand === "tools") {
|
|
985
|
+
return runCliTools(subArgs);
|
|
986
|
+
}
|
|
987
|
+
if (subcommand === "call") {
|
|
988
|
+
return runCliCall(subArgs);
|
|
989
|
+
}
|
|
990
|
+
process.stderr.write(`Unknown CLI subcommand: ${subcommand}\n`);
|
|
991
|
+
printCliHelp();
|
|
992
|
+
return 2;
|
|
993
|
+
}
|
|
994
|
+
async function runCliTools(args) {
|
|
995
|
+
let options;
|
|
996
|
+
try {
|
|
997
|
+
options = parseCliFlags(args);
|
|
998
|
+
}
|
|
999
|
+
catch (error) {
|
|
1000
|
+
process.stderr.write(`${error instanceof Error ? error.message : "Invalid CLI options"}\n`);
|
|
1001
|
+
return 2;
|
|
1002
|
+
}
|
|
1003
|
+
if (options.help) {
|
|
1004
|
+
process.stdout.write("Usage: qingflow-mcp cli tools [--json]\n");
|
|
1005
|
+
return 0;
|
|
1006
|
+
}
|
|
1007
|
+
if (options.argsText !== undefined) {
|
|
1008
|
+
process.stderr.write("--args is not supported for 'cli tools'\n");
|
|
1009
|
+
return 2;
|
|
1010
|
+
}
|
|
1011
|
+
const call = await callLocalMcp("tools");
|
|
1012
|
+
if (call.ok) {
|
|
1013
|
+
if (options.json) {
|
|
1014
|
+
process.stdout.write(`${JSON.stringify(call.tools, null, 2)}\n`);
|
|
1015
|
+
return 0;
|
|
1016
|
+
}
|
|
1017
|
+
for (const tool of call.tools) {
|
|
1018
|
+
process.stdout.write(`${tool.name}\t${tool.description ?? ""}\n`);
|
|
1019
|
+
}
|
|
1020
|
+
return 0;
|
|
1021
|
+
}
|
|
1022
|
+
process.stderr.write(`${JSON.stringify(call.error, null, 2)}\n`);
|
|
1023
|
+
return 1;
|
|
1024
|
+
}
|
|
1025
|
+
async function runCliCall(args) {
|
|
1026
|
+
if (args.length === 0) {
|
|
1027
|
+
process.stderr.write("Usage: qingflow-mcp cli call <tool_name> [--args '{\"key\":\"value\"}']\n");
|
|
1028
|
+
return 2;
|
|
1029
|
+
}
|
|
1030
|
+
const [toolName, ...flagArgs] = args;
|
|
1031
|
+
let options;
|
|
1032
|
+
try {
|
|
1033
|
+
options = parseCliFlags(flagArgs);
|
|
1034
|
+
}
|
|
1035
|
+
catch (error) {
|
|
1036
|
+
process.stderr.write(`${error instanceof Error ? error.message : "Invalid CLI options"}\n`);
|
|
1037
|
+
return 2;
|
|
1038
|
+
}
|
|
1039
|
+
if (options.help) {
|
|
1040
|
+
process.stdout.write("Usage: qingflow-mcp cli call <tool_name> [--args '{\"key\":\"value\"}'] [--json]\n");
|
|
1041
|
+
return 0;
|
|
1042
|
+
}
|
|
1043
|
+
const inputText = options.argsText ?? (process.stdin.isTTY ? "{}" : await readStdinText());
|
|
1044
|
+
let parsedInput;
|
|
1045
|
+
try {
|
|
1046
|
+
parsedInput = inputText.trim() ? JSON.parse(inputText) : {};
|
|
1047
|
+
}
|
|
1048
|
+
catch {
|
|
1049
|
+
process.stderr.write("Invalid JSON for --args or stdin body\n");
|
|
1050
|
+
return 2;
|
|
1051
|
+
}
|
|
1052
|
+
if (!parsedInput || typeof parsedInput !== "object" || Array.isArray(parsedInput)) {
|
|
1053
|
+
process.stderr.write("Tool arguments must be a JSON object\n");
|
|
1054
|
+
return 2;
|
|
1055
|
+
}
|
|
1056
|
+
const call = await callLocalMcp("call", {
|
|
1057
|
+
toolName,
|
|
1058
|
+
args: parsedInput
|
|
1059
|
+
});
|
|
1060
|
+
if (call.ok) {
|
|
1061
|
+
if (options.json || typeof call.payload === "object") {
|
|
1062
|
+
process.stdout.write(`${JSON.stringify(call.payload, null, 2)}\n`);
|
|
1063
|
+
return 0;
|
|
1064
|
+
}
|
|
1065
|
+
process.stdout.write(`${String(call.payload)}\n`);
|
|
1066
|
+
return 0;
|
|
1067
|
+
}
|
|
1068
|
+
process.stderr.write(`${JSON.stringify(call.error, null, 2)}\n`);
|
|
1069
|
+
return 1;
|
|
1070
|
+
}
|
|
1071
|
+
function parseCliFlags(args) {
|
|
1072
|
+
let argsText;
|
|
1073
|
+
let help = false;
|
|
1074
|
+
let json = false;
|
|
1075
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
1076
|
+
const token = args[i];
|
|
1077
|
+
if (token === "--help" || token === "-h") {
|
|
1078
|
+
help = true;
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
if (token === "--json") {
|
|
1082
|
+
json = true;
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
if (token === "--args") {
|
|
1086
|
+
const next = args[i + 1];
|
|
1087
|
+
if (next === undefined) {
|
|
1088
|
+
throw new Error("--args requires a JSON value");
|
|
1089
|
+
}
|
|
1090
|
+
argsText = next;
|
|
1091
|
+
i += 1;
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
if (token.startsWith("--args=")) {
|
|
1095
|
+
argsText = token.slice("--args=".length);
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
throw new Error(`Unknown CLI option: ${token}`);
|
|
1099
|
+
}
|
|
1100
|
+
return { argsText, help, json };
|
|
1101
|
+
}
|
|
1102
|
+
async function callLocalMcp(mode, params) {
|
|
1103
|
+
const entrypoint = process.argv[1];
|
|
1104
|
+
if (!entrypoint) {
|
|
1105
|
+
return { ok: false, error: { message: "Cannot locate current executable entrypoint" } };
|
|
1106
|
+
}
|
|
1107
|
+
const childEnv = {};
|
|
1108
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
1109
|
+
if (value !== undefined) {
|
|
1110
|
+
childEnv[key] = value;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
const transport = new StdioClientTransport({
|
|
1114
|
+
command: process.execPath,
|
|
1115
|
+
args: [entrypoint, "--stdio-server"],
|
|
1116
|
+
cwd: process.cwd(),
|
|
1117
|
+
env: childEnv,
|
|
1118
|
+
stderr: "pipe"
|
|
1119
|
+
});
|
|
1120
|
+
if (transport.stderr) {
|
|
1121
|
+
transport.stderr.on("data", () => {
|
|
1122
|
+
// Keep stderr drained to avoid child process backpressure.
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
const localClient = new Client({
|
|
1126
|
+
name: "qingflow-mcp-cli",
|
|
1127
|
+
version: "0.3.0"
|
|
1128
|
+
});
|
|
1129
|
+
try {
|
|
1130
|
+
await localClient.connect(transport);
|
|
1131
|
+
if (mode === "tools") {
|
|
1132
|
+
const listed = await localClient.listTools();
|
|
1133
|
+
const tools = listed.tools.map((tool) => ({
|
|
1134
|
+
name: tool.name,
|
|
1135
|
+
description: tool.description
|
|
1136
|
+
}));
|
|
1137
|
+
return { ok: true, tools };
|
|
1138
|
+
}
|
|
1139
|
+
if (!params) {
|
|
1140
|
+
throw new Error("Missing tool call params");
|
|
1141
|
+
}
|
|
1142
|
+
const result = await localClient.callTool({
|
|
1143
|
+
name: params.toolName,
|
|
1144
|
+
arguments: params.args
|
|
1145
|
+
});
|
|
1146
|
+
if (result.isError) {
|
|
1147
|
+
const payload = tryParseToolPayload(result);
|
|
1148
|
+
return { ok: false, error: payload ?? { message: `Tool ${params.toolName} failed`, result } };
|
|
1149
|
+
}
|
|
1150
|
+
const payload = tryParseToolPayload(result);
|
|
1151
|
+
return { ok: true, payload: payload ?? result };
|
|
1152
|
+
}
|
|
1153
|
+
catch (error) {
|
|
1154
|
+
return {
|
|
1155
|
+
ok: false,
|
|
1156
|
+
error: error instanceof Error ? { message: error.message } : error
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
finally {
|
|
1160
|
+
await localClient.close().catch(() => { });
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function tryParseToolPayload(result) {
|
|
1164
|
+
const obj = asObject(result);
|
|
1165
|
+
if (!obj) {
|
|
1166
|
+
return null;
|
|
1167
|
+
}
|
|
1168
|
+
if (obj.structuredContent !== undefined) {
|
|
1169
|
+
return obj.structuredContent;
|
|
1170
|
+
}
|
|
1171
|
+
const textItems = Array.isArray(obj.content)
|
|
1172
|
+
? obj.content.filter((item) => Boolean(item) &&
|
|
1173
|
+
typeof item === "object" &&
|
|
1174
|
+
item.type === "text" &&
|
|
1175
|
+
typeof item.text === "string")
|
|
1176
|
+
: [];
|
|
1177
|
+
for (const item of textItems) {
|
|
1178
|
+
const text = item.text;
|
|
1179
|
+
try {
|
|
1180
|
+
return JSON.parse(text);
|
|
1181
|
+
}
|
|
1182
|
+
catch {
|
|
1183
|
+
// keep scanning and fallback
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
async function readStdinText() {
|
|
1189
|
+
const chunks = [];
|
|
1190
|
+
for await (const chunk of process.stdin) {
|
|
1191
|
+
chunks.push(String(chunk));
|
|
1192
|
+
}
|
|
1193
|
+
return chunks.join("");
|
|
1194
|
+
}
|
|
1195
|
+
function printCliHelp() {
|
|
1196
|
+
process.stdout.write(`qingflow-mcp usage
|
|
1197
|
+
|
|
1198
|
+
Default (MCP stdio server):
|
|
1199
|
+
qingflow-mcp
|
|
1200
|
+
|
|
1201
|
+
CLI mode:
|
|
1202
|
+
qingflow-mcp cli tools [--json]
|
|
1203
|
+
qingflow-mcp cli call <tool_name> [--args '{"key":"value"}'] [--json]
|
|
1204
|
+
echo '{"app_key":"xxx","mode":"all","select_columns":[1001]}' | qingflow-mcp cli call qf_query
|
|
1205
|
+
`);
|
|
1206
|
+
}
|
|
906
1207
|
function hasWritePayload(answers, fields) {
|
|
907
1208
|
return Boolean((answers && answers.length > 0) || (fields && Object.keys(fields).length > 0));
|
|
908
1209
|
}
|
|
@@ -913,6 +1214,227 @@ function buildMeta(response) {
|
|
|
913
1214
|
base_url: baseUrl
|
|
914
1215
|
};
|
|
915
1216
|
}
|
|
1217
|
+
function missingRequiredFieldError(params) {
|
|
1218
|
+
return new InputValidationError({
|
|
1219
|
+
message: `Missing required field "${params.field}" for ${params.tool}`,
|
|
1220
|
+
errorCode: "MISSING_REQUIRED_FIELD",
|
|
1221
|
+
fixHint: params.fixHint,
|
|
1222
|
+
details: {
|
|
1223
|
+
field: params.field,
|
|
1224
|
+
tool: params.tool
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
function normalizeListInput(raw) {
|
|
1229
|
+
const obj = asObject(raw);
|
|
1230
|
+
if (!obj) {
|
|
1231
|
+
return raw;
|
|
1232
|
+
}
|
|
1233
|
+
return {
|
|
1234
|
+
...obj,
|
|
1235
|
+
page_num: coerceNumberLike(obj.page_num),
|
|
1236
|
+
page_size: coerceNumberLike(obj.page_size),
|
|
1237
|
+
requested_pages: coerceNumberLike(obj.requested_pages),
|
|
1238
|
+
scan_max_pages: coerceNumberLike(obj.scan_max_pages),
|
|
1239
|
+
type: coerceNumberLike(obj.type),
|
|
1240
|
+
max_rows: coerceNumberLike(obj.max_rows),
|
|
1241
|
+
max_items: coerceNumberLike(obj.max_items),
|
|
1242
|
+
max_columns: coerceNumberLike(obj.max_columns),
|
|
1243
|
+
strict_full: coerceBooleanLike(obj.strict_full),
|
|
1244
|
+
include_answers: coerceBooleanLike(obj.include_answers),
|
|
1245
|
+
apply_ids: normalizeIdArrayInput(obj.apply_ids),
|
|
1246
|
+
sort: normalizeSortInput(obj.sort),
|
|
1247
|
+
filters: normalizeFiltersInput(obj.filters),
|
|
1248
|
+
select_columns: normalizeSelectorListInput(obj.select_columns),
|
|
1249
|
+
time_range: normalizeTimeRangeInput(obj.time_range)
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
function normalizeRecordGetInput(raw) {
|
|
1253
|
+
const obj = asObject(raw);
|
|
1254
|
+
if (!obj) {
|
|
1255
|
+
return raw;
|
|
1256
|
+
}
|
|
1257
|
+
return {
|
|
1258
|
+
...obj,
|
|
1259
|
+
apply_id: coerceNumberLike(obj.apply_id),
|
|
1260
|
+
max_columns: coerceNumberLike(obj.max_columns),
|
|
1261
|
+
select_columns: normalizeSelectorListInput(obj.select_columns)
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
function normalizeQueryInput(raw) {
|
|
1265
|
+
const obj = asObject(raw);
|
|
1266
|
+
if (!obj) {
|
|
1267
|
+
return raw;
|
|
1268
|
+
}
|
|
1269
|
+
return {
|
|
1270
|
+
...obj,
|
|
1271
|
+
page_num: coerceNumberLike(obj.page_num),
|
|
1272
|
+
page_size: coerceNumberLike(obj.page_size),
|
|
1273
|
+
requested_pages: coerceNumberLike(obj.requested_pages),
|
|
1274
|
+
scan_max_pages: coerceNumberLike(obj.scan_max_pages),
|
|
1275
|
+
type: coerceNumberLike(obj.type),
|
|
1276
|
+
max_rows: coerceNumberLike(obj.max_rows),
|
|
1277
|
+
max_items: coerceNumberLike(obj.max_items),
|
|
1278
|
+
max_columns: coerceNumberLike(obj.max_columns),
|
|
1279
|
+
apply_id: coerceNumberLike(obj.apply_id),
|
|
1280
|
+
strict_full: coerceBooleanLike(obj.strict_full),
|
|
1281
|
+
include_answers: coerceBooleanLike(obj.include_answers),
|
|
1282
|
+
apply_ids: normalizeIdArrayInput(obj.apply_ids),
|
|
1283
|
+
sort: normalizeSortInput(obj.sort),
|
|
1284
|
+
filters: normalizeFiltersInput(obj.filters),
|
|
1285
|
+
select_columns: normalizeSelectorListInput(obj.select_columns),
|
|
1286
|
+
time_range: normalizeTimeRangeInput(obj.time_range)
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
function normalizeAggregateInput(raw) {
|
|
1290
|
+
const obj = asObject(raw);
|
|
1291
|
+
if (!obj) {
|
|
1292
|
+
return raw;
|
|
1293
|
+
}
|
|
1294
|
+
return {
|
|
1295
|
+
...obj,
|
|
1296
|
+
page_num: coerceNumberLike(obj.page_num),
|
|
1297
|
+
page_size: coerceNumberLike(obj.page_size),
|
|
1298
|
+
requested_pages: coerceNumberLike(obj.requested_pages),
|
|
1299
|
+
scan_max_pages: coerceNumberLike(obj.scan_max_pages),
|
|
1300
|
+
type: coerceNumberLike(obj.type),
|
|
1301
|
+
max_groups: coerceNumberLike(obj.max_groups),
|
|
1302
|
+
strict_full: coerceBooleanLike(obj.strict_full),
|
|
1303
|
+
group_by: normalizeSelectorListInput(obj.group_by),
|
|
1304
|
+
amount_column: coerceNumberLike(obj.amount_column),
|
|
1305
|
+
apply_ids: normalizeIdArrayInput(obj.apply_ids),
|
|
1306
|
+
sort: normalizeSortInput(obj.sort),
|
|
1307
|
+
filters: normalizeFiltersInput(obj.filters),
|
|
1308
|
+
time_range: normalizeTimeRangeInput(obj.time_range)
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
function coerceNumberLike(value) {
|
|
1312
|
+
if (typeof value === "string") {
|
|
1313
|
+
const trimmed = value.trim();
|
|
1314
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
1315
|
+
const parsed = Number(trimmed);
|
|
1316
|
+
if (Number.isFinite(parsed)) {
|
|
1317
|
+
return parsed;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return value;
|
|
1322
|
+
}
|
|
1323
|
+
function coerceBooleanLike(value) {
|
|
1324
|
+
if (typeof value === "string") {
|
|
1325
|
+
const trimmed = value.trim().toLowerCase();
|
|
1326
|
+
if (trimmed === "true") {
|
|
1327
|
+
return true;
|
|
1328
|
+
}
|
|
1329
|
+
if (trimmed === "false") {
|
|
1330
|
+
return false;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
return value;
|
|
1334
|
+
}
|
|
1335
|
+
function parseJsonLike(value) {
|
|
1336
|
+
if (typeof value !== "string") {
|
|
1337
|
+
return value;
|
|
1338
|
+
}
|
|
1339
|
+
const trimmed = value.trim();
|
|
1340
|
+
if (!trimmed) {
|
|
1341
|
+
return value;
|
|
1342
|
+
}
|
|
1343
|
+
if (!trimmed.startsWith("[") && !trimmed.startsWith("{")) {
|
|
1344
|
+
return value;
|
|
1345
|
+
}
|
|
1346
|
+
try {
|
|
1347
|
+
return JSON.parse(trimmed);
|
|
1348
|
+
}
|
|
1349
|
+
catch {
|
|
1350
|
+
return value;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
function normalizeSelectorListInput(value) {
|
|
1354
|
+
const parsed = parseJsonLike(value);
|
|
1355
|
+
if (Array.isArray(parsed)) {
|
|
1356
|
+
return parsed.map((item) => coerceNumberLike(item));
|
|
1357
|
+
}
|
|
1358
|
+
if (typeof parsed === "string") {
|
|
1359
|
+
const trimmed = parsed.trim();
|
|
1360
|
+
if (!trimmed) {
|
|
1361
|
+
return parsed;
|
|
1362
|
+
}
|
|
1363
|
+
if (trimmed.includes(",")) {
|
|
1364
|
+
return trimmed
|
|
1365
|
+
.split(",")
|
|
1366
|
+
.map((item) => item.trim())
|
|
1367
|
+
.filter((item) => item.length > 0)
|
|
1368
|
+
.map((item) => coerceNumberLike(item));
|
|
1369
|
+
}
|
|
1370
|
+
return [coerceNumberLike(trimmed)];
|
|
1371
|
+
}
|
|
1372
|
+
if (parsed !== undefined && parsed !== null) {
|
|
1373
|
+
return [coerceNumberLike(parsed)];
|
|
1374
|
+
}
|
|
1375
|
+
return parsed;
|
|
1376
|
+
}
|
|
1377
|
+
function normalizeIdArrayInput(value) {
|
|
1378
|
+
const parsed = parseJsonLike(value);
|
|
1379
|
+
if (Array.isArray(parsed)) {
|
|
1380
|
+
return parsed.map((item) => coerceNumberLike(item));
|
|
1381
|
+
}
|
|
1382
|
+
if (typeof parsed === "string" && parsed.includes(",")) {
|
|
1383
|
+
return parsed
|
|
1384
|
+
.split(",")
|
|
1385
|
+
.map((item) => item.trim())
|
|
1386
|
+
.filter((item) => item.length > 0)
|
|
1387
|
+
.map((item) => coerceNumberLike(item));
|
|
1388
|
+
}
|
|
1389
|
+
return parsed;
|
|
1390
|
+
}
|
|
1391
|
+
function normalizeSortInput(value) {
|
|
1392
|
+
const parsed = parseJsonLike(value);
|
|
1393
|
+
if (!Array.isArray(parsed)) {
|
|
1394
|
+
return parsed;
|
|
1395
|
+
}
|
|
1396
|
+
return parsed.map((item) => {
|
|
1397
|
+
const obj = asObject(item);
|
|
1398
|
+
if (!obj) {
|
|
1399
|
+
return item;
|
|
1400
|
+
}
|
|
1401
|
+
return {
|
|
1402
|
+
...obj,
|
|
1403
|
+
que_id: coerceNumberLike(obj.que_id),
|
|
1404
|
+
ascend: coerceBooleanLike(obj.ascend)
|
|
1405
|
+
};
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
function normalizeFiltersInput(value) {
|
|
1409
|
+
const parsed = parseJsonLike(value);
|
|
1410
|
+
if (parsed === undefined || parsed === null) {
|
|
1411
|
+
return parsed;
|
|
1412
|
+
}
|
|
1413
|
+
const list = Array.isArray(parsed) ? parsed : [parsed];
|
|
1414
|
+
return list.map((item) => {
|
|
1415
|
+
const obj = asObject(item);
|
|
1416
|
+
if (!obj) {
|
|
1417
|
+
return item;
|
|
1418
|
+
}
|
|
1419
|
+
return {
|
|
1420
|
+
...obj,
|
|
1421
|
+
que_id: coerceNumberLike(obj.que_id),
|
|
1422
|
+
scope: coerceNumberLike(obj.scope),
|
|
1423
|
+
search_options: normalizeIdArrayInput(obj.search_options)
|
|
1424
|
+
};
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
function normalizeTimeRangeInput(value) {
|
|
1428
|
+
const parsed = parseJsonLike(value);
|
|
1429
|
+
const obj = asObject(parsed);
|
|
1430
|
+
if (!obj) {
|
|
1431
|
+
return parsed;
|
|
1432
|
+
}
|
|
1433
|
+
return {
|
|
1434
|
+
...obj,
|
|
1435
|
+
column: coerceNumberLike(obj.column)
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
916
1438
|
function resolveStartPage(pageNum, pageToken, appKey) {
|
|
917
1439
|
if (!pageToken) {
|
|
918
1440
|
return pageNum ?? 1;
|
|
@@ -988,10 +1510,18 @@ function resolveQueryMode(args) {
|
|
|
988
1510
|
}
|
|
989
1511
|
function buildListArgsFromQuery(args) {
|
|
990
1512
|
if (!args.app_key) {
|
|
991
|
-
throw
|
|
1513
|
+
throw missingRequiredFieldError({
|
|
1514
|
+
field: "app_key",
|
|
1515
|
+
tool: "qf_query(list)",
|
|
1516
|
+
fixHint: "Provide app_key, for example: {\"query_mode\":\"list\",\"app_key\":\"21b3d559\",...}"
|
|
1517
|
+
});
|
|
992
1518
|
}
|
|
993
1519
|
if (!args.select_columns?.length) {
|
|
994
|
-
throw
|
|
1520
|
+
throw missingRequiredFieldError({
|
|
1521
|
+
field: "select_columns",
|
|
1522
|
+
tool: "qf_query(list)",
|
|
1523
|
+
fixHint: "Provide select_columns as an array (<=10), for example: {\"select_columns\":[0,\"客户全称\",\"报价总金额\"]}"
|
|
1524
|
+
});
|
|
995
1525
|
}
|
|
996
1526
|
const filters = buildListFiltersFromQuery(args);
|
|
997
1527
|
return listInputSchema.parse({
|
|
@@ -1047,10 +1577,18 @@ function appendTimeRangeFilter(inputFilters, timeRange) {
|
|
|
1047
1577
|
}
|
|
1048
1578
|
function buildRecordGetArgsFromQuery(args) {
|
|
1049
1579
|
if (args.apply_id === undefined) {
|
|
1050
|
-
throw
|
|
1580
|
+
throw missingRequiredFieldError({
|
|
1581
|
+
field: "apply_id",
|
|
1582
|
+
tool: "qf_query(record)",
|
|
1583
|
+
fixHint: "Provide apply_id, for example: {\"query_mode\":\"record\",\"apply_id\":\"497600278750478338\",...}"
|
|
1584
|
+
});
|
|
1051
1585
|
}
|
|
1052
1586
|
if (!args.select_columns?.length) {
|
|
1053
|
-
throw
|
|
1587
|
+
throw missingRequiredFieldError({
|
|
1588
|
+
field: "select_columns",
|
|
1589
|
+
tool: "qf_query(record)",
|
|
1590
|
+
fixHint: "Provide select_columns as an array (<=10), for example: {\"select_columns\":[0,\"客户全称\"]}"
|
|
1591
|
+
});
|
|
1054
1592
|
}
|
|
1055
1593
|
return recordGetInputSchema.parse({
|
|
1056
1594
|
apply_id: args.apply_id,
|
|
@@ -1059,6 +1597,20 @@ function buildRecordGetArgsFromQuery(args) {
|
|
|
1059
1597
|
});
|
|
1060
1598
|
}
|
|
1061
1599
|
async function executeRecordsList(args) {
|
|
1600
|
+
if (!args.app_key) {
|
|
1601
|
+
throw missingRequiredFieldError({
|
|
1602
|
+
field: "app_key",
|
|
1603
|
+
tool: "qf_records_list",
|
|
1604
|
+
fixHint: "Provide app_key, for example: {\"app_key\":\"21b3d559\",...}"
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
if (!args.select_columns?.length) {
|
|
1608
|
+
throw missingRequiredFieldError({
|
|
1609
|
+
field: "select_columns",
|
|
1610
|
+
tool: "qf_records_list",
|
|
1611
|
+
fixHint: "Provide select_columns as an array (<=10), for example: {\"select_columns\":[0,\"客户全称\",\"报价总金额\"]}"
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1062
1614
|
const queryId = randomUUID();
|
|
1063
1615
|
const pageNum = resolveStartPage(args.page_num, args.page_token, args.app_key);
|
|
1064
1616
|
const pageSize = args.page_size ?? DEFAULT_PAGE_SIZE;
|
|
@@ -1171,11 +1723,12 @@ async function executeRecordsList(args) {
|
|
|
1171
1723
|
}
|
|
1172
1724
|
: null
|
|
1173
1725
|
};
|
|
1726
|
+
const evidence = buildEvidencePayload(listState, sourcePages);
|
|
1174
1727
|
if (args.strict_full && !isComplete) {
|
|
1175
1728
|
throw new NeedMoreDataError("List result is incomplete. Increase requested_pages/max_rows or continue with next_page_token.", {
|
|
1176
1729
|
code: "NEED_MORE_DATA",
|
|
1177
1730
|
completeness,
|
|
1178
|
-
evidence
|
|
1731
|
+
evidence
|
|
1179
1732
|
});
|
|
1180
1733
|
}
|
|
1181
1734
|
const responsePayload = {
|
|
@@ -1196,8 +1749,13 @@ async function executeRecordsList(args) {
|
|
|
1196
1749
|
selected_columns: columnProjection.selectedColumns
|
|
1197
1750
|
},
|
|
1198
1751
|
completeness,
|
|
1199
|
-
evidence
|
|
1752
|
+
evidence
|
|
1200
1753
|
},
|
|
1754
|
+
completeness,
|
|
1755
|
+
evidence,
|
|
1756
|
+
error_code: null,
|
|
1757
|
+
fix_hint: null,
|
|
1758
|
+
next_page_token: completeness.next_page_token,
|
|
1201
1759
|
meta: responseMeta
|
|
1202
1760
|
};
|
|
1203
1761
|
return {
|
|
@@ -1210,6 +1768,13 @@ async function executeRecordsList(args) {
|
|
|
1210
1768
|
};
|
|
1211
1769
|
}
|
|
1212
1770
|
async function executeRecordGet(args) {
|
|
1771
|
+
if (!args.select_columns?.length) {
|
|
1772
|
+
throw missingRequiredFieldError({
|
|
1773
|
+
field: "select_columns",
|
|
1774
|
+
tool: "qf_record_get",
|
|
1775
|
+
fixHint: "Provide select_columns as an array (<=10), for example: {\"apply_id\":\"...\",\"select_columns\":[0]}"
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1213
1778
|
const queryId = randomUUID();
|
|
1214
1779
|
const response = await client.getRecord(String(args.apply_id));
|
|
1215
1780
|
const record = asObject(response.result) ?? {};
|
|
@@ -1253,6 +1818,27 @@ async function executeRecordGet(args) {
|
|
|
1253
1818
|
selected_columns: projection.selectedColumns ?? []
|
|
1254
1819
|
}
|
|
1255
1820
|
},
|
|
1821
|
+
completeness: {
|
|
1822
|
+
result_amount: 1,
|
|
1823
|
+
returned_items: 1,
|
|
1824
|
+
fetched_pages: 1,
|
|
1825
|
+
requested_pages: 1,
|
|
1826
|
+
actual_scanned_pages: 1,
|
|
1827
|
+
has_more: false,
|
|
1828
|
+
next_page_token: null,
|
|
1829
|
+
is_complete: true,
|
|
1830
|
+
partial: false,
|
|
1831
|
+
omitted_items: 0,
|
|
1832
|
+
omitted_chars: 0
|
|
1833
|
+
},
|
|
1834
|
+
evidence: {
|
|
1835
|
+
query_id: queryId,
|
|
1836
|
+
apply_id: String(args.apply_id),
|
|
1837
|
+
selected_columns: projection.selectedColumns ?? []
|
|
1838
|
+
},
|
|
1839
|
+
error_code: null,
|
|
1840
|
+
fix_hint: null,
|
|
1841
|
+
next_page_token: null,
|
|
1256
1842
|
meta: buildMeta(response)
|
|
1257
1843
|
},
|
|
1258
1844
|
message: `Fetched record ${String(args.apply_id)}`
|
|
@@ -1260,10 +1846,18 @@ async function executeRecordGet(args) {
|
|
|
1260
1846
|
}
|
|
1261
1847
|
async function executeRecordsSummary(args) {
|
|
1262
1848
|
if (!args.app_key) {
|
|
1263
|
-
throw
|
|
1849
|
+
throw missingRequiredFieldError({
|
|
1850
|
+
field: "app_key",
|
|
1851
|
+
tool: "qf_query(summary)",
|
|
1852
|
+
fixHint: "Provide app_key, for example: {\"query_mode\":\"summary\",\"app_key\":\"21b3d559\",...}"
|
|
1853
|
+
});
|
|
1264
1854
|
}
|
|
1265
1855
|
if (!args.select_columns?.length) {
|
|
1266
|
-
throw
|
|
1856
|
+
throw missingRequiredFieldError({
|
|
1857
|
+
field: "select_columns",
|
|
1858
|
+
tool: "qf_query(summary)",
|
|
1859
|
+
fixHint: "Provide select_columns as an array (<=10), for example: {\"select_columns\":[\"客户全称\"]}"
|
|
1860
|
+
});
|
|
1267
1861
|
}
|
|
1268
1862
|
const queryId = randomUUID();
|
|
1269
1863
|
const strictFull = args.strict_full ?? true;
|
|
@@ -1705,6 +2299,11 @@ async function executeRecordsAggregate(args) {
|
|
|
1705
2299
|
}
|
|
1706
2300
|
}
|
|
1707
2301
|
},
|
|
2302
|
+
completeness,
|
|
2303
|
+
evidence,
|
|
2304
|
+
error_code: null,
|
|
2305
|
+
fix_hint: null,
|
|
2306
|
+
next_page_token: completeness.next_page_token,
|
|
1708
2307
|
meta: responseMeta
|
|
1709
2308
|
},
|
|
1710
2309
|
message: isComplete
|
|
@@ -1773,12 +2372,19 @@ function extractSummaryColumnValue(answers, column) {
|
|
|
1773
2372
|
}
|
|
1774
2373
|
function extractAnswerDisplayValue(answer) {
|
|
1775
2374
|
const tableValues = answer.tableValues ?? answer.table_values;
|
|
1776
|
-
if (tableValues
|
|
2375
|
+
if (Array.isArray(tableValues)) {
|
|
2376
|
+
// Qingflow often sends tableValues: [] for non-table fields.
|
|
2377
|
+
// Prefer non-empty tableValues; otherwise fallback to values.
|
|
2378
|
+
if (tableValues.length > 0) {
|
|
2379
|
+
return tableValues;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
else if (tableValues !== undefined && tableValues !== null) {
|
|
1777
2383
|
return tableValues;
|
|
1778
2384
|
}
|
|
1779
2385
|
const values = asArray(answer.values);
|
|
1780
2386
|
if (values.length === 0) {
|
|
1781
|
-
return null;
|
|
2387
|
+
return Array.isArray(tableValues) ? tableValues : null;
|
|
1782
2388
|
}
|
|
1783
2389
|
const normalized = values.map((item) => extractAnswerValueCell(item));
|
|
1784
2390
|
return normalized.length === 1 ? normalized[0] : normalized;
|
|
@@ -2351,40 +2957,69 @@ function errorResult(error) {
|
|
|
2351
2957
|
}
|
|
2352
2958
|
function toErrorPayload(error) {
|
|
2353
2959
|
if (error instanceof NeedMoreDataError) {
|
|
2960
|
+
const details = asObject(error.details);
|
|
2961
|
+
const completeness = asObject(details?.completeness);
|
|
2354
2962
|
return {
|
|
2355
2963
|
ok: false,
|
|
2356
2964
|
code: error.code,
|
|
2965
|
+
error_code: error.code,
|
|
2357
2966
|
status: "need_more_data",
|
|
2358
2967
|
message: error.message,
|
|
2968
|
+
fix_hint: "Continue with next_page_token or increase requested_pages/scan_max_pages.",
|
|
2969
|
+
next_page_token: asNullableString(completeness?.next_page_token),
|
|
2970
|
+
details: error.details
|
|
2971
|
+
};
|
|
2972
|
+
}
|
|
2973
|
+
if (error instanceof InputValidationError) {
|
|
2974
|
+
return {
|
|
2975
|
+
ok: false,
|
|
2976
|
+
error_code: error.errorCode,
|
|
2977
|
+
message: error.message,
|
|
2978
|
+
fix_hint: error.fixHint,
|
|
2979
|
+
next_page_token: null,
|
|
2359
2980
|
details: error.details
|
|
2360
2981
|
};
|
|
2361
2982
|
}
|
|
2362
2983
|
if (error instanceof QingflowApiError) {
|
|
2363
2984
|
return {
|
|
2364
2985
|
ok: false,
|
|
2986
|
+
error_code: "QINGFLOW_API_ERROR",
|
|
2365
2987
|
message: error.message,
|
|
2366
2988
|
err_code: error.errCode,
|
|
2367
2989
|
err_msg: error.errMsg || null,
|
|
2368
2990
|
http_status: error.httpStatus,
|
|
2991
|
+
fix_hint: "Check app_key/accessToken and request body against qf_form_get field definitions.",
|
|
2992
|
+
next_page_token: null,
|
|
2369
2993
|
details: error.details ?? null
|
|
2370
2994
|
};
|
|
2371
2995
|
}
|
|
2372
2996
|
if (error instanceof z.ZodError) {
|
|
2997
|
+
const firstIssue = error.issues[0];
|
|
2998
|
+
const firstPath = firstIssue?.path?.join(".") || "arguments";
|
|
2373
2999
|
return {
|
|
2374
3000
|
ok: false,
|
|
3001
|
+
error_code: "INVALID_ARGUMENTS",
|
|
2375
3002
|
message: "Invalid arguments",
|
|
3003
|
+
fix_hint: `Fix field "${firstPath}" and retry with schema-compliant values.`,
|
|
3004
|
+
next_page_token: null,
|
|
2376
3005
|
issues: error.issues
|
|
2377
3006
|
};
|
|
2378
3007
|
}
|
|
2379
3008
|
if (error instanceof Error) {
|
|
2380
3009
|
return {
|
|
2381
3010
|
ok: false,
|
|
2382
|
-
|
|
3011
|
+
error_code: "INTERNAL_ERROR",
|
|
3012
|
+
message: error.message,
|
|
3013
|
+
fix_hint: "Retry the request. If it persists, report query_id and input payload.",
|
|
3014
|
+
next_page_token: null
|
|
2383
3015
|
};
|
|
2384
3016
|
}
|
|
2385
3017
|
return {
|
|
2386
3018
|
ok: false,
|
|
3019
|
+
error_code: "UNKNOWN_ERROR",
|
|
2387
3020
|
message: "Unknown error",
|
|
3021
|
+
fix_hint: "Retry the request with explicit app_key/select_columns and deterministic page parameters.",
|
|
3022
|
+
next_page_token: null,
|
|
2388
3023
|
details: error
|
|
2389
3024
|
};
|
|
2390
3025
|
}
|