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:
@@ -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 列表
|
||||
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user