@josephyan/qingflow-app-builder-mcp 0.2.0-beta.9 → 0.2.0-beta.91

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 (78) hide show
  1. package/README.md +5 -3
  2. package/docs/local-agent-install.md +21 -5
  3. package/npm/bin/qingflow-app-builder-mcp.mjs +1 -1
  4. package/npm/lib/runtime.mjs +168 -12
  5. package/package.json +1 -1
  6. package/pyproject.toml +4 -1
  7. package/skills/qingflow-app-builder/SKILL.md +159 -27
  8. package/skills/qingflow-app-builder/references/create-app.md +52 -22
  9. package/skills/qingflow-app-builder/references/environments.md +1 -1
  10. package/skills/qingflow-app-builder/references/flow-actors-and-permissions.md +123 -0
  11. package/skills/qingflow-app-builder/references/gotchas.md +31 -4
  12. package/skills/qingflow-app-builder/references/solution-playbooks.md +23 -21
  13. package/skills/qingflow-app-builder/references/tool-selection.md +52 -24
  14. package/skills/qingflow-app-builder/references/update-flow.md +112 -25
  15. package/skills/qingflow-app-builder/references/update-layout.md +15 -28
  16. package/skills/qingflow-app-builder/references/update-schema.md +5 -27
  17. package/skills/qingflow-app-builder/references/update-views.md +87 -21
  18. package/skills/qingflow-app-builder-code-integrations/SKILL.md +137 -0
  19. package/skills/qingflow-app-builder-code-integrations/agents/openai.yaml +4 -0
  20. package/skills/qingflow-app-builder-code-integrations/references/code-block.md +66 -0
  21. package/skills/qingflow-app-builder-code-integrations/references/q-linker.md +77 -0
  22. package/src/qingflow_mcp/__init__.py +1 -1
  23. package/src/qingflow_mcp/backend_client.py +313 -0
  24. package/src/qingflow_mcp/builder_facade/models.py +1497 -9
  25. package/src/qingflow_mcp/builder_facade/service.py +14064 -2400
  26. package/src/qingflow_mcp/cli/__init__.py +1 -0
  27. package/src/qingflow_mcp/cli/commands/__init__.py +18 -0
  28. package/src/qingflow_mcp/cli/commands/app.py +40 -0
  29. package/src/qingflow_mcp/cli/commands/auth.py +78 -0
  30. package/src/qingflow_mcp/cli/commands/builder.py +538 -0
  31. package/src/qingflow_mcp/cli/commands/chart.py +18 -0
  32. package/src/qingflow_mcp/cli/commands/common.py +62 -0
  33. package/src/qingflow_mcp/cli/commands/imports.py +96 -0
  34. package/src/qingflow_mcp/cli/commands/portal.py +25 -0
  35. package/src/qingflow_mcp/cli/commands/record.py +331 -0
  36. package/src/qingflow_mcp/cli/commands/repo.py +80 -0
  37. package/src/qingflow_mcp/cli/commands/task.py +89 -0
  38. package/src/qingflow_mcp/cli/commands/view.py +18 -0
  39. package/src/qingflow_mcp/cli/commands/workspace.py +33 -0
  40. package/src/qingflow_mcp/cli/context.py +60 -0
  41. package/src/qingflow_mcp/cli/formatters.py +355 -0
  42. package/src/qingflow_mcp/cli/json_io.py +50 -0
  43. package/src/qingflow_mcp/cli/main.py +178 -0
  44. package/src/qingflow_mcp/config.py +186 -0
  45. package/src/qingflow_mcp/import_store.py +121 -0
  46. package/src/qingflow_mcp/list_type_labels.py +24 -0
  47. package/src/qingflow_mcp/public_surface.py +237 -0
  48. package/src/qingflow_mcp/repository_store.py +71 -0
  49. package/src/qingflow_mcp/response_trim.py +477 -0
  50. package/src/qingflow_mcp/server.py +160 -18
  51. package/src/qingflow_mcp/server_app_builder.py +310 -95
  52. package/src/qingflow_mcp/server_app_user.py +243 -191
  53. package/src/qingflow_mcp/session_store.py +41 -1
  54. package/src/qingflow_mcp/solution/compiler/form_compiler.py +42 -3
  55. package/src/qingflow_mcp/solution/compiler/icon_utils.py +119 -45
  56. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +32 -0
  57. package/src/qingflow_mcp/solution/executor.py +73 -4
  58. package/src/qingflow_mcp/solution/spec_models.py +2 -0
  59. package/src/qingflow_mcp/tools/ai_builder_tools.py +2546 -209
  60. package/src/qingflow_mcp/tools/app_tools.py +415 -12
  61. package/src/qingflow_mcp/tools/approval_tools.py +556 -73
  62. package/src/qingflow_mcp/tools/auth_tools.py +398 -2
  63. package/src/qingflow_mcp/tools/code_block_tools.py +756 -0
  64. package/src/qingflow_mcp/tools/custom_button_tools.py +179 -0
  65. package/src/qingflow_mcp/tools/directory_tools.py +203 -31
  66. package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
  67. package/src/qingflow_mcp/tools/file_tools.py +1 -0
  68. package/src/qingflow_mcp/tools/import_tools.py +2150 -0
  69. package/src/qingflow_mcp/tools/package_tools.py +67 -4
  70. package/src/qingflow_mcp/tools/portal_tools.py +31 -0
  71. package/src/qingflow_mcp/tools/qingbi_report_tools.py +109 -7
  72. package/src/qingflow_mcp/tools/record_tools.py +11052 -1365
  73. package/src/qingflow_mcp/tools/repository_dev_tools.py +533 -0
  74. package/src/qingflow_mcp/tools/resource_read_tools.py +399 -0
  75. package/src/qingflow_mcp/tools/solution_tools.py +115 -3
  76. package/src/qingflow_mcp/tools/task_context_tools.py +2163 -0
  77. package/src/qingflow_mcp/tools/task_tools.py +376 -225
  78. package/src/qingflow_mcp/tools/workspace_tools.py +163 -19
package/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @josephyan/qingflow-app-builder-mcp@0.2.0-beta.9
6
+ npm install @josephyan/qingflow-app-builder-mcp@0.2.0-beta.91
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-app-builder-mcp@0.2.0-beta.9 qingflow-app-builder-mcp
12
+ npx -y -p @josephyan/qingflow-app-builder-mcp@0.2.0-beta.91 qingflow-app-builder-mcp
13
13
  ```
14
14
 
15
15
  Environment:
@@ -20,11 +20,13 @@ Environment:
20
20
 
21
21
  This package bootstraps a local Python runtime on first install and then starts the `qingflow-app-builder-mcp` stdio MCP server.
22
22
 
23
- Bundled skill:
23
+ Bundled skills:
24
24
 
25
25
  - `skills/qingflow-app-builder`
26
+ - `skills/qingflow-app-builder-code-integrations`
26
27
 
27
28
  Note:
28
29
 
29
30
  - The skill files are included in the npm package.
30
31
  - On install, the package copies them to `$CODEX_HOME/skills` (or `~/.codex/skills` if `CODEX_HOME` is unset).
32
+ - If a stdio MCP client reports `Transport closed`, delete `.npm-python`, reinstall the package, and make sure CLI/user/builder packages are on the same version. The stdio entrypoints refuse runtime bootstrap so install logs never corrupt MCP stdout.
@@ -1,9 +1,10 @@
1
1
  # 本地智能体安装
2
2
 
3
- 这个目录下现在只保留两个本地命令入口:
3
+ 这个目录下现在有三个本地命令入口:
4
4
 
5
- 1. 记录/待办优先的 `qingflow-app-user-mcp`
6
- 2. 精简 builder 的 `qingflow-app-builder-mcp`
5
+ 1. 统一 CLI:`qingflow`
6
+ 2. 记录/待办优先的 `qingflow-app-user-mcp`
7
+ 3. 精简 builder 的 `qingflow-app-builder-mcp`
7
8
 
8
9
  ## npm 安装器适用场景
9
10
 
@@ -62,6 +63,7 @@ npm run pack:npm
62
63
  会生成:
63
64
 
64
65
  ```bash
66
+ dist/npm/josephyan-qingflow-cli-<version>.tgz
65
67
  dist/npm/josephyan-qingflow-app-user-mcp-<version>.tgz
66
68
  dist/npm/josephyan-qingflow-app-builder-mcp-<version>.tgz
67
69
  ```
@@ -69,6 +71,7 @@ dist/npm/josephyan-qingflow-app-builder-mcp-<version>.tgz
69
71
  然后在目标机器安装:
70
72
 
71
73
  ```bash
74
+ npm install /absolute/path/to/dist/npm/josephyan-qingflow-cli-<version>.tgz
72
75
  npm install /absolute/path/to/dist/npm/josephyan-qingflow-app-user-mcp-<version>.tgz
73
76
  npm install /absolute/path/to/dist/npm/josephyan-qingflow-app-builder-mcp-<version>.tgz
74
77
  ```
@@ -78,7 +81,7 @@ npm install /absolute/path/to/dist/npm/josephyan-qingflow-app-builder-mcp-<versi
78
81
  1. 创建 `.npm-python/`
79
82
  2. 在其中建立 Python 虚拟环境
80
83
  3. 执行 `pip install .`
81
- 4. 在安装位置暴露 `qingflow-app-user-mcp`、`qingflow-app-builder-mcp` 命令
84
+ 4. 在安装位置暴露 `qingflow`、`qingflow-app-user-mcp`、`qingflow-app-builder-mcp` 命令
82
85
 
83
86
  ## 本地验证
84
87
 
@@ -86,6 +89,7 @@ npm install /absolute/path/to/dist/npm/josephyan-qingflow-app-builder-mcp-<versi
86
89
 
87
90
  ```bash
88
91
  cd qingflow-support/mcp-server
92
+ node ./npm/bin/qingflow.mjs --help
89
93
  node ./npm/bin/qingflow-app-user-mcp.mjs
90
94
  node ./npm/bin/qingflow-app-builder-mcp.mjs
91
95
  ```
@@ -93,6 +97,7 @@ node ./npm/bin/qingflow-app-builder-mcp.mjs
93
97
  如果你是全局安装:
94
98
 
95
99
  ```bash
100
+ qingflow --help
96
101
  qingflow-app-user-mcp
97
102
  qingflow-app-builder-mcp
98
103
  ```
@@ -100,6 +105,7 @@ qingflow-app-builder-mcp
100
105
  如果你是把包安装到了某个本地 agent workspace,命令通常位于:
101
106
 
102
107
  ```bash
108
+ /absolute/path/to/agent-workspace/node_modules/.bin/qingflow
103
109
  /absolute/path/to/agent-workspace/node_modules/.bin/qingflow-app-user-mcp
104
110
  /absolute/path/to/agent-workspace/node_modules/.bin/qingflow-app-builder-mcp
105
111
  ```
@@ -107,6 +113,7 @@ qingflow-app-builder-mcp
107
113
  如果你是从 tgz 安装到某个空目录,命令通常位于:
108
114
 
109
115
  ```bash
116
+ /absolute/path/to/install-dir/node_modules/.bin/qingflow
110
117
  /absolute/path/to/install-dir/node_modules/.bin/qingflow-app-user-mcp
111
118
  /absolute/path/to/install-dir/node_modules/.bin/qingflow-app-builder-mcp
112
119
  ```
@@ -202,7 +209,7 @@ qingflow-app-builder-mcp
202
209
  ```
203
210
 
204
211
  说明:
205
- - 源码目录 `npm install` 不会把命令加到全局 PATH;这种模式请用 `node ./npm/bin/qingflow-app-user-mcp.mjs` 或 `node ./npm/bin/qingflow-app-builder-mcp.mjs`
212
+ - 源码目录 `npm install` 不会把命令加到全局 PATH;这种模式请用 `node ./npm/bin/qingflow.mjs`、`node ./npm/bin/qingflow-app-user-mcp.mjs` 或 `node ./npm/bin/qingflow-app-builder-mcp.mjs`
206
213
  - `npx` 方式适合临时安装或容器化本地 agent
207
214
  - 全局安装方式更适合长期固定使用的本机开发环境
208
215
 
@@ -226,3 +233,12 @@ rm -rf .npm-python
226
233
  ```bash
227
234
  npm install
228
235
  ```
236
+
237
+ 如果 MCP 客户端一调用工具就报 `Transport closed`,优先检查这几件事:
238
+
239
+ 1. 不要混用不同版本的 `@josephyan/qingflow-cli`、`@josephyan/qingflow-app-user-mcp`、`@josephyan/qingflow-app-builder-mcp`
240
+ 2. 删除安装目录下的 `.npm-python`
241
+ 3. 重新执行 `npm install` 或重新安装对应 tgz/npm 包
242
+ 4. 再启动 MCP 客户端
243
+
244
+ 现在 stdio MCP 入口会拒绝在启动瞬间“边启动边重建 Python 运行时”,因为安装日志一旦写进 stdout,就会破坏 MCP 握手并表现成 `Transport closed`。如果运行时缺失或版本不一致,入口会直接报错并提示重装,而不是静默自修复。
@@ -4,4 +4,4 @@ import { getPackageRoot, spawnServer } from "../lib/runtime.mjs";
4
4
 
5
5
  const packageRoot = getPackageRoot(import.meta.url);
6
6
 
7
- spawnServer(packageRoot, process.argv.slice(2), "qingflow-app-builder-mcp");
7
+ spawnServer(packageRoot, process.argv.slice(2), "qingflow-app-builder-mcp", { allowRuntimeBootstrap: false });
@@ -7,14 +7,16 @@ const WINDOWS = process.platform === "win32";
7
7
 
8
8
  function runChecked(command, args, options = {}) {
9
9
  const result = spawnSync(command, args, {
10
- stdio: "inherit",
10
+ encoding: "utf8",
11
+ stdio: ["ignore", "pipe", "pipe"],
11
12
  ...options,
12
13
  });
13
14
  if (result.error) {
14
15
  throw result.error;
15
16
  }
16
17
  if (result.status !== 0) {
17
- throw new Error(`Command failed: ${command} ${args.join(" ")}`);
18
+ const details = [result.stderr, result.stdout].filter((value) => typeof value === "string" && value.trim()).join("\n");
19
+ throw new Error(details ? `Command failed: ${command} ${args.join(" ")}\n${details}` : `Command failed: ${command} ${args.join(" ")}`);
18
20
  }
19
21
  }
20
22
 
@@ -82,6 +84,105 @@ export function getVenvServerCommand(packageRoot, commandName = "qingflow-mcp")
82
84
  : path.join(getVenvDir(packageRoot), "bin", commandName);
83
85
  }
84
86
 
87
+ function readPackageVersion(packageRoot) {
88
+ const packageJsonPath = path.join(packageRoot, "package.json");
89
+ try {
90
+ const payload = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
91
+ return typeof payload.version === "string" && payload.version.trim() ? payload.version.trim() : null;
92
+ } catch {
93
+ return null;
94
+ }
95
+ }
96
+
97
+ function readBootstrapStamp(stampPath) {
98
+ if (!fs.existsSync(stampPath)) {
99
+ return { exists: false, version: null };
100
+ }
101
+ try {
102
+ const payload = JSON.parse(fs.readFileSync(stampPath, "utf8"));
103
+ const version = typeof payload.package_version === "string" && payload.package_version.trim() ? payload.package_version.trim() : null;
104
+ return { exists: true, version };
105
+ } catch {
106
+ return { exists: true, version: null };
107
+ }
108
+ }
109
+
110
+ export function inspectPythonEnv(packageRoot, commandName = "qingflow-mcp") {
111
+ const venvDir = getVenvDir(packageRoot);
112
+ const serverCommand = getVenvServerCommand(packageRoot, commandName);
113
+ const stampPath = path.join(venvDir, ".bootstrap.json");
114
+ const packageVersion = readPackageVersion(packageRoot);
115
+ const stamp = readBootstrapStamp(stampPath);
116
+ const problems = [];
117
+
118
+ if (!packageVersion) {
119
+ problems.push("package-version-missing");
120
+ }
121
+ if (!fs.existsSync(serverCommand)) {
122
+ problems.push("server-command-missing");
123
+ }
124
+ if (!stamp.exists) {
125
+ problems.push("bootstrap-stamp-missing");
126
+ } else if (!stamp.version) {
127
+ problems.push("bootstrap-stamp-invalid");
128
+ } else if (packageVersion && stamp.version !== packageVersion) {
129
+ problems.push("bootstrap-version-mismatch");
130
+ }
131
+
132
+ return {
133
+ ready: problems.length === 0,
134
+ packageVersion,
135
+ stampVersion: stamp.version,
136
+ stampExists: stamp.exists,
137
+ stampPath,
138
+ serverCommand,
139
+ venvDir,
140
+ problems,
141
+ };
142
+ }
143
+
144
+ function formatRuntimeProblem(runtime, commandName, { allowRuntimeBootstrap = false } = {}) {
145
+ const problemLines = [];
146
+ for (const problem of runtime.problems) {
147
+ switch (problem) {
148
+ case "package-version-missing":
149
+ problemLines.push("- package.json is missing a valid version field");
150
+ break;
151
+ case "server-command-missing":
152
+ problemLines.push(`- missing Python entrypoint: ${runtime.serverCommand}`);
153
+ break;
154
+ case "bootstrap-stamp-missing":
155
+ problemLines.push(`- missing bootstrap stamp: ${runtime.stampPath}`);
156
+ break;
157
+ case "bootstrap-stamp-invalid":
158
+ problemLines.push(`- bootstrap stamp is unreadable or invalid: ${runtime.stampPath}`);
159
+ break;
160
+ case "bootstrap-version-mismatch":
161
+ problemLines.push(
162
+ `- bootstrap version mismatch: package=${runtime.packageVersion ?? "unknown"}, runtime=${runtime.stampVersion ?? "unknown"}`
163
+ );
164
+ break;
165
+ default:
166
+ problemLines.push(`- ${problem}`);
167
+ break;
168
+ }
169
+ }
170
+
171
+ const action = allowRuntimeBootstrap
172
+ ? "Delete .npm-python and retry, or rerun npm install to rebuild the embedded Python runtime."
173
+ : "Delete .npm-python and rerun npm install, or reinstall the npm package before starting the MCP server.";
174
+
175
+ const bootstrapNote = allowRuntimeBootstrap
176
+ ? ""
177
+ : "\nRuntime bootstrap is disabled for stdio MCP entrypoints so install logs can never corrupt the MCP stdout transport.";
178
+
179
+ return [
180
+ `[qingflow-mcp] Python runtime for ${commandName} is not ready.`,
181
+ ...problemLines,
182
+ action + bootstrapNote,
183
+ ].join("\n");
184
+ }
185
+
85
186
  function getVenvPip(packageRoot) {
86
187
  return WINDOWS
87
188
  ? path.join(getVenvDir(packageRoot), "Scripts", "pip.exe")
@@ -119,12 +220,11 @@ export function findPython() {
119
220
 
120
221
  export function ensurePythonEnv(packageRoot, { force = false, commandName = "qingflow-mcp" } = {}) {
121
222
  const python = findPython();
122
- const venvDir = getVenvDir(packageRoot);
223
+ const runtime = inspectPythonEnv(packageRoot, commandName);
123
224
  const venvPython = getVenvPython(packageRoot);
124
- const serverCommand = getVenvServerCommand(packageRoot, commandName);
125
- const stampPath = path.join(venvDir, ".bootstrap.json");
225
+ const { packageVersion, serverCommand, stampPath, venvDir, stampVersion } = runtime;
126
226
 
127
- if (!force && fs.existsSync(serverCommand) && fs.existsSync(stampPath)) {
227
+ if (!force && fs.existsSync(serverCommand) && fs.existsSync(stampPath) && stampVersion && stampVersion === packageVersion) {
128
228
  return serverCommand;
129
229
  }
130
230
 
@@ -145,6 +245,7 @@ export function ensurePythonEnv(packageRoot, { force = false, commandName = "qin
145
245
  {
146
246
  installed_at: new Date().toISOString(),
147
247
  installer: "npm",
248
+ package_version: packageVersion,
148
249
  },
149
250
  null,
150
251
  2
@@ -158,17 +259,72 @@ export function ensurePythonEnv(packageRoot, { force = false, commandName = "qin
158
259
  return serverCommand;
159
260
  }
160
261
 
161
- export function spawnServer(packageRoot, args, commandName = "qingflow-mcp") {
162
- const serverCommand = fs.existsSync(getVenvServerCommand(packageRoot, commandName))
163
- ? getVenvServerCommand(packageRoot, commandName)
164
- : ensurePythonEnv(packageRoot, { commandName });
262
+ function proxyStreams(child) {
263
+ if (process.stdin.readable && child.stdin) {
264
+ process.stdin.pipe(child.stdin);
265
+ child.stdin.on("error", (error) => {
266
+ if (error.code !== "EPIPE") {
267
+ console.error(`[qingflow-mcp] Failed to forward stdin: ${error.message}`);
268
+ }
269
+ });
270
+ } else if (child.stdin) {
271
+ child.stdin.end();
272
+ }
273
+
274
+ if (child.stdout) {
275
+ child.stdout.pipe(process.stdout);
276
+ }
277
+ if (child.stderr) {
278
+ child.stderr.pipe(process.stderr);
279
+ }
280
+ }
281
+
282
+ function forwardSignal(child, signal) {
283
+ process.on(signal, () => {
284
+ if (!child.killed) {
285
+ child.kill(signal);
286
+ }
287
+ });
288
+ }
289
+
290
+ export function spawnServer(packageRoot, args, commandName = "qingflow-mcp", { allowRuntimeBootstrap = false } = {}) {
291
+ let runtime = inspectPythonEnv(packageRoot, commandName);
292
+ let serverCommand = runtime.serverCommand;
293
+
294
+ if (!runtime.ready) {
295
+ if (!allowRuntimeBootstrap) {
296
+ console.error(formatRuntimeProblem(runtime, commandName, { allowRuntimeBootstrap }));
297
+ process.exit(1);
298
+ return;
299
+ }
300
+
301
+ try {
302
+ serverCommand = ensurePythonEnv(packageRoot, { commandName });
303
+ runtime = inspectPythonEnv(packageRoot, commandName);
304
+ } catch (error) {
305
+ console.error(`[qingflow-mcp] Failed to prepare Python runtime for ${commandName}: ${error.message}`);
306
+ process.exit(1);
307
+ return;
308
+ }
309
+
310
+ if (!runtime.ready) {
311
+ console.error(formatRuntimeProblem(runtime, commandName, { allowRuntimeBootstrap }));
312
+ process.exit(1);
313
+ return;
314
+ }
315
+ }
165
316
 
166
317
  const child = spawn(serverCommand, args, {
167
- stdio: "inherit",
318
+ stdio: ["pipe", "pipe", "pipe"],
168
319
  env: process.env,
320
+ windowsHide: true,
169
321
  });
170
322
 
171
- child.on("exit", (code, signal) => {
323
+ proxyStreams(child);
324
+ forwardSignal(child, "SIGINT");
325
+ forwardSignal(child, "SIGTERM");
326
+
327
+ child.on("close", (code, signal) => {
172
328
  if (signal) {
173
329
  process.kill(process.pid, signal);
174
330
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@josephyan/qingflow-app-builder-mcp",
3
- "version": "0.2.0-beta.9",
3
+ "version": "0.2.0-beta.91",
4
4
  "description": "Builder MCP for Qingflow app/package/system design and staged solution workflows.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qingflow-mcp"
7
- version = "0.2.0b9"
7
+ version = "0.2.0b91"
8
8
  description = "User-authenticated MCP server for Qingflow"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -26,8 +26,10 @@ dependencies = [
26
26
  "mcp>=1.9.4,<2.0.0",
27
27
  "httpx>=0.27,<1.0",
28
28
  "keyring>=25.5,<26.0",
29
+ "openpyxl>=3.1,<4.0",
29
30
  "pydantic>=2.8,<3.0",
30
31
  "pycryptodome>=3.20,<4.0",
32
+ "python-socketio[client]>=5.11,<6.0",
31
33
  ]
32
34
 
33
35
  [project.optional-dependencies]
@@ -44,6 +46,7 @@ build = [
44
46
  [project.scripts]
45
47
  qingflow-app-user-mcp = "qingflow_mcp.server_app_user:main"
46
48
  qingflow-app-builder-mcp = "qingflow_mcp.server_app_builder:main"
49
+ qingflow = "qingflow_mcp.cli.main:main"
47
50
 
48
51
  [project.urls]
49
52
  Homepage = "https://github.com/qingflow/qingflow-mcp"
@@ -17,23 +17,80 @@ Before any write or verification flow, identify whether the task targets `test`
17
17
 
18
18
  Pick the smallest tool layer that can finish the task.
19
19
 
20
+ ## Shared Helper
21
+
22
+ - `feedback_submit` is a cross-cutting helper for product feedback submission
23
+ - It does not require Qingflow login or workspace selection
24
+ - If builder capability is unsupported or still cannot satisfy the need after reasonable use, summarize the gap, ask whether to submit feedback, and call `feedback_submit` only after explicit user confirmation.
25
+
26
+ ## Mental Model
27
+
28
+ Model builder requests in four layers. Do not flatten them.
29
+
30
+ - `package`: the solution container or app bundle, for example “研发项目管理” or “费控管理系统”
31
+ - `app`: one form/app inside that package, for example “项目”, “需求”, “任务”, “缺陷”, “团队”
32
+ - `field`: one field inside one app
33
+ - `relation`: a field that links one app to another app
34
+
35
+ Interpret user intent with this hierarchy:
36
+
37
+ - If the user says “应用包”, “系统”, “包含多个表单”, “多个模块”, or asks for several named forms that relate to each other, treat it as a `package` with multiple `apps`
38
+ - Do not compress a multi-app system into one app with several text fields
39
+ - Names like “项目/需求/任务/缺陷/团队” or “费用申请/预算管理/报销审批” are usually separate apps, not text fields
40
+ - Build the apps first, then add `relation` fields to connect them
41
+
42
+ Default modeling rules:
43
+
44
+ - One business object -> one app
45
+ - Attributes of that object -> fields inside that app
46
+ - Another business object -> a separate app, not a text field
47
+ - Cross-object links -> relation fields, not text fields
48
+
20
49
  - Authentication and workspace: `auth_*`, `workspace_*`
21
50
  - File upload: `file_upload_local`
22
- - Resource resolve/read: `package_list`, `package_resolve`, `app_resolve`, `app_read_summary`, `app_read_fields`, `app_read_layout_summary`, `app_read_views_summary`, `app_read_flow_summary`
23
- - Resource plan: `app_schema_plan`, `app_layout_plan`, `app_flow_plan`, `app_views_plan`
24
- - Resource patch: `app_schema_apply`, `app_layout_apply`, `app_flow_apply`, `app_views_apply`, `package_attach_app`, `app_release_edit_lock_if_mine`
51
+ - Resource resolve/read: `package_list`, `package_resolve`, `package_create`, `builder_tool_contract`, `member_search`, `role_search`, `app_resolve`, `app_get`, `app_get_fields`, `app_get_layout`, `app_get_views`, `app_get_flow`, `app_get_charts`, `portal_list`, `portal_get`, `view_get`, `chart_get`
52
+ - Resource patch: `app_schema_apply`, `app_layout_apply`, `app_flow_apply`, `app_views_apply`, `app_charts_apply`, `portal_apply`, `package_attach_app`, `app_release_edit_lock_if_mine`, `role_create`
25
53
  - Publish and verify: `app_publish_verify`
26
54
 
27
55
  Note:
28
56
  - Do not try to handcraft raw Qingflow schema or internal solution payloads.
29
- - Do not rely on low-level `view_*`, `workflow_*`, or `qingbi_report_*` write payloads from public builder flows.
30
- - Public builder work should stay on the resource path: `resolve -> summary read -> plan -> apply -> attach -> publish_verify`.
57
+ - Do not rely on low-level `view_*`, `workflow_*`, `qingbi_report_*`, or `portal_*` write payloads from public builder flows.
58
+ - Public builder work should stay on the resource path: `resolve -> summary read -> apply -> attach -> publish_verify`.
31
59
  - `app_schema_apply` / `app_layout_apply` / `app_flow_apply` / `app_views_apply` publish by default; pass `publish=false` only when you intentionally want to leave changes in draft.
60
+ - `app_charts_apply` is immediate-live and does not publish; it resolves targets by `chart_id` first and then exact unique chart name.
61
+ - `portal_apply` uses replace semantics for sections; remove a section by omitting it from the next full sections list. `publish=false` only guarantees draft/base-info updates, and `chart_ref/view_ref` resolve by `id/key` first and exact unique name second.
62
+ - `app_get_charts` is the compact discovery path for current chart inventory; use it before `app_charts_apply` when you need exact `chart_id` values.
63
+ - `chart_get` returns one chart's base info and config only; public builder flows should treat data reads as user-side access, not builder config access.
64
+ - `portal_list` is the discovery path for builder-configurable portals.
65
+ - `portal_get` returns portal-level config detail plus a component inventory; it does not inline chart/view detail or user-side chart/view data.
66
+ - `view_get` returns one view's definition detail only; use `record_list` separately when you need rows from that view.
67
+ - `app_schema_apply` / `app_layout_apply` / `app_flow_apply` / `app_views_apply` now perform planning, normalization, and dependency checks internally; when prechecks block, read the returned blocking issues and `suggested_next_call` directly from the apply result.
68
+ - If you are unsure about a public builder tool's keys, aliases, presets, or minimal legal shape, call `builder_tool_contract` instead of guessing.
69
+ - For views, always write the canonical key `columns`. Do not emit `column_names`; treat `fields` only as a tolerated legacy alias, not the preferred shape.
70
+ - For flow presets, map natural language to canonical values before calling MCP:
71
+ - “默认审批/基础审批/普通审批” -> `basic_approval`
72
+ - “先填报再审批/提交后审批” -> `basic_fill_then_approve`
73
+ - Public flow building is intentionally limited to linear workflows:
74
+ - `start`
75
+ - `approve`
76
+ - `fill`
77
+ - `copy`
78
+ - `webhook`
79
+ - `end`
80
+ Do not generate `branch` or `condition` nodes in the public builder surface. The backend workflow route is not front-end stable for those node types.
81
+ - For first-time flow or view work in a session, read `builder_tool_contract` before planning so keys, aliases, presets, and minimal examples come from MCP instead of memory.
82
+ - For workflow assignees, prefer roles over explicit members:
83
+ - use `role_search` first
84
+ - use `member_search` only when the user explicitly names members or no stable role exists
85
+ - use `role_create` when the business owner wants a reusable directory role instead of hard-coded members
86
+ - On any `VALIDATION_ERROR`, inspect `suggested_next_call` first and prefer reusing the MCP-normalized arguments over re-guessing from the original natural language.
87
+ - If the current MCP capability is unsupported, the workflow is awkward, or the user's need still cannot be satisfied after reasonable use, summarize the gap, ask whether to submit feedback, and call `feedback_submit` only after explicit user confirmation.
32
88
 
33
89
  Default policy:
34
90
 
35
- - Creating or updating one app inside an existing package: resolve the package/app, read compact state, plan patches on the server, then apply schema/layout/flow/views patches explicitly.
36
- - Do not create a new package unless the user explicitly asks for package separation.
91
+ - Creating or updating one app inside an existing package: resolve the package/app, read compact state, then apply schema/layout/flow/views patches explicitly.
92
+ - If package creation looks necessary or beneficial, ask the user to confirm before calling `package_create`.
93
+ - If the user describes a system/package with multiple forms or modules, do not start with `app_schema_apply` on the package name. Resolve or create the package first, then create each app separately.
37
94
 
38
95
  ## Standard Operating Order
39
96
 
@@ -45,19 +102,51 @@ Before any business tool:
45
102
 
46
103
  For builder work:
47
104
 
48
- 1. Resolve the target package with `package_resolve` when the user gives a package name. If resolution is ambiguous or you need a read-only fallback, use `package_list`.
49
- 2. Resolve the target app with `app_resolve` if the request is an update.
50
- 3. Read only the smallest summary you need: `app_read_summary`, `app_read_fields`, `app_read_layout_summary`, `app_read_views_summary`, `app_read_flow_summary`.
51
- 4. Use `app_schema_plan`, `app_layout_plan`, `app_flow_plan`, or `app_views_plan` before any non-trivial write.
52
- 5. Use `app_schema_apply` for create/upsert/remove field work. It publishes by default after the patch lands.
53
- 6. If the app must belong to a package, use `package_attach_app` explicitly after schema work unless readback already shows the target `tag_id`.
54
- 7. Use `app_layout_apply` only when the user is explicitly changing layout. Prefer the default `mode=merge`; use `mode=replace` only when you intend to place every field explicitly. It publishes by default.
55
- 8. Use `app_flow_apply` after schema exists. It publishes by default.
56
- 9. Use `app_views_apply` when the user wants explicit list/card/board views. It publishes by default.
105
+ 1. Resolve the target package with `package_resolve`; if resolution is ambiguous or you need a read-only fallback, use `package_list`. If you believe a new package should be created, ask the user to confirm before calling `package_create`.
106
+ 2. Decide whether the target is one app or a multi-app package:
107
+ - one app: continue with `app_resolve`
108
+ - multi-app package/system: create or resolve the package, then create each app separately before adding relations
109
+ 3. Resolve the target app with `app_resolve` if the request is an update. Use exactly one selector mode: `app_key`, or `app_name + package_tag_id`.
110
+ 4. Read only the smallest config slice you need: `app_get`, `app_get_fields`, `app_get_layout`, `app_get_views`, `app_get_flow`, `app_get_charts`, `portal_get`.
111
+ 5. Use `app_schema_apply` for create/upsert/remove field work. It publishes by default after the patch lands; noop schema requests do not publish.
112
+ 6. If the app must belong to a package, use `package_attach_app` explicitly after schema work with `tag_id + app_key` unless readback already shows the target `tag_id`.
113
+ 7. Use `app_layout_apply` only when the user is explicitly changing layout. Prefer the default `mode=merge`; use `mode=replace` only when you intend to place every field explicitly. It publishes by default after the patch lands; noop layout requests do not publish.
114
+ 8. Use `app_flow_apply` after schema exists. It publishes by default; pass `publish=false` when you only want draft/precheck behavior.
115
+ 9. Use `app_views_apply` when the user wants explicit table/card/board/gantt views. It publishes by default after the patch lands; noop view requests do not publish.
57
116
  10. Use `app_publish_verify` only when the user explicitly wants final publish/live verification or you need an explicit verification pass.
58
117
  11. If a write fails with `APP_EDIT_LOCKED`, stop normal writes. Only use `app_release_edit_lock_if_mine` when the failed result shows the lock owner is the current logged-in user.
59
118
 
60
- In `prod`, keep `plan` and `apply` as separate phases unless the user explicitly asks for a direct live execution.
119
+ For view work, keep the order strict:
120
+
121
+ 1. `builder_tool_contract`
122
+ 2. `app_get_fields`
123
+ 3. `app_get_views`
124
+ 4. `app_views_apply`
125
+ 5. `app_get_views` again whenever `app_views_apply` returns `failed` or `partial_success`
126
+
127
+ For flow work, keep the order strict:
128
+
129
+ 1. `builder_tool_contract`
130
+ 2. `app_get_fields`
131
+ 3. `app_get_flow`
132
+ 4. `role_search` or `member_search` if assignees need to come from the directory
133
+ 5. `role_create` if the user wants a reusable role and no suitable role exists yet
134
+ 6. Start from a canonical preset when possible
135
+ 7. Use patch-style edits to that skeleton instead of freehand full-graph generation
136
+ 8. When patching a preset skeleton, reuse the preset node ids:
137
+ - `basic_approval` -> patch `approve_1`
138
+ - `basic_fill_then_approve` -> patch `fill_1` and `approve_1`
139
+ Do not invent a second approval/fill node id unless you are intentionally replacing the skeleton and removing the preset node.
140
+ 9. Declare approver/fill/copy assignees explicitly:
141
+ - prefer `assignees.role_names`
142
+ - support `assignees.member_names` / `assignees.member_emails` / `assignees.member_uids`
143
+ 10. When a node must edit specific fields, declare `permissions.editable_fields`
144
+ 11. `app_flow_apply`
145
+ 12. `app_get_flow` after apply whenever the user asked for verification or apply returns `partial_success`
146
+ 13. Use `app_get_charts` before chart work whenever exact current `chart_id` values matter
147
+ 14. Use `app_charts_apply` for QingBI chart creation and updates, not raw `qingbi_report_*` writes
148
+ 15. Use `portal_get` before portal work whenever exact current portal config and section inventory matter
149
+ 16. Use `portal_apply` for builder-side portal work; treat sections as a full replacement list
61
150
 
62
151
  For additive work on existing systems:
63
152
 
@@ -70,10 +159,29 @@ For additive work on existing systems:
70
159
 
71
160
  - When using `package_name`, expect deterministic resolution only on a unique exact package name match; if multiple packages match, stop and resolve the ambiguity instead of guessing
72
161
  - `app_schema_apply` is the only public patch tool allowed to create an app shell; `app_layout_apply`, `app_flow_apply`, and `app_views_apply` require an existing app.
73
- - Prefer `*_plan` before `*_apply`; the plan tools do server-side normalization and return the next executable call skeleton.
162
+ - Prefer reading compact state plus `builder_tool_contract` before `*_apply`; apply already performs server-side normalization and returns blocking issues or the next executable call shape when needed.
163
+ - For abstract requests like “默认视图”, “基础审批流”, “灵活流程”, or “美观布局”, first translate the intent into a stable preset or explicit patch. Do not send those phrases to MCP unchanged.
164
+ - If the user asks for a business system or package that contains several forms, do not use the system/package name as `app_name` and do not try to store the child app names as text fields.
165
+ - For flexible workflow requests, split the work into two steps:
166
+ 1. create a base skeleton with a preset
167
+ 2. apply explicit business-specific changes as patchable nodes/transitions
168
+ - For preset-based flows, treat preset node ids as part of the public contract. Patch the skeleton nodes by the same ids instead of creating a parallel node with a new id and leaving the preset node unassigned.
169
+ - Approval, fill, and copy nodes must declare at least one assignee. Treat this as a hard requirement, not an optional detail.
170
+ - For workflow nodes, use the canonical public shape:
171
+ - `assignees.role_names`
172
+ - `assignees.member_names`
173
+ - `permissions.editable_fields`
174
+ - For layout work, keep the public section shape canonical:
175
+ - `title`
176
+ - `rows`
177
+ Do not invent top-level layout `columns`, and do not prefer `fields` or `field_ids` once `rows` is known.
178
+ - Translate natural language like “一行四个字段 / 四列布局 / 每行放四个” into `rows` matrices, not guessed layout parameters.
179
+ - If the same layout-shape `VALIDATION_ERROR` repeats twice, stop guessing and re-read `builder_tool_contract(app_layout_apply)` or the layout reference before trying again.
180
+ - Do not guess role ids or member ids. Resolve them from the directory first.
74
181
  - `app_schema_apply` does not treat package attachment as success criteria; if package ownership matters, verify `tag_ids_after` and call `package_attach_app` explicitly.
75
182
  - `package_attach_app` is the source of truth for package ownership; do not assume app creation or publish implicitly attaches the app.
76
183
  - `relation` and `subtable` must be explicit; do not infer them from vague natural language.
184
+ - Another app is not a field. If two business objects should both have their own records, build two apps and connect them with relation fields.
77
185
  - In `prod`, prefer explicit patch tools and avoid any speculative create flow.
78
186
  - Never try to bypass collaborative edit locks. `app_release_edit_lock_if_mine` is only for the case where the lock owner is the current authenticated user.
79
187
 
@@ -81,27 +189,50 @@ For additive work on existing systems:
81
189
 
82
190
  - low-level list totals from the backend may report `0` while rows are present; prefer summary or aggregate readbacks for final conclusions
83
191
  - `app_publish_verify` is the publish source of truth.
192
+ - All public builder tools expose top-level `warnings`, `verification`, and `verified`. Read them before deciding whether a run is fully done.
193
+ - For read tools, `status=success` can still pair with `verified=false` when some optional readback is unavailable; in that case prefer `warnings` and `verification` over the bare status code.
194
+ - For `app_charts_apply`, `portal_apply`, and `app_publish_verify`, treat `success` as “write and verification completed” and `partial_success` as “write executed but verification is incomplete”.
195
+ - For `app_schema_apply`, multiple relation fields are a known high-risk backend area. If you see `RELATION_FIELD_LIMIT_RISK` or `verification.relation_field_limit_verified=false`, do not describe the schema as fully safe.
196
+ - For `app_layout_apply`, trust `verification.layout_verified` first and `verification.layout_summary_verified` second. `LAYOUT_SUMMARY_UNVERIFIED` means the raw form readback is more trustworthy than the compact summary.
197
+ - For `app_views_apply`, treat `verification.views_verified` and `verification.view_filters_verified` separately. A created view with unverified filters is not a finished business view.
198
+ - For `app_flow_apply`, treat only linear node structure as publicly supported. If you see `FLOW_NODE_TYPE_UNSUPPORTED`, redesign the workflow as a stable linear flow instead of retrying branch/condition nodes.
84
199
  - If readback mismatches the UI, compare `request_route` and do not assume the builder hit the same `qf_version` as the browser
85
200
  - Treat post-write readback as the source of truth, not just write status codes
201
+ - For views, a top-level `VIEW_APPLY_FAILED` does not prove all requested views failed. Read back the view list and verify which views actually landed.
202
+ - For views, “view exists” is not the same as “filters are active”. If `app_views_apply` returns `partial_success`, `views_verified=false`, or `details.filter_mismatches`, report the view as created but the filters as unverified until readback confirms them.
203
+ - If multiple views share the same name, do not guess which one to update. Read `view_key` from `app_get_views` and pass it explicitly in `upsert_views[]`.
204
+ - In final user-facing summaries, distinguish clearly between:
205
+ - contract is visible / canonical shape is known
206
+ - apply precheck succeeded
207
+ - apply landed and readback verified it
208
+ - base template/skeleton applied
209
+ - business-specific rules completed
210
+ - remaining gaps or follow-up patches
211
+ - Do not report “流程已满足业务需求” when only a preset skeleton has landed.
86
212
 
87
213
  ## Practical Patterns
88
214
 
89
215
  - List packages: `package_list`
90
216
  - Resolve one package: `package_resolve`
217
+ - Create one package: `package_create`
218
+ - Read one public tool contract: `builder_tool_contract`
91
219
  - Resolve one app: `app_resolve`
92
- - Read one app summary: `app_read_summary`
93
- - Read fields only: `app_read_fields`
94
- - Read layout summary: `app_read_layout_summary`
95
- - Read views summary: `app_read_views_summary`
96
- - Read flow summary: `app_read_flow_summary`
97
- - Plan schema patch: `app_schema_plan`
98
- - Plan layout patch: `app_layout_plan`
99
- - Plan workflow patch: `app_flow_plan`
100
- - Plan view patch: `app_views_plan`
220
+ - Read one app summary: `app_get`
221
+ - Read fields only: `app_get_fields`
222
+ - Read layout summary: `app_get_layout`
223
+ - Read views summary: `app_get_views`
224
+ - Read flow summary: `app_get_flow`
225
+ - Read chart summary: `app_get_charts`
226
+ - Read portal config: `portal_get`
227
+ - Search members for workflow assignees: `member_search`
228
+ - Search roles for workflow assignees: `role_search`
229
+ - Create reusable workflow role: `role_create`
101
230
  - Add/update/remove fields: `app_schema_apply`
102
231
  - Merge or replace layout: `app_layout_apply`
103
232
  - Replace workflow: `app_flow_apply`
104
233
  - Upsert/remove views: `app_views_apply`
234
+ - Upsert/remove/reorder QingBI charts: `app_charts_apply`
235
+ - Create or replace-update portal pages: `portal_apply`
105
236
  - Attach one app to a package: `package_attach_app`
106
237
  - Release your own stale edit lock: `app_release_edit_lock_if_mine`
107
238
  - Publish and verify: `app_publish_verify` when you need a separate verification pass beyond the default auto-publish behavior in apply tools
@@ -115,5 +246,6 @@ Detailed playbooks:
115
246
  - Update fields only: [references/update-schema.md](references/update-schema.md)
116
247
  - Update layout only: [references/update-layout.md](references/update-layout.md)
117
248
  - Update workflow only: [references/update-flow.md](references/update-flow.md)
249
+ - Workflow assignees and node permissions: [references/flow-actors-and-permissions.md](references/flow-actors-and-permissions.md)
118
250
  - Update views only: [references/update-views.md](references/update-views.md)
119
251
  - Standard end-to-end builder sequences: [references/solution-playbooks.md](references/solution-playbooks.md)