qingflow-mcp 0.3.8 → 0.3.10

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.
Files changed (3) hide show
  1. package/README.md +16 -12
  2. package/dist/server.js +421 -199
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -161,17 +161,20 @@ Summary mode output:
161
161
 
162
162
  1. `summary`: aggregated stats (`total_count`, `total_amount`, `by_day`, `missing_count`).
163
163
  2. `rows`: strict column rows (only requested `select_columns`).
164
- 3. `meta`: field mapping, filter scope, stat policy, execution limits.
164
+ 3. `meta`: field mapping, filter scope, stat policy, execution limits (`output_profile=verbose` only).
165
165
 
166
166
  Return shape:
167
167
 
168
- 1. success: structured payload `{ "ok": true, "data": ..., "meta": ... }`
168
+ 1. success: structured payload `{ "ok": true, "data": ... }` (`meta` only in `output_profile=verbose`)
169
169
  2. failure: MCP `isError=true`, and text content is JSON payload like `{ "ok": false, "message": ..., ... }`
170
170
  3. incomplete strict queries fail with `{ "code": "NEED_MORE_DATA", "status": "need_more_data", ... }`
171
171
 
172
172
  Deterministic read protocol (list/summary/aggregate):
173
173
 
174
- 1. `completeness` is always returned:
174
+ 1. output profile:
175
+ - default `output_profile=compact`: return core data only (`rows/row/groups/summary` + `next_page_token`)
176
+ - `output_profile=verbose`: include full contract (`completeness` + `evidence` + `meta`)
177
+ 2. when `output_profile=verbose`, `completeness` fields are:
175
178
  - `result_amount`
176
179
  - `returned_items`
177
180
  - `fetched_pages`
@@ -183,15 +186,14 @@ Deterministic read protocol (list/summary/aggregate):
183
186
  - `partial`
184
187
  - `omitted_items`
185
188
  - `omitted_chars`
186
- 2. `evidence` is always returned:
189
+ 3. when `output_profile=verbose`, `evidence` fields are:
187
190
  - `query_id`
188
191
  - `app_key`
189
192
  - `filters`
190
193
  - `selected_columns`
191
194
  - `time_range`
192
195
  - `source_pages`
193
- 3. `strict_full=true` makes incomplete results fail fast with `NEED_MORE_DATA`.
194
- 4. Success payloads also expose top-level `error_code=null`, `fix_hint=null`, `next_page_token`.
196
+ 4. `strict_full=true` makes incomplete results fail fast with `NEED_MORE_DATA`.
195
197
  5. Error payloads expose `error_code` and `fix_hint` for actionable retries.
196
198
  6. Parameter tolerance supports stringified JSON and numeric/boolean strings for key query fields.
197
199
 
@@ -201,17 +203,17 @@ Strict mode (`qf_records_list`):
201
203
 
202
204
  1. `select_columns` is required.
203
205
  2. `include_answers=false` is not allowed.
204
- 3. Output `items[].answers` contains only selected columns, not full answers.
206
+ 3. Output is flat `rows[]` (no raw `answers` payload).
205
207
 
206
208
  1. For `qf_records_list.sort[].que_id`, use a real field `que_id` (numeric) or exact field title from `qf_form_get`.
207
209
  2. Avoid aliases like `create_time`; Qingflow often rejects them.
208
210
  3. Use `max_rows` (or `max_items`) to cap returned rows. Default row cap is 200.
209
- 4. Use `max_columns` to cap answers per row when `include_answers=true`.
211
+ 4. Use `max_columns` to cap returned columns per row.
210
212
  5. Use `select_columns` to return only specific columns (supports `que_id` or exact field title).
211
213
  6. The server may still trim by response-size guardrail (`QINGFLOW_LIST_MAX_ITEMS_BYTES`) when payload is too large.
212
214
  7. Use `requested_pages` and `scan_max_pages` for deterministic page scan.
213
215
  8. Continue with `page_token` from previous `next_page_token`.
214
- 9. Column limits: `select_columns <= 10`, `max_columns <= 10`.
216
+ 9. Column limits: `select_columns <= 3`, `max_columns <= 3`.
215
217
 
216
218
  Example:
217
219
 
@@ -224,8 +226,9 @@ Example:
224
226
  "scan_max_pages": 1,
225
227
  "include_answers": true,
226
228
  "max_rows": 10,
227
- "max_columns": 5,
229
+ "max_columns": 3,
228
230
  "select_columns": [1, "客户名称", "1003"],
231
+ "output_profile": "compact",
229
232
  "strict_full": false
230
233
  }
231
234
  ```
@@ -235,8 +238,9 @@ For single record details (`qf_record_get`), the same column controls are suppor
235
238
  ```json
236
239
  {
237
240
  "apply_id": "497600278750478338",
238
- "max_columns": 5,
239
- "select_columns": [1, "客户名称"]
241
+ "max_columns": 3,
242
+ "select_columns": [1, "客户名称"],
243
+ "output_profile": "compact"
240
244
  }
241
245
  ```
242
246
 
package/dist/server.js CHANGED
@@ -46,10 +46,12 @@ const formCache = new Map();
46
46
  const DEFAULT_PAGE_SIZE = 50;
47
47
  const DEFAULT_SCAN_MAX_PAGES = 10;
48
48
  const DEFAULT_ROW_LIMIT = 200;
49
+ const MAX_COLUMN_LIMIT = 3;
50
+ const DEFAULT_OUTPUT_PROFILE = "compact";
49
51
  const MAX_LIST_ITEMS_BYTES = toPositiveInt(process.env.QINGFLOW_LIST_MAX_ITEMS_BYTES) ?? 400000;
50
52
  const REQUEST_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_REQUEST_TIMEOUT_MS) ?? 18000;
51
53
  const EXECUTION_BUDGET_MS = toPositiveInt(process.env.QINGFLOW_EXECUTION_BUDGET_MS) ?? 20000;
52
- const SERVER_VERSION = "0.3.8";
54
+ const SERVER_VERSION = "0.3.10";
53
55
  const accessToken = process.env.QINGFLOW_ACCESS_TOKEN;
54
56
  const baseUrl = process.env.QINGFLOW_BASE_URL;
55
57
  if (!accessToken) {
@@ -119,6 +121,7 @@ const apiMetaSchema = z.object({
119
121
  provider_err_msg: z.string().nullable(),
120
122
  base_url: z.string()
121
123
  });
124
+ const outputProfileSchema = z.enum(["compact", "verbose"]);
122
125
  const completenessSchema = z.object({
123
126
  result_amount: z.number().int().nonnegative(),
124
127
  returned_items: z.number().int().nonnegative(),
@@ -148,11 +151,12 @@ const evidenceSchema = z.object({
148
151
  source_pages: z.array(z.number().int().positive())
149
152
  });
150
153
  const queryContractFields = {
151
- completeness: completenessSchema,
152
- evidence: z.record(z.unknown()),
153
- error_code: z.null(),
154
- fix_hint: z.null(),
155
- next_page_token: z.string().nullable()
154
+ output_profile: outputProfileSchema.optional(),
155
+ completeness: completenessSchema.optional(),
156
+ evidence: z.record(z.unknown()).optional(),
157
+ error_code: z.null().optional(),
158
+ fix_hint: z.null().optional(),
159
+ next_page_token: z.string().nullable().optional()
156
160
  };
157
161
  const appSchema = z.object({
158
162
  appKey: z.string(),
@@ -165,14 +169,6 @@ const fieldSummarySchema = z.object({
165
169
  has_sub_fields: z.boolean(),
166
170
  sub_field_count: z.number().int().nonnegative()
167
171
  });
168
- const recordItemSchema = z.object({
169
- apply_id: z.union([z.string(), z.number(), z.null()]),
170
- app_key: z.string().nullable(),
171
- apply_num: z.union([z.number(), z.string(), z.null()]),
172
- apply_time: z.string().nullable(),
173
- last_update_time: z.string().nullable(),
174
- answers: z.array(z.unknown()).optional()
175
- });
176
172
  const operationResultSchema = z.object({
177
173
  request_id: z.string(),
178
174
  operation_result: z.unknown()
@@ -293,15 +289,16 @@ const listInputSchema = z
293
289
  .optional(),
294
290
  max_rows: z.number().int().positive().max(200).optional(),
295
291
  max_items: z.number().int().positive().max(200).optional(),
296
- max_columns: z.number().int().positive().max(10).optional(),
292
+ max_columns: z.number().int().positive().max(MAX_COLUMN_LIMIT).optional(),
297
293
  // Strict mode: callers must explicitly choose columns.
298
294
  select_columns: z
299
295
  .array(z.union([z.string().min(1), z.number().int()]))
300
296
  .min(1)
301
- .max(10)
297
+ .max(MAX_COLUMN_LIMIT)
302
298
  .optional(),
303
299
  include_answers: z.boolean().optional(),
304
- strict_full: z.boolean().optional()
300
+ strict_full: z.boolean().optional(),
301
+ output_profile: outputProfileSchema.optional()
305
302
  }))
306
303
  .refine((value) => value.include_answers !== false, {
307
304
  message: "include_answers=false is not allowed in strict column mode"
@@ -319,7 +316,7 @@ const listSuccessOutputSchema = z.object({
319
316
  page_amount: z.number().int().nonnegative().nullable(),
320
317
  result_amount: z.number().int().nonnegative()
321
318
  }),
322
- items: z.array(recordItemSchema),
319
+ rows: z.array(z.record(z.unknown())),
323
320
  applied_limits: z
324
321
  .object({
325
322
  include_answers: z.boolean(),
@@ -328,43 +325,43 @@ const listSuccessOutputSchema = z.object({
328
325
  selected_columns: z.array(z.string())
329
326
  })
330
327
  .optional(),
331
- completeness: completenessSchema,
332
- evidence: evidenceSchema
328
+ completeness: completenessSchema.optional(),
329
+ evidence: evidenceSchema.optional()
333
330
  }),
334
331
  ...queryContractFields,
335
- meta: apiMetaSchema
332
+ meta: apiMetaSchema.optional()
336
333
  });
337
334
  const listOutputSchema = listSuccessOutputSchema;
338
335
  const recordGetInputSchema = z.preprocess(normalizeRecordGetInput, z.object({
339
336
  apply_id: z.union([z.string().min(1), z.number().int()]),
340
- max_columns: z.number().int().positive().max(10).optional(),
337
+ max_columns: z.number().int().positive().max(MAX_COLUMN_LIMIT).optional(),
341
338
  select_columns: z
342
339
  .array(z.union([z.string().min(1), z.number().int()]))
343
340
  .min(1)
344
- .max(10)
345
- .optional()
341
+ .max(MAX_COLUMN_LIMIT)
342
+ .optional(),
343
+ output_profile: outputProfileSchema.optional()
346
344
  }));
347
345
  const recordGetSuccessOutputSchema = z.object({
348
346
  ok: z.literal(true),
349
347
  data: z.object({
350
348
  apply_id: z.union([z.string(), z.number(), z.null()]),
351
- answer_count: z.number().int().nonnegative(),
352
- record: z.unknown(),
349
+ row: z.record(z.unknown()),
353
350
  applied_limits: z
354
351
  .object({
355
352
  column_cap: z.number().int().positive().nullable(),
356
353
  selected_columns: z.array(z.string()).nullable()
357
354
  })
358
355
  .optional(),
359
- completeness: completenessSchema,
356
+ completeness: completenessSchema.optional(),
360
357
  evidence: z.object({
361
358
  query_id: z.string(),
362
359
  apply_id: z.string(),
363
360
  selected_columns: z.array(z.string())
364
- })
361
+ }).optional()
365
362
  }),
366
363
  ...queryContractFields,
367
- meta: apiMetaSchema
364
+ meta: apiMetaSchema.optional()
368
365
  });
369
366
  const recordGetOutputSchema = recordGetSuccessOutputSchema;
370
367
  const createInputSchema = z
@@ -476,11 +473,11 @@ const queryInputSchema = z
476
473
  .optional(),
477
474
  max_rows: z.number().int().positive().max(200).optional(),
478
475
  max_items: z.number().int().positive().max(200).optional(),
479
- max_columns: z.number().int().positive().max(10).optional(),
476
+ max_columns: z.number().int().positive().max(MAX_COLUMN_LIMIT).optional(),
480
477
  select_columns: z
481
478
  .array(z.union([z.string().min(1), z.number().int()]))
482
479
  .min(1)
483
- .max(10)
480
+ .max(MAX_COLUMN_LIMIT)
484
481
  .optional(),
485
482
  include_answers: z.boolean().optional(),
486
483
  amount_column: z.union([z.string().min(1), z.number().int()]).optional(),
@@ -499,7 +496,8 @@ const queryInputSchema = z
499
496
  })
500
497
  .optional(),
501
498
  scan_max_pages: z.number().int().positive().max(500).optional(),
502
- strict_full: z.boolean().optional()
499
+ strict_full: z.boolean().optional(),
500
+ output_profile: outputProfileSchema.optional()
503
501
  }))
504
502
  .refine((value) => !(value.page_num !== undefined && value.page_token !== undefined), {
505
503
  message: "page_num and page_token cannot be used together"
@@ -516,8 +514,8 @@ const querySummaryOutputSchema = z.object({
516
514
  missing_count: z.number().int().nonnegative()
517
515
  }),
518
516
  rows: z.array(z.record(z.unknown())),
519
- completeness: completenessSchema,
520
- evidence: evidenceSchema,
517
+ completeness: completenessSchema.optional(),
518
+ evidence: evidenceSchema.optional(),
521
519
  meta: z.object({
522
520
  field_mapping: z.array(z.object({
523
521
  role: z.enum(["row", "amount", "time"]),
@@ -549,7 +547,7 @@ const querySummaryOutputSchema = z.object({
549
547
  column_cap: z.number().int().positive().nullable(),
550
548
  scan_max_pages: z.number().int().positive()
551
549
  })
552
- })
550
+ }).optional()
553
551
  });
554
552
  const querySuccessOutputSchema = z.object({
555
553
  ok: z.literal(true),
@@ -561,7 +559,7 @@ const querySuccessOutputSchema = z.object({
561
559
  summary: querySummaryOutputSchema.optional()
562
560
  }),
563
561
  ...queryContractFields,
564
- meta: apiMetaSchema
562
+ meta: apiMetaSchema.optional()
565
563
  });
566
564
  const queryOutputSchema = querySuccessOutputSchema;
567
565
  const aggregateInputSchema = z
@@ -628,7 +626,8 @@ const aggregateInputSchema = z
628
626
  })
629
627
  .optional(),
630
628
  max_groups: z.number().int().positive().max(2000).optional(),
631
- strict_full: z.boolean().optional()
629
+ strict_full: z.boolean().optional(),
630
+ output_profile: outputProfileSchema.optional()
632
631
  }))
633
632
  .refine((value) => !(value.page_num !== undefined && value.page_token !== undefined), {
634
633
  message: "page_num and page_token cannot be used together"
@@ -648,8 +647,8 @@ const aggregateOutputSchema = z.object({
648
647
  amount_total: z.number().nullable(),
649
648
  amount_ratio: z.number().nullable()
650
649
  })),
651
- completeness: completenessSchema,
652
- evidence: evidenceSchema,
650
+ completeness: completenessSchema.optional(),
651
+ evidence: evidenceSchema.optional(),
653
652
  meta: z.object({
654
653
  field_mapping: z.array(z.object({
655
654
  role: z.enum(["group_by", "amount", "time"]),
@@ -662,10 +661,10 @@ const aggregateOutputSchema = z.object({
662
661
  include_negative: z.boolean(),
663
662
  include_null: z.boolean()
664
663
  })
665
- })
664
+ }).optional()
666
665
  }),
667
666
  ...queryContractFields,
668
- meta: apiMetaSchema
667
+ meta: apiMetaSchema.optional()
669
668
  });
670
669
  server.registerTool("qf_tool_spec_get", {
671
670
  title: "Qingflow Tool Spec Get",
@@ -841,8 +840,8 @@ server.registerTool("qf_query", {
841
840
  if (routedMode === "record") {
842
841
  const recordArgs = buildRecordGetArgsFromQuery(args);
843
842
  const executed = await executeRecordGet(recordArgs);
844
- const completeness = executed.payload.completeness;
845
- const evidence = executed.payload.evidence;
843
+ const completeness = executed.completeness;
844
+ const evidence = executed.evidence;
846
845
  return okResult({
847
846
  ok: true,
848
847
  data: {
@@ -850,18 +849,27 @@ server.registerTool("qf_query", {
850
849
  source_tool: "qf_record_get",
851
850
  record: executed.payload.data
852
851
  },
853
- completeness,
854
- evidence,
855
- error_code: null,
856
- fix_hint: null,
852
+ output_profile: executed.outputProfile,
853
+ ...(isVerboseProfile(executed.outputProfile)
854
+ ? {
855
+ completeness,
856
+ evidence,
857
+ error_code: null,
858
+ fix_hint: null
859
+ }
860
+ : {}),
857
861
  next_page_token: completeness.next_page_token,
858
- meta: executed.payload.meta
862
+ ...(isVerboseProfile(executed.outputProfile)
863
+ ? {
864
+ meta: executed.payload.meta
865
+ }
866
+ : {})
859
867
  }, executed.message);
860
868
  }
861
869
  if (routedMode === "summary") {
862
870
  const executed = await executeRecordsSummary(args);
863
- const completeness = executed.data.completeness;
864
- const evidence = executed.data.evidence;
871
+ const completeness = executed.completeness;
872
+ const evidence = executed.evidence;
865
873
  return okResult({
866
874
  ok: true,
867
875
  data: {
@@ -869,18 +877,27 @@ server.registerTool("qf_query", {
869
877
  source_tool: "qf_records_summary",
870
878
  summary: executed.data
871
879
  },
872
- completeness,
873
- evidence,
874
- error_code: null,
875
- fix_hint: null,
880
+ output_profile: executed.outputProfile,
881
+ ...(isVerboseProfile(executed.outputProfile)
882
+ ? {
883
+ completeness,
884
+ evidence,
885
+ error_code: null,
886
+ fix_hint: null
887
+ }
888
+ : {}),
876
889
  next_page_token: completeness.next_page_token,
877
- meta: executed.meta
890
+ ...(isVerboseProfile(executed.outputProfile)
891
+ ? {
892
+ meta: executed.meta
893
+ }
894
+ : {})
878
895
  }, executed.message);
879
896
  }
880
897
  const listArgs = buildListArgsFromQuery(args);
881
898
  const executed = await executeRecordsList(listArgs);
882
- const completeness = executed.payload.completeness;
883
- const evidence = executed.payload.evidence;
899
+ const completeness = executed.completeness;
900
+ const evidence = executed.evidence;
884
901
  return okResult({
885
902
  ok: true,
886
903
  data: {
@@ -888,12 +905,21 @@ server.registerTool("qf_query", {
888
905
  source_tool: "qf_records_list",
889
906
  list: executed.payload.data
890
907
  },
891
- completeness,
892
- evidence,
893
- error_code: null,
894
- fix_hint: null,
908
+ output_profile: executed.outputProfile,
909
+ ...(isVerboseProfile(executed.outputProfile)
910
+ ? {
911
+ completeness,
912
+ evidence,
913
+ error_code: null,
914
+ fix_hint: null
915
+ }
916
+ : {}),
895
917
  next_page_token: completeness.next_page_token,
896
- meta: executed.payload.meta
918
+ ...(isVerboseProfile(executed.outputProfile)
919
+ ? {
920
+ meta: executed.payload.meta
921
+ }
922
+ : {})
897
923
  }, executed.message);
898
924
  }
899
925
  catch (error) {
@@ -1288,6 +1314,12 @@ function buildMeta(response) {
1288
1314
  base_url: baseUrl
1289
1315
  };
1290
1316
  }
1317
+ function resolveOutputProfile(value) {
1318
+ return value === "verbose" ? "verbose" : DEFAULT_OUTPUT_PROFILE;
1319
+ }
1320
+ function isVerboseProfile(profile) {
1321
+ return profile === "verbose";
1322
+ }
1291
1323
  function missingRequiredFieldError(params) {
1292
1324
  return new InputValidationError({
1293
1325
  message: `Missing required field "${params.field}" for ${params.tool}`,
@@ -1335,6 +1367,9 @@ const COMMON_INPUT_ALIASES = {
1335
1367
  statPolicy: "stat_policy",
1336
1368
  groupBy: "group_by",
1337
1369
  strictFull: "strict_full",
1370
+ outputProfile: "output_profile",
1371
+ responseProfile: "output_profile",
1372
+ profile: "output_profile",
1338
1373
  forceRefresh: "force_refresh",
1339
1374
  forceRefreshForm: "force_refresh_form",
1340
1375
  applyUser: "apply_user"
@@ -1416,8 +1451,9 @@ function buildToolSpecCatalog() {
1416
1451
  scan_max_pages_max: 500,
1417
1452
  max_rows_max: 200,
1418
1453
  max_items_max: 200,
1419
- max_columns_max: 10,
1420
- select_columns_max: 10
1454
+ max_columns_max: MAX_COLUMN_LIMIT,
1455
+ select_columns_max: MAX_COLUMN_LIMIT,
1456
+ output_profile: "compact|verbose (default compact)"
1421
1457
  },
1422
1458
  aliases: collectAliasHints([
1423
1459
  "app_key",
@@ -1431,35 +1467,41 @@ function buildToolSpecCatalog() {
1431
1467
  "max_items",
1432
1468
  "max_columns",
1433
1469
  "select_columns",
1470
+ "output_profile",
1434
1471
  "strict_full",
1435
1472
  "include_answers",
1436
1473
  "time_range"
1437
1474
  ], {
1438
1475
  select_columns: ["columns", "selected_columns", "selectedColumns"],
1439
- max_rows: ["limit", "row_limit", "rowLimit"]
1476
+ max_rows: ["limit", "row_limit", "rowLimit"],
1477
+ output_profile: ["outputProfile", "responseProfile", "profile"]
1440
1478
  }),
1441
1479
  minimal_example: {
1442
1480
  app_key: "21b3d559",
1443
1481
  mode: "all",
1444
1482
  page_size: 50,
1445
1483
  max_rows: 20,
1446
- select_columns: [0, "客户名称", "报价总金额"]
1484
+ select_columns: [0, "客户名称", "报价总金额"],
1485
+ output_profile: "compact"
1447
1486
  }
1448
1487
  },
1449
1488
  {
1450
1489
  tool: "qf_record_get",
1451
1490
  required: ["apply_id", "select_columns"],
1452
1491
  limits: {
1453
- max_columns_max: 10,
1454
- select_columns_max: 10
1492
+ max_columns_max: MAX_COLUMN_LIMIT,
1493
+ select_columns_max: MAX_COLUMN_LIMIT,
1494
+ output_profile: "compact|verbose (default compact)"
1455
1495
  },
1456
- aliases: collectAliasHints(["apply_id", "max_columns", "select_columns"], {
1457
- select_columns: ["columns", "selected_columns", "selectedColumns"]
1496
+ aliases: collectAliasHints(["apply_id", "max_columns", "select_columns", "output_profile"], {
1497
+ select_columns: ["columns", "selected_columns", "selectedColumns"],
1498
+ output_profile: ["outputProfile", "responseProfile", "profile"]
1458
1499
  }),
1459
1500
  minimal_example: {
1460
1501
  apply_id: "497600278750478338",
1461
1502
  select_columns: [0, "客户名称"],
1462
- max_columns: 5
1503
+ max_columns: 3,
1504
+ output_profile: "compact"
1463
1505
  }
1464
1506
  },
1465
1507
  {
@@ -1476,8 +1518,9 @@ function buildToolSpecCatalog() {
1476
1518
  scan_max_pages_max: 500,
1477
1519
  max_rows_max: 200,
1478
1520
  max_items_max: 200,
1479
- max_columns_max: 10,
1480
- select_columns_max: 10
1521
+ max_columns_max: MAX_COLUMN_LIMIT,
1522
+ select_columns_max: MAX_COLUMN_LIMIT,
1523
+ output_profile: "compact|verbose (default compact)"
1481
1524
  },
1482
1525
  aliases: collectAliasHints([
1483
1526
  "query_mode",
@@ -1492,6 +1535,7 @@ function buildToolSpecCatalog() {
1492
1535
  "max_items",
1493
1536
  "max_columns",
1494
1537
  "select_columns",
1538
+ "output_profile",
1495
1539
  "amount_column",
1496
1540
  "time_range",
1497
1541
  "stat_policy",
@@ -1500,7 +1544,8 @@ function buildToolSpecCatalog() {
1500
1544
  select_columns: ["columns", "selected_columns", "selectedColumns"],
1501
1545
  max_rows: ["limit", "row_limit", "rowLimit"],
1502
1546
  amount_column: ["amount_que_ids", "amountQueIds", "amountColumns"],
1503
- time_range: ["date_field + date_from + date_to"]
1547
+ time_range: ["date_field + date_from + date_to"],
1548
+ output_profile: ["outputProfile", "responseProfile", "profile"]
1504
1549
  }),
1505
1550
  minimal_example: {
1506
1551
  query_mode: "list",
@@ -1509,6 +1554,7 @@ function buildToolSpecCatalog() {
1509
1554
  page_size: 50,
1510
1555
  max_rows: 20,
1511
1556
  select_columns: [0, "客户名称", "报价总金额"],
1557
+ output_profile: "compact",
1512
1558
  time_range: {
1513
1559
  column: 6299264,
1514
1560
  from: "2026-03-05",
@@ -1524,7 +1570,8 @@ function buildToolSpecCatalog() {
1524
1570
  requested_pages_max: 500,
1525
1571
  scan_max_pages_max: 500,
1526
1572
  max_groups_max: 2000,
1527
- group_by_max: 20
1573
+ group_by_max: 20,
1574
+ output_profile: "compact|verbose (default compact)"
1528
1575
  },
1529
1576
  aliases: collectAliasHints([
1530
1577
  "app_key",
@@ -1535,18 +1582,21 @@ function buildToolSpecCatalog() {
1535
1582
  "page_size",
1536
1583
  "requested_pages",
1537
1584
  "scan_max_pages",
1585
+ "output_profile",
1538
1586
  "time_range",
1539
1587
  "stat_policy",
1540
1588
  "strict_full"
1541
1589
  ], {
1542
1590
  amount_column: ["amount_que_ids", "amountQueIds", "amountColumns"],
1543
- time_range: ["date_field + date_from + date_to"]
1591
+ time_range: ["date_field + date_from + date_to"],
1592
+ output_profile: ["outputProfile", "responseProfile", "profile"]
1544
1593
  }),
1545
1594
  minimal_example: {
1546
1595
  app_key: "21b3d559",
1547
1596
  mode: "all",
1548
1597
  group_by: [9500572],
1549
1598
  amount_column: 6299263,
1599
+ output_profile: "compact",
1550
1600
  time_range: {
1551
1601
  column: 6299264,
1552
1602
  from: "2026-03-05",
@@ -1647,6 +1697,7 @@ function normalizeListInput(raw) {
1647
1697
  max_columns: coerceNumberLike(normalizedObj.max_columns),
1648
1698
  strict_full: coerceBooleanLike(normalizedObj.strict_full),
1649
1699
  include_answers: coerceBooleanLike(normalizedObj.include_answers),
1700
+ output_profile: normalizeOutputProfileInput(normalizedObj.output_profile),
1650
1701
  apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
1651
1702
  sort: normalizeSortInput(normalizedObj.sort),
1652
1703
  filters: normalizeFiltersInput(normalizedObj.filters),
@@ -1666,7 +1717,8 @@ function normalizeRecordGetInput(raw) {
1666
1717
  ...normalizedObj,
1667
1718
  apply_id: coerceNumberLike(normalizedObj.apply_id),
1668
1719
  max_columns: coerceNumberLike(normalizedObj.max_columns),
1669
- select_columns: normalizeSelectorListInput(selectColumns)
1720
+ select_columns: normalizeSelectorListInput(selectColumns),
1721
+ output_profile: normalizeOutputProfileInput(normalizedObj.output_profile)
1670
1722
  };
1671
1723
  }
1672
1724
  function normalizeQueryInput(raw) {
@@ -1691,6 +1743,7 @@ function normalizeQueryInput(raw) {
1691
1743
  apply_id: coerceNumberLike(normalizedObj.apply_id),
1692
1744
  strict_full: coerceBooleanLike(normalizedObj.strict_full),
1693
1745
  include_answers: coerceBooleanLike(normalizedObj.include_answers),
1746
+ output_profile: normalizeOutputProfileInput(normalizedObj.output_profile),
1694
1747
  amount_column: normalizeAmountColumnInput(normalizedObj.amount_column),
1695
1748
  apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
1696
1749
  sort: normalizeSortInput(normalizedObj.sort),
@@ -1717,6 +1770,7 @@ function normalizeAggregateInput(raw) {
1717
1770
  type: coerceNumberLike(normalizedObj.type),
1718
1771
  max_groups: coerceNumberLike(normalizedObj.max_groups),
1719
1772
  strict_full: coerceBooleanLike(normalizedObj.strict_full),
1773
+ output_profile: normalizeOutputProfileInput(normalizedObj.output_profile),
1720
1774
  group_by: normalizeSelectorListInput(normalizedObj.group_by),
1721
1775
  amount_column: normalizeAmountColumnInput(normalizedObj.amount_column),
1722
1776
  apply_ids: normalizeIdArrayInput(normalizedObj.apply_ids),
@@ -1758,6 +1812,23 @@ function coerceBooleanLike(value) {
1758
1812
  }
1759
1813
  return parsed;
1760
1814
  }
1815
+ function normalizeOutputProfileInput(value) {
1816
+ const parsed = parseJsonLikeDeep(value);
1817
+ if (typeof parsed !== "string") {
1818
+ return parsed;
1819
+ }
1820
+ const normalized = parsed.trim().toLowerCase();
1821
+ if (!normalized) {
1822
+ return parsed;
1823
+ }
1824
+ if (normalized === "compact" || normalized === "lite" || normalized === "minimal") {
1825
+ return "compact";
1826
+ }
1827
+ if (normalized === "verbose" || normalized === "full" || normalized === "debug") {
1828
+ return "verbose";
1829
+ }
1830
+ return parsed;
1831
+ }
1761
1832
  function parseJsonLikeDeep(value, maxDepth = 4) {
1762
1833
  let current = value;
1763
1834
  for (let i = 0; i < maxDepth; i += 1) {
@@ -2198,7 +2269,7 @@ function buildListArgsFromQuery(args) {
2198
2269
  throw missingRequiredFieldError({
2199
2270
  field: "select_columns",
2200
2271
  tool: "qf_query(list)",
2201
- fixHint: "Provide select_columns as an array (<=10), for example: {\"select_columns\":[0,\"客户全称\",\"报价总金额\"]}"
2272
+ fixHint: "Provide select_columns as an array (<=3), for example: {\"select_columns\":[0,\"客户全称\",\"报价总金额\"]}"
2202
2273
  });
2203
2274
  }
2204
2275
  const filters = buildListFiltersFromQuery(args);
@@ -2223,7 +2294,8 @@ function buildListArgsFromQuery(args) {
2223
2294
  max_columns: args.max_columns,
2224
2295
  select_columns: args.select_columns,
2225
2296
  include_answers: args.include_answers,
2226
- strict_full: args.strict_full
2297
+ strict_full: args.strict_full,
2298
+ output_profile: args.output_profile
2227
2299
  });
2228
2300
  }
2229
2301
  function buildListFiltersFromQuery(args) {
@@ -2392,13 +2464,14 @@ function buildRecordGetArgsFromQuery(args) {
2392
2464
  throw missingRequiredFieldError({
2393
2465
  field: "select_columns",
2394
2466
  tool: "qf_query(record)",
2395
- fixHint: "Provide select_columns as an array (<=10), for example: {\"select_columns\":[0,\"客户全称\"]}"
2467
+ fixHint: "Provide select_columns as an array (<=3), for example: {\"select_columns\":[0,\"客户全称\"]}"
2396
2468
  });
2397
2469
  }
2398
2470
  return recordGetInputSchema.parse({
2399
2471
  apply_id: args.apply_id,
2400
2472
  max_columns: args.max_columns,
2401
- select_columns: args.select_columns
2473
+ select_columns: args.select_columns,
2474
+ output_profile: args.output_profile
2402
2475
  });
2403
2476
  }
2404
2477
  async function executeRecordsList(args) {
@@ -2413,9 +2486,10 @@ async function executeRecordsList(args) {
2413
2486
  throw missingRequiredFieldError({
2414
2487
  field: "select_columns",
2415
2488
  tool: "qf_records_list",
2416
- fixHint: "Provide select_columns as an array (<=10), for example: {\"select_columns\":[0,\"客户全称\",\"报价总金额\"]}"
2489
+ fixHint: "Provide select_columns as an array (<=3), for example: {\"select_columns\":[0,\"客户全称\",\"报价总金额\"]}"
2417
2490
  });
2418
2491
  }
2492
+ const outputProfile = resolveOutputProfile(args.output_profile);
2419
2493
  const queryId = randomUUID();
2420
2494
  const pageNum = resolveStartPage(args.page_num, args.page_token, args.app_key);
2421
2495
  const pageSize = args.page_size ?? DEFAULT_PAGE_SIZE;
@@ -2485,6 +2559,7 @@ async function executeRecordsList(args) {
2485
2559
  const items = collectedRawItems
2486
2560
  .slice(0, listLimit.limit)
2487
2561
  .map((raw) => normalizeRecordItem(raw, includeAnswers));
2562
+ const sourceItemsForRows = items.slice();
2488
2563
  const columnProjection = projectRecordItemsColumns({
2489
2564
  items,
2490
2565
  includeAnswers,
@@ -2503,16 +2578,23 @@ async function executeRecordsList(args) {
2503
2578
  }
2504
2579
  });
2505
2580
  }
2506
- const fitted = fitListItemsWithinSize({
2507
- items: columnProjection.items,
2581
+ const selectedColumnsForRows = args.max_columns !== undefined
2582
+ ? columnProjection.selectedColumns.slice(0, args.max_columns)
2583
+ : columnProjection.selectedColumns;
2584
+ const rows = buildFlatRowsFromItems({
2585
+ items: sourceItemsForRows,
2586
+ selectedColumns: selectedColumnsForRows
2587
+ });
2588
+ const fittedRows = fitListItemsWithinSize({
2589
+ items: rows,
2508
2590
  limitBytes: MAX_LIST_ITEMS_BYTES
2509
2591
  });
2510
- const truncationReason = mergeTruncationReasons(listLimit.reason, columnProjection.reason, fitted.reason);
2511
- const omittedItems = Math.max(0, knownResultAmount - fitted.items.length);
2592
+ const truncationReason = mergeTruncationReasons(listLimit.reason, columnProjection.reason, fittedRows.reason);
2593
+ const omittedItems = Math.max(0, knownResultAmount - fittedRows.items.length);
2512
2594
  const isComplete = !hasMore &&
2513
2595
  omittedItems === 0 &&
2514
- fitted.omittedItems === 0 &&
2515
- fitted.omittedChars === 0;
2596
+ fittedRows.omittedItems === 0 &&
2597
+ fittedRows.omittedChars === 0;
2516
2598
  const nextPageToken = hasMore && nextPageNum
2517
2599
  ? encodeContinuationToken({
2518
2600
  app_key: args.app_key,
@@ -2522,7 +2604,7 @@ async function executeRecordsList(args) {
2522
2604
  : null;
2523
2605
  const completeness = {
2524
2606
  result_amount: knownResultAmount,
2525
- returned_items: fitted.items.length,
2607
+ returned_items: fittedRows.items.length,
2526
2608
  fetched_pages: fetchedPages,
2527
2609
  requested_pages: requestedPages,
2528
2610
  actual_scanned_pages: fetchedPages,
@@ -2531,7 +2613,7 @@ async function executeRecordsList(args) {
2531
2613
  is_complete: isComplete,
2532
2614
  partial: !isComplete,
2533
2615
  omitted_items: omittedItems,
2534
- omitted_chars: fitted.omittedChars
2616
+ omitted_chars: fittedRows.omittedChars
2535
2617
  };
2536
2618
  const listState = {
2537
2619
  query_id: queryId,
@@ -2565,30 +2647,46 @@ async function executeRecordsList(args) {
2565
2647
  page_amount: pageAmount,
2566
2648
  result_amount: knownResultAmount
2567
2649
  },
2568
- items: fitted.items,
2650
+ rows: fittedRows.items,
2569
2651
  applied_limits: {
2570
2652
  include_answers: includeAnswers,
2571
2653
  row_cap: listLimit.limit,
2572
2654
  column_cap: args.max_columns ?? null,
2573
- selected_columns: columnProjection.selectedColumns
2655
+ selected_columns: selectedColumnsForRows
2574
2656
  },
2575
- completeness,
2576
- evidence
2657
+ ...(isVerboseProfile(outputProfile)
2658
+ ? {
2659
+ completeness,
2660
+ evidence
2661
+ }
2662
+ : {})
2577
2663
  },
2578
- completeness,
2579
- evidence,
2580
- error_code: null,
2581
- fix_hint: null,
2664
+ output_profile: outputProfile,
2665
+ ...(isVerboseProfile(outputProfile)
2666
+ ? {
2667
+ completeness,
2668
+ evidence,
2669
+ error_code: null,
2670
+ fix_hint: null
2671
+ }
2672
+ : {}),
2582
2673
  next_page_token: completeness.next_page_token,
2583
- meta: responseMeta
2674
+ ...(isVerboseProfile(outputProfile)
2675
+ ? {
2676
+ meta: responseMeta
2677
+ }
2678
+ : {})
2584
2679
  };
2585
2680
  return {
2586
2681
  payload: responsePayload,
2587
2682
  message: buildRecordsListMessage({
2588
- returned: fitted.items.length,
2683
+ returned: fittedRows.items.length,
2589
2684
  total: knownResultAmount,
2590
2685
  truncationReason
2591
- })
2686
+ }),
2687
+ completeness,
2688
+ evidence,
2689
+ outputProfile
2592
2690
  };
2593
2691
  }
2594
2692
  async function executeRecordGet(args) {
@@ -2596,9 +2694,10 @@ async function executeRecordGet(args) {
2596
2694
  throw missingRequiredFieldError({
2597
2695
  field: "select_columns",
2598
2696
  tool: "qf_record_get",
2599
- fixHint: "Provide select_columns as an array (<=10), for example: {\"apply_id\":\"...\",\"select_columns\":[0]}"
2697
+ fixHint: "Provide select_columns as an array (<=3), for example: {\"apply_id\":\"...\",\"select_columns\":[0]}"
2600
2698
  });
2601
2699
  }
2700
+ const outputProfile = resolveOutputProfile(args.output_profile);
2602
2701
  const queryId = randomUUID();
2603
2702
  const response = await client.getRecord(String(args.apply_id));
2604
2703
  const record = asObject(response.result) ?? {};
@@ -2607,65 +2706,69 @@ async function executeRecordGet(args) {
2607
2706
  maxColumns: args.max_columns,
2608
2707
  selectColumns: args.select_columns
2609
2708
  });
2610
- const projectedRecord = {
2611
- ...record,
2612
- answers: projection.answers
2709
+ const selectedColumnsForRow = args.max_columns !== undefined
2710
+ ? (projection.selectedColumns ?? []).slice(0, args.max_columns)
2711
+ : projection.selectedColumns ?? [];
2712
+ const row = buildFlatRowFromAnswers({
2713
+ applyId: record.applyId ?? null,
2714
+ answers: asArray(record.answers),
2715
+ selectedColumns: selectedColumnsForRow
2716
+ });
2717
+ const completeness = {
2718
+ result_amount: 1,
2719
+ returned_items: 1,
2720
+ fetched_pages: 1,
2721
+ requested_pages: 1,
2722
+ actual_scanned_pages: 1,
2723
+ has_more: false,
2724
+ next_page_token: null,
2725
+ is_complete: true,
2726
+ partial: false,
2727
+ omitted_items: 0,
2728
+ omitted_chars: 0
2729
+ };
2730
+ const evidence = {
2731
+ query_id: queryId,
2732
+ apply_id: String(args.apply_id),
2733
+ selected_columns: selectedColumnsForRow
2613
2734
  };
2614
- const answerCount = projection.answers.length;
2615
2735
  return {
2616
2736
  payload: {
2617
2737
  ok: true,
2618
2738
  data: {
2619
2739
  apply_id: record.applyId ?? null,
2620
- answer_count: answerCount,
2621
- record: projectedRecord,
2740
+ row,
2622
2741
  applied_limits: {
2623
2742
  column_cap: args.max_columns ?? null,
2624
- selected_columns: projection.selectedColumns
2743
+ selected_columns: selectedColumnsForRow
2625
2744
  },
2626
- completeness: {
2627
- result_amount: 1,
2628
- returned_items: 1,
2629
- fetched_pages: 1,
2630
- requested_pages: 1,
2631
- actual_scanned_pages: 1,
2632
- has_more: false,
2633
- next_page_token: null,
2634
- is_complete: true,
2635
- partial: false,
2636
- omitted_items: 0,
2637
- omitted_chars: 0
2638
- },
2639
- evidence: {
2640
- query_id: queryId,
2641
- apply_id: String(args.apply_id),
2642
- selected_columns: projection.selectedColumns ?? []
2643
- }
2644
- },
2645
- completeness: {
2646
- result_amount: 1,
2647
- returned_items: 1,
2648
- fetched_pages: 1,
2649
- requested_pages: 1,
2650
- actual_scanned_pages: 1,
2651
- has_more: false,
2652
- next_page_token: null,
2653
- is_complete: true,
2654
- partial: false,
2655
- omitted_items: 0,
2656
- omitted_chars: 0
2657
- },
2658
- evidence: {
2659
- query_id: queryId,
2660
- apply_id: String(args.apply_id),
2661
- selected_columns: projection.selectedColumns ?? []
2745
+ ...(isVerboseProfile(outputProfile)
2746
+ ? {
2747
+ completeness,
2748
+ evidence
2749
+ }
2750
+ : {})
2662
2751
  },
2663
- error_code: null,
2664
- fix_hint: null,
2752
+ output_profile: outputProfile,
2753
+ ...(isVerboseProfile(outputProfile)
2754
+ ? {
2755
+ completeness,
2756
+ evidence,
2757
+ error_code: null,
2758
+ fix_hint: null
2759
+ }
2760
+ : {}),
2665
2761
  next_page_token: null,
2666
- meta: buildMeta(response)
2762
+ ...(isVerboseProfile(outputProfile)
2763
+ ? {
2764
+ meta: buildMeta(response)
2765
+ }
2766
+ : {})
2667
2767
  },
2668
- message: `Fetched record ${String(args.apply_id)}`
2768
+ message: `Fetched record ${String(args.apply_id)}`,
2769
+ completeness,
2770
+ evidence,
2771
+ outputProfile
2669
2772
  };
2670
2773
  }
2671
2774
  async function executeRecordsSummary(args) {
@@ -2680,9 +2783,10 @@ async function executeRecordsSummary(args) {
2680
2783
  throw missingRequiredFieldError({
2681
2784
  field: "select_columns",
2682
2785
  tool: "qf_query(summary)",
2683
- fixHint: "Provide select_columns as an array (<=10), for example: {\"select_columns\":[\"客户全称\"]}"
2786
+ fixHint: "Provide select_columns as an array (<=3), for example: {\"select_columns\":[\"客户全称\"]}"
2684
2787
  });
2685
2788
  }
2789
+ const outputProfile = resolveOutputProfile(args.output_profile);
2686
2790
  const queryId = randomUUID();
2687
2791
  const strictFull = args.strict_full ?? true;
2688
2792
  const includeNegative = args.stat_policy?.include_negative ?? true;
@@ -2892,44 +2996,52 @@ async function executeRecordsSummary(args) {
2892
2996
  missing_count: missingCount
2893
2997
  },
2894
2998
  rows,
2895
- completeness,
2896
- evidence,
2897
- meta: {
2898
- field_mapping: fieldMapping,
2899
- filters: {
2900
- app_key: args.app_key,
2901
- time_range: timeColumn
2902
- ? {
2903
- column: timeColumn.requested,
2904
- from: args.time_range?.from ?? null,
2905
- to: args.time_range?.to ?? null,
2906
- timezone
2999
+ ...(isVerboseProfile(outputProfile)
3000
+ ? {
3001
+ completeness,
3002
+ evidence,
3003
+ meta: {
3004
+ field_mapping: fieldMapping,
3005
+ filters: {
3006
+ app_key: args.app_key,
3007
+ time_range: timeColumn
3008
+ ? {
3009
+ column: timeColumn.requested,
3010
+ from: args.time_range?.from ?? null,
3011
+ to: args.time_range?.to ?? null,
3012
+ timezone
3013
+ }
3014
+ : null
3015
+ },
3016
+ stat_policy: {
3017
+ include_negative: includeNegative,
3018
+ include_null: includeNull
3019
+ },
3020
+ execution: {
3021
+ scanned_records: scannedRecords,
3022
+ scanned_pages: scannedPages,
3023
+ truncated: !isComplete,
3024
+ row_cap: rowCap,
3025
+ column_cap: args.max_columns ?? null,
3026
+ scan_max_pages: scanMaxPages
2907
3027
  }
2908
- : null
2909
- },
2910
- stat_policy: {
2911
- include_negative: includeNegative,
2912
- include_null: includeNull
2913
- },
2914
- execution: {
2915
- scanned_records: scannedRecords,
2916
- scanned_pages: scannedPages,
2917
- truncated: !isComplete,
2918
- row_cap: rowCap,
2919
- column_cap: args.max_columns ?? null,
2920
- scan_max_pages: scanMaxPages
3028
+ }
2921
3029
  }
2922
- }
3030
+ : {})
2923
3031
  },
2924
3032
  meta: summaryMeta,
2925
3033
  message: isComplete
2926
3034
  ? `Summarized ${scannedRecords} records`
2927
- : `Summarized ${scannedRecords}/${knownResultAmount} records (partial)`
3035
+ : `Summarized ${scannedRecords}/${knownResultAmount} records (partial)`,
3036
+ completeness,
3037
+ evidence,
3038
+ outputProfile
2928
3039
  };
2929
3040
  }
2930
3041
  async function executeRecordsAggregate(args) {
2931
3042
  const queryId = randomUUID();
2932
3043
  const strictFull = args.strict_full ?? true;
3044
+ const outputProfile = resolveOutputProfile(args.output_profile);
2933
3045
  const includeNegative = args.stat_policy?.include_negative ?? true;
2934
3046
  const includeNull = args.stat_policy?.include_null ?? false;
2935
3047
  const pageSize = args.page_size ?? DEFAULT_PAGE_SIZE;
@@ -3127,22 +3239,35 @@ async function executeRecordsAggregate(args) {
3127
3239
  total_amount: amountColumn ? totalAmount : null
3128
3240
  },
3129
3241
  groups,
3130
- completeness,
3131
- evidence,
3132
- meta: {
3133
- field_mapping: fieldMapping,
3134
- stat_policy: {
3135
- include_negative: includeNegative,
3136
- include_null: includeNull
3242
+ ...(isVerboseProfile(outputProfile)
3243
+ ? {
3244
+ completeness,
3245
+ evidence,
3246
+ meta: {
3247
+ field_mapping: fieldMapping,
3248
+ stat_policy: {
3249
+ include_negative: includeNegative,
3250
+ include_null: includeNull
3251
+ }
3252
+ }
3137
3253
  }
3138
- }
3254
+ : {})
3139
3255
  },
3140
- completeness,
3141
- evidence,
3142
- error_code: null,
3143
- fix_hint: null,
3256
+ output_profile: outputProfile,
3257
+ ...(isVerboseProfile(outputProfile)
3258
+ ? {
3259
+ completeness,
3260
+ evidence,
3261
+ error_code: null,
3262
+ fix_hint: null
3263
+ }
3264
+ : {}),
3144
3265
  next_page_token: completeness.next_page_token,
3145
- meta: responseMeta
3266
+ ...(isVerboseProfile(outputProfile)
3267
+ ? {
3268
+ meta: responseMeta
3269
+ }
3270
+ : {})
3146
3271
  },
3147
3272
  message: isComplete
3148
3273
  ? `Aggregated ${scannedRecords} records`
@@ -3635,10 +3760,13 @@ function projectRecordItemsColumns(params) {
3635
3760
  projected = projected.slice(0, params.maxColumns);
3636
3761
  columnCapped = true;
3637
3762
  }
3638
- matchedAnswersCount += projected.length;
3763
+ const slimProjected = projected
3764
+ .map((answer) => simplifyAnswerForOutput(answer))
3765
+ .filter((answer) => Boolean(answer));
3766
+ matchedAnswersCount += slimProjected.length;
3639
3767
  return {
3640
3768
  ...item,
3641
- answers: projected
3769
+ answers: slimProjected
3642
3770
  };
3643
3771
  });
3644
3772
  const reason = mergeTruncationReasons(selectorSet.size > 0 ? `selected columns=${normalizedSelectors.length}` : null, columnCapped && params.maxColumns !== undefined
@@ -3661,11 +3789,99 @@ function projectAnswersForOutput(params) {
3661
3789
  if (params.maxColumns !== undefined && projected.length > params.maxColumns) {
3662
3790
  projected = projected.slice(0, params.maxColumns);
3663
3791
  }
3792
+ const slimProjected = projected
3793
+ .map((answer) => simplifyAnswerForOutput(answer))
3794
+ .filter((answer) => Boolean(answer));
3664
3795
  return {
3665
- answers: projected,
3796
+ answers: slimProjected,
3666
3797
  selectedColumns: normalizedSelectors.length > 0 ? normalizedSelectors : null
3667
3798
  };
3668
3799
  }
3800
+ function simplifyAnswerForOutput(answer) {
3801
+ const obj = asObject(answer);
3802
+ if (!obj) {
3803
+ return null;
3804
+ }
3805
+ const queId = obj.queId ?? obj.que_id ?? null;
3806
+ const queTitle = asNullableString(obj.queTitle ?? obj.que_title);
3807
+ const queType = obj.queType ?? obj.que_type;
3808
+ const value = extractAnswerDisplayValue(obj);
3809
+ const slim = {
3810
+ queId,
3811
+ queTitle
3812
+ };
3813
+ if (queType !== undefined && queType !== null) {
3814
+ slim.queType = queType;
3815
+ }
3816
+ if (value !== undefined) {
3817
+ slim.value = value;
3818
+ }
3819
+ return slim;
3820
+ }
3821
+ function buildFlatRowsFromItems(params) {
3822
+ return params.items.map((item) => buildFlatRowFromAnswers({
3823
+ applyId: item.apply_id ?? null,
3824
+ answers: asArray(item.answers),
3825
+ selectedColumns: params.selectedColumns
3826
+ }));
3827
+ }
3828
+ function buildFlatRowFromAnswers(params) {
3829
+ const row = {
3830
+ apply_id: params.applyId
3831
+ };
3832
+ const selectors = normalizeColumnSelectors(params.selectedColumns);
3833
+ for (const selector of selectors) {
3834
+ const hit = findAnswerBySelector(params.answers, selector);
3835
+ const keyBase = resolveFlatRowColumnKey(selector, hit);
3836
+ const key = getUniqueRowKey(row, keyBase);
3837
+ row[key] = hit ? extractAnswerDisplayValue(hit) : null;
3838
+ }
3839
+ return row;
3840
+ }
3841
+ function findAnswerBySelector(answers, selector) {
3842
+ const target = normalizeColumnSelector(selector);
3843
+ for (const answerRaw of answers) {
3844
+ const answer = asObject(answerRaw);
3845
+ if (!answer) {
3846
+ continue;
3847
+ }
3848
+ const answerQueId = asNullableString(answer.queId ?? answer.que_id);
3849
+ if (answerQueId && normalizeColumnSelector(answerQueId) === target) {
3850
+ return answer;
3851
+ }
3852
+ const answerQueTitle = asNullableString(answer.queTitle ?? answer.que_title);
3853
+ if (answerQueTitle && normalizeColumnSelector(answerQueTitle) === target) {
3854
+ return answer;
3855
+ }
3856
+ }
3857
+ return null;
3858
+ }
3859
+ function resolveFlatRowColumnKey(selector, answer) {
3860
+ const trimmed = selector.trim();
3861
+ if (!trimmed) {
3862
+ return "column";
3863
+ }
3864
+ if (!isNumericKey(trimmed)) {
3865
+ return trimmed;
3866
+ }
3867
+ const title = asNullableString(answer?.queTitle ?? answer?.que_title);
3868
+ if (title && title.trim()) {
3869
+ return title;
3870
+ }
3871
+ return trimmed;
3872
+ }
3873
+ function getUniqueRowKey(row, preferred) {
3874
+ if (!(preferred in row)) {
3875
+ return preferred;
3876
+ }
3877
+ let index = 2;
3878
+ let candidate = `${preferred}#${index}`;
3879
+ while (candidate in row) {
3880
+ index += 1;
3881
+ candidate = `${preferred}#${index}`;
3882
+ }
3883
+ return candidate;
3884
+ }
3669
3885
  function fitListItemsWithinSize(params) {
3670
3886
  let candidate = params.items;
3671
3887
  const originalSize = jsonSizeBytes(candidate);
@@ -4041,7 +4257,8 @@ function buildBaseExampleCall(params) {
4041
4257
  tool: "qf_record_get",
4042
4258
  arguments: {
4043
4259
  apply_id: "your_apply_id",
4044
- select_columns: params.selectColumns
4260
+ select_columns: params.selectColumns,
4261
+ output_profile: "compact"
4045
4262
  }
4046
4263
  };
4047
4264
  }
@@ -4053,7 +4270,8 @@ function buildBaseExampleCall(params) {
4053
4270
  mode: "all",
4054
4271
  page_size: 50,
4055
4272
  max_rows: 20,
4056
- select_columns: params.selectColumns
4273
+ select_columns: params.selectColumns,
4274
+ output_profile: "compact"
4057
4275
  }
4058
4276
  };
4059
4277
  }
@@ -4067,7 +4285,8 @@ function buildBaseExampleCall(params) {
4067
4285
  page_size: 50,
4068
4286
  requested_pages: 3,
4069
4287
  scan_max_pages: 3,
4070
- strict_full: false
4288
+ strict_full: false,
4289
+ output_profile: "compact"
4071
4290
  }
4072
4291
  };
4073
4292
  }
@@ -4078,7 +4297,8 @@ function buildBaseExampleCall(params) {
4078
4297
  arguments: {
4079
4298
  query_mode: "record",
4080
4299
  apply_id: "your_apply_id",
4081
- select_columns: params.selectColumns
4300
+ select_columns: params.selectColumns,
4301
+ output_profile: "compact"
4082
4302
  }
4083
4303
  };
4084
4304
  }
@@ -4092,7 +4312,8 @@ function buildBaseExampleCall(params) {
4092
4312
  select_columns: params.selectColumns,
4093
4313
  page_size: 50,
4094
4314
  scan_max_pages: 3,
4095
- strict_full: false
4315
+ strict_full: false,
4316
+ output_profile: "compact"
4096
4317
  }
4097
4318
  };
4098
4319
  }
@@ -4104,7 +4325,8 @@ function buildBaseExampleCall(params) {
4104
4325
  mode: "all",
4105
4326
  page_size: 50,
4106
4327
  max_rows: 20,
4107
- select_columns: params.selectColumns
4328
+ select_columns: params.selectColumns,
4329
+ output_profile: "compact"
4108
4330
  }
4109
4331
  };
4110
4332
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qingflow-mcp",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",