@josephyan/qingflow-app-user-mcp 1.1.25 → 1.1.27
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 +5 -7
- package/docs/local-agent-install.md +6 -57
- package/entry_point.py +1 -1
- package/npm/bin/qingflow-app-user-mcp.mjs +33 -2
- package/npm/lib/runtime.mjs +101 -21
- package/npm/scripts/postinstall.mjs +10 -1
- package/package.json +2 -3
- package/pyproject.toml +1 -1
- package/skills/qingflow-app-user/SKILL.md +15 -16
- package/skills/qingflow-app-user/references/data-gotchas.md +2 -2
- package/skills/qingflow-app-user/references/public-surface-sync.md +7 -5
- package/skills/qingflow-app-user/references/record-patterns.md +5 -5
- package/skills/qingflow-app-user/references/workflow-usage.md +5 -4
- package/skills/qingflow-mcp-setup/SKILL.md +9 -13
- package/skills/qingflow-mcp-setup/references/claude-desktop.md +1 -1
- package/skills/qingflow-mcp-setup/references/environments.md +1 -2
- package/skills/qingflow-mcp-setup/references/generic-stdio.md +5 -6
- package/skills/qingflow-record-analysis/SKILL.md +8 -9
- package/skills/qingflow-record-delete/SKILL.md +3 -7
- package/skills/qingflow-record-import/SKILL.md +2 -35
- package/skills/qingflow-record-insert/SKILL.md +6 -78
- package/skills/qingflow-record-update/SKILL.md +24 -39
- package/skills/qingflow-task-ops/SKILL.md +27 -30
- package/skills/qingflow-task-ops/references/environments.md +1 -0
- package/skills/qingflow-task-ops/references/workflow-usage.md +6 -4
- package/src/qingflow_mcp/__init__.py +1 -1
- package/src/qingflow_mcp/__main__.py +2 -6
- package/src/qingflow_mcp/builder_facade/models.py +49 -330
- package/src/qingflow_mcp/builder_facade/service.py +1621 -6149
- package/src/qingflow_mcp/cli/commands/builder.py +366 -322
- package/src/qingflow_mcp/cli/commands/chart.py +1 -1
- package/src/qingflow_mcp/cli/commands/common.py +3 -12
- package/src/qingflow_mcp/cli/commands/exports.py +2 -2
- package/src/qingflow_mcp/cli/commands/imports.py +3 -3
- package/src/qingflow_mcp/cli/commands/portal.py +2 -2
- package/src/qingflow_mcp/cli/commands/record.py +27 -101
- package/src/qingflow_mcp/cli/commands/task.py +47 -28
- package/src/qingflow_mcp/cli/commands/view.py +1 -1
- package/src/qingflow_mcp/cli/context.py +3 -0
- package/src/qingflow_mcp/cli/formatters.py +16 -784
- package/src/qingflow_mcp/cli/main.py +41 -120
- package/src/qingflow_mcp/errors.py +2 -43
- package/src/qingflow_mcp/public_surface.py +17 -26
- package/src/qingflow_mcp/response_trim.py +17 -81
- package/src/qingflow_mcp/server.py +12 -14
- package/src/qingflow_mcp/server_app_builder.py +39 -81
- package/src/qingflow_mcp/server_app_user.py +16 -22
- package/src/qingflow_mcp/session_store.py +7 -11
- package/src/qingflow_mcp/solution/compiler/__init__.py +1 -3
- package/src/qingflow_mcp/solution/executor.py +18 -245
- package/src/qingflow_mcp/tools/ai_builder_tools.py +1483 -3139
- package/src/qingflow_mcp/tools/app_tools.py +43 -184
- package/src/qingflow_mcp/tools/approval_tools.py +35 -197
- package/src/qingflow_mcp/tools/auth_tools.py +16 -92
- package/src/qingflow_mcp/tools/code_block_tools.py +40 -298
- package/src/qingflow_mcp/tools/custom_button_tools.py +10 -64
- package/src/qingflow_mcp/tools/directory_tools.py +72 -236
- package/src/qingflow_mcp/tools/export_tools.py +34 -244
- package/src/qingflow_mcp/tools/feedback_tools.py +0 -9
- package/src/qingflow_mcp/tools/file_tools.py +3 -9
- package/src/qingflow_mcp/tools/import_tools.py +49 -336
- package/src/qingflow_mcp/tools/navigation_tools.py +12 -91
- package/src/qingflow_mcp/tools/package_tools.py +6 -118
- package/src/qingflow_mcp/tools/portal_tools.py +3 -39
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +7 -116
- package/src/qingflow_mcp/tools/record_tools.py +360 -1145
- package/src/qingflow_mcp/tools/resource_read_tools.py +39 -188
- package/src/qingflow_mcp/tools/role_tools.py +9 -80
- package/src/qingflow_mcp/tools/solution_tools.py +45 -59
- package/src/qingflow_mcp/tools/task_context_tools.py +158 -662
- package/src/qingflow_mcp/tools/task_tools.py +29 -113
- package/src/qingflow_mcp/tools/view_tools.py +3 -106
- package/src/qingflow_mcp/tools/workflow_tools.py +4 -48
- package/src/qingflow_mcp/tools/workspace_tools.py +3 -71
- package/src/qingflow_mcp/version.py +2 -0
- package/npm/bin/qingflow-skills.mjs +0 -5
- package/skills/qingflow-record-analysis/manifest.yaml +0 -10
- package/skills/qingflow-record-insert/manifest.yaml +0 -6
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +0 -173
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @josephyan/qingflow-app-user-mcp@1.1.
|
|
6
|
+
npm install @josephyan/qingflow-app-user-mcp@1.1.27
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-app-user-mcp@1.1.
|
|
12
|
+
npx -y -p @josephyan/qingflow-app-user-mcp@1.1.27 qingflow-app-user-mcp
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
|
@@ -23,19 +23,17 @@ This package bootstraps a local Python runtime on first install and then starts
|
|
|
23
23
|
Bundled skills:
|
|
24
24
|
|
|
25
25
|
- `skills/qingflow-app-user`
|
|
26
|
-
- `skills/qingflow-mcp-setup`
|
|
27
26
|
- `skills/qingflow-record-insert`
|
|
28
27
|
- `skills/qingflow-record-update`
|
|
29
28
|
- `skills/qingflow-record-delete`
|
|
30
29
|
- `skills/qingflow-record-import`
|
|
31
30
|
- `skills/qingflow-task-ops`
|
|
32
31
|
- `skills/qingflow-record-analysis`
|
|
32
|
+
- `skills/qingflow-mcp-setup`
|
|
33
33
|
|
|
34
34
|
Note:
|
|
35
35
|
|
|
36
36
|
- The skill files are included in the npm package.
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
- For one-shot `npx -p` installs, prefer `--copy` because symlinks would point into the npm execution cache.
|
|
40
|
-
- Use `qingflow-app-user-mcp-skills list` to inspect bundled skills. The installer defaults to symlinks for stable package installs and refuses to overwrite existing skills unless `--force` is provided.
|
|
37
|
+
- On install, the package installs bundled skills into `$CODEX_HOME/skills` (or `~/.codex/skills` if `CODEX_HOME` is unset) by symlink and refreshes them on package updates.
|
|
38
|
+
- You can also run `qingflow-app-user-mcp skills list` or `qingflow-app-user-mcp skills install --agent codex|claude-code|cursor|generic --scope user|project [--copy] [--force]`.
|
|
41
39
|
- 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.
|
|
@@ -101,51 +101,7 @@ npm install /absolute/path/to/dist/npm/qingflow-tech-qingflow-app-builder-mcp-<v
|
|
|
101
101
|
1. 创建 `.npm-python/`
|
|
102
102
|
2. 在其中建立 Python 虚拟环境
|
|
103
103
|
3. 执行 `pip install .`
|
|
104
|
-
4.
|
|
105
|
-
5. 携带 `skills/<skill-name>/SKILL.md`,但不在 `postinstall` 阶段自动覆盖本机 agent skills
|
|
106
|
-
|
|
107
|
-
## Skills 挂载
|
|
108
|
-
|
|
109
|
-
显式查看包内 skills:
|
|
110
|
-
|
|
111
|
-
```bash
|
|
112
|
-
qingflow-skills list
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
独立 MCP split 包使用包专属 skills 命令,避免全局安装多个包时发生 bin 覆盖:
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
qingflow-app-user-mcp-skills list
|
|
119
|
-
qingflow-app-builder-mcp-skills list
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
显式挂载到 Codex 用户目录:
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
qingflow-skills install --agent codex --scope user
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
如果通过一次性 `npx -p <package>` 执行安装,请加 `--copy`,避免 symlink 指向 npm 临时执行缓存:
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
npx -y -p @qingflow-tech/qingflow-cli qingflow-skills install --agent codex --scope user --copy
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
也可以挂载到项目级 agent 目录:
|
|
135
|
-
|
|
136
|
-
```bash
|
|
137
|
-
qingflow-skills install --agent claude-code --scope project
|
|
138
|
-
qingflow-skills install --agent cursor --scope project --copy
|
|
139
|
-
qingflow-skills install --agent all --scope project
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
默认行为:
|
|
143
|
-
|
|
144
|
-
- `--mode symlink`:使用 symlink,让 npm 包版本成为单一来源
|
|
145
|
-
- `--scope user`:安装到用户级 agent skills 目录
|
|
146
|
-
- `--agent codex`:目标 agent 为 Codex
|
|
147
|
-
- 不覆盖已有同名 skill;需要替换时显式加 `--force`
|
|
148
|
-
- 每次安装会在目标 skills 目录下写入 `.qingflow-skill-sources/<skill>.json`,记录 package、version、source、destination、agent、scope、mode
|
|
104
|
+
4. 在安装位置暴露 `qingflow`、`qingflow-app-user-mcp`、`qingflow-app-builder-mcp` 命令
|
|
149
105
|
|
|
150
106
|
## 本地验证
|
|
151
107
|
|
|
@@ -154,31 +110,24 @@ qingflow-skills install --agent all --scope project
|
|
|
154
110
|
```bash
|
|
155
111
|
cd qingflow-support/mcp-server
|
|
156
112
|
node ./npm/bin/qingflow.mjs --help
|
|
157
|
-
node ./npm/bin/qingflow-skills.mjs list
|
|
158
113
|
node ./npm/bin/qingflow-app-user-mcp.mjs
|
|
159
114
|
node ./npm/bin/qingflow-app-builder-mcp.mjs
|
|
160
115
|
```
|
|
161
116
|
|
|
162
|
-
|
|
117
|
+
如果你是全局安装:
|
|
163
118
|
|
|
164
119
|
```bash
|
|
165
120
|
qingflow --help
|
|
166
|
-
qingflow-skills list
|
|
167
121
|
qingflow-app-user-mcp
|
|
168
|
-
qingflow-app-user-mcp-skills list
|
|
169
122
|
qingflow-app-builder-mcp
|
|
170
|
-
qingflow-app-builder-mcp-skills list
|
|
171
123
|
```
|
|
172
124
|
|
|
173
|
-
如果你是把包安装到了某个本地 agent workspace
|
|
125
|
+
如果你是把包安装到了某个本地 agent workspace,命令通常位于:
|
|
174
126
|
|
|
175
127
|
```bash
|
|
176
128
|
/absolute/path/to/agent-workspace/node_modules/.bin/qingflow
|
|
177
|
-
/absolute/path/to/agent-workspace/node_modules/.bin/qingflow-skills
|
|
178
129
|
/absolute/path/to/agent-workspace/node_modules/.bin/qingflow-app-user-mcp
|
|
179
|
-
/absolute/path/to/agent-workspace/node_modules/.bin/qingflow-app-user-mcp-skills
|
|
180
130
|
/absolute/path/to/agent-workspace/node_modules/.bin/qingflow-app-builder-mcp
|
|
181
|
-
/absolute/path/to/agent-workspace/node_modules/.bin/qingflow-app-builder-mcp-skills
|
|
182
131
|
```
|
|
183
132
|
|
|
184
133
|
如果你是从 tgz 安装到某个空目录,命令通常位于:
|
|
@@ -268,7 +217,7 @@ qingflow-app-builder-mcp-skills list
|
|
|
268
217
|
"command": "npx",
|
|
269
218
|
"args": [
|
|
270
219
|
"-y",
|
|
271
|
-
"@
|
|
220
|
+
"@josephyan/qingflow-app-user-mcp"
|
|
272
221
|
],
|
|
273
222
|
"env": {
|
|
274
223
|
"QINGFLOW_MCP_DEFAULT_BASE_URL": "https://qingflow.com/api",
|
|
@@ -281,7 +230,7 @@ qingflow-app-builder-mcp-skills list
|
|
|
281
230
|
"command": "npx",
|
|
282
231
|
"args": [
|
|
283
232
|
"-y",
|
|
284
|
-
"@
|
|
233
|
+
"@josephyan/qingflow-app-builder-mcp"
|
|
285
234
|
],
|
|
286
235
|
"env": {
|
|
287
236
|
"QINGFLOW_MCP_DEFAULT_BASE_URL": "https://qingflow.com/api",
|
|
@@ -323,7 +272,7 @@ npm install
|
|
|
323
272
|
|
|
324
273
|
如果 MCP 客户端一调用工具就报 `Transport closed`,优先检查这几件事:
|
|
325
274
|
|
|
326
|
-
1. 不要混用不同版本的 `@
|
|
275
|
+
1. 不要混用不同版本的 `@josephyan/qingflow-cli`、`@josephyan/qingflow-app-user-mcp`、`@josephyan/qingflow-app-builder-mcp`
|
|
327
276
|
2. 删除安装目录下的 `.npm-python`
|
|
328
277
|
3. 重新执行 `npm install` 或重新安装对应 tgz/npm 包
|
|
329
278
|
4. 再启动 MCP 客户端
|
package/entry_point.py
CHANGED
|
@@ -7,7 +7,7 @@ src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "src"))
|
|
|
7
7
|
if src_path not in sys.path:
|
|
8
8
|
sys.path.insert(0, src_path)
|
|
9
9
|
|
|
10
|
-
from qingflow_mcp.
|
|
10
|
+
from qingflow_mcp.server import main
|
|
11
11
|
|
|
12
12
|
if __name__ == "__main__":
|
|
13
13
|
main()
|
|
@@ -1,7 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
const PKG_BY_BIN = {
|
|
7
|
+
qingflow: "qingflow-cli",
|
|
8
|
+
"qingflow-app-user-mcp": "qingflow-app-user-mcp",
|
|
9
|
+
"qingflow-app-builder-mcp": "qingflow-app-builder-mcp",
|
|
10
|
+
};
|
|
4
11
|
|
|
5
|
-
|
|
12
|
+
function resolvePackageModule(metaUrl, ...segments) {
|
|
13
|
+
const scriptPath = fileURLToPath(metaUrl);
|
|
14
|
+
const scriptDir = path.dirname(scriptPath);
|
|
15
|
+
// bin files live in <pkg>/npm/bin/, runtime.mjs lives in the sibling <pkg>/npm/lib/,
|
|
16
|
+
// so step up one level before joining the requested segments.
|
|
17
|
+
const direct = path.join(scriptDir, "..", ...segments);
|
|
18
|
+
if (fs.existsSync(direct)) {
|
|
19
|
+
return pathToFileURL(direct).href;
|
|
20
|
+
}
|
|
21
|
+
if (path.basename(scriptDir) === ".bin") {
|
|
22
|
+
const binName = path.basename(scriptPath);
|
|
23
|
+
const pkgName = PKG_BY_BIN[binName];
|
|
24
|
+
if (!pkgName) {
|
|
25
|
+
throw new Error(`Unknown qingflow command: ${binName}`);
|
|
26
|
+
}
|
|
27
|
+
const scope = process.env.QINGFLOW_NPM_SCOPE || "@josephyan";
|
|
28
|
+
const fromBin = path.join(scriptDir, "..", scope, pkgName, "npm", ...segments);
|
|
29
|
+
if (fs.existsSync(fromBin)) {
|
|
30
|
+
return pathToFileURL(fromBin).href;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`Cannot locate ${segments.join("/")} from ${scriptPath}`);
|
|
34
|
+
}
|
|
6
35
|
|
|
36
|
+
const { getPackageRoot, spawnServer } = await import(resolvePackageModule(import.meta.url, "lib", "runtime.mjs"));
|
|
37
|
+
const packageRoot = getPackageRoot(import.meta.url);
|
|
7
38
|
spawnServer(packageRoot, process.argv.slice(2), "qingflow-app-user-mcp", { allowRuntimeBootstrap: false });
|
package/npm/lib/runtime.mjs
CHANGED
|
@@ -5,6 +5,12 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
|
|
6
6
|
const WINDOWS = process.platform === "win32";
|
|
7
7
|
|
|
8
|
+
const PKG_BY_BIN = {
|
|
9
|
+
qingflow: "qingflow-cli",
|
|
10
|
+
"qingflow-app-user-mcp": "qingflow-app-user-mcp",
|
|
11
|
+
"qingflow-app-builder-mcp": "qingflow-app-builder-mcp",
|
|
12
|
+
};
|
|
13
|
+
|
|
8
14
|
function runChecked(command, args, options = {}) {
|
|
9
15
|
const result = spawnSync(command, args, {
|
|
10
16
|
encoding: "utf8",
|
|
@@ -28,7 +34,18 @@ function commandWorks(command, args) {
|
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
export function getPackageRoot(metaUrl) {
|
|
31
|
-
|
|
37
|
+
const scriptPath = fileURLToPath(metaUrl);
|
|
38
|
+
const scriptDir = path.dirname(scriptPath);
|
|
39
|
+
if (path.basename(scriptDir) === ".bin") {
|
|
40
|
+
const binName = path.basename(scriptPath);
|
|
41
|
+
const pkgName = PKG_BY_BIN[binName];
|
|
42
|
+
if (!pkgName) {
|
|
43
|
+
throw new Error(`Unknown qingflow command: ${binName}`);
|
|
44
|
+
}
|
|
45
|
+
const scope = process.env.QINGFLOW_NPM_SCOPE || "@josephyan";
|
|
46
|
+
return path.join(scriptDir, "..", scope, pkgName);
|
|
47
|
+
}
|
|
48
|
+
return path.resolve(scriptDir, "..", "..");
|
|
32
49
|
}
|
|
33
50
|
|
|
34
51
|
export function getCodexHome() {
|
|
@@ -137,6 +154,43 @@ function writeSkillProvenance(skillsDestRoot, skillName, payload) {
|
|
|
137
154
|
fs.writeFileSync(path.join(provenanceRoot, `${skillName}.json`), `${JSON.stringify(payload, null, 2)}\n`);
|
|
138
155
|
}
|
|
139
156
|
|
|
157
|
+
function sleepMs(ms) {
|
|
158
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function withSkillInstallLock(skillsDestRoot, callback) {
|
|
162
|
+
const lockDir = path.join(skillsDestRoot, ".qingflow-skill-install.lock");
|
|
163
|
+
const startedAt = Date.now();
|
|
164
|
+
while (true) {
|
|
165
|
+
try {
|
|
166
|
+
fs.mkdirSync(lockDir);
|
|
167
|
+
break;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (error.code !== "EEXIST") {
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const stat = fs.statSync(lockDir);
|
|
174
|
+
if (Date.now() - stat.mtimeMs > 300_000) {
|
|
175
|
+
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (Date.now() - startedAt > 30_000) {
|
|
182
|
+
throw new Error(`Timed out waiting for skill install lock: ${lockDir}`);
|
|
183
|
+
}
|
|
184
|
+
sleepMs(100);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
return callback();
|
|
189
|
+
} finally {
|
|
190
|
+
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
140
194
|
export function installBundledSkills(
|
|
141
195
|
packageRoot,
|
|
142
196
|
{
|
|
@@ -180,27 +234,29 @@ export function installBundledSkills(
|
|
|
180
234
|
mode,
|
|
181
235
|
};
|
|
182
236
|
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
237
|
+
return withSkillInstallLock(skillsDestRoot, () => {
|
|
238
|
+
for (const skillName of wanted) {
|
|
239
|
+
const src = path.join(skillsSrc, skillName);
|
|
240
|
+
const dest = path.join(skillsDestRoot, skillName);
|
|
241
|
+
const installResult = installOneSkill(src, dest, { force, mode });
|
|
242
|
+
result[installResult.status === "conflict" ? "conflicts" : installResult.status].push(skillName);
|
|
243
|
+
if (installResult.status !== "conflict") {
|
|
244
|
+
writeSkillProvenance(skillsDestRoot, skillName, {
|
|
245
|
+
package: packageJson.name ?? null,
|
|
246
|
+
version: packageJson.version ?? null,
|
|
247
|
+
skill: skillName,
|
|
248
|
+
source: src,
|
|
249
|
+
destination: dest,
|
|
250
|
+
agent,
|
|
251
|
+
scope,
|
|
252
|
+
mode,
|
|
253
|
+
installed_at: new Date().toISOString(),
|
|
254
|
+
});
|
|
255
|
+
}
|
|
200
256
|
}
|
|
201
|
-
}
|
|
202
257
|
|
|
203
|
-
|
|
258
|
+
return result;
|
|
259
|
+
});
|
|
204
260
|
}
|
|
205
261
|
|
|
206
262
|
function parseSkillsCliArgs(args) {
|
|
@@ -480,6 +536,17 @@ function getVenvPip(packageRoot) {
|
|
|
480
536
|
: path.join(getVenvDir(packageRoot), "bin", "pip");
|
|
481
537
|
}
|
|
482
538
|
|
|
539
|
+
function findOfflineProjectWheel(findLinksDir) {
|
|
540
|
+
const wheels = fs
|
|
541
|
+
.readdirSync(findLinksDir)
|
|
542
|
+
.filter((name) => /^qingflow_mcp-.*\.whl$/i.test(name))
|
|
543
|
+
.sort();
|
|
544
|
+
if (wheels.length === 0) {
|
|
545
|
+
throw new Error(`Offline install expected a qingflow_mcp wheel in ${findLinksDir}`);
|
|
546
|
+
}
|
|
547
|
+
return path.join(findLinksDir, wheels[wheels.length - 1]);
|
|
548
|
+
}
|
|
549
|
+
|
|
483
550
|
export function findPython() {
|
|
484
551
|
const preferred = process.env.QINGFLOW_MCP_PYTHON?.trim();
|
|
485
552
|
const candidates = preferred
|
|
@@ -528,7 +595,20 @@ export function ensurePythonEnv(packageRoot, { force = false, commandName = "qin
|
|
|
528
595
|
}
|
|
529
596
|
|
|
530
597
|
const pip = getVenvPip(packageRoot);
|
|
531
|
-
|
|
598
|
+
const pipArgs = ["install", "--disable-pip-version-check"];
|
|
599
|
+
const offlineFindLinks = process.env.QINGFLOW_MCP_PIP_FIND_LINKS?.trim();
|
|
600
|
+
if (process.env.QINGFLOW_MCP_PIP_NO_INDEX === "1") {
|
|
601
|
+
pipArgs.push("--no-index");
|
|
602
|
+
}
|
|
603
|
+
if (offlineFindLinks) {
|
|
604
|
+
pipArgs.push("--find-links", offlineFindLinks);
|
|
605
|
+
}
|
|
606
|
+
if (process.env.QINGFLOW_MCP_PIP_NO_INDEX === "1" && offlineFindLinks) {
|
|
607
|
+
pipArgs.push(findOfflineProjectWheel(offlineFindLinks));
|
|
608
|
+
} else {
|
|
609
|
+
pipArgs.push(".");
|
|
610
|
+
}
|
|
611
|
+
runChecked(pip, pipArgs, { cwd: packageRoot });
|
|
532
612
|
|
|
533
613
|
fs.writeFileSync(
|
|
534
614
|
stampPath,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ensurePythonEnv, getPackageRoot } from "../lib/runtime.mjs";
|
|
1
|
+
import { ensurePythonEnv, getPackageRoot, installBundledSkills } from "../lib/runtime.mjs";
|
|
2
2
|
|
|
3
3
|
const packageRoot = getPackageRoot(import.meta.url);
|
|
4
4
|
|
|
@@ -6,6 +6,15 @@ try {
|
|
|
6
6
|
console.log("[qingflow-mcp] Bootstrapping Python runtime...");
|
|
7
7
|
ensurePythonEnv(packageRoot, { commandName: "qingflow-app-user-mcp" });
|
|
8
8
|
console.log("[qingflow-mcp] Python runtime is ready.");
|
|
9
|
+
const skills = installBundledSkills(packageRoot, { force: true });
|
|
10
|
+
if (!skills.skipped) {
|
|
11
|
+
const changed = [
|
|
12
|
+
skills.installed.length ? `installed=${skills.installed.join(",")}` : "",
|
|
13
|
+
skills.unchanged.length ? `unchanged=${skills.unchanged.join(",")}` : "",
|
|
14
|
+
skills.conflicts.length ? `conflicts=${skills.conflicts.join(",")}` : "",
|
|
15
|
+
].filter(Boolean).join(" ");
|
|
16
|
+
console.log(`[qingflow-mcp] Installed skills to ${skills.destination}: ${changed || "none"}`);
|
|
17
|
+
}
|
|
9
18
|
} catch (error) {
|
|
10
19
|
console.error(`[qingflow-mcp] postinstall failed: ${error.message}`);
|
|
11
20
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@josephyan/qingflow-app-user-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.27",
|
|
4
4
|
"description": "Operational end-user MCP for Qingflow records, tasks, comments, and directory workflows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"qingflow-app-user-mcp": "./npm/bin/qingflow-app-user-mcp.mjs"
|
|
9
|
-
"qingflow-app-user-mcp-skills": "./npm/bin/qingflow-skills.mjs"
|
|
8
|
+
"qingflow-app-user-mcp": "./npm/bin/qingflow-app-user-mcp.mjs"
|
|
10
9
|
},
|
|
11
10
|
"scripts": {
|
|
12
11
|
"postinstall": "node ./npm/scripts/postinstall.mjs"
|
package/pyproject.toml
CHANGED
|
@@ -1,67 +1,66 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: qingflow-app-user
|
|
3
|
-
description: Route Qingflow end-user requests to the right specialized operational skill
|
|
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
5
|
short-description: Router for Qingflow operational skills
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Qingflow App User
|
|
9
9
|
|
|
10
|
-
> **Skill 版本**:`qingflow-skills-2026.06.
|
|
10
|
+
> **Skill 版本**:`qingflow-skills-2026.06.30.1`
|
|
11
11
|
|
|
12
12
|
## Overview
|
|
13
13
|
|
|
14
14
|
This skill is a lightweight router for operational Qingflow work.
|
|
15
|
-
|
|
15
|
+
Assumes MCP is connected, authenticated, and on the correct workspace.
|
|
16
16
|
Before routing, skim the shared maintenance baseline: [public-surface-sync.md](references/public-surface-sync.md).
|
|
17
17
|
|
|
18
18
|
## Default Paths
|
|
19
19
|
|
|
20
20
|
Route to exactly one of these specialized paths:
|
|
21
21
|
|
|
22
|
-
1. Record insert
|
|
22
|
+
1. Record insert
|
|
23
23
|
Switch to [$qingflow-record-insert](../qingflow-record-insert/SKILL.md)
|
|
24
24
|
|
|
25
|
-
2. Record update
|
|
25
|
+
2. Record update
|
|
26
26
|
Switch to [$qingflow-record-update](../qingflow-record-update/SKILL.md)
|
|
27
27
|
|
|
28
|
-
3. Record delete
|
|
28
|
+
3. Record delete
|
|
29
29
|
Switch to [$qingflow-record-delete](../qingflow-record-delete/SKILL.md)
|
|
30
30
|
|
|
31
|
-
4. Record import
|
|
31
|
+
4. Record import
|
|
32
32
|
Switch to [$qingflow-record-import](../qingflow-record-import/SKILL.md)
|
|
33
33
|
|
|
34
|
-
5. Task workflow operations
|
|
34
|
+
5. Task workflow operations
|
|
35
35
|
Switch to [$qingflow-task-ops](../qingflow-task-ops/SKILL.md)
|
|
36
36
|
|
|
37
|
-
6. Analysis
|
|
37
|
+
6. Analysis
|
|
38
38
|
Switch to [$qingflow-record-analysis](../qingflow-record-analysis/SKILL.md)
|
|
39
39
|
|
|
40
|
-
7.
|
|
40
|
+
7. MCP connection / auth / workspace selection
|
|
41
41
|
Switch to [$qingflow-mcp-setup](../qingflow-mcp-setup/SKILL.md)
|
|
42
42
|
|
|
43
43
|
8. App / view / workflow / chart / portal / package configuration
|
|
44
|
-
This is
|
|
44
|
+
This is builder work, not app-user work. Use the `qingflow-cli` skill or install/use the `qingflow-app-builder` package.
|
|
45
45
|
|
|
46
46
|
## Routing Rules
|
|
47
47
|
|
|
48
|
-
- If the user does not know the target `app_key`, discover apps first with `app_list`
|
|
48
|
+
- 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
|
|
49
49
|
- If the app is known but the available data range is unclear, call `app_get` first and inspect `accessible_views`
|
|
50
50
|
- If the task is about creating or new record entry, switch to `$qingflow-record-insert`
|
|
51
51
|
- If the task is about editing an existing record directly, switch to `$qingflow-record-update`
|
|
52
52
|
- If the task is about deleting records directly, switch to `$qingflow-record-delete`
|
|
53
53
|
- 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`
|
|
54
54
|
- 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`
|
|
55
|
-
- If the task is about package, app, field, layout, workflow, view, chart, portal, visibility, icon, or app base configuration,
|
|
55
|
+
- If the task is about package, app, field, layout, workflow, view, chart, portal, visibility, icon, or app base configuration, stop this app-user route and use the `qingflow-cli` skill or the `qingflow-app-builder` package
|
|
56
56
|
- 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
|
|
57
|
-
- For member/department field ambiguity, keep the record insert/update route and use `record_member_candidates` / `record_department_candidates`; do not switch to `directory_*`, builder member search, external-contact lookup, or contact-directory management queries. App User MCP only exposes `directory_search` for member-visible keyword search, not directory tree/list management.
|
|
58
57
|
- 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
|
|
59
58
|
- 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
|
|
60
59
|
- If the task is insert-focused and readback/detail context matters, keep the same route and prefer the single-record detail readback after the write; use normalized list readback only when batch row shape is needed
|
|
61
60
|
- 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
|
|
62
61
|
- If the task is about task discovery by natural language query, still route to `$qingflow-task-ops`; `task_list --query` now uses backend search first and only falls back to local matching when backend returns zero rows
|
|
63
62
|
- If the task is about grouped distributions, ratios, rankings, trends, insights, or any final statistical conclusion, switch to `$qingflow-record-analysis`
|
|
64
|
-
-
|
|
63
|
+
- If the MCP is not connected, authenticated, or bound to the right workspace, switch to `$qingflow-mcp-setup`
|
|
65
64
|
|
|
66
65
|
## Shared Preconditions
|
|
67
66
|
|
|
@@ -89,4 +88,4 @@ Route to exactly one of these specialized paths:
|
|
|
89
88
|
- Record import: [$qingflow-record-import](../qingflow-record-import/SKILL.md)
|
|
90
89
|
- Task workflow operations: [$qingflow-task-ops](../qingflow-task-ops/SKILL.md)
|
|
91
90
|
- Dedicated analysis workflow: [$qingflow-record-analysis](../qingflow-record-analysis/SKILL.md)
|
|
92
|
-
- Builder workflow
|
|
91
|
+
- Builder workflow: use the `qingflow-cli` skill or the `qingflow-app-builder` package
|
|
@@ -9,12 +9,12 @@ For final statistics, grouped distributions, rankings, trends, or insight-style
|
|
|
9
9
|
- `record_get` is for one exact record and downloads readable detail-page images into `media_assets.items[].local_path` plus attachments/documents/tables into `file_assets.items[].local_path`
|
|
10
10
|
- Use `record_browse_schema_get` when field titles are uncertain instead of guessing ids
|
|
11
11
|
- Do not present paged browse output as if it were a grouped or full-population conclusion
|
|
12
|
-
- Use `record_export_direct` only when the user explicitly asks for export/download/Excel output
|
|
12
|
+
- Use `record_export_direct` only when the user explicitly asks for export/download/Excel output
|
|
13
13
|
|
|
14
14
|
## Direct Writes
|
|
15
15
|
|
|
16
16
|
- `record_insert` is schema-first through `record_insert_schema_get`; default to `items=[{"fields": {...}}]`
|
|
17
|
-
- `record_update` is
|
|
17
|
+
- `record_update` is schema-first through `record_update_schema_get`
|
|
18
18
|
- `record_delete` does not need a schema-get step
|
|
19
19
|
- For batch insert, `partial_success` means some rows were created; use `created_record_ids`, failed `row_number`, and `failed_fields` to repair only failed rows
|
|
20
20
|
- If a direct-write tool returns `write_executed=false`, the write was blocked and not executed for that item
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Qingflow Core Public Surface Sync
|
|
2
2
|
|
|
3
|
-
Use this file as the maintenance baseline for the core Qingflow skills.
|
|
3
|
+
Use this file as the maintenance baseline for the core Qingflow skills.
|
|
4
4
|
It is not a user-facing product spec. It exists to prevent skill drift.
|
|
5
5
|
|
|
6
6
|
## Current Public Defaults
|
|
@@ -12,9 +12,9 @@ It is not a user-facing product spec. It exists to prevent skill drift.
|
|
|
12
12
|
- Standard flows:
|
|
13
13
|
- analyze: `app_get -> record_browse_schema_get -> record_access -> Python`
|
|
14
14
|
- browse detail: `app_get -> record_browse_schema_get -> record_list / record_get`
|
|
15
|
-
- explicit export/download/Excel: `
|
|
15
|
+
- explicit export/download/Excel: `view_get -> record_export_*` or `record_export_direct`
|
|
16
16
|
- insert: `record_insert_schema_get -> record_insert(items)`
|
|
17
|
-
- update: `
|
|
17
|
+
- update: `record_update_schema_get -> record_update`
|
|
18
18
|
|
|
19
19
|
### Tasks
|
|
20
20
|
|
|
@@ -33,20 +33,22 @@ It is not a user-facing product spec. It exists to prevent skill drift.
|
|
|
33
33
|
|
|
34
34
|
### Builder
|
|
35
35
|
|
|
36
|
+
- Builder work belongs to the `qingflow-cli` skill or the `qingflow-app-builder` package; this app-user package only records the boundary to prevent routing drift.
|
|
36
37
|
- Official package entry: `package_get`, `package_apply`
|
|
37
38
|
- Official builder writes:
|
|
38
39
|
- `app_schema_apply`
|
|
39
40
|
- `app_layout_apply`
|
|
40
41
|
- `app_flow_apply`
|
|
41
42
|
- `app_views_apply`
|
|
42
|
-
- `app_custom_buttons_apply`
|
|
43
43
|
- `app_associated_resources_apply`
|
|
44
44
|
- `app_charts_apply`
|
|
45
45
|
- `portal_apply`
|
|
46
46
|
- `app_publish_verify`
|
|
47
|
+
- Complete-system main path: package -> schema apps with `form.sections` and `target_app_ref` -> views with `action_buttons` -> portal with inline `sections[].chart`
|
|
48
|
+
- Single-app main path: resolve/get -> schema with `form.sections` -> views with `action_buttons`; do not default to portal
|
|
47
49
|
- `portal_apply` edit mode may omit `sections` for base-info-only updates
|
|
48
50
|
- `app_charts_apply.visibility` is a public capability and should be treated as a base-only visibility update
|
|
49
|
-
- Existing object parameter replacement should use `patch_views`, `
|
|
51
|
+
- Existing object parameter replacement should use `patch_views`, `patch_resources`, and `patch_charts`; `upsert_*` is for creation or full target config
|
|
50
52
|
- `app_get.editability` uses:
|
|
51
53
|
- `can_edit_app_base`
|
|
52
54
|
- `can_edit_form`
|
|
@@ -41,13 +41,13 @@ Use `record_insert_schema_get -> record_insert(items)`.
|
|
|
41
41
|
|
|
42
42
|
## Update Pattern
|
|
43
43
|
|
|
44
|
-
Use `
|
|
44
|
+
Use `record_update_schema_get -> record_update`.
|
|
45
45
|
|
|
46
46
|
1. Confirm the target app and `record_id`
|
|
47
|
-
2. Read `
|
|
48
|
-
3.
|
|
49
|
-
4.
|
|
50
|
-
5.
|
|
47
|
+
2. Read `writable_fields` and `payload_template`
|
|
48
|
+
3. Update only fields present in `writable_fields`
|
|
49
|
+
4. Let MCP auto-select the first matched accessible view that can execute the payload
|
|
50
|
+
5. Run `record_update`
|
|
51
51
|
|
|
52
52
|
## Delete Pattern
|
|
53
53
|
|
|
@@ -14,12 +14,13 @@ Examples:
|
|
|
14
14
|
|
|
15
15
|
Rules:
|
|
16
16
|
|
|
17
|
-
- if the user starts from inbox, todo, workload, cc, or bottleneck language, use
|
|
18
|
-
- use `
|
|
19
|
-
- use `
|
|
17
|
+
- if the user starts from inbox, todo, workload, cc, or bottleneck language, use `task_*` first
|
|
18
|
+
- use `task_summary` for headline counts
|
|
19
|
+
- use `task_list` for flat browsing
|
|
20
|
+
- use `task_facets` when worksheet or workflow-node buckets matter
|
|
20
21
|
- treat task counts as task-center counts, not record counts
|
|
21
22
|
- switch to `record_*` only after locating the exact business record behind a task
|
|
22
23
|
- identify the exact target first
|
|
23
|
-
- for approve or reject, identify the exact
|
|
24
|
+
- for approve or reject, identify the exact `workflow_node_id` first; prefer task-center results or current audit info, then use `task_approve` or `task_reject`
|
|
24
25
|
- avoid usage-side workflow actions on ambiguous records
|
|
25
26
|
- summarize the final action and target task ids or record ids
|