@josephyan/qingflow-app-user-mcp 0.2.0-beta.6 → 0.2.0-beta.61
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 +10 -3
- package/docs/local-agent-install.md +21 -5
- package/npm/bin/qingflow-app-user-mcp.mjs +1 -1
- package/npm/lib/runtime.mjs +168 -12
- package/package.json +1 -1
- package/pyproject.toml +4 -1
- package/skills/qingflow-app-user/SKILL.md +67 -146
- package/skills/qingflow-app-user/agents/openai.yaml +2 -2
- package/skills/qingflow-app-user/references/data-gotchas.md +19 -39
- package/skills/qingflow-app-user/references/record-patterns.md +31 -52
- package/skills/qingflow-app-user/references/workflow-usage.md +10 -8
- package/skills/qingflow-record-analysis/SKILL.md +158 -0
- package/skills/qingflow-record-analysis/agents/openai.yaml +4 -0
- package/skills/qingflow-record-analysis/references/analysis-gotchas.md +145 -0
- package/skills/qingflow-record-analysis/references/analysis-patterns.md +125 -0
- package/skills/qingflow-record-analysis/references/confidence-reporting.md +92 -0
- package/skills/qingflow-record-analysis/references/dsl-templates.md +93 -0
- package/skills/qingflow-record-delete/SKILL.md +29 -0
- package/skills/qingflow-record-import/SKILL.md +31 -0
- package/skills/qingflow-record-insert/SKILL.md +58 -0
- package/skills/qingflow-record-update/SKILL.md +42 -0
- package/skills/qingflow-task-ops/SKILL.md +123 -0
- package/skills/qingflow-task-ops/agents/openai.yaml +4 -0
- package/skills/qingflow-task-ops/references/environments.md +44 -0
- package/skills/qingflow-task-ops/references/workflow-usage.md +27 -0
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/backend_client.py +211 -0
- package/src/qingflow_mcp/builder_facade/models.py +640 -1
- package/src/qingflow_mcp/builder_facade/service.py +5777 -271
- package/src/qingflow_mcp/cli/__init__.py +1 -0
- package/src/qingflow_mcp/cli/commands/__init__.py +15 -0
- package/src/qingflow_mcp/cli/commands/app.py +40 -0
- package/src/qingflow_mcp/cli/commands/auth.py +78 -0
- package/src/qingflow_mcp/cli/commands/builder.py +184 -0
- package/src/qingflow_mcp/cli/commands/common.py +47 -0
- package/src/qingflow_mcp/cli/commands/imports.py +86 -0
- package/src/qingflow_mcp/cli/commands/record.py +202 -0
- package/src/qingflow_mcp/cli/commands/task.py +87 -0
- package/src/qingflow_mcp/cli/commands/workspace.py +33 -0
- package/src/qingflow_mcp/cli/context.py +48 -0
- package/src/qingflow_mcp/cli/formatters.py +269 -0
- package/src/qingflow_mcp/cli/json_io.py +50 -0
- package/src/qingflow_mcp/cli/main.py +147 -0
- package/src/qingflow_mcp/config.py +39 -0
- package/src/qingflow_mcp/import_store.py +121 -0
- package/src/qingflow_mcp/list_type_labels.py +24 -0
- package/src/qingflow_mcp/server.py +159 -18
- package/src/qingflow_mcp/server_app_builder.py +132 -72
- package/src/qingflow_mcp/server_app_user.py +172 -189
- package/src/qingflow_mcp/session_store.py +41 -1
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +14 -4
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +41 -2
- package/src/qingflow_mcp/solution/executor.py +107 -11
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1390 -131
- package/src/qingflow_mcp/tools/app_tools.py +390 -12
- package/src/qingflow_mcp/tools/approval_tools.py +411 -76
- package/src/qingflow_mcp/tools/auth_tools.py +139 -1
- package/src/qingflow_mcp/tools/code_block_tools.py +679 -0
- package/src/qingflow_mcp/tools/directory_tools.py +203 -31
- package/src/qingflow_mcp/tools/feedback_tools.py +230 -0
- package/src/qingflow_mcp/tools/file_tools.py +1 -0
- package/src/qingflow_mcp/tools/import_tools.py +1971 -0
- package/src/qingflow_mcp/tools/package_tools.py +17 -4
- package/src/qingflow_mcp/tools/portal_tools.py +31 -0
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +34 -0
- package/src/qingflow_mcp/tools/record_tools.py +9542 -1110
- package/src/qingflow_mcp/tools/solution_tools.py +115 -3
- package/src/qingflow_mcp/tools/task_context_tools.py +1423 -0
- package/src/qingflow_mcp/tools/task_tools.py +376 -225
- package/src/qingflow_mcp/tools/workflow_tools.py +78 -4
- 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
|
+
npm install @josephyan/qingflow-app-user-mcp@0.2.0-beta.61
|
|
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.
|
|
12
|
+
npx -y -p @josephyan/qingflow-app-user-mcp@0.2.0-beta.61 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
|
|
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.
|
|
6
|
-
2.
|
|
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 });
|
package/npm/lib/runtime.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
223
|
+
const runtime = inspectPythonEnv(packageRoot, commandName);
|
|
123
224
|
const venvPython = getVenvPython(packageRoot);
|
|
124
|
-
const serverCommand
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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: "
|
|
318
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
168
319
|
env: process.env,
|
|
320
|
+
windowsHide: true,
|
|
169
321
|
});
|
|
170
322
|
|
|
171
|
-
child
|
|
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
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.
|
|
7
|
+
version = "0.2.0b61"
|
|
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:
|
|
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:
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- `
|
|
44
|
-
- `
|
|
45
|
-
- `
|
|
46
|
-
- `
|
|
47
|
-
- `
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
- `
|
|
53
|
-
- `
|
|
54
|
-
- `
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
- `
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
- `
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
-
|
|
156
|
-
- Record
|
|
157
|
-
-
|
|
158
|
-
-
|
|
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: "
|
|
4
|
-
default_prompt: "Use $qingflow-app-user
|
|
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."
|