qingflow-mcp 0.3.16 → 0.3.17

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 (2) hide show
  1. package/dist/server.js +114 -59
  2. package/package.json +1 -1
package/dist/server.js CHANGED
@@ -65,7 +65,7 @@ const REQUEST_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_REQUEST_TIMEOUT_MS
65
65
  const EXECUTION_BUDGET_MS = toPositiveInt(process.env.QINGFLOW_EXECUTION_BUDGET_MS) ?? 20000;
66
66
  const WAIT_RESULT_DEFAULT_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_WAIT_RESULT_TIMEOUT_MS) ?? 5000;
67
67
  const WAIT_RESULT_POLL_INTERVAL_MS = toPositiveInt(process.env.QINGFLOW_WAIT_RESULT_POLL_INTERVAL_MS) ?? 500;
68
- const SERVER_VERSION = "0.3.16";
68
+ const SERVER_VERSION = "0.3.17";
69
69
  const accessToken = process.env.QINGFLOW_ACCESS_TOKEN;
70
70
  const baseUrl = process.env.QINGFLOW_BASE_URL;
71
71
  if (!accessToken) {
@@ -517,12 +517,23 @@ const createInputSchema = z
517
517
  const createSuccessOutputSchema = z.object({
518
518
  ok: z.literal(true),
519
519
  data: z.object({
520
+ status: z.enum(["completed", "pending", "timeout", "failed"]),
520
521
  request_id: z.string().nullable(),
521
522
  apply_id: z.union([z.string(), z.number(), z.null()]),
522
- resolved: z.boolean().optional(),
523
- timed_out: z.boolean().optional(),
524
- operation_result: z.unknown().optional(),
525
- async_hint: z.string()
523
+ resource: z
524
+ .object({
525
+ type: z.literal("record"),
526
+ apply_id: z.union([z.string(), z.number()])
527
+ })
528
+ .nullable(),
529
+ next_action: z
530
+ .object({
531
+ tool: z.string(),
532
+ arguments: z.record(z.unknown()),
533
+ reason: z.string().optional()
534
+ })
535
+ .nullable(),
536
+ raw: z.object({ operation_result: z.unknown() }).nullable()
526
537
  }),
527
538
  meta: apiMetaSchema
528
539
  });
@@ -555,12 +566,23 @@ const updateInputSchema = z
555
566
  const updateSuccessOutputSchema = z.object({
556
567
  ok: z.literal(true),
557
568
  data: z.object({
569
+ status: z.enum(["completed", "pending", "timeout", "failed"]),
558
570
  request_id: z.string().nullable(),
559
- apply_id: z.union([z.string(), z.number(), z.null()]).optional(),
560
- resolved: z.boolean().optional(),
561
- timed_out: z.boolean().optional(),
562
- operation_result: z.unknown().optional(),
563
- async_hint: z.string()
571
+ apply_id: z.union([z.string(), z.number(), z.null()]),
572
+ resource: z
573
+ .object({
574
+ type: z.literal("record"),
575
+ apply_id: z.union([z.string(), z.number()])
576
+ })
577
+ .nullable(),
578
+ next_action: z
579
+ .object({
580
+ tool: z.string(),
581
+ arguments: z.record(z.unknown()),
582
+ reason: z.string().optional()
583
+ })
584
+ .nullable(),
585
+ raw: z.object({ operation_result: z.unknown() }).nullable()
564
586
  }),
565
587
  meta: apiMetaSchema
566
588
  });
@@ -1578,30 +1600,36 @@ server.registerTool("qf_record_create", {
1578
1600
  const immediateApplyId = result?.applyId ?? null;
1579
1601
  const shouldWaitForResult = (parsedArgs.wait_result ?? false) && requestId !== null && immediateApplyId === null;
1580
1602
  let finalApplyId = immediateApplyId;
1581
- let isResolved = immediateApplyId !== null;
1582
- let isTimedOut = false;
1583
- let operationResult = null;
1603
+ let waitStatus = immediateApplyId !== null ? "completed" : "pending";
1604
+ let rawOperationResult = null;
1584
1605
  if (shouldWaitForResult) {
1585
1606
  const waited = await waitForOperationResolution({
1586
1607
  requestId: requestId,
1587
1608
  timeoutMs: parsedArgs.wait_timeout_ms ?? WAIT_RESULT_DEFAULT_TIMEOUT_MS
1588
1609
  });
1589
- isResolved = waited.resolved;
1590
- isTimedOut = waited.timedOut;
1591
- operationResult = waited.operationResult;
1610
+ waitStatus = waited.status;
1611
+ rawOperationResult = waited.operationResult;
1592
1612
  finalApplyId = waited.applyId;
1593
1613
  }
1614
+ const createResource = finalApplyId !== null ? { type: "record", apply_id: finalApplyId } : null;
1615
+ const createNextAction = waitStatus === "pending" || waitStatus === "timeout"
1616
+ ? {
1617
+ tool: "qf_operation_get",
1618
+ arguments: { request_id: requestId },
1619
+ reason: waitStatus === "timeout"
1620
+ ? "Operation timed out; poll again to check completion."
1621
+ : "Operation is async; poll to check completion."
1622
+ }
1623
+ : null;
1594
1624
  return okResult({
1595
1625
  ok: true,
1596
1626
  data: {
1627
+ status: waitStatus,
1597
1628
  request_id: requestId,
1598
1629
  apply_id: finalApplyId,
1599
- resolved: isResolved,
1600
- ...(isTimedOut ? { timed_out: true } : {}),
1601
- ...(operationResult !== null ? { operation_result: operationResult } : {}),
1602
- async_hint: isResolved
1603
- ? "Record created and resolved."
1604
- : "Use qf_operation_get with request_id to fetch the result."
1630
+ resource: createResource,
1631
+ next_action: createNextAction,
1632
+ raw: rawOperationResult !== null ? { operation_result: rawOperationResult } : null
1605
1633
  },
1606
1634
  meta: buildMeta(response)
1607
1635
  }, `Create request sent for app ${parsedArgs.app_key}`);
@@ -1624,7 +1652,11 @@ server.registerTool("qf_record_update", {
1624
1652
  const parsedArgs = updateInputSchema.parse(args);
1625
1653
  const requiresForm = needsFormResolution(parsedArgs.fields);
1626
1654
  if (requiresForm && !parsedArgs.app_key) {
1627
- throw new Error("app_key is required when fields uses title-based keys");
1655
+ throw missingRequiredFieldError({
1656
+ field: "app_key",
1657
+ tool: "qf_record_update",
1658
+ fixHint: "Provide app_key when fields uses title-based keys, or switch fields to numeric que_id."
1659
+ });
1628
1660
  }
1629
1661
  const form = requiresForm && parsedArgs.app_key
1630
1662
  ? await getFormCached(parsedArgs.app_key, parsedArgs.user_id, Boolean(parsedArgs.force_refresh_form))
@@ -1639,31 +1671,45 @@ server.registerTool("qf_record_update", {
1639
1671
  const result = asObject(response.result);
1640
1672
  const updateRequestId = asNullableString(result?.requestId);
1641
1673
  const shouldWaitForUpdate = (parsedArgs.wait_result ?? false) && updateRequestId !== null;
1642
- let updateIsResolved = false;
1643
- let updateIsTimedOut = false;
1644
- let updateOperationResult = null;
1645
- let updateApplyId = null;
1674
+ let updateStatus = "pending";
1675
+ let updateRawOperationResult = null;
1676
+ let updateApplyId = parsedArgs.apply_id;
1646
1677
  if (shouldWaitForUpdate) {
1647
1678
  const waited = await waitForOperationResolution({
1648
1679
  requestId: updateRequestId,
1649
1680
  timeoutMs: parsedArgs.wait_timeout_ms ?? WAIT_RESULT_DEFAULT_TIMEOUT_MS
1650
1681
  });
1651
- updateIsResolved = waited.resolved;
1652
- updateIsTimedOut = waited.timedOut;
1653
- updateOperationResult = waited.operationResult;
1654
- updateApplyId = waited.applyId;
1682
+ updateStatus = waited.status;
1683
+ updateRawOperationResult = waited.operationResult;
1684
+ // For updates, the apply_id is already known from input; keep it unless operation returned a different one
1685
+ if (waited.applyId !== null) {
1686
+ updateApplyId = waited.applyId;
1687
+ }
1688
+ }
1689
+ else if (updateRequestId === null) {
1690
+ // No async operation — synchronous completion
1691
+ updateStatus = "completed";
1655
1692
  }
1693
+ // else: wait_result=false but has requestId — submitted, not polled, stays "pending"
1694
+ const updateResource = updateApplyId !== null ? { type: "record", apply_id: updateApplyId } : null;
1695
+ const updateNextAction = updateStatus === "pending" || updateStatus === "timeout"
1696
+ ? {
1697
+ tool: "qf_operation_get",
1698
+ arguments: { request_id: updateRequestId },
1699
+ reason: updateStatus === "timeout"
1700
+ ? "Operation timed out; poll again to check completion."
1701
+ : "Operation is async; poll to check completion."
1702
+ }
1703
+ : null;
1656
1704
  return okResult({
1657
1705
  ok: true,
1658
1706
  data: {
1707
+ status: updateStatus,
1659
1708
  request_id: updateRequestId,
1660
- ...(updateApplyId !== null ? { apply_id: updateApplyId } : {}),
1661
- resolved: updateIsResolved,
1662
- ...(updateIsTimedOut ? { timed_out: true } : {}),
1663
- ...(updateOperationResult !== null ? { operation_result: updateOperationResult } : {}),
1664
- async_hint: updateIsResolved
1665
- ? "Record updated and resolved."
1666
- : "Use qf_operation_get with request_id to fetch the update result."
1709
+ apply_id: updateApplyId,
1710
+ resource: updateResource,
1711
+ next_action: updateNextAction,
1712
+ raw: updateRawOperationResult !== null ? { operation_result: updateRawOperationResult } : null
1667
1713
  },
1668
1714
  meta: buildMeta(response)
1669
1715
  }, `Update request sent for apply ${String(parsedArgs.apply_id)}`);
@@ -3362,7 +3408,7 @@ function inferPlanMissingRequired(tool, args) {
3362
3408
  missing.push("select_columns");
3363
3409
  }
3364
3410
  }
3365
- else {
3411
+ else if (queryMode === "list") {
3366
3412
  if (!hasAppKey) {
3367
3413
  missing.push("app_key");
3368
3414
  }
@@ -3370,6 +3416,9 @@ function inferPlanMissingRequired(tool, args) {
3370
3416
  missing.push("select_columns");
3371
3417
  }
3372
3418
  }
3419
+ else if (!hasAppKey) {
3420
+ missing.push("app_key");
3421
+ }
3373
3422
  }
3374
3423
  return missing;
3375
3424
  }
@@ -4516,12 +4565,12 @@ async function executeRecordsExport(format, args) {
4516
4565
  app_key: args.app_key,
4517
4566
  selected_columns: selectedColumnsForRows,
4518
4567
  filters: echoFilters(effectiveFilters),
4519
- time_range: args.time_range
4568
+ time_range: timeRangeResolution.mapping
4520
4569
  ? {
4521
- column: String(args.time_range.column),
4522
- from: args.time_range.from ?? null,
4523
- to: args.time_range.to ?? null,
4524
- timezone: args.time_range.timezone ?? null
4570
+ column: String(args.time_range?.column ?? timeRangeResolution.mapping.requested ?? ""),
4571
+ from: timeRangeResolution.time_range?.from ?? null,
4572
+ to: timeRangeResolution.time_range?.to ?? null,
4573
+ timezone: timeRangeResolution.time_range?.timezone ?? null
4525
4574
  }
4526
4575
  : null
4527
4576
  }, sourcePages);
@@ -4547,7 +4596,6 @@ async function executeRecordsExport(format, args) {
4547
4596
  ? {
4548
4597
  completeness,
4549
4598
  evidence,
4550
- resolved_mappings: resolvedMappings,
4551
4599
  execution: {
4552
4600
  scanned_pages: fetchedPages,
4553
4601
  requested_pages: requestedPages,
@@ -4562,6 +4610,7 @@ async function executeRecordsExport(format, args) {
4562
4610
  ? {
4563
4611
  completeness,
4564
4612
  evidence,
4613
+ resolved_mappings: resolvedMappings,
4565
4614
  error_code: null,
4566
4615
  fix_hint: null
4567
4616
  }
@@ -6441,6 +6490,13 @@ function isPendingOperationStatus(status) {
6441
6490
  return ["PENDING", "PROCESSING", "RUNNING", "IN_PROGRESS", "QUEUED"].includes(status);
6442
6491
  }
6443
6492
  function extractOperationApplyId(operationResult) {
6493
+ // Handle case where operationResult itself is a numeric string or number (the apply_id directly)
6494
+ if (typeof operationResult === "string" && /^\d+$/.test(operationResult.trim())) {
6495
+ return operationResult.trim();
6496
+ }
6497
+ if (typeof operationResult === "number" && Number.isFinite(operationResult)) {
6498
+ return operationResult;
6499
+ }
6444
6500
  const obj = asObject(operationResult);
6445
6501
  return obj?.applyId ?? obj?.apply_id ?? null;
6446
6502
  }
@@ -6450,15 +6506,14 @@ async function waitForOperationResolution(params) {
6450
6506
  while (Date.now() <= deadline) {
6451
6507
  const response = await client.getOperation(params.requestId);
6452
6508
  lastResult = response.result;
6453
- const status = extractOperationStatus(lastResult);
6509
+ const opStatus = extractOperationStatus(lastResult);
6454
6510
  const applyId = extractOperationApplyId(lastResult);
6455
- if ((status && !isPendingOperationStatus(status)) || applyId !== null) {
6456
- return {
6457
- resolved: true,
6458
- timedOut: false,
6459
- operationResult: lastResult,
6460
- applyId
6461
- };
6511
+ if (applyId !== null) {
6512
+ return { status: "completed", operationResult: lastResult, applyId };
6513
+ }
6514
+ if (opStatus && !isPendingOperationStatus(opStatus)) {
6515
+ // Non-pending status but no apply_id — treat as failed
6516
+ return { status: "failed", operationResult: lastResult, applyId: null };
6462
6517
  }
6463
6518
  const remaining = deadline - Date.now();
6464
6519
  if (remaining <= 0) {
@@ -6466,12 +6521,12 @@ async function waitForOperationResolution(params) {
6466
6521
  }
6467
6522
  await delay(Math.min(WAIT_RESULT_POLL_INTERVAL_MS, remaining));
6468
6523
  }
6469
- return {
6470
- resolved: false,
6471
- timedOut: true,
6472
- operationResult: lastResult,
6473
- applyId: extractOperationApplyId(lastResult)
6474
- };
6524
+ // Timed out — check if last result has apply_id anyway (edge case)
6525
+ const finalApplyId = extractOperationApplyId(lastResult);
6526
+ if (finalApplyId !== null) {
6527
+ return { status: "completed", operationResult: lastResult, applyId: finalApplyId };
6528
+ }
6529
+ return { status: "timeout", operationResult: lastResult, applyId: null };
6475
6530
  }
6476
6531
  function resolveListItemLimit(params) {
6477
6532
  if (params.total <= 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qingflow-mcp",
3
- "version": "0.3.16",
3
+ "version": "0.3.17",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",