wsl-round-mcp:修复代理劫持导致的 502 + 输入嗅探增强

- 两个脚本的 HTTP 请求改为 ProxyHandler({}) 强制绕过系统代理:
  环境有 HTTP_PROXY/HTTPS_PROXY 时,urllib 会把 localhost 请求也丢给代理,
  结果代理回 502,看起来像 OC 挂了其实是被劫走(本轮实测复现)
  需要走代理时用 MCPCTL_USE_PROXY=1 / OCSKILL_USE_PROXY=1 显式开启
- ocskill 输入嗅探增强:未知后缀按魔数/内容识别 .b64、zip、markdown
- frontmatter 支持 YAML 块标量(description: > |),中文折行不补空格
- 新增子仓库自身 .gitignore(父仓库的 ignore 对子仓库无效,__pycache__ 曾被误提交)
- README 补充代理 502 与 401 随机密码的排查说明
This commit is contained in:
ypshidifu
2026-09-23 12:24:31 +08:00
parent 873d965ef3
commit 35513f1cdf
3 changed files with 33 additions and 6 deletions
+12 -1
View File
@@ -116,9 +116,20 @@ C:\...\opencode.json ← 第3行:删了 MCP 的安静配置
## 三、连不上 OC 怎么办
**先看这一条(最常见):有 HTTP 代理时会假报 502。**
两个脚本已默认**绕过系统代理**直连(`ProxyHandler({})`)。
如果环境里有 `HTTP_PROXY` / `HTTPS_PROXY`,`urllib` 默认会把 `localhost` 请求也丢给代理,
结果是**代理回 502**,看起来像 OC 挂了、其实是被劫走了。
确实需要走代理时,设环境变量再运行:`MCPCTL_USE_PROXY=1` / `OCSKILL_USE_PROXY=1`。
其余排查:
1. 确认 WSL 里的 OC 起了:`opencode serve --port 4096`
2. 端口不对就加 `--port`,或设环境变量 `OC_PORT`
3. 提示 `鉴权失败 (401)` → 用 `--password` 传 `opencode.json` 里 `server.password` 的值
3. 提示 `401 WWW-Authenticate: Basic realm="Secure Area"` → OC 启用了服务密码。
用 `--password` 传值(默认自动从 `opencode.json` 的 `server.password` 读;
若该字段为空但 OC 仍要 401,说明它用了随机密码,需启动时设 `OPENCODE_SERVER_PASSWORD`)
4. `localhost` 不通 → 工具会自动 `wsl hostname -I` 取 WSL 真实 IP 再试
5. 全都失败 → `list` 会自动降级为**直接扫目录**,照样能看到 skill 列表
+10 -2
View File
@@ -167,6 +167,14 @@ def _wsl_ip(distro: str) -> str:
return ""
# 关键:本机/WSL 的 OC 绝不能走系统代理。
# 环境里若有 HTTP_PROXY/HTTPS_PROXY,urlopen 会把 localhost 请求丢给代理,
# 结果是代理回 502 —— 看起来像 OC 挂了,其实是被劫走了。
_NO_PROXY_OPENER = urllib.request.build_opener(urllib.request.ProxyHandler({}))
if os.environ.get("MCPCTL_USE_PROXY"):
_NO_PROXY_OPENER = urllib.request.build_opener()
def _post(url: str, password: str, timeout: float) -> tuple[int, str]:
req = urllib.request.Request(url, method="POST", data=b"{}")
req.add_header("content-type", "application/json")
@@ -174,7 +182,7 @@ def _post(url: str, password: str, timeout: float) -> tuple[int, str]:
if password:
req.add_header("authorization", f"Bearer {password}")
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
with _NO_PROXY_OPENER.open(req, timeout=timeout) as resp:
return resp.status, resp.read().decode("utf-8", "replace")
except urllib.error.HTTPError as exc:
if exc.code == 401 and password:
@@ -183,7 +191,7 @@ def _post(url: str, password: str, timeout: float) -> tuple[int, str]:
req2.add_header("content-type", "application/json")
req2.add_header("authorization", f"Basic {raw}")
try:
with urllib.request.urlopen(req2, timeout=timeout) as resp:
with _NO_PROXY_OPENER.open(req2, timeout=timeout) as resp:
return resp.status, resp.read().decode("utf-8", "replace")
except urllib.error.HTTPError as exc2:
return exc2.code, str(exc2)
+11 -3
View File
@@ -130,11 +130,19 @@ def _build_request(url: str, password: str, method: str = "GET") -> urllib.reque
return req
# 关键:本机/WSL 的 OC 绝不能走系统代理。
# 环境里若有 HTTP_PROXY/HTTPS_PROXY(很多工具会设),urlopen 会把 localhost 请求也丢给代理,
# 结果是代理回 502 —— 看起来像 OC 挂了,其实是被劫走了。
_NO_PROXY_OPENER = urllib.request.build_opener(urllib.request.ProxyHandler({}))
if os.environ.get("OCSKILL_USE_PROXY"):
_NO_PROXY_OPENER = urllib.request.build_opener() # 显式要求时才走系统代理
def _try_fetch(url: str, password: str, timeout: float) -> tuple[int, bytes]:
"""返回 (status, body)。401 时自动改用 Basic 重试一次。"""
"""返回 (status, body)。401 时自动改用 Basic 重试一次。全程绕过系统代理。"""
req = _build_request(url, password)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
with _NO_PROXY_OPENER.open(req, timeout=timeout) as resp:
return resp.status, resp.read()
except urllib.error.HTTPError as exc:
if exc.code == 401 and password:
@@ -142,7 +150,7 @@ def _try_fetch(url: str, password: str, timeout: float) -> tuple[int, bytes]:
req2 = _build_request(url, "")
req2.add_header("authorization", f"Basic {raw}")
try:
with urllib.request.urlopen(req2, timeout=timeout) as resp:
with _NO_PROXY_OPENER.open(req2, timeout=timeout) as resp:
return resp.status, resp.read()
except urllib.error.HTTPError as exc2:
return exc2.code, exc2.read()