@josephyan/qingflow-app-user-mcp 0.2.0-beta.10 → 0.2.0-beta.1000
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 +95 -14
- package/npm/bin/qingflow-app-user-mcp.mjs +1 -1
- package/npm/lib/runtime.mjs +175 -12
- package/package.json +1 -1
- package/pyproject.toml +4 -1
- package/skills/qingflow-app-user/SKILL.md +76 -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/public-surface-sync.md +70 -0
- 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 +200 -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 +61 -0
- package/skills/qingflow-record-update/SKILL.md +45 -0
- package/skills/qingflow-task-ops/SKILL.md +144 -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 +33 -1
- package/src/qingflow_mcp/backend_client.py +313 -0
- package/src/qingflow_mcp/builder_facade/models.py +1472 -12
- package/src/qingflow_mcp/builder_facade/service.py +15241 -2389
- package/src/qingflow_mcp/cli/__init__.py +1 -0
- package/src/qingflow_mcp/cli/commands/__init__.py +18 -0
- package/src/qingflow_mcp/cli/commands/app.py +40 -0
- package/src/qingflow_mcp/cli/commands/auth.py +112 -0
- package/src/qingflow_mcp/cli/commands/builder.py +539 -0
- package/src/qingflow_mcp/cli/commands/chart.py +18 -0
- package/src/qingflow_mcp/cli/commands/common.py +62 -0
- package/src/qingflow_mcp/cli/commands/imports.py +96 -0
- package/src/qingflow_mcp/cli/commands/portal.py +25 -0
- package/src/qingflow_mcp/cli/commands/record.py +331 -0
- package/src/qingflow_mcp/cli/commands/repo.py +80 -0
- package/src/qingflow_mcp/cli/commands/task.py +141 -0
- package/src/qingflow_mcp/cli/commands/view.py +18 -0
- package/src/qingflow_mcp/cli/commands/workspace.py +110 -0
- package/src/qingflow_mcp/cli/context.py +60 -0
- package/src/qingflow_mcp/cli/formatters.py +573 -0
- package/src/qingflow_mcp/cli/json_io.py +50 -0
- package/src/qingflow_mcp/cli/main.py +186 -0
- package/src/qingflow_mcp/cli/qingflow_login.py +116 -0
- package/src/qingflow_mcp/cli/terminal_ui.py +173 -0
- package/src/qingflow_mcp/config.py +225 -0
- package/src/qingflow_mcp/errors.py +2 -2
- package/src/qingflow_mcp/id_utils.py +49 -0
- package/src/qingflow_mcp/import_store.py +121 -0
- package/src/qingflow_mcp/list_type_labels.py +24 -0
- package/src/qingflow_mcp/public_surface.py +243 -0
- package/src/qingflow_mcp/repository_store.py +71 -0
- package/src/qingflow_mcp/response_trim.py +841 -0
- package/src/qingflow_mcp/server.py +164 -18
- package/src/qingflow_mcp/server_app_builder.py +317 -125
- package/src/qingflow_mcp/server_app_user.py +269 -217
- package/src/qingflow_mcp/session_store.py +141 -21
- package/src/qingflow_mcp/solution/compiler/form_compiler.py +43 -4
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +119 -45
- package/src/qingflow_mcp/solution/compiler/workflow_compiler.py +32 -0
- package/src/qingflow_mcp/solution/executor.py +75 -6
- package/src/qingflow_mcp/solution/spec_models.py +2 -0
- package/src/qingflow_mcp/tools/ai_builder_tools.py +2546 -282
- package/src/qingflow_mcp/tools/app_tools.py +467 -13
- package/src/qingflow_mcp/tools/approval_tools.py +641 -77
- package/src/qingflow_mcp/tools/auth_tools.py +823 -249
- package/src/qingflow_mcp/tools/base.py +204 -4
- package/src/qingflow_mcp/tools/code_block_tools.py +777 -0
- package/src/qingflow_mcp/tools/custom_button_tools.py +202 -0
- package/src/qingflow_mcp/tools/directory_tools.py +234 -35
- package/src/qingflow_mcp/tools/feedback_tools.py +238 -0
- package/src/qingflow_mcp/tools/file_tools.py +26 -1
- package/src/qingflow_mcp/tools/import_tools.py +2223 -0
- package/src/qingflow_mcp/tools/navigation_tools.py +34 -1
- package/src/qingflow_mcp/tools/package_tools.py +104 -5
- package/src/qingflow_mcp/tools/portal_tools.py +59 -1
- package/src/qingflow_mcp/tools/qingbi_report_tools.py +147 -8
- package/src/qingflow_mcp/tools/record_tools.py +11161 -1177
- package/src/qingflow_mcp/tools/repository_dev_tools.py +552 -0
- package/src/qingflow_mcp/tools/resource_read_tools.py +503 -0
- package/src/qingflow_mcp/tools/role_tools.py +19 -1
- package/src/qingflow_mcp/tools/solution_tools.py +171 -4
- package/src/qingflow_mcp/tools/task_context_tools.py +2986 -0
- package/src/qingflow_mcp/tools/task_tools.py +426 -229
- package/src/qingflow_mcp/tools/view_tools.py +56 -1
- package/src/qingflow_mcp/tools/workflow_tools.py +65 -1
- package/src/qingflow_mcp/tools/workspace_tools.py +141 -67
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.1000
|
|
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.1000 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,30 @@
|
|
|
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`
|
|
8
|
+
|
|
9
|
+
## 本地鉴权推荐方案
|
|
10
|
+
|
|
11
|
+
本地模式现在推荐优先使用 `credential` 建立会话,而不是直接注入 `token`。
|
|
12
|
+
|
|
13
|
+
推荐链路:
|
|
14
|
+
|
|
15
|
+
1. createClaw 或其它本地宿主为当前实例保存 `credential`
|
|
16
|
+
2. 本地 MCP 调用 `auth_use_credential`
|
|
17
|
+
3. MCP 用该 `credential` 请求 apaas `/mcp/auth/context`
|
|
18
|
+
4. 解析并保存 `token / wsId / qfVersion / uid`
|
|
19
|
+
5. 业务工具直接使用这份上下文
|
|
20
|
+
|
|
21
|
+
`auth_use_credential` 是本地唯一鉴权主路径。
|
|
22
|
+
|
|
23
|
+
补充说明:
|
|
24
|
+
|
|
25
|
+
- 对 stdio MCP 来说,主路径仍然只有 `auth_use_credential`。
|
|
26
|
+
- 如果你是在终端里直接使用 `qingflow` CLI,可以额外使用 `qingflow auth login` 作为“人类登录”入口;默认会提示轻流邮箱和隐藏密码,拿到 `token` 后建立本地 CLI 会话。
|
|
27
|
+
- 也就是说,这次新增的是 CLI 的登录入口,不是给 MCP 增加第二套会话模型。
|
|
7
28
|
|
|
8
29
|
## npm 安装器适用场景
|
|
9
30
|
|
|
@@ -62,15 +83,17 @@ npm run pack:npm
|
|
|
62
83
|
会生成:
|
|
63
84
|
|
|
64
85
|
```bash
|
|
65
|
-
dist/npm/
|
|
66
|
-
dist/npm/
|
|
86
|
+
dist/npm/qingflow-tech-qingflow-cli-<version>.tgz
|
|
87
|
+
dist/npm/qingflow-tech-qingflow-app-user-mcp-<version>.tgz
|
|
88
|
+
dist/npm/qingflow-tech-qingflow-app-builder-mcp-<version>.tgz
|
|
67
89
|
```
|
|
68
90
|
|
|
69
91
|
然后在目标机器安装:
|
|
70
92
|
|
|
71
93
|
```bash
|
|
72
|
-
npm install /absolute/path/to/dist/npm/
|
|
73
|
-
npm install /absolute/path/to/dist/npm/
|
|
94
|
+
npm install /absolute/path/to/dist/npm/qingflow-tech-qingflow-cli-<version>.tgz
|
|
95
|
+
npm install /absolute/path/to/dist/npm/qingflow-tech-qingflow-app-user-mcp-<version>.tgz
|
|
96
|
+
npm install /absolute/path/to/dist/npm/qingflow-tech-qingflow-app-builder-mcp-<version>.tgz
|
|
74
97
|
```
|
|
75
98
|
|
|
76
99
|
安装时会自动:
|
|
@@ -78,7 +101,7 @@ npm install /absolute/path/to/dist/npm/josephyan-qingflow-app-builder-mcp-<versi
|
|
|
78
101
|
1. 创建 `.npm-python/`
|
|
79
102
|
2. 在其中建立 Python 虚拟环境
|
|
80
103
|
3. 执行 `pip install .`
|
|
81
|
-
4. 在安装位置暴露 `qingflow-app-user-mcp`、`qingflow-app-builder-mcp` 命令
|
|
104
|
+
4. 在安装位置暴露 `qingflow`、`qingflow-app-user-mcp`、`qingflow-app-builder-mcp` 命令
|
|
82
105
|
|
|
83
106
|
## 本地验证
|
|
84
107
|
|
|
@@ -86,6 +109,7 @@ npm install /absolute/path/to/dist/npm/josephyan-qingflow-app-builder-mcp-<versi
|
|
|
86
109
|
|
|
87
110
|
```bash
|
|
88
111
|
cd qingflow-support/mcp-server
|
|
112
|
+
node ./npm/bin/qingflow.mjs --help
|
|
89
113
|
node ./npm/bin/qingflow-app-user-mcp.mjs
|
|
90
114
|
node ./npm/bin/qingflow-app-builder-mcp.mjs
|
|
91
115
|
```
|
|
@@ -93,6 +117,7 @@ node ./npm/bin/qingflow-app-builder-mcp.mjs
|
|
|
93
117
|
如果你是全局安装:
|
|
94
118
|
|
|
95
119
|
```bash
|
|
120
|
+
qingflow --help
|
|
96
121
|
qingflow-app-user-mcp
|
|
97
122
|
qingflow-app-builder-mcp
|
|
98
123
|
```
|
|
@@ -100,6 +125,7 @@ qingflow-app-builder-mcp
|
|
|
100
125
|
如果你是把包安装到了某个本地 agent workspace,命令通常位于:
|
|
101
126
|
|
|
102
127
|
```bash
|
|
128
|
+
/absolute/path/to/agent-workspace/node_modules/.bin/qingflow
|
|
103
129
|
/absolute/path/to/agent-workspace/node_modules/.bin/qingflow-app-user-mcp
|
|
104
130
|
/absolute/path/to/agent-workspace/node_modules/.bin/qingflow-app-builder-mcp
|
|
105
131
|
```
|
|
@@ -107,6 +133,7 @@ qingflow-app-builder-mcp
|
|
|
107
133
|
如果你是从 tgz 安装到某个空目录,命令通常位于:
|
|
108
134
|
|
|
109
135
|
```bash
|
|
136
|
+
/absolute/path/to/install-dir/node_modules/.bin/qingflow
|
|
110
137
|
/absolute/path/to/install-dir/node_modules/.bin/qingflow-app-user-mcp
|
|
111
138
|
/absolute/path/to/install-dir/node_modules/.bin/qingflow-app-builder-mcp
|
|
112
139
|
```
|
|
@@ -129,7 +156,10 @@ qingflow-app-builder-mcp
|
|
|
129
156
|
],
|
|
130
157
|
"env": {
|
|
131
158
|
"QINGFLOW_MCP_DEFAULT_BASE_URL": "https://qingflow.com/api",
|
|
132
|
-
"QINGFLOW_MCP_HOME": "/absolute/path/to/.qingflow-mcp"
|
|
159
|
+
"QINGFLOW_MCP_HOME": "/absolute/path/to/.qingflow-mcp",
|
|
160
|
+
"QINGFLOW_MCP_CREDIT_METER_ENABLED": "true",
|
|
161
|
+
"QINGFLOW_MCP_CREDIT_APAAS_BASE_URL": "https://apaas.internal.example.com",
|
|
162
|
+
"QINGFLOW_MCP_CREDIT_APAAS_PATH": "/user/credit/usage"
|
|
133
163
|
}
|
|
134
164
|
}
|
|
135
165
|
}
|
|
@@ -146,7 +176,10 @@ qingflow-app-builder-mcp
|
|
|
146
176
|
"args": [],
|
|
147
177
|
"env": {
|
|
148
178
|
"QINGFLOW_MCP_DEFAULT_BASE_URL": "https://qingflow.com/api",
|
|
149
|
-
"QINGFLOW_MCP_HOME": "/absolute/path/to/.qingflow-mcp"
|
|
179
|
+
"QINGFLOW_MCP_HOME": "/absolute/path/to/.qingflow-mcp",
|
|
180
|
+
"QINGFLOW_MCP_CREDIT_METER_ENABLED": "true",
|
|
181
|
+
"QINGFLOW_MCP_CREDIT_APAAS_BASE_URL": "https://apaas.internal.example.com",
|
|
182
|
+
"QINGFLOW_MCP_CREDIT_APAAS_PATH": "/user/credit/usage"
|
|
150
183
|
}
|
|
151
184
|
}
|
|
152
185
|
}
|
|
@@ -163,7 +196,10 @@ qingflow-app-builder-mcp
|
|
|
163
196
|
"args": [],
|
|
164
197
|
"env": {
|
|
165
198
|
"QINGFLOW_MCP_DEFAULT_BASE_URL": "https://qingflow.com/api",
|
|
166
|
-
"QINGFLOW_MCP_HOME": "/absolute/path/to/.qingflow-mcp"
|
|
199
|
+
"QINGFLOW_MCP_HOME": "/absolute/path/to/.qingflow-mcp",
|
|
200
|
+
"QINGFLOW_MCP_CREDIT_METER_ENABLED": "true",
|
|
201
|
+
"QINGFLOW_MCP_CREDIT_APAAS_BASE_URL": "https://apaas.internal.example.com",
|
|
202
|
+
"QINGFLOW_MCP_CREDIT_APAAS_PATH": "/user/credit/usage"
|
|
167
203
|
}
|
|
168
204
|
}
|
|
169
205
|
}
|
|
@@ -184,7 +220,10 @@ qingflow-app-builder-mcp
|
|
|
184
220
|
"@josephyan/qingflow-app-user-mcp"
|
|
185
221
|
],
|
|
186
222
|
"env": {
|
|
187
|
-
"QINGFLOW_MCP_DEFAULT_BASE_URL": "https://qingflow.com/api"
|
|
223
|
+
"QINGFLOW_MCP_DEFAULT_BASE_URL": "https://qingflow.com/api",
|
|
224
|
+
"QINGFLOW_MCP_CREDIT_METER_ENABLED": "true",
|
|
225
|
+
"QINGFLOW_MCP_CREDIT_APAAS_BASE_URL": "https://apaas.internal.example.com",
|
|
226
|
+
"QINGFLOW_MCP_CREDIT_APAAS_PATH": "/user/credit/usage"
|
|
188
227
|
}
|
|
189
228
|
},
|
|
190
229
|
"qingflow-builder": {
|
|
@@ -194,7 +233,10 @@ qingflow-app-builder-mcp
|
|
|
194
233
|
"@josephyan/qingflow-app-builder-mcp"
|
|
195
234
|
],
|
|
196
235
|
"env": {
|
|
197
|
-
"QINGFLOW_MCP_DEFAULT_BASE_URL": "https://qingflow.com/api"
|
|
236
|
+
"QINGFLOW_MCP_DEFAULT_BASE_URL": "https://qingflow.com/api",
|
|
237
|
+
"QINGFLOW_MCP_CREDIT_METER_ENABLED": "true",
|
|
238
|
+
"QINGFLOW_MCP_CREDIT_APAAS_BASE_URL": "https://apaas.internal.example.com",
|
|
239
|
+
"QINGFLOW_MCP_CREDIT_APAAS_PATH": "/user/credit/usage"
|
|
198
240
|
}
|
|
199
241
|
}
|
|
200
242
|
}
|
|
@@ -202,9 +244,10 @@ qingflow-app-builder-mcp
|
|
|
202
244
|
```
|
|
203
245
|
|
|
204
246
|
说明:
|
|
205
|
-
- 源码目录 `npm install` 不会把命令加到全局 PATH;这种模式请用 `node ./npm/bin/qingflow-app-user-mcp.mjs` 或 `node ./npm/bin/qingflow-app-builder-mcp.mjs`
|
|
247
|
+
- 源码目录 `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
248
|
- `npx` 方式适合临时安装或容器化本地 agent
|
|
207
249
|
- 全局安装方式更适合长期固定使用的本机开发环境
|
|
250
|
+
- 计费接口使用当前登录会话的 `token` 与 `wsId` 请求头,可通过 `QINGFLOW_MCP_CREDIT_APAAS_BASE_URL/PATH` 覆盖调用记录接口地址
|
|
208
251
|
|
|
209
252
|
## 排障
|
|
210
253
|
|
|
@@ -226,3 +269,41 @@ rm -rf .npm-python
|
|
|
226
269
|
```bash
|
|
227
270
|
npm install
|
|
228
271
|
```
|
|
272
|
+
|
|
273
|
+
如果 MCP 客户端一调用工具就报 `Transport closed`,优先检查这几件事:
|
|
274
|
+
|
|
275
|
+
1. 不要混用不同版本的 `@josephyan/qingflow-cli`、`@josephyan/qingflow-app-user-mcp`、`@josephyan/qingflow-app-builder-mcp`
|
|
276
|
+
2. 删除安装目录下的 `.npm-python`
|
|
277
|
+
3. 重新执行 `npm install` 或重新安装对应 tgz/npm 包
|
|
278
|
+
4. 再启动 MCP 客户端
|
|
279
|
+
|
|
280
|
+
现在 stdio MCP 入口会拒绝在启动瞬间“边启动边重建 Python 运行时”,因为安装日志一旦写进 stdout,就会破坏 MCP 握手并表现成 `Transport closed`。如果运行时缺失或版本不一致,入口会直接报错并提示重装,而不是静默自修复。
|
|
281
|
+
|
|
282
|
+
## createClaw 本地接入示例
|
|
283
|
+
|
|
284
|
+
如果 createClaw 已经为当前本地实例保存了 `credential`,推荐在首次建链时调用:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
qingflow auth use-credential \
|
|
288
|
+
--base-url https://qingflow.com/api \
|
|
289
|
+
--credential-stdin
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
然后把 `credential` 写到 stdin。
|
|
293
|
+
|
|
294
|
+
等价 MCP 工具调用参数:
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{
|
|
298
|
+
"profile": "default",
|
|
299
|
+
"base_url": "https://qingflow.com/api",
|
|
300
|
+
"credential": "1602853_277941",
|
|
301
|
+
"persist": false
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
说明:
|
|
306
|
+
|
|
307
|
+
- 本地会把解析后的 `token` 和原始 `credential` 写入 profile 文件,用于后续 CLI 命令恢复会话
|
|
308
|
+
- `persist=true` 时,本地还会优先把解析后的 `token` 和原始 `credential` 同步写入系统 keychain
|
|
309
|
+
- 当前工作区以 `/mcp/auth/context` 返回的 `wsId` 为准,不再通过本地 MCP 显式切换
|
|
@@ -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,79 @@ 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(
|
|
291
|
+
packageRoot,
|
|
292
|
+
args,
|
|
293
|
+
commandName = "qingflow-mcp",
|
|
294
|
+
{ allowRuntimeBootstrap = false, stdio = "proxy" } = {},
|
|
295
|
+
) {
|
|
296
|
+
let runtime = inspectPythonEnv(packageRoot, commandName);
|
|
297
|
+
let serverCommand = runtime.serverCommand;
|
|
298
|
+
|
|
299
|
+
if (!runtime.ready) {
|
|
300
|
+
if (!allowRuntimeBootstrap) {
|
|
301
|
+
console.error(formatRuntimeProblem(runtime, commandName, { allowRuntimeBootstrap }));
|
|
302
|
+
process.exit(1);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
serverCommand = ensurePythonEnv(packageRoot, { commandName });
|
|
308
|
+
runtime = inspectPythonEnv(packageRoot, commandName);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.error(`[qingflow-mcp] Failed to prepare Python runtime for ${commandName}: ${error.message}`);
|
|
311
|
+
process.exit(1);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (!runtime.ready) {
|
|
316
|
+
console.error(formatRuntimeProblem(runtime, commandName, { allowRuntimeBootstrap }));
|
|
317
|
+
process.exit(1);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
165
321
|
|
|
166
322
|
const child = spawn(serverCommand, args, {
|
|
167
|
-
stdio: "inherit",
|
|
323
|
+
stdio: stdio === "inherit" ? "inherit" : ["pipe", "pipe", "pipe"],
|
|
168
324
|
env: process.env,
|
|
325
|
+
windowsHide: true,
|
|
169
326
|
});
|
|
170
327
|
|
|
171
|
-
|
|
328
|
+
if (stdio !== "inherit") {
|
|
329
|
+
proxyStreams(child);
|
|
330
|
+
}
|
|
331
|
+
forwardSignal(child, "SIGINT");
|
|
332
|
+
forwardSignal(child, "SIGTERM");
|
|
333
|
+
|
|
334
|
+
child.on("close", (code, signal) => {
|
|
172
335
|
if (signal) {
|
|
173
336
|
process.kill(process.pid, signal);
|
|
174
337
|
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.0b1000"
|
|
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"
|