From 35513f1cdf17595e71f23070b50007e565fc110d Mon Sep 17 00:00:00 2001 From: ypshidifu Date: Wed, 23 Sep 2026 12:24:31 +0800 Subject: [PATCH] =?UTF-8?q?wsl-round-mcp=EF=BC=9A=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E5=8A=AB=E6=8C=81=E5=AF=BC=E8=87=B4=E7=9A=84?= =?UTF-8?q?=20502=20+=20=E8=BE=93=E5=85=A5=E5=97=85=E6=8E=A2=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 两个脚本的 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 随机密码的排查说明 --- README.md | 13 ++++++++++++- mcpctl.py | 12 ++++++++++-- ocskill.py | 14 +++++++++++--- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 66becb7..8aac5e9 100644 --- a/README.md +++ b/README.md @@ -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 列表 diff --git a/mcpctl.py b/mcpctl.py index be4c31f..98d3217 100644 --- a/mcpctl.py +++ b/mcpctl.py @@ -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) diff --git a/ocskill.py b/ocskill.py index 88df7e5..50cfdda 100644 --- a/ocskill.py +++ b/ocskill.py @@ -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()