qingflow-mcp 0.3.22 → 0.3.23

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 +40 -3
  2. package/dist/server.js +275 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -117,7 +117,7 @@ npm i -g git+https://github.com/853046310/qingflow-mcp.git
117
117
  Install from npm (pinned version):
118
118
 
119
119
  ```bash
120
- npm i -g qingflow-mcp@0.3.22
120
+ npm i -g qingflow-mcp@0.3.23
121
121
  ```
122
122
 
123
123
  Or one-click installer:
@@ -153,7 +153,7 @@ MCP client config example:
153
153
  ## Recommended Flow
154
154
 
155
155
  1. `qf_apps_list` to pick app.
156
- 2. `qf_form_get` to inspect field ids/titles.
156
+ 2. `qf_form_get` to inspect field ids/titles and `field_summaries[].write_format`.
157
157
  3. `qf_record_create` or `qf_record_update`.
158
158
  4. If create/update returns only `request_id`, call `qf_operation_get` to resolve async result.
159
159
 
@@ -180,6 +180,43 @@ Full calling contract (Chinese):
180
180
 
181
181
  - [MCP 调用规范](./docs/MCP_CALLING_SPEC.md)
182
182
 
183
+ ## Write Format Discovery
184
+
185
+ For create/update, `0.3.23` now makes special write formats explicit:
186
+
187
+ 1. `qf_form_get`
188
+ - `field_summaries[].write_format` is populated for member/department fields.
189
+ 2. `qf_tool_spec_get`
190
+ - `qf_record_create` / `qf_record_update` include member/department examples in `limits.special_field_write_formats`.
191
+ 3. `qf_record_create` / `qf_record_update`
192
+ - invalid member/department values fail fast with `FIELD_VALUE_FORMAT_ERROR`.
193
+
194
+ Examples:
195
+
196
+ ```json
197
+ {
198
+ "fields": {
199
+ "归属销售": [
200
+ { "userId": "u_123", "userName": "张三" }
201
+ ],
202
+ "归属部门": [
203
+ { "deptId": 111, "deptName": "销售部" }
204
+ ]
205
+ }
206
+ }
207
+ ```
208
+
209
+ Invalid examples that will now fail:
210
+
211
+ ```json
212
+ {
213
+ "fields": {
214
+ "归属销售": "张三",
215
+ "归属部门": "销售部"
216
+ }
217
+ }
218
+ ```
219
+
183
220
  ## Unified Query (`qf_query`)
184
221
 
185
222
  `qf_query` is the recommended read entry for agents.
@@ -429,7 +466,7 @@ If you see runtime errors around `Headers` or missing web APIs:
429
466
  2. Upgrade package to latest:
430
467
 
431
468
  ```bash
432
- npm i -g qingflow-mcp@latest
469
+ npm i -g qingflow-mcp@0.3.23
433
470
  ```
434
471
 
435
472
  3. Verify runtime:
package/dist/server.js CHANGED
@@ -68,7 +68,9 @@ const REQUEST_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_REQUEST_TIMEOUT_MS
68
68
  const EXECUTION_BUDGET_MS = toPositiveInt(process.env.QINGFLOW_EXECUTION_BUDGET_MS) ?? 20000;
69
69
  const WAIT_RESULT_DEFAULT_TIMEOUT_MS = toPositiveInt(process.env.QINGFLOW_WAIT_RESULT_TIMEOUT_MS) ?? 5000;
70
70
  const WAIT_RESULT_POLL_INTERVAL_MS = toPositiveInt(process.env.QINGFLOW_WAIT_RESULT_POLL_INTERVAL_MS) ?? 500;
71
- const SERVER_VERSION = "0.3.22";
71
+ const SERVER_VERSION = "0.3.23";
72
+ const MEMBER_QUE_TYPE_KEYWORDS = ["member", "user", "成员", "人员"];
73
+ const DEPARTMENT_QUE_TYPE_KEYWORDS = ["department", "dept", "部门"];
72
74
  const accessToken = process.env.QINGFLOW_ACCESS_TOKEN;
73
75
  const baseUrl = process.env.QINGFLOW_BASE_URL;
74
76
  if (!accessToken) {
@@ -192,6 +194,15 @@ const fieldSummarySchema = z.object({
192
194
  que_id: z.union([z.number(), z.string(), z.null()]),
193
195
  que_title: z.string().nullable(),
194
196
  que_type: z.unknown(),
197
+ write_format: z
198
+ .object({
199
+ kind: z.enum(["member_list", "department_list"]),
200
+ description: z.string(),
201
+ item_shape: z.record(z.string()),
202
+ example: z.array(z.record(z.unknown())),
203
+ resolution_hint: z.string()
204
+ })
205
+ .nullable(),
195
206
  has_sub_fields: z.boolean(),
196
207
  sub_field_count: z.number().int().nonnegative()
197
208
  });
@@ -2388,7 +2399,8 @@ server.registerTool("qf_record_create", {
2388
2399
  }, async (args) => {
2389
2400
  try {
2390
2401
  const parsedArgs = createInputSchema.parse(args);
2391
- const form = needsFormResolution(parsedArgs.fields) || Boolean(parsedArgs.force_refresh_form)
2402
+ const shouldFetchForm = hasWritePayload(parsedArgs.answers, parsedArgs.fields) || Boolean(parsedArgs.force_refresh_form);
2403
+ const form = shouldFetchForm
2392
2404
  ? await getFormCached(parsedArgs.app_key, parsedArgs.user_id, Boolean(parsedArgs.force_refresh_form))
2393
2405
  : null;
2394
2406
  const normalizedAnswers = resolveAnswers({
@@ -2464,16 +2476,18 @@ server.registerTool("qf_record_update", {
2464
2476
  }, async (args) => {
2465
2477
  try {
2466
2478
  const parsedArgs = updateInputSchema.parse(args);
2467
- const requiresForm = needsFormResolution(parsedArgs.fields);
2468
- if (requiresForm && !parsedArgs.app_key) {
2479
+ const resolvedAppKey = parsedArgs.app_key ?? getCachedApplyAppKey(parsedArgs.apply_id);
2480
+ const requiresFormByTitle = needsFormResolution(parsedArgs.fields);
2481
+ if (requiresFormByTitle && !resolvedAppKey) {
2469
2482
  throw missingRequiredFieldError({
2470
2483
  field: "app_key",
2471
2484
  tool: "qf_record_update",
2472
2485
  fixHint: "Provide app_key when fields uses title-based keys, or switch fields to numeric que_id."
2473
2486
  });
2474
2487
  }
2475
- const form = requiresForm && parsedArgs.app_key
2476
- ? await getFormCached(parsedArgs.app_key, parsedArgs.user_id, Boolean(parsedArgs.force_refresh_form))
2488
+ const form = (hasWritePayload(parsedArgs.answers, parsedArgs.fields) || Boolean(parsedArgs.force_refresh_form)) &&
2489
+ resolvedAppKey
2490
+ ? await getFormCached(resolvedAppKey, parsedArgs.user_id, Boolean(parsedArgs.force_refresh_form))
2477
2491
  : null;
2478
2492
  const normalizedAnswers = resolveAnswers({
2479
2493
  explicitAnswers: parsedArgs.answers,
@@ -3534,7 +3548,8 @@ function buildToolSpecCatalog() {
3534
3548
  tool: "qf_form_get",
3535
3549
  required: ["app_key"],
3536
3550
  limits: {
3537
- app_key: "required string"
3551
+ app_key: "required string",
3552
+ write_format_hint: "field_summaries[].write_format is populated for member/department fields so agents can discover exact create/update payload shapes before writing."
3538
3553
  },
3539
3554
  aliases: collectAliasHints(["app_key", "user_id", "force_refresh"], {}),
3540
3555
  minimal_example: {
@@ -3751,7 +3766,11 @@ function buildToolSpecCatalog() {
3751
3766
  write_mode: "Provide either answers[] or fields{}",
3752
3767
  wait_result: "optional boolean; when true, polls qf_operation_get internally and returns resolved apply_id directly",
3753
3768
  wait_timeout_ms: "optional int (max 20000); default 5000ms",
3754
- input_contract: "strict JSON only; answers must be array and fields must be object"
3769
+ input_contract: "strict JSON only; answers must be array and fields must be object",
3770
+ special_field_write_formats: {
3771
+ member_list: [{ userId: "u_123", userName: "张三" }],
3772
+ department_list: [{ deptId: 111, deptName: "销售部" }]
3773
+ }
3755
3774
  },
3756
3775
  aliases: {},
3757
3776
  minimal_example: {
@@ -3769,7 +3788,11 @@ function buildToolSpecCatalog() {
3769
3788
  write_mode: "Provide either answers[] or fields{}",
3770
3789
  wait_result: "optional boolean; when true, polls qf_operation_get internally and returns resolved result directly",
3771
3790
  wait_timeout_ms: "optional int (max 20000); default 5000ms",
3772
- input_contract: "strict JSON only; answers must be array and fields must be object"
3791
+ input_contract: "strict JSON only; answers must be array and fields must be object",
3792
+ special_field_write_formats: {
3793
+ member_list: [{ userId: "u_123", userName: "张三" }],
3794
+ department_list: [{ deptId: 111, deptName: "销售部" }]
3795
+ }
3773
3796
  },
3774
3797
  aliases: {},
3775
3798
  minimal_example: {
@@ -7533,8 +7556,9 @@ function normalizeRecordItem(raw, includeAnswers) {
7533
7556
  return normalized;
7534
7557
  }
7535
7558
  function resolveAnswers(params) {
7536
- const normalizedFromFields = resolveFieldAnswers(params.fields, params.form, params.tool);
7537
- const normalizedExplicit = normalizeExplicitAnswers(params.explicitAnswers);
7559
+ const index = params.form ? buildFieldIndex(params.form) : null;
7560
+ const normalizedFromFields = resolveFieldAnswers(params.fields, index, params.tool);
7561
+ const normalizedExplicit = normalizeExplicitAnswers(params.explicitAnswers, index, params.tool);
7538
7562
  const merged = new Map();
7539
7563
  for (const answer of normalizedFromFields) {
7540
7564
  merged.set(String(answer.queId), answer);
@@ -7547,12 +7571,12 @@ function resolveAnswers(params) {
7547
7571
  }
7548
7572
  return Array.from(merged.values());
7549
7573
  }
7550
- function normalizeExplicitAnswers(answers) {
7574
+ function normalizeExplicitAnswers(answers, index, tool = "qf_record_create") {
7551
7575
  if (!answers?.length) {
7552
7576
  return [];
7553
7577
  }
7554
7578
  const output = [];
7555
- for (const item of answers) {
7579
+ for (const [itemIndex, item] of answers.entries()) {
7556
7580
  const queId = item.que_id ?? item.queId;
7557
7581
  if (queId === undefined || queId === null || String(queId).trim() === "") {
7558
7582
  throw new Error("answer item requires que_id or queId");
@@ -7578,27 +7602,32 @@ function normalizeExplicitAnswers(answers) {
7578
7602
  if (values === undefined) {
7579
7603
  throw new Error(`answer item ${String(queId)} requires values or table_values`);
7580
7604
  }
7581
- normalized.values = values.map((value) => normalizeAnswerValue(value));
7605
+ const field = resolveExplicitAnswerField(item, index);
7606
+ normalized.values = values.map((value, valueIndex) => normalizeAnswerValue(value, {
7607
+ field,
7608
+ tool,
7609
+ location: `answers[${itemIndex}].values[${valueIndex}]`
7610
+ }));
7582
7611
  output.push(normalized);
7583
7612
  }
7584
7613
  return output;
7585
7614
  }
7586
- function resolveFieldAnswers(fields, form, tool = "qf_record_create") {
7615
+ function resolveFieldAnswers(fields, index, tool = "qf_record_create") {
7587
7616
  const entries = Object.entries(fields ?? {});
7588
7617
  if (entries.length === 0) {
7589
7618
  return [];
7590
7619
  }
7591
- const index = buildFieldIndex(form);
7620
+ const resolvedIndex = index ?? { byId: new Map(), byTitle: new Map() };
7592
7621
  const answers = [];
7593
7622
  for (const [fieldKey, fieldValue] of entries) {
7594
7623
  let field;
7595
7624
  if (isNumericKey(fieldKey)) {
7596
- field = resolveFieldByKey(fieldKey, index);
7625
+ field = resolveFieldByKey(fieldKey, resolvedIndex);
7597
7626
  }
7598
7627
  else {
7599
7628
  field = resolveFieldSelectorStrict({
7600
7629
  fieldKey,
7601
- index,
7630
+ index: resolvedIndex,
7602
7631
  tool,
7603
7632
  location: `fields.${fieldKey}`
7604
7633
  });
@@ -7612,15 +7641,15 @@ function resolveFieldAnswers(fields, form, tool = "qf_record_create") {
7612
7641
  tool,
7613
7642
  location: `fields.${fieldKey}`,
7614
7643
  requested: fieldKey,
7615
- suggestions: buildFieldSuggestions(fieldKey, index)
7644
+ suggestions: buildFieldSuggestions(fieldKey, resolvedIndex)
7616
7645
  }
7617
7646
  });
7618
7647
  }
7619
- answers.push(makeAnswerFromField(field, fieldValue));
7648
+ answers.push(makeAnswerFromField(field, fieldValue, tool));
7620
7649
  }
7621
7650
  return answers;
7622
7651
  }
7623
- function makeAnswerFromField(field, value) {
7652
+ function makeAnswerFromField(field, value, tool = "qf_record_create") {
7624
7653
  const base = {
7625
7654
  queId: field.queId
7626
7655
  };
@@ -7641,7 +7670,11 @@ function makeAnswerFromField(field, value) {
7641
7670
  if ("values" in objectValue) {
7642
7671
  return {
7643
7672
  ...base,
7644
- values: asArray(objectValue.values).map((item) => normalizeAnswerValue(item))
7673
+ values: asArray(objectValue.values).map((item, index) => normalizeAnswerValue(item, {
7674
+ field,
7675
+ tool,
7676
+ location: `fields.${String(field.queTitle ?? field.queId ?? "field")}.values[${index}]`
7677
+ }))
7645
7678
  };
7646
7679
  }
7647
7680
  }
@@ -7654,10 +7687,38 @@ function makeAnswerFromField(field, value) {
7654
7687
  const valueArray = Array.isArray(value) ? value : [value];
7655
7688
  return {
7656
7689
  ...base,
7657
- values: valueArray.map((item) => normalizeAnswerValue(item))
7690
+ values: valueArray.map((item, index) => normalizeAnswerValue(item, {
7691
+ field,
7692
+ tool,
7693
+ location: `fields.${String(field.queTitle ?? field.queId ?? "field")}.values[${index}]`
7694
+ }))
7658
7695
  };
7659
7696
  }
7660
- function normalizeAnswerValue(value) {
7697
+ function resolveExplicitAnswerField(item, index) {
7698
+ if (index) {
7699
+ const rawQueId = item.que_id ?? item.queId;
7700
+ if (rawQueId !== undefined && rawQueId !== null) {
7701
+ const hit = index.byId.get(String(normalizeQueId(rawQueId)));
7702
+ if (hit) {
7703
+ return hit;
7704
+ }
7705
+ }
7706
+ const rawQueTitle = item.que_title ?? item.queTitle;
7707
+ if (typeof rawQueTitle === "string" && rawQueTitle.trim()) {
7708
+ const candidates = index.byTitle.get(rawQueTitle.trim().toLowerCase()) ?? [];
7709
+ if (candidates.length === 1) {
7710
+ return candidates[0];
7711
+ }
7712
+ }
7713
+ }
7714
+ return {
7715
+ queId: item.que_id ?? item.queId,
7716
+ queTitle: item.que_title ?? item.queTitle,
7717
+ queType: item.que_type ?? item.queType
7718
+ };
7719
+ }
7720
+ function normalizeAnswerValue(value, params) {
7721
+ validateWriteValueShape(params?.field ?? null, value, params);
7661
7722
  if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
7662
7723
  return {
7663
7724
  value,
@@ -7666,6 +7727,70 @@ function normalizeAnswerValue(value) {
7666
7727
  }
7667
7728
  return value;
7668
7729
  }
7730
+ function validateWriteValueShape(field, value, params) {
7731
+ const writeFormat = field ? inferFieldWriteFormat(field) : null;
7732
+ if (!writeFormat) {
7733
+ return;
7734
+ }
7735
+ const validatedField = field;
7736
+ if (!validatedField) {
7737
+ return;
7738
+ }
7739
+ if (writeFormat.kind === "member_list" && !isValidMemberWriteValue(value)) {
7740
+ throw fieldValueFormatError({
7741
+ field: validatedField,
7742
+ writeFormat,
7743
+ value,
7744
+ tool: params?.tool,
7745
+ location: params?.location
7746
+ });
7747
+ }
7748
+ if (writeFormat.kind === "department_list" && !isValidDepartmentWriteValue(value)) {
7749
+ throw fieldValueFormatError({
7750
+ field: validatedField,
7751
+ writeFormat,
7752
+ value,
7753
+ tool: params?.tool,
7754
+ location: params?.location
7755
+ });
7756
+ }
7757
+ }
7758
+ function isValidMemberWriteValue(value) {
7759
+ const obj = asObject(value);
7760
+ return Boolean(obj && asNullableString(obj.userId)?.trim());
7761
+ }
7762
+ function isValidDepartmentWriteValue(value) {
7763
+ const obj = asObject(value);
7764
+ if (!obj) {
7765
+ return false;
7766
+ }
7767
+ const deptId = obj.deptId;
7768
+ return ((typeof deptId === "string" && deptId.trim().length > 0) ||
7769
+ (typeof deptId === "number" && Number.isFinite(deptId)));
7770
+ }
7771
+ function fieldValueFormatError(params) {
7772
+ const fieldLabel = asNullableString(params.field.queTitle)?.trim() ||
7773
+ String(params.field.queId ?? "unknown_field");
7774
+ const formatName = params.writeFormat.kind === "member_list" ? "成员字段" : "部门字段";
7775
+ return new InputValidationError({
7776
+ message: `${formatName} "${fieldLabel}" 的写入值格式不正确`,
7777
+ errorCode: "FIELD_VALUE_FORMAT_ERROR",
7778
+ fixHint: params.writeFormat.kind === "member_list"
7779
+ ? '传对象数组,例如 [{"userId":"u_123","userName":"张三"}];不要传纯字符串或 user_id。'
7780
+ : '传对象数组,例如 [{"deptId":111,"deptName":"销售部"}];不要传纯字符串或 dept_id。',
7781
+ details: {
7782
+ tool: params.tool ?? "qf_record_create",
7783
+ location: params.location ?? null,
7784
+ field: {
7785
+ que_id: params.field.queId ?? null,
7786
+ que_title: asNullableString(params.field.queTitle),
7787
+ que_type: params.field.queType ?? null
7788
+ },
7789
+ expected_format: params.writeFormat,
7790
+ received_value: params.value
7791
+ }
7792
+ });
7793
+ }
7669
7794
  function needsFormResolution(fields) {
7670
7795
  const keys = Object.keys(fields ?? {});
7671
7796
  if (!keys.length) {
@@ -7697,11 +7822,95 @@ function extractFieldSummaries(form) {
7697
7822
  que_id: field.queId ?? null,
7698
7823
  que_title: asNullableString(field.queTitle),
7699
7824
  que_type: field.queType,
7825
+ write_format: inferFieldWriteFormat(field),
7700
7826
  has_sub_fields: sub.length > 0,
7701
7827
  sub_field_count: sub.length
7702
7828
  };
7703
7829
  });
7704
7830
  }
7831
+ function inferFieldWriteFormat(field) {
7832
+ const kind = inferFieldWriteFormatKind(field.queType);
7833
+ if (kind === "member_list") {
7834
+ return {
7835
+ kind,
7836
+ description: "Pass one or more member objects in values[]. Each item must contain userId.",
7837
+ item_shape: {
7838
+ userId: "string",
7839
+ userName: "string? (recommended)",
7840
+ name: "string? (accepted alias)"
7841
+ },
7842
+ example: [
7843
+ {
7844
+ userId: "u_123",
7845
+ userName: "张三"
7846
+ }
7847
+ ],
7848
+ resolution_hint: "Use qf_users_list or qf_department_users_list to resolve valid userId values before writing."
7849
+ };
7850
+ }
7851
+ if (kind === "department_list") {
7852
+ return {
7853
+ kind,
7854
+ description: "Pass one or more department objects in values[]. Each item must contain deptId.",
7855
+ item_shape: {
7856
+ deptId: "string|number",
7857
+ deptName: "string? (recommended)",
7858
+ name: "string? (accepted alias)"
7859
+ },
7860
+ example: [
7861
+ {
7862
+ deptId: 111,
7863
+ deptName: "销售部"
7864
+ }
7865
+ ],
7866
+ resolution_hint: "Use qf_departments_list to resolve valid deptId values before writing."
7867
+ };
7868
+ }
7869
+ return null;
7870
+ }
7871
+ function inferFieldWriteFormatKind(queType) {
7872
+ const tokens = collectQueTypeTokens(queType);
7873
+ if (tokens.some((token) => MEMBER_QUE_TYPE_KEYWORDS.some((keyword) => token.includes(keyword)))) {
7874
+ return "member_list";
7875
+ }
7876
+ if (tokens.some((token) => DEPARTMENT_QUE_TYPE_KEYWORDS.some((keyword) => token.includes(keyword)))) {
7877
+ return "department_list";
7878
+ }
7879
+ return null;
7880
+ }
7881
+ function collectQueTypeTokens(queType) {
7882
+ const tokens = new Set();
7883
+ const queue = [queType];
7884
+ while (queue.length > 0) {
7885
+ const current = queue.shift();
7886
+ if (current === null || current === undefined) {
7887
+ continue;
7888
+ }
7889
+ if (typeof current === "string") {
7890
+ const normalized = current.trim().toLowerCase();
7891
+ if (normalized) {
7892
+ tokens.add(normalized);
7893
+ }
7894
+ continue;
7895
+ }
7896
+ if (typeof current === "number" || typeof current === "boolean") {
7897
+ tokens.add(String(current));
7898
+ continue;
7899
+ }
7900
+ if (Array.isArray(current)) {
7901
+ queue.push(...current);
7902
+ continue;
7903
+ }
7904
+ const obj = asObject(current);
7905
+ if (!obj) {
7906
+ continue;
7907
+ }
7908
+ for (const value of Object.values(obj)) {
7909
+ queue.push(value);
7910
+ }
7911
+ }
7912
+ return Array.from(tokens);
7913
+ }
7705
7914
  function buildFieldIndex(form) {
7706
7915
  const byId = new Map();
7707
7916
  const byTitle = new Map();
@@ -8442,6 +8651,48 @@ function buildErrorExampleCalls(params) {
8442
8651
  }
8443
8652
  ];
8444
8653
  }
8654
+ if (errorCode === "FIELD_VALUE_FORMAT_ERROR") {
8655
+ const expectedFormat = asObject(params.details?.expected_format);
8656
+ const kind = asNullableString(expectedFormat?.kind);
8657
+ if (kind === "member_list") {
8658
+ return [
8659
+ {
8660
+ tool: "qf_form_get",
8661
+ arguments: {
8662
+ app_key: appKey
8663
+ },
8664
+ note: "先查看字段 write_format,确认成员字段写入 shape"
8665
+ },
8666
+ {
8667
+ tool: "qf_department_users_list",
8668
+ arguments: {
8669
+ dept_id: 111,
8670
+ fetch_child: false
8671
+ },
8672
+ note: "查询有效成员 userId,再按 [{userId,userName}] 写入"
8673
+ }
8674
+ ];
8675
+ }
8676
+ if (kind === "department_list") {
8677
+ return [
8678
+ {
8679
+ tool: "qf_form_get",
8680
+ arguments: {
8681
+ app_key: appKey
8682
+ },
8683
+ note: "先查看字段 write_format,确认部门字段写入 shape"
8684
+ },
8685
+ {
8686
+ tool: "qf_departments_list",
8687
+ arguments: {
8688
+ keyword: "销售",
8689
+ limit: 20
8690
+ },
8691
+ note: "查询有效部门 deptId,再按 [{deptId,deptName}] 写入"
8692
+ }
8693
+ ];
8694
+ }
8695
+ }
8445
8696
  if (errorCode === "INTERNAL_ERROR" || errorCode === "UNKNOWN_ERROR") {
8446
8697
  return [
8447
8698
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qingflow-mcp",
3
- "version": "0.3.22",
3
+ "version": "0.3.23",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",