@qingflow-tech/qingflow-app-user-mcp 1.0.44 → 1.1.1
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 +2 -2
- package/npm/bin/qingflow-app-user-mcp.mjs +33 -2
- package/npm/lib/runtime.mjs +43 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-user/SKILL.md +1 -1
- package/skills/qingflow-mcp-setup/SKILL.md +1 -1
- package/skills/qingflow-record-analysis/SKILL.md +1 -1
- package/skills/qingflow-record-delete/SKILL.md +1 -1
- package/skills/qingflow-record-import/SKILL.md +1 -1
- package/skills/qingflow-record-insert/SKILL.md +1 -1
- package/skills/qingflow-record-update/SKILL.md +1 -1
- package/skills/qingflow-task-ops/SKILL.md +1 -1
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/builder_facade/models.py +0 -39
- package/src/qingflow_mcp/builder_facade/service.py +262 -862
- package/src/qingflow_mcp/builder_facade/workflow_spec.py +111 -0
- package/src/qingflow_mcp/cli/commands/builder.py +44 -12
- package/src/qingflow_mcp/public_surface.py +2 -0
- package/src/qingflow_mcp/server_app_builder.py +16 -8
- package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
- package/src/qingflow_mcp/solution/executor.py +3 -133
- package/src/qingflow_mcp/tools/ai_builder_tools.py +92 -233
- package/src/qingflow_mcp/tools/solution_tools.py +30 -2
- package/src/qingflow_mcp/tools/workflow_tools.py +3 -31
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @qingflow-tech/qingflow-app-user-mcp@1.
|
|
6
|
+
npm install @qingflow-tech/qingflow-app-user-mcp@1.1.1
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.
|
|
12
|
+
npx -y -p @qingflow-tech/qingflow-app-user-mcp@1.1.1 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
|
@@ -1,7 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
const PKG_BY_BIN = {
|
|
7
|
+
qingflow: "qingflow-cli",
|
|
8
|
+
"qingflow-app-user-mcp": "qingflow-app-user-mcp",
|
|
9
|
+
"qingflow-app-builder-mcp": "qingflow-app-builder-mcp",
|
|
10
|
+
};
|
|
4
11
|
|
|
5
|
-
|
|
12
|
+
function resolvePackageModule(metaUrl, ...segments) {
|
|
13
|
+
const scriptPath = fileURLToPath(metaUrl);
|
|
14
|
+
const scriptDir = path.dirname(scriptPath);
|
|
15
|
+
// bin files live in <pkg>/npm/bin/, runtime.mjs lives in the sibling <pkg>/npm/lib/,
|
|
16
|
+
// so step up one level before joining the requested segments.
|
|
17
|
+
const direct = path.join(scriptDir, "..", ...segments);
|
|
18
|
+
if (fs.existsSync(direct)) {
|
|
19
|
+
return pathToFileURL(direct).href;
|
|
20
|
+
}
|
|
21
|
+
if (path.basename(scriptDir) === ".bin") {
|
|
22
|
+
const binName = path.basename(scriptPath);
|
|
23
|
+
const pkgName = PKG_BY_BIN[binName];
|
|
24
|
+
if (!pkgName) {
|
|
25
|
+
throw new Error(`Unknown qingflow command: ${binName}`);
|
|
26
|
+
}
|
|
27
|
+
const scope = process.env.QINGFLOW_NPM_SCOPE || "@qingflow-tech";
|
|
28
|
+
const fromBin = path.join(scriptDir, "..", scope, pkgName, "npm", ...segments);
|
|
29
|
+
if (fs.existsSync(fromBin)) {
|
|
30
|
+
return pathToFileURL(fromBin).href;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`Cannot locate ${segments.join("/")} from ${scriptPath}`);
|
|
34
|
+
}
|
|
6
35
|
|
|
36
|
+
const { getPackageRoot, spawnServer } = await import(resolvePackageModule(import.meta.url, "lib", "runtime.mjs"));
|
|
37
|
+
const packageRoot = getPackageRoot(import.meta.url);
|
|
7
38
|
spawnServer(packageRoot, process.argv.slice(2), "qingflow-app-user-mcp", { allowRuntimeBootstrap: false });
|
package/npm/lib/runtime.mjs
CHANGED
|
@@ -5,6 +5,12 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
|
|
6
6
|
const WINDOWS = process.platform === "win32";
|
|
7
7
|
|
|
8
|
+
const PKG_BY_BIN = {
|
|
9
|
+
qingflow: "qingflow-cli",
|
|
10
|
+
"qingflow-app-user-mcp": "qingflow-app-user-mcp",
|
|
11
|
+
"qingflow-app-builder-mcp": "qingflow-app-builder-mcp",
|
|
12
|
+
};
|
|
13
|
+
|
|
8
14
|
function runChecked(command, args, options = {}) {
|
|
9
15
|
const result = spawnSync(command, args, {
|
|
10
16
|
encoding: "utf8",
|
|
@@ -28,7 +34,18 @@ function commandWorks(command, args) {
|
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
export function getPackageRoot(metaUrl) {
|
|
31
|
-
|
|
37
|
+
const scriptPath = fileURLToPath(metaUrl);
|
|
38
|
+
const scriptDir = path.dirname(scriptPath);
|
|
39
|
+
if (path.basename(scriptDir) === ".bin") {
|
|
40
|
+
const binName = path.basename(scriptPath);
|
|
41
|
+
const pkgName = PKG_BY_BIN[binName];
|
|
42
|
+
if (!pkgName) {
|
|
43
|
+
throw new Error(`Unknown qingflow command: ${binName}`);
|
|
44
|
+
}
|
|
45
|
+
const scope = process.env.QINGFLOW_NPM_SCOPE || "@qingflow-tech";
|
|
46
|
+
return path.join(scriptDir, "..", scope, pkgName);
|
|
47
|
+
}
|
|
48
|
+
return path.resolve(scriptDir, "..", "..");
|
|
32
49
|
}
|
|
33
50
|
|
|
34
51
|
export function getCodexHome() {
|
|
@@ -480,6 +497,17 @@ function getVenvPip(packageRoot) {
|
|
|
480
497
|
: path.join(getVenvDir(packageRoot), "bin", "pip");
|
|
481
498
|
}
|
|
482
499
|
|
|
500
|
+
function findOfflineProjectWheel(findLinksDir) {
|
|
501
|
+
const wheels = fs
|
|
502
|
+
.readdirSync(findLinksDir)
|
|
503
|
+
.filter((name) => /^qingflow_mcp-.*\.whl$/i.test(name))
|
|
504
|
+
.sort();
|
|
505
|
+
if (wheels.length === 0) {
|
|
506
|
+
throw new Error(`Offline install expected a qingflow_mcp wheel in ${findLinksDir}`);
|
|
507
|
+
}
|
|
508
|
+
return path.join(findLinksDir, wheels[wheels.length - 1]);
|
|
509
|
+
}
|
|
510
|
+
|
|
483
511
|
export function findPython() {
|
|
484
512
|
const preferred = process.env.QINGFLOW_MCP_PYTHON?.trim();
|
|
485
513
|
const candidates = preferred
|
|
@@ -528,7 +556,20 @@ export function ensurePythonEnv(packageRoot, { force = false, commandName = "qin
|
|
|
528
556
|
}
|
|
529
557
|
|
|
530
558
|
const pip = getVenvPip(packageRoot);
|
|
531
|
-
|
|
559
|
+
const pipArgs = ["install", "--disable-pip-version-check"];
|
|
560
|
+
const offlineFindLinks = process.env.QINGFLOW_MCP_PIP_FIND_LINKS?.trim();
|
|
561
|
+
if (process.env.QINGFLOW_MCP_PIP_NO_INDEX === "1") {
|
|
562
|
+
pipArgs.push("--no-index");
|
|
563
|
+
}
|
|
564
|
+
if (offlineFindLinks) {
|
|
565
|
+
pipArgs.push("--find-links", offlineFindLinks);
|
|
566
|
+
}
|
|
567
|
+
if (process.env.QINGFLOW_MCP_PIP_NO_INDEX === "1" && offlineFindLinks) {
|
|
568
|
+
pipArgs.push(findOfflineProjectWheel(offlineFindLinks));
|
|
569
|
+
} else {
|
|
570
|
+
pipArgs.push(".");
|
|
571
|
+
}
|
|
572
|
+
runChecked(pip, pipArgs, { cwd: packageRoot });
|
|
532
573
|
|
|
533
574
|
fs.writeFileSync(
|
|
534
575
|
stampPath,
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -7,7 +7,7 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Qingflow App User
|
|
9
9
|
|
|
10
|
-
> **Skill 版本**:`qingflow-skills-2026.06.24.
|
|
10
|
+
> **Skill 版本**:`qingflow-skills-2026.06.24.2`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
|
|
11
11
|
|
|
12
12
|
## Overview
|
|
13
13
|
|
|
@@ -7,7 +7,7 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Qingflow MCP Setup
|
|
9
9
|
|
|
10
|
-
> **Skill 版本**:`qingflow-skills-2026.06.24.
|
|
10
|
+
> **Skill 版本**:`qingflow-skills-2026.06.24.2`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
|
|
11
11
|
|
|
12
12
|
## Overview
|
|
13
13
|
|
|
@@ -7,7 +7,7 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Qingflow Record Analysis
|
|
9
9
|
|
|
10
|
-
> **Skill 版本**:`qingflow-skills-2026.06.24.
|
|
10
|
+
> **Skill 版本**:`qingflow-skills-2026.06.24.2`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
|
|
11
11
|
|
|
12
12
|
Use this skill only for final statistical conclusions: counts, distributions, ratios, averages, rankings, trends, comparisons, and analysis reports.
|
|
13
13
|
|
|
@@ -7,7 +7,7 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Qingflow Record Delete
|
|
9
9
|
|
|
10
|
-
> **Skill 版本**:`qingflow-skills-2026.06.24.
|
|
10
|
+
> **Skill 版本**:`qingflow-skills-2026.06.24.2`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
|
|
11
11
|
|
|
12
12
|
## Default Path
|
|
13
13
|
|
|
@@ -7,7 +7,7 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Qingflow Record Import
|
|
9
9
|
|
|
10
|
-
> **Skill 版本**:`qingflow-skills-2026.06.24.
|
|
10
|
+
> **Skill 版本**:`qingflow-skills-2026.06.24.2`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
|
|
11
11
|
|
|
12
12
|
## Default Path
|
|
13
13
|
|
|
@@ -8,7 +8,7 @@ metadata:
|
|
|
8
8
|
|
|
9
9
|
# Qingflow Record Insert
|
|
10
10
|
|
|
11
|
-
> **Skill 版本**:`qingflow-skills-2026.06.24.
|
|
11
|
+
> **Skill 版本**:`qingflow-skills-2026.06.24.2`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
|
|
12
12
|
|
|
13
13
|
## Default Path
|
|
14
14
|
|
|
@@ -7,7 +7,7 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Qingflow Record Update
|
|
9
9
|
|
|
10
|
-
> **Skill 版本**:`qingflow-skills-2026.06.24.
|
|
10
|
+
> **Skill 版本**:`qingflow-skills-2026.06.24.2`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
|
|
11
11
|
|
|
12
12
|
## Default Path
|
|
13
13
|
|
|
@@ -7,7 +7,7 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Qingflow Task Ops
|
|
9
9
|
|
|
10
|
-
> **Skill 版本**:`qingflow-skills-2026.06.24.
|
|
10
|
+
> **Skill 版本**:`qingflow-skills-2026.06.24.2`(入口文档版本;如需确认 CLI 包版本,使用 `qingflow --version` 或 `qingflow --json version`)。
|
|
11
11
|
|
|
12
12
|
## Overview
|
|
13
13
|
|
|
@@ -203,10 +203,6 @@ class LayoutPreset(str, Enum):
|
|
|
203
203
|
single_section = "single_section"
|
|
204
204
|
|
|
205
205
|
|
|
206
|
-
class FlowPreset(str, Enum):
|
|
207
|
-
basic_approval = "basic_approval"
|
|
208
|
-
basic_fill_then_approve = "basic_fill_then_approve"
|
|
209
|
-
|
|
210
206
|
|
|
211
207
|
class ViewsPreset(str, Enum):
|
|
212
208
|
default_table = "default_table"
|
|
@@ -2342,7 +2338,6 @@ AppReadSummaryResponse = AppGetResponse
|
|
|
2342
2338
|
AppFieldsReadResponse = AppGetFieldsResponse
|
|
2343
2339
|
AppLayoutReadResponse = AppGetLayoutResponse
|
|
2344
2340
|
AppViewsReadResponse = AppGetViewsResponse
|
|
2345
|
-
AppFlowReadResponse = AppGetFlowResponse
|
|
2346
2341
|
AppChartsReadResponse = AppGetChartsResponse
|
|
2347
2342
|
|
|
2348
2343
|
|
|
@@ -2432,40 +2427,6 @@ class LayoutPlanRequest(StrictModel):
|
|
|
2432
2427
|
return payload
|
|
2433
2428
|
|
|
2434
2429
|
|
|
2435
|
-
class FlowPlanRequest(StrictModel):
|
|
2436
|
-
app_key: str
|
|
2437
|
-
mode: str = "replace"
|
|
2438
|
-
nodes: list[FlowNodePatch] = Field(default_factory=list)
|
|
2439
|
-
transitions: list[FlowTransitionPatch] = Field(default_factory=list)
|
|
2440
|
-
preset: FlowPreset | None = None
|
|
2441
|
-
|
|
2442
|
-
@model_validator(mode="before")
|
|
2443
|
-
@classmethod
|
|
2444
|
-
def normalize_mode_alias(cls, value: Any) -> Any:
|
|
2445
|
-
if not isinstance(value, dict):
|
|
2446
|
-
return value
|
|
2447
|
-
payload = dict(value)
|
|
2448
|
-
if str(payload.get("mode") or "").strip().lower() == "overwrite":
|
|
2449
|
-
payload["mode"] = "replace"
|
|
2450
|
-
raw_preset = payload.get("preset")
|
|
2451
|
-
if raw_preset is None and isinstance(payload.get("base_preset"), str):
|
|
2452
|
-
raw_preset = payload["base_preset"]
|
|
2453
|
-
payload["preset"] = raw_preset
|
|
2454
|
-
if isinstance(raw_preset, str):
|
|
2455
|
-
normalized_preset = raw_preset.strip().lower()
|
|
2456
|
-
preset_aliases = {
|
|
2457
|
-
"default_approval": FlowPreset.basic_approval.value,
|
|
2458
|
-
"approval": FlowPreset.basic_approval.value,
|
|
2459
|
-
"basic approval": FlowPreset.basic_approval.value,
|
|
2460
|
-
"default_fill_then_approve": FlowPreset.basic_fill_then_approve.value,
|
|
2461
|
-
"default-fill-then-approve": FlowPreset.basic_fill_then_approve.value,
|
|
2462
|
-
"fill_then_approve": FlowPreset.basic_fill_then_approve.value,
|
|
2463
|
-
}
|
|
2464
|
-
if normalized_preset in preset_aliases:
|
|
2465
|
-
payload["preset"] = preset_aliases[normalized_preset]
|
|
2466
|
-
return payload
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
2430
|
class ViewsPlanRequest(StrictModel):
|
|
2470
2431
|
app_key: str
|
|
2471
2432
|
upsert_views: list[ViewUpsertPatch] = Field(default_factory=list)
|