@josephyan/qingflow-app-user-mcp 0.2.0-beta.6 → 0.2.0-beta.60

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 (71) hide show
  1. package/README.md +10 -3
  2. package/docs/local-agent-install.md +21 -5
  3. package/npm/bin/qingflow-app-user-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-user/SKILL.md +67 -146
  8. package/skills/qingflow-app-user/agents/openai.yaml +2 -2
  9. package/skills/qingflow-app-user/references/data-gotchas.md +19 -39
  10. package/skills/qingflow-app-user/references/record-patterns.md +31 -52
  11. package/skills/qingflow-app-user/references/workflow-usage.md +10 -8
  12. package/skills/qingflow-record-analysis/SKILL.md +158 -0
  13. package/skills/qingflow-record-analysis/agents/openai.yaml +4 -0
  14. package/skills/qingflow-record-analysis/references/analysis-gotchas.md +145 -0
  15. package/skills/qingflow-record-analysis/references/analysis-patterns.md +125 -0
  16. package/skills/qingflow-record-analysis/references/confidence-reporting.md +92 -0
  17. package/skills/qingflow-record-analysis/references/dsl-templates.md +93 -0
  18. package/skills/qingflow-record-delete/SKILL.md +29 -0
  19. package/skills/qingflow-record-import/SKILL.md +31 -0
  20. package/skills/qingflow-record-insert/SKILL.md +58 -0
  21. package/skills/qingflow-record-update/SKILL.md +42 -0
  22. package/skills/qingflow-task-ops/SKILL.md +123 -0
  23. package/skills/qingflow-task-ops/agents/openai.yaml +4 -0
  24. package/skills/qingflow-task-ops/references/environments.md +44 -0
  25. package/skills/qingflow-task-ops/references/workflow-usage.md +27 -0
  26. package/src/qingflow_mcp/__init__.py +1 -1
  27. package/src/qingflow_mcp/backend_client.py +211 -0
  28. package/src/qingflow_mcp/builder_facade/models.py +640 -1
  29. package/src/qingflow_mcp/builder_facade/service.py +5767 -271
  30. package/src/qingflow_mcp/cli/__init__.py +1 -0
  31. package/src/qingflow_mcp/cli/commands/__init__.py +15 -0
  32. package/src/qingflow_mcp/cli/commands/app.py +40 -0
  33. package/src/qingflow_mcp/cli/commands/auth.py +78 -0
  34. package/src/qingflow_mcp/cli/commands/builder.py +184 -0
  35. package/src/qingflow_mcp/cli/commands/common.py +47 -0
  36. package/src/qingflow_mcp/cli/commands/imports.py +86 -0
  37. package/src/qingflow_mcp/cli/commands/record.py +202 -0
  38. package/src/qingflow_mcp/cli/commands/task.py +87 -0
  39. package/src/qingflow_mcp/cli/commands/workspace.py +33 -0
  40. package/src/qingflow_mcp/cli/context.py +48 -0
  41. package/src/qingflow_mcp/cli/formatters.py +269 -0
  42. package/src/qingflow_mcp/cli/json_io.py +50 -0
  43. package/src/qingflow_mcp/cli/main.py +147 -0
  44. package/src/qingflow_mcp/config.py +39 -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/server.py +159 -18
  48. package/src/qingflow_mcp/server_app_builder.py +132 -72
  49. package/src/qingflow_mcp/server_app_user.py +172 -189
  50. package/src/qingflow_mcp/session_store.py +41 -1
  51. package/src/qingflow_mcp/solution/compiler/form_compiler.py +14 -4
  52. package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +41 -2
  53. package/src/qingflow_mcp/solution/executor.py +107 -11
  54. package/src/qingflow_mcp/tools/ai_builder_tools.py +1390 -131
  55. package/src/qingflow_mcp/tools/app_tools.py +390 -12
  56. package/src/qingflow_mcp/tools/approval_tools.py +411 -76
  57. package/src/qingflow_mcp/tools/auth_tools.py +139 -1
  58. package/src/qingflow_mcp/tools/code_block_tools.py +679 -0
  59. package/src/qingflow_mcp/tools/directory_tools.py +203 -31
  60. package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
  61. package/src/qingflow_mcp/tools/file_tools.py +1 -0
  62. package/src/qingflow_mcp/tools/import_tools.py +1971 -0
  63. package/src/qingflow_mcp/tools/package_tools.py +17 -4
  64. package/src/qingflow_mcp/tools/portal_tools.py +31 -0
  65. package/src/qingflow_mcp/tools/qingbi_report_tools.py +34 -0
  66. package/src/qingflow_mcp/tools/record_tools.py +9542 -1110
  67. package/src/qingflow_mcp/tools/solution_tools.py +115 -3
  68. package/src/qingflow_mcp/tools/task_context_tools.py +1423 -0
  69. package/src/qingflow_mcp/tools/task_tools.py +376 -225
  70. package/src/qingflow_mcp/tools/workflow_tools.py +78 -4
  71. package/src/qingflow_mcp/tools/workspace_tools.py +39 -12
package/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  Install:
4
4
 
5
5
  ```bash
6
- npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.6
6
+ npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.60
7
7
  ```
8
8
 
9
9
  Run:
10
10
 
11
11
  ```bash
12
- npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.6 qingflow-app-user-mcp
12
+ npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.60 qingflow-app-user-mcp
13
13
  ```
14
14
 
15
15
  Environment:
@@ -20,11 +20,18 @@ Environment:
20
20
 
21
21
  This package bootstraps a local Python runtime on first install and then starts the `qingflow-app-user-mcp` stdio MCP server.
22
22
 
23
- Bundled skill:
23
+ Bundled skills:
24
24
 
25
25
  - `skills/qingflow-app-user`
26
+ - `skills/qingflow-record-insert`
27
+ - `skills/qingflow-record-update`
28
+ - `skills/qingflow-record-delete`
29
+ - `skills/qingflow-record-import`
30
+ - `skills/qingflow-task-ops`
31
+ - `skills/qingflow-record-analysis`
26
32
 
27
33
  Note:
28
34
 
29
35
  - The skill files are included in the npm package.
30
36
  - On install, the package copies them to `$CODEX_HOME/skills` (or `~/.codex/skills` if `CODEX_HOME` is unset).
37
+ - 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-user-mcp");
7
+ spawnServer(packageRoot, process.argv.slice(2), "qingflow-app-user-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-user-mcp",
3
- "version": "0.2.0-beta.6",
3
+ "version": "0.2.0-beta.60",
4
4
  "description": "Operational end-user MCP for Qingflow records, tasks, comments, and directory 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.0b6"
7
+ version = "0.2.0b60"
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"
@@ -1,158 +1,79 @@
1
1
  ---
2
2
  name: qingflow-app-user
3
- description: Use Qingflow apps as an operational end user after the MCP is already connected and authenticated. Use when the user wants to create, search, read, update, or delete business records, inspect or manage task-center work, add comments, or perform workflow usage actions inside an existing app. Do not use this skill to design apps, modify schemas, or build a brand new SolutionSpec.
3
+ description: Route Qingflow end-user requests to the right specialized operational skill after the MCP is already connected and authenticated. Use when the task is operational but it is not yet clear whether it is record CRUD or final analysis.
4
4
  metadata:
5
- short-description: Use Qingflow apps for business data and task operations
5
+ short-description: Router for Qingflow operational skills
6
6
  ---
7
7
 
8
8
  # Qingflow App User
9
9
 
10
10
  ## Overview
11
11
 
12
- This skill is for business-user operations inside existing Qingflow apps. It focuses on records, task-center usage, comments, and usage-side workflow actions, not app design or system configuration. If the task is about building or changing app structure, switch to `$qingflow-app-builder`.
13
-
14
- Before operating on data, identify whether the task targets `test` or `prod` and read [references/environments.md](references/environments.md). If the user did not specify one, default to `prod`.
15
- When the task is in `prod`, browser parity matters, or the user says "the page has data but MCP does not", restate the expected `base_url` and `qf_version`, then prefer tools that expose `request_route` so you can confirm the live route before concluding.
16
-
17
- ## Tool Scope
18
-
19
- Primary record and data tools:
20
-
21
- - `record_query`
22
- - `record_query_plan`
23
- - `record_write_plan`
24
- - `record_field_resolve`
25
- - `record_aggregate`
26
- - `record_create`
27
- - `record_get`
28
- - `record_update`
29
- - `record_delete`
30
-
31
- Directory and organization lookup tools when the user is asking about internal members, departments, org structure, ownership, approver candidates, or wants full contact exports:
32
-
33
- - `directory_search`
34
- - `directory_list_internal_users`
35
- - `directory_list_all_internal_users`
36
- - `directory_list_internal_departments`
37
- - `directory_list_all_departments`
38
- - `directory_list_sub_departments`
39
- - `directory_list_external_members`
40
-
41
- Usage-side collaboration and flow tools when needed:
42
-
43
- - `record_comment_*`
44
- - `task_approve`
45
- - `task_reject`
46
- - `task_rollback*`
47
- - `task_transfer*`
48
-
49
- Task-center and inbox tools when the user is asking about pending work, processed work, cc, or workflow workload:
50
-
51
- - `task_list`
52
- - `task_list_grouped`
53
- - `task_statistics`
54
- - `task_urge`
55
-
56
- Do not use builder-side tools here:
57
-
58
- - `app_*`
59
- - `view_*`
60
- - `workflow_*`
61
- - `portal_*`
62
- - `navigation_*`
63
- - `package_*`
64
- - `solution_*`
65
-
66
- ## Standard Operating Order
67
-
68
- 1. Ensure auth exists
69
- 2. Ensure workspace is selected
70
- 3. Confirm target app, task scope, and operation type
71
- 4. For org, member, department, approver, or ownership questions, start with `directory_*`
72
- 5. For inbox, pending, processed, cc, or workload questions, start with `task_statistics`, `task_list`, or `task_list_grouped`
73
- 6. When a task query identifies the target record, switch to `record_get` or `record_query` for business data details
74
- 7. For non-trivial record reads, start with `record_query` and use `record_query_plan` first when field ids, filters, or scan scope are uncertain
75
- 8. For non-trivial writes, start with `record_write_plan`, especially when using `fields`
76
- 9. Prefer read-first when changing existing records
77
- 10. Report the affected task ids, record ids, member ids, department ids, or counts after actions
78
- 11. For `prod`, complex forms, attachments, or any unfamiliar schema, prefer `record_create(..., verify_write=true)` or read back immediately after create/update
79
-
80
- ## Data Rules
81
-
82
- - Prefer `record_query` as the default read entry
83
- - Treat `record_query(list)` as the default wide-table browse and export endpoint; pass explicit `select_columns`, do not expect raw answer arrays there, and let the tool auto-batch columns when the backend per-request field cap is hit
84
- - Use `request_route` from tool responses to verify the active `base_url` and `qf_version` whenever route mismatches are plausible
85
- - Use `directory_search` for fuzzy internal lookup across both members and departments
86
- - Use `directory_list_all_internal_users` when the user explicitly wants a complete internal member list within the current workspace or within a specific department or role
87
- - Use `directory_list_all_departments` when the user explicitly wants the full department tree or all departments under a root
88
- - Use `directory_list_internal_departments` for keyword-based department search, not full exports
89
- - Use `task_statistics` before `task_list` when the user only needs counts
90
- - Use `task_list_grouped` when worksheet or group buckets matter
91
- - Use `task_urge` only when the user clearly wants a reminder sent for a pending task
92
- - Use `record_query_plan` before final statistics or when field selectors are ambiguous
93
- - For precise record lookup, use `record_get` when `apply_id` is known
94
- - Use `record_field_resolve` when the user gives field titles and you are not fully sure about the exact schema; do not guess ambiguous fields silently
95
- - Treat field selectors as schema-first and platform-generic. Prefer exact field titles, then neutral aliases such as `创建时间`, `新增时间`, `负责人`, `部门`, `时间`, or `阶段` only when the tool resolves them clearly. Do not assume CRM shorthand like `销售`, `商机阶段`, `客户全称`, or similar domain shortcuts apply across arbitrary Qingflow apps
96
- - For updates, inspect current data first unless the user already provided the exact target and patch
97
- - For deletes, confirm the exact record scope and report the deleted ids
98
- - When validating business data volume, use `effective_count` over raw backend totals
99
- - For summary or aggregate conclusions, prefer `strict_full=true`
100
- - In `prod`, prefer read-first even more strictly and avoid deletes unless the record scope is explicit in the conversation
101
- - For attachments, first run `file_upload_local`, then pass the returned `attachment_value` into `record_create` or `record_update`; do not try to write local file paths directly into attachment fields
102
- - For relation fields, first query the target app and resolve the referenced record `apply_id`; do not assume titles, numbers, or business keys can be written directly into a relation field
103
- - For subtable fields, write a list of row objects keyed by the subfield titles. When updating existing rows, include `rowId` / `row_id` / `__row_id__` only if the source record already exposes it
104
- - Treat `14/34/35/36` as unsupported direct-write field types in app-user flows:
105
- - `14`: time range
106
- - `34`: image recognition
107
- - `35`: image generation
108
- - `36`: document parsing
109
- - For those unsupported types, stop and explain the limitation instead of inventing payloads
110
- - Use `record_write_plan` to inspect `write_format.support_level` before non-trivial writes:
111
- - `full`: generic scalar/select/date writes are directly supported
112
- - `restricted`: member/department/attachment/relation/subtable writes need the documented presteps
113
- - `unsupported`: stop and explain the limitation
114
- - For relation-heavy, attachment, subtable, or production writes, default to `verify_write=true` so field drops are surfaced immediately instead of being reported as success
115
-
116
- ## Mock and Demo Data
117
-
118
- When the user asks for demo data, seed, smoke data, or mock data:
119
-
120
- - default to at least `5` records for the relevant entity unless the user asks for fewer
121
- - keep titles realistic and business-like
122
- - vary statuses, dates, and categories enough to make views and charts useful
123
- - if the task is `prod`, do not create mock or smoke data unless the user explicitly asks for it
124
-
125
- ## Response Interpretation
126
-
127
- - low-level list totals from the backend may report `0` while rows are present; prefer `record_query(summary)` or `record_aggregate` for final conclusions
128
- - `record_query(summary)` and `record_aggregate` expose `completeness`; do not treat partial scans as final conclusions
129
- - `record_write_plan` is static preflight, not a guarantee that submit will pass runtime linkage or visibility checks
130
- - `record_create` now returns integer `apply_id`; you can pass that id directly into `record_get`, `record_update`, or `record_delete`
131
- - `verify_write=true` means the tool read the record back and compared the written fields; if it returns `status=verification_failed` or `ok=false`, do not report the create or update as successful
132
- - Relation writes are `apply_id`-based; if the user only gives a title, number, or business key, query the target app first and resolve the real record id before writing
133
- - Task counts and record counts are not interchangeable; a task query reflects task-center workload, not the underlying record total
134
- - When reporting task results, include the task dimension that was used, such as pending, processed, cc, node, or worksheet
135
- - Prefer summarizing titles and counts instead of dumping raw answer arrays
136
- - When records reference other entities, verify references are coherent before reporting success
137
- - `file_upload_local` may transparently change `effective_upload_kind` and `upload_protocol`; surface those fields when debugging production upload behavior instead of assuming all uploads are direct `PUT`
138
-
139
- ## Practical Patterns
140
-
141
- - Bulk mock data creation: query current data first, run `record_write_plan`, then create missing records
142
- - Data correction: query, inspect, preflight, update, and re-read
143
- - Inbox triage: use `task_statistics` first, then `task_list` or `task_list_grouped`, then switch to `record_*` for the underlying record when needed
144
- - Bottleneck analysis: start with `task_statistics` and `task_list_grouped` before drilling into specific records
145
- - Workflow collaboration: comment, transfer, or reassign only after identifying the exact record
146
- - Approval actions: identify the exact record and current node first, then use `task_approve` or `task_reject`; do not guess `nodeId`
147
- - Demo validation: create at least `5` rows and confirm they are queryable
148
- - Org export: use `directory_list_all_internal_users` for full member exports and `directory_list_all_departments` for full org-tree exports before mapping owners or departments into record operations
149
- - Attachment write: upload first, write the returned URL object second, and prefer `verify_write=true`
150
- - Relation write: query the target app first, capture the referenced record `apply_id`, then write the relation field and verify the readback
151
- - Production discrepancy triage: compare the response `request_route` with the browser environment before assuming the data query is wrong
12
+ This skill is a lightweight router for operational Qingflow work.
13
+ Assumes MCP is connected, authenticated, and on the correct workspace.
14
+
15
+ ## Default Paths
16
+
17
+ Route to exactly one of these specialized paths:
18
+
19
+ 1. Record insert
20
+ Switch to [$qingflow-record-insert](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-insert/SKILL.md)
21
+
22
+ 2. Record update
23
+ Switch to [$qingflow-record-update](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-update/SKILL.md)
24
+
25
+ 3. Record delete
26
+ Switch to [$qingflow-record-delete](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-delete/SKILL.md)
27
+
28
+ 4. Record import
29
+ Switch to [$qingflow-record-import](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-import/SKILL.md)
30
+
31
+ 5. Task workflow operations
32
+ Switch to [$qingflow-task-ops](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-task-ops/SKILL.md)
33
+
34
+ 6. Analysis
35
+ Switch to [$qingflow-record-analysis](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-analysis/SKILL.md)
36
+
37
+ 7. MCP connection / auth / workspace selection
38
+ Switch to [$qingflow-mcp-setup](/Users/yanqidong/.codex/skills/qingflow-mcp-setup/SKILL.md)
39
+
40
+ ## Routing Rules
41
+
42
+ - If the user does not know the target `app_key`, discover apps first with `app_list` or `app_search`, then route to the specialized skill
43
+ - If the app is known but the available data range is unclear, call `app_get` first and inspect `accessible_views`
44
+ - If the task is about creating or new record entry, switch to `$qingflow-record-insert`
45
+ - If the task is about editing an existing record directly, switch to `$qingflow-record-update`
46
+ - If the task is about deleting records directly, switch to `$qingflow-record-delete`
47
+ - If the task is about import templates, import capability discovery, import-file verification, authorized local file repair, import execution, or import status, switch to `$qingflow-record-import`
48
+ - If the task is about todo discovery, task context, approval actions, rollback or transfer, associated report review, or workflow log review, switch to `$qingflow-task-ops`
49
+ - If the task involves member, department, or relation fields and the user only has natural names/titles, keep the same route; direct write now supports backend-native auto resolution and may return `needs_confirmation` with candidates instead of failing blind
50
+ - If the task involves linked visibility, upstream/downstream field dependencies, reference-driven auto fill, or formula-driven defaulting, keep the same insert/update route and read field-level `linkage` from the schema before composing payloads
51
+ - If the task is about subtable writes, still route to the matching insert/update skill, but shape the payload as parent subtable field -> row array; do not route users toward top-level leaf selectors
52
+ - If the task is insert-focused and readback consistency matters, keep the same route and prefer `record_get / record_list` with `output_profile="normalized"` after the write
53
+ - If the user sounds like an ordinary workflow assignee rather than a system operator, prefer `$qingflow-task-ops` over direct record mutation whenever both paths could fit
54
+ - If the task is about grouped distributions, ratios, rankings, trends, insights, or any final statistical conclusion, switch to `$qingflow-record-analysis`
55
+ - If the MCP is not connected, authenticated, or bound to the right workspace, switch to `$qingflow-mcp-setup`
56
+
57
+ ## Shared Preconditions
58
+
59
+ - prefer canonical app ids, record ids, task ids, and workflow node ids over guessed names
60
+ - if a field or target is still ambiguous after schema/task lookup, ask the user to confirm from a short candidate list instead of guessing
61
+ - if schema fields include `linkage.sources` or `linkage.affects_fields`, treat those as the preferred high-level explanation of field dependencies instead of trying to infer hidden front-end logic
62
+ - if the task can stay read-only, do not write or act
63
+ - if the task involves a user-uploaded import file, do not modify the file unless the user explicitly authorizes repair or normalization
64
+ - if the task involves record import, call `app_get` first and inspect `data.import_capability` before template download, file repair, or import start
65
+ - 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
66
+
67
+ ## Shared Helper
68
+
69
+ - `feedback_submit` is a cross-cutting helper for product feedback submission
70
+ - It does not require Qingflow login or workspace selection
71
+ - Use it only after the user explicitly confirms they want to submit feedback
152
72
 
153
73
  ## Resources
154
74
 
155
- - Environment switching: [references/environments.md](references/environments.md)
156
- - Record operation patterns: [references/record-patterns.md](references/record-patterns.md)
157
- - Workflow usage actions: [references/workflow-usage.md](references/workflow-usage.md)
158
- - Data gotchas: [references/data-gotchas.md](references/data-gotchas.md)
75
+ - Record insert: [$qingflow-record-insert](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-insert/SKILL.md)
76
+ - Record update: [$qingflow-record-update](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-update/SKILL.md)
77
+ - Record delete: [$qingflow-record-delete](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-delete/SKILL.md)
78
+ - Record import: [$qingflow-record-import](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-import/SKILL.md)
79
+ - Dedicated analysis workflow: [$qingflow-record-analysis](/Users/yanqidong/Documents/qingflow-next/.codex/skills/qingflow-record-analysis/SKILL.md)
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Qingflow App User"
3
- short_description: "Use Qingflow apps for business data and task operations"
4
- default_prompt: "Use $qingflow-app-user to query members or departments, inspect workload, and safely create, query, update, validate, approve, reject, or otherwise operate records in an existing Qingflow app, paying attention to request_route, verify_write, nodeId accuracy, and attachment upload flows when needed."
3
+ short_description: "Route Qingflow operational tasks to insert, update, delete, import, task ops, or analysis"
4
+ default_prompt: "Use $qingflow-app-user as a router: switch to $qingflow-record-insert for new record entry, $qingflow-record-update for direct edits, $qingflow-record-delete for deletes, $qingflow-record-import for bulk import, $qingflow-task-ops for task-center, comments, directory, and workflow usage actions, and $qingflow-record-analysis for grouped analysis or final statistical conclusions."