@hupan56/wlkj 2.6.0 → 2.7.1
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/bin/cli.js +269 -11
- package/package.json +1 -1
- package/templates/qoder/agents/insight-planning.md +67 -0
- package/templates/qoder/agents/insight-research.md +61 -0
- package/templates/qoder/agents/prd-reference.md +8 -2
- package/templates/qoder/commands/optional/wl-insight.md +275 -0
- package/templates/qoder/commands/{wl-report.md → optional/wl-report.md} +13 -5
- package/templates/qoder/commands/{wl-spec.md → optional/wl-spec.md} +1 -1
- package/templates/qoder/commands/{wl-status.md → optional/wl-status.md} +28 -2
- package/templates/qoder/commands/wl-code.md +10 -2
- package/templates/qoder/commands/wl-commit.md +1 -1
- package/templates/qoder/commands/wl-design-draw.md +78 -0
- package/templates/qoder/commands/wl-design-scan.md +108 -0
- package/templates/qoder/commands/wl-design-spec.md +154 -0
- package/templates/qoder/commands/wl-design.md +32 -0
- package/templates/qoder/commands/wl-init.md +24 -3
- package/templates/qoder/commands/wl-prd-full.md +226 -0
- package/templates/qoder/commands/wl-prd-quick.md +134 -0
- package/templates/qoder/commands/wl-prd-review.md +104 -0
- package/templates/qoder/commands/wl-prd.md +17 -311
- package/templates/qoder/commands/wl-search.md +66 -30
- package/templates/qoder/commands/wl-task.md +290 -59
- package/templates/qoder/commands/wl-test.md +92 -24
- package/templates/qoder/config.yaml +51 -15
- package/templates/qoder/hooks/inject-workflow-state.py +2 -2
- package/templates/qoder/hooks/session-start.py +82 -51
- package/templates/qoder/rules/wl-pipeline.md +216 -105
- package/templates/qoder/scripts/__pycache__/search_index.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/archive_prd.py +377 -0
- package/templates/qoder/scripts/autotest.py +1715 -0
- package/templates/qoder/scripts/autotest_batch.py +224 -0
- package/templates/qoder/scripts/autotest_run.py +297 -0
- package/templates/qoder/scripts/benchmark.py +210 -209
- package/templates/qoder/scripts/build_style_index.py +444 -4
- package/templates/qoder/scripts/check_carriers.py +238 -0
- package/templates/qoder/scripts/check_mcp.py +298 -0
- package/templates/qoder/scripts/check_qoderwork_consistency.py +166 -0
- package/templates/qoder/scripts/common/events.py +46 -0
- package/templates/qoder/scripts/common/extract.py +419 -0
- package/templates/qoder/scripts/common/graph_traverse.py +533 -0
- package/templates/qoder/scripts/common/paths.py +89 -0
- package/templates/qoder/scripts/common/pip_install.py +144 -0
- package/templates/qoder/scripts/common/platform_guard.py +61 -0
- package/templates/qoder/scripts/common/search_engine.py +205 -205
- package/templates/qoder/scripts/common/terms.py +57 -0
- package/templates/qoder/scripts/common/ts_extract.py +536 -0
- package/templates/qoder/scripts/context_pack.py +73 -13
- package/templates/qoder/scripts/enrich_prompt.py +226 -0
- package/templates/qoder/scripts/eval_prd.py +318 -231
- package/templates/qoder/scripts/export.py +487 -487
- package/templates/qoder/scripts/extract_api_params.py +246 -0
- package/templates/qoder/scripts/extract_routes.py +54 -0
- package/templates/qoder/scripts/extract_routes_tree.py +78 -0
- package/templates/qoder/scripts/fill_prototype.py +707 -0
- package/templates/qoder/scripts/gen_design_doc.py +394 -0
- package/templates/qoder/scripts/git_sync.py +27 -15
- package/templates/qoder/scripts/init_doctor.py +292 -40
- package/templates/qoder/scripts/install_qoderwork.py +366 -9
- package/templates/qoder/scripts/kg.py +708 -0
- package/templates/qoder/scripts/kg_auto_login.py +196 -0
- package/templates/qoder/scripts/kg_build.py +612 -0
- package/templates/qoder/scripts/kg_build_db.py +327 -0
- package/templates/qoder/scripts/kg_duckdb.py +549 -0
- package/templates/qoder/scripts/kg_incremental.py +393 -0
- package/templates/qoder/scripts/kg_link_db.py +224 -0
- package/templates/qoder/scripts/kg_mcp_server.py +801 -0
- package/templates/qoder/scripts/kg_semantic.py +150 -0
- package/templates/qoder/scripts/kg_test_runner.py +241 -0
- package/templates/qoder/scripts/lanhu_stdio_wrapper.py +119 -0
- package/templates/qoder/scripts/learn.py +118 -39
- package/templates/qoder/scripts/learn_aggregate.py +201 -0
- package/templates/qoder/scripts/mcp_launcher.py +359 -0
- package/templates/qoder/scripts/mysql_mcp_server.py +396 -0
- package/templates/qoder/scripts/repo_root.py +106 -0
- package/templates/qoder/scripts/role.py +12 -0
- package/templates/qoder/scripts/run_weekly_update.bat +5 -0
- package/templates/qoder/scripts/run_weekly_update.sh +5 -0
- package/templates/qoder/scripts/search_index.py +307 -60
- package/templates/qoder/scripts/secure-ls.js +5640 -0
- package/templates/qoder/scripts/setup.py +706 -641
- package/templates/qoder/scripts/setup_lanhu.py +963 -0
- package/templates/qoder/scripts/status.py +250 -11
- package/templates/qoder/scripts/sync_carriers.py +259 -0
- package/templates/qoder/scripts/syncgate.py +2 -2
- package/templates/qoder/scripts/task.py +75 -0
- package/templates/qoder/scripts/team_sync.py +60 -4
- package/templates/qoder/scripts/workspace_init.py +1 -1
- package/templates/qoder/skills/design-import/SKILL.md +226 -0
- package/templates/qoder/skills/design-import/figma-workflow.md +81 -0
- package/templates/qoder/skills/design-review/SKILL.md +82 -25
- package/templates/qoder/skills/prd-generator/SKILL.md +184 -60
- package/templates/qoder/skills/prd-review/SKILL.md +18 -1
- package/templates/qoder/skills/prompt-enrich/SKILL.md +90 -0
- package/templates/qoder/skills/prototype-generator/SKILL.md +256 -141
- package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
- package/templates/qoder/skills/spec-coder/SKILL.md +18 -1
- package/templates/qoder/skills/spec-generator/SKILL.md +18 -1
- package/templates/qoder/skills/test-generator/SKILL.md +15 -2
- package/templates/qoder/skills/wl-code/SKILL.md +55 -36
- package/templates/qoder/skills/wl-commit/SKILL.md +89 -76
- package/templates/qoder/skills/wl-design/SKILL.md +55 -0
- package/templates/qoder/skills/wl-init/SKILL.md +76 -67
- package/templates/qoder/skills/wl-insight/SKILL.md +201 -81
- package/templates/qoder/skills/wl-prd-full/SKILL.md +69 -0
- package/templates/qoder/skills/wl-prd-quick/SKILL.md +49 -0
- package/templates/qoder/skills/wl-prd-review/SKILL.md +34 -0
- package/templates/qoder/skills/wl-report/SKILL.md +131 -107
- package/templates/qoder/skills/wl-search/SKILL.md +141 -75
- package/templates/qoder/skills/wl-spec/SKILL.md +49 -39
- package/templates/qoder/skills/wl-status/SKILL.md +83 -61
- package/templates/qoder/skills/wl-task/SKILL.md +132 -58
- package/templates/qoder/skills/wl-test/SKILL.md +406 -40
- package/templates/qoder/templates/prototype-app.html +13 -8
- package/templates/qoder/templates/prototype-web.html +376 -93
- package/templates/root/AGENTS.md +89 -34
- package/templates/root/requirements.txt +21 -0
- package/templates/root//344/275/277/347/224/250/350/257/264/346/230/216.md +259 -259
- package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +186 -186
- package/templates/qoder/agents/prd-planning.md +0 -57
- package/templates/qoder/agents/prd-research.md +0 -34
- package/templates/qoder/commands/wl-insight.md +0 -51
- package/templates/qoder/scripts/__pycache__/init_doctor.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/__pycache__/setup.cpython-39.pyc +0 -0
- package/templates/qoder/scripts/common/__pycache__/developer.cpython-39.pyc +0 -0
- package/templates/qoder/skills/wl-prd/SKILL.md +0 -92
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
mcp_launcher.py - 统一 MCP 启动器(跨 Win/Mac/Linux,消除写死路径)
|
|
5
|
+
|
|
6
|
+
被谁调用:
|
|
7
|
+
QoderWork 按 ~/.qoderwork/mcp.json 的 command 拉起本脚本。
|
|
8
|
+
mcp.json 的 args = [<本脚本路径>, <MCP名>]
|
|
9
|
+
例: {"command": "py", "args": ["C:/Users/HP/.qoderwork/mcp_launcher.py", "kg"]}
|
|
10
|
+
|
|
11
|
+
为什么需要它:
|
|
12
|
+
之前 mcp.json 写死绝对路径 (E:\\...\\python.exe + 仓库路径 + venv路径),
|
|
13
|
+
换电脑/换系统/别人 clone 全废。本 launcher 运行时自定位仓库根 + 找对的 python,
|
|
14
|
+
mcp.json 只需写 python 命令名 (py/python3) + 本脚本路径 (在 ~/.qoderwork/)。
|
|
15
|
+
|
|
16
|
+
做什么:
|
|
17
|
+
1. 解析 argv[1] = MCP 名 (kg / mysql / lanhu)
|
|
18
|
+
2. find_repo_root(): 环境变量 QODER_REPO > ~/.qoderwork/.repo-root 文件 > 向上找 .qoder/
|
|
19
|
+
3. 按 MCP 名选 python + server 脚本:
|
|
20
|
+
- kg: sys.executable, <repo>/.qoder/scripts/kg_mcp_server.py
|
|
21
|
+
- mysql: sys.executable, <repo>/.qoder/scripts/mysql_mcp_server.py (env 从 .mcp-mysql-env.json 读)
|
|
22
|
+
- lanhu: <repo>/external/lanhu-mcp/.venv python (3.10+), 复用 lanhu_stdio_wrapper 逻辑 (读角色 cookie)
|
|
23
|
+
4. os.execv 启动 (接管进程, QoderWork 管生死)
|
|
24
|
+
|
|
25
|
+
跨平台:
|
|
26
|
+
find_repo_root 用 os.path, venv python 用 _venv_python (win Scripts/mac bin)。
|
|
27
|
+
本脚本被 python3/py/python 跑都行 (它只做定位+execv, 不依赖特定版本)。
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import os
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
|
37
|
+
except (AttributeError, TypeError, OSError):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _home():
|
|
42
|
+
"""跨平台 home 目录。"""
|
|
43
|
+
return Path(os.environ.get('USERPROFILE') or os.path.expanduser('~'))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _log(msg):
|
|
47
|
+
"""调试日志到 stderr (MCP stdio 用 stdout, 不能污染)。"""
|
|
48
|
+
sys.stderr.write('[mcp-launcher] ' + msg + '\n')
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _launch_disabled_mcp(name, reason):
|
|
52
|
+
"""没装/没配时, 启动一个空壳 MCP (只回 initialize + tools/list 空)。
|
|
53
|
+
让 QoderWork 显示绿(已连接)但无工具, 而不是黄(报错)。
|
|
54
|
+
reason 写进 serverInfo 让用户知道为什么没工具。"""
|
|
55
|
+
import json as _j
|
|
56
|
+
_log(f'{name} MCP 以"未启用"模式启动: {reason}')
|
|
57
|
+
# 简单的 stdio 循环, 只响应 initialize 和 tools/list
|
|
58
|
+
while True:
|
|
59
|
+
try:
|
|
60
|
+
line = sys.stdin.buffer.readline()
|
|
61
|
+
except (EOFError, OSError):
|
|
62
|
+
break
|
|
63
|
+
if not line:
|
|
64
|
+
break
|
|
65
|
+
try:
|
|
66
|
+
req = _j.loads(line.decode('utf-8'))
|
|
67
|
+
except Exception:
|
|
68
|
+
continue
|
|
69
|
+
method = req.get('method', '')
|
|
70
|
+
rid = req.get('id')
|
|
71
|
+
if method == 'initialize':
|
|
72
|
+
sys.stdout.write(_j.dumps({
|
|
73
|
+
"jsonrpc": "2.0", "id": rid,
|
|
74
|
+
"result": {
|
|
75
|
+
"protocolVersion": "2024-11-05",
|
|
76
|
+
"capabilities": {"tools": {}},
|
|
77
|
+
"serverInfo": {"name": name, "version": "0.0.0-disabled",
|
|
78
|
+
"reason": reason}
|
|
79
|
+
}
|
|
80
|
+
}) + '\n')
|
|
81
|
+
sys.stdout.flush()
|
|
82
|
+
elif method == 'tools/list':
|
|
83
|
+
sys.stdout.write(_j.dumps({
|
|
84
|
+
"jsonrpc": "2.0", "id": rid,
|
|
85
|
+
"result": {"tools": []}
|
|
86
|
+
}) + '\n')
|
|
87
|
+
sys.stdout.flush()
|
|
88
|
+
elif method == 'notifications/initialized':
|
|
89
|
+
pass
|
|
90
|
+
elif rid is not None:
|
|
91
|
+
sys.stdout.write(_j.dumps({
|
|
92
|
+
"jsonrpc": "2.0", "id": rid,
|
|
93
|
+
"error": {"code": -32601, "message": reason}
|
|
94
|
+
}) + '\n')
|
|
95
|
+
sys.stdout.flush()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _find_python_with_deps(required_module='duckdb'):
|
|
99
|
+
"""找一个装了指定依赖的 Python。优先级:
|
|
100
|
+
1. sys.executable (当前 Python, 先测它有没有)
|
|
101
|
+
2. 常见 Python 路径 (anaconda/python3等) 逐个测
|
|
102
|
+
返回 python 路径(str) 或 None。
|
|
103
|
+
解决"py -3 选的 Python 没装 duckdb"问题。
|
|
104
|
+
"""
|
|
105
|
+
# 1. 当前 Python 有没有
|
|
106
|
+
try:
|
|
107
|
+
__import__(required_module)
|
|
108
|
+
return sys.executable
|
|
109
|
+
except ImportError:
|
|
110
|
+
pass
|
|
111
|
+
# 2. 找其它 Python
|
|
112
|
+
import shutil
|
|
113
|
+
candidates = []
|
|
114
|
+
for name in ('python', 'python3', 'py'):
|
|
115
|
+
p = shutil.which(name)
|
|
116
|
+
if p and p != sys.executable:
|
|
117
|
+
candidates.append(p)
|
|
118
|
+
# 常见安装路径 (Windows)
|
|
119
|
+
if sys.platform == 'win32':
|
|
120
|
+
for p in [
|
|
121
|
+
os.path.join(os.environ.get('USERPROFILE', ''), 'Anaconda3', 'python.exe'),
|
|
122
|
+
os.path.join(os.environ.get('USERPROFILE', ''), 'anaconda3', 'python.exe'),
|
|
123
|
+
os.path.join(os.environ.get('USERPROFILE', ''), 'miniconda3', 'python.exe'),
|
|
124
|
+
r'C:\ProgramData\Anaconda3\python.exe',
|
|
125
|
+
]:
|
|
126
|
+
if os.path.isfile(p) and p not in candidates:
|
|
127
|
+
candidates.append(p)
|
|
128
|
+
for py in candidates:
|
|
129
|
+
try:
|
|
130
|
+
r = __import__('subprocess').run(
|
|
131
|
+
[py, '-c', 'import %s' % required_module],
|
|
132
|
+
capture_output=True, timeout=5)
|
|
133
|
+
if r.returncode == 0:
|
|
134
|
+
_log(f'找到装了 {required_module} 的 Python: {py}')
|
|
135
|
+
return py
|
|
136
|
+
except Exception:
|
|
137
|
+
continue
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def find_repo_root():
|
|
142
|
+
"""定位 QODER 仓库根。优先级:
|
|
143
|
+
1. 环境变量 QODER_REPO (用户显式设, 最可靠)
|
|
144
|
+
2. ~/.qoderwork/.repo-root 文件 (install 时写)
|
|
145
|
+
3. 向上找 .qoder/scripts/ (cwd 在仓库内, IDE/CLI 场景)
|
|
146
|
+
4. 本脚本若在 <repo>/.qoder/scripts/ 下 → 上三级 (开发态)
|
|
147
|
+
返回 Path 或 None。
|
|
148
|
+
"""
|
|
149
|
+
# 1. 环境变量
|
|
150
|
+
env_repo = os.environ.get('QODER_REPO')
|
|
151
|
+
if env_repo and (Path(env_repo) / '.qoder' / 'scripts').is_dir():
|
|
152
|
+
return Path(env_repo)
|
|
153
|
+
|
|
154
|
+
# 2. ~/.qoderwork/.repo-root 文件 (install 写的, 主路径)
|
|
155
|
+
anchor = _home() / '.qoderwork' / '.repo-root'
|
|
156
|
+
if anchor.is_file():
|
|
157
|
+
try:
|
|
158
|
+
line = anchor.read_text(encoding='utf-8').strip()
|
|
159
|
+
if line and (Path(line) / '.qoder' / 'scripts').is_dir():
|
|
160
|
+
return Path(line)
|
|
161
|
+
except OSError:
|
|
162
|
+
pass
|
|
163
|
+
|
|
164
|
+
# 3. 向上找 .qoder/scripts/
|
|
165
|
+
here = Path(os.getcwd()).resolve()
|
|
166
|
+
for _ in range(8):
|
|
167
|
+
if (here / '.qoder' / 'scripts').is_dir():
|
|
168
|
+
return here
|
|
169
|
+
parent = here.parent
|
|
170
|
+
if parent == here:
|
|
171
|
+
break
|
|
172
|
+
here = parent
|
|
173
|
+
|
|
174
|
+
# 4. 本脚本位置 (开发态: 在 <repo>/.qoder/scripts/mcp_launcher.py)
|
|
175
|
+
try:
|
|
176
|
+
s = Path(__file__).resolve()
|
|
177
|
+
root = s.parent.parent.parent # scripts/ -> .qoder/ -> repo
|
|
178
|
+
if (root / '.qoder' / 'scripts').is_dir():
|
|
179
|
+
return root
|
|
180
|
+
except Exception:
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _venv_python(venv):
|
|
187
|
+
"""venv 内 python 路径 (跨平台: win Scripts/python.exe, mac bin/python)。"""
|
|
188
|
+
if sys.platform == 'win32':
|
|
189
|
+
return venv / 'Scripts' / 'python.exe'
|
|
190
|
+
return venv / 'bin' / 'python'
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _read_current_developer(repo):
|
|
194
|
+
"""读 .qoder/.developer 当前角色。"""
|
|
195
|
+
try:
|
|
196
|
+
sys.path.insert(0, str(repo / '.qoder' / 'scripts'))
|
|
197
|
+
from common.paths import get_developer
|
|
198
|
+
return get_developer(repo)
|
|
199
|
+
except Exception:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _read_lanhu_cookie(repo, dev):
|
|
204
|
+
"""读角色的蓝湖 cookie。"""
|
|
205
|
+
try:
|
|
206
|
+
sys.path.insert(0, str(repo / '.qoder' / 'scripts'))
|
|
207
|
+
from setup_lanhu import _read_env_cookie, lanhu_env_file, COOKIE_PLACEHOLDER
|
|
208
|
+
ck = _read_env_cookie(dev)
|
|
209
|
+
return ck if (ck and ck != COOKIE_PLACEHOLDER) else None
|
|
210
|
+
except Exception:
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def launch_kg(repo):
|
|
215
|
+
"""启动知识图谱 MCP。找装了 duckdb 的 Python (不是盲目用 sys.executable)。"""
|
|
216
|
+
server = repo / '.qoder' / 'scripts' / 'kg_mcp_server.py'
|
|
217
|
+
if not server.is_file():
|
|
218
|
+
_log(f'kg server 不存在: {server}')
|
|
219
|
+
sys.exit(1)
|
|
220
|
+
py = _find_python_with_deps('duckdb')
|
|
221
|
+
if not py:
|
|
222
|
+
_log('⚠️ 找不到装了 duckdb 的 Python! 跑: pip install duckdb')
|
|
223
|
+
_log(' 或: npx @hupan56/wlkj install-env (自动装依赖)')
|
|
224
|
+
_launch_disabled_mcp('qoder-knowledge-graph',
|
|
225
|
+
'duckdb 未装。跑 pip install duckdb 或 npx @hupan56/wlkj install-env')
|
|
226
|
+
return
|
|
227
|
+
_log(f'启动 kg (python={py})')
|
|
228
|
+
os.execv(py, [py, str(server)])
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def launch_mysql(repo):
|
|
232
|
+
"""启动 MySQL MCP。连接信息优先从 config.yaml 读 (团队共享, git 里有)。
|
|
233
|
+
回退: ~/.qoderwork/.mcp-mysql-env.json (旧版个人文件)。"""
|
|
234
|
+
server = repo / '.qoder' / 'scripts' / 'mysql_mcp_server.py'
|
|
235
|
+
if not server.is_file():
|
|
236
|
+
_log(f'mysql server 不存在: {server}')
|
|
237
|
+
sys.exit(1)
|
|
238
|
+
# 优先: 从 config.yaml 读 (团队共享, clone 就有)
|
|
239
|
+
cfg_path = repo / '.qoder' / 'config.yaml'
|
|
240
|
+
mysql_cfg = {}
|
|
241
|
+
if cfg_path.is_file():
|
|
242
|
+
try:
|
|
243
|
+
import yaml
|
|
244
|
+
d = yaml.safe_load(cfg_path.read_text(encoding='utf-8')) or {}
|
|
245
|
+
mysql_cfg = d.get('mysql', {}) or {}
|
|
246
|
+
except Exception:
|
|
247
|
+
pass
|
|
248
|
+
if mysql_cfg:
|
|
249
|
+
for k in ('host', 'port', 'user', 'password'):
|
|
250
|
+
v = mysql_cfg.get(k)
|
|
251
|
+
if v:
|
|
252
|
+
os.environ['MYSQL_' + k.upper()] = str(v)
|
|
253
|
+
if mysql_cfg.get('env_name'):
|
|
254
|
+
os.environ['MYSQL_ENV_NAME'] = str(mysql_cfg['env_name'])
|
|
255
|
+
_log(f'mysql env 从 config.yaml 注入 ({len(mysql_cfg)} 项)')
|
|
256
|
+
else:
|
|
257
|
+
# 回退: 旧版个人 env 文件 (~/.qoderwork/.mcp-mysql-env.json)
|
|
258
|
+
env_file = _home() / '.qoderwork' / '.mcp-mysql-env.json'
|
|
259
|
+
if env_file.is_file():
|
|
260
|
+
try:
|
|
261
|
+
cfg = json.loads(env_file.read_text(encoding='utf-8'))
|
|
262
|
+
for k, v in cfg.items():
|
|
263
|
+
if v:
|
|
264
|
+
os.environ[k] = str(v)
|
|
265
|
+
_log(f'mysql env 从个人文件注入 ({len(cfg)} 项)')
|
|
266
|
+
except (json.JSONDecodeError, OSError):
|
|
267
|
+
pass
|
|
268
|
+
else:
|
|
269
|
+
_log('⚠️ mysql 配置缺失: config.yaml 无 mysql 段, 且 ~/.qoderwork/.mcp-mysql-env.json 不存在')
|
|
270
|
+
_log(f'启动 mysql')
|
|
271
|
+
py = _find_python_with_deps('pymysql')
|
|
272
|
+
if not py:
|
|
273
|
+
_log('⚠️ 找不到装了 pymysql 的 Python! 跑: pip install pymysql')
|
|
274
|
+
_launch_disabled_mcp('qoder-mysql',
|
|
275
|
+
'pymysql 未装。跑 pip install pymysql 或 npx @hupan56/wlkj install-env')
|
|
276
|
+
return
|
|
277
|
+
_log(f' python={py}')
|
|
278
|
+
os.execv(py, [py, str(server)])
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def launch_lanhu(repo):
|
|
282
|
+
"""启动蓝湖 MCP。用 venv 的 3.10+ python (fastmcp 要求), 读角色 cookie。
|
|
283
|
+
没装 lanhu venv 或没配 cookie 时, 输出友好提示并启动一个空壳 MCP
|
|
284
|
+
(让 QoderWork 显示"未启用"而不是黄/报错)。"""
|
|
285
|
+
venv = repo / 'external' / 'lanhu-mcp' / '.venv'
|
|
286
|
+
server = repo / 'external' / 'lanhu-mcp' / 'lanhu_mcp_server.py'
|
|
287
|
+
|
|
288
|
+
# 检查 venv
|
|
289
|
+
if not venv.is_dir():
|
|
290
|
+
_log('lanhu venv 未安装 — MCP 将以"未启用"模式启动')
|
|
291
|
+
_launch_disabled_mcp('lanhu',
|
|
292
|
+
'蓝湖 MCP 未安装。跑 python .qoder/scripts/setup_lanhu.py 安装')
|
|
293
|
+
return
|
|
294
|
+
py = _venv_python(venv)
|
|
295
|
+
if not py.is_file():
|
|
296
|
+
_log(f'lanhu venv python 不存在: {py}')
|
|
297
|
+
_launch_disabled_mcp('lanhu', '蓝湖 MCP venv 异常, 重装: setup_lanhu.py')
|
|
298
|
+
return
|
|
299
|
+
if not server.is_file():
|
|
300
|
+
_log(f'lanhu server 不存在: {server}')
|
|
301
|
+
_launch_disabled_mcp('lanhu', '蓝湖 MCP server 文件缺失')
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
# 读角色 cookie
|
|
305
|
+
dev = _read_current_developer(repo)
|
|
306
|
+
if not dev:
|
|
307
|
+
_log('未初始化开发者 (.qoder/.developer), 跑 /wl-init')
|
|
308
|
+
_launch_disabled_mcp('lanhu', '未初始化开发者, 跑 /wl-init')
|
|
309
|
+
return
|
|
310
|
+
cookie = _read_lanhu_cookie(repo, dev)
|
|
311
|
+
if not cookie:
|
|
312
|
+
_log(f'角色 {dev} 的蓝湖 cookie 未配置 (占位/缺失)')
|
|
313
|
+
_log(f' 编辑 workspace/members/{dev}/.secrets/lanhu.env')
|
|
314
|
+
_launch_disabled_mcp('lanhu',
|
|
315
|
+
f'蓝湖 cookie 未配置: workspace/members/{dev}/.secrets/lanhu.env')
|
|
316
|
+
return
|
|
317
|
+
|
|
318
|
+
# 注入 env (角色隔离: cookie + 用户名都是当前角色的)
|
|
319
|
+
os.environ['LANHU_COOKIE'] = cookie
|
|
320
|
+
os.environ['MCP_TRANSPORT'] = 'stdio'
|
|
321
|
+
os.environ['LANHU_USER_NAME'] = dev or 'Developer'
|
|
322
|
+
os.environ['LANHU_USER_ROLE'] = 'Developer'
|
|
323
|
+
_log(f'启动 lanhu (角色={dev}, python={py})')
|
|
324
|
+
os.execv(str(py), [str(py), str(server)])
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
LAUNCHERS = {
|
|
328
|
+
'kg': launch_kg,
|
|
329
|
+
'knowledge-graph': launch_kg,
|
|
330
|
+
'mysql': launch_mysql,
|
|
331
|
+
'lanhu': launch_lanhu,
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def main():
|
|
336
|
+
if len(sys.argv) < 2:
|
|
337
|
+
_log('用法: mcp_launcher.py <kg|mysql|lanhu>')
|
|
338
|
+
_log(f' argv={sys.argv}')
|
|
339
|
+
sys.exit(1)
|
|
340
|
+
name = sys.argv[1].lower()
|
|
341
|
+
|
|
342
|
+
repo = find_repo_root()
|
|
343
|
+
if not repo:
|
|
344
|
+
_log('无法定位 QODER 仓库根。')
|
|
345
|
+
_log(' 方式1: 设环境变量 QODER_REPO=<仓库根>')
|
|
346
|
+
_log(' 方式2: 重跑 python .qoder/scripts/install_qoderwork.py (写 .repo-root)')
|
|
347
|
+
_log(' 方式3: 在仓库目录内运行')
|
|
348
|
+
sys.exit(1)
|
|
349
|
+
_log(f'仓库根: {repo}')
|
|
350
|
+
|
|
351
|
+
fn = LAUNCHERS.get(name)
|
|
352
|
+
if not fn:
|
|
353
|
+
_log(f'未知 MCP 名: {name} (支持: {list(LAUNCHERS)})')
|
|
354
|
+
sys.exit(1)
|
|
355
|
+
fn(repo)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
if __name__ == '__main__':
|
|
359
|
+
main()
|