Skip to content

Commit ff556e3

Browse files
committed
feat(mcp): 修正局域网接入地址展示
1 parent a9f24f8 commit ff556e3

6 files changed

Lines changed: 441 additions & 5 deletions

File tree

desktop/src/main.cjs

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const crypto = require("crypto");
2121
const fs = require("fs");
2222
const http = require("http");
2323
const net = require("net");
24+
const os = require("os");
2425
const path = require("path");
2526
const { Worker } = require("worker_threads");
2627
const {
@@ -101,6 +102,84 @@ function getBackendAccessHost() {
101102
return host || "127.0.0.1";
102103
}
103104

105+
function getInterfacePenalty(name) {
106+
const lower = String(name || "").toLowerCase();
107+
if (/(docker|hyper-v|loopback|npcap|tailscale|virtual|virtualbox|vmware|vethernet|wsl|zerotier)/i.test(lower)) {
108+
return 30;
109+
}
110+
if (/(ethernet|wi-fi|wifi|wireless|wlan||线)/i.test(lower)) {
111+
return 0;
112+
}
113+
return 10;
114+
}
115+
116+
function isReachableClientIpv4(address) {
117+
const text = String(address || "").trim();
118+
const parts = text.split(".");
119+
if (parts.length !== 4) return false;
120+
const nums = parts.map((part) => Number(part));
121+
if (!nums.every((n) => Number.isInteger(n) && n >= 0 && n <= 255)) return false;
122+
if (nums[0] === 0 || nums[0] === 127 || nums[0] >= 224) return false;
123+
if (nums[0] === 169 && nums[1] === 254) return false;
124+
return true;
125+
}
126+
127+
function isPrivateIpv4(address) {
128+
const nums = String(address || "").trim().split(".").map((part) => Number(part));
129+
if (nums.length !== 4 || !nums.every((n) => Number.isInteger(n))) return false;
130+
return (
131+
nums[0] === 10 ||
132+
(nums[0] === 172 && nums[1] >= 16 && nums[1] <= 31) ||
133+
(nums[0] === 192 && nums[1] === 168)
134+
);
135+
}
136+
137+
function getLanAccessHost(defaultHost = DEFAULT_BACKEND_HOST) {
138+
const candidates = [];
139+
const seen = new Set();
140+
const addCandidate = (address, interfaceName = "", sourceOrder = 0) => {
141+
const value = String(address || "").trim();
142+
if (!isReachableClientIpv4(value) || seen.has(value)) return;
143+
seen.add(value);
144+
candidates.push([
145+
isPrivateIpv4(value) ? 0 : 1,
146+
getInterfacePenalty(interfaceName),
147+
sourceOrder,
148+
value,
149+
]);
150+
};
151+
152+
try {
153+
const interfaces = os.networkInterfaces();
154+
for (const [name, addresses] of Object.entries(interfaces || {})) {
155+
for (const item of addresses || []) {
156+
if (!item || (item.family !== "IPv4" && item.family !== 4) || item.internal) continue;
157+
addCandidate(item.address, name, 0);
158+
}
159+
}
160+
} catch {}
161+
162+
candidates.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
163+
return candidates[0]?.[3] || defaultHost;
164+
}
165+
166+
function getMcpAccessHost(bindHost = getBackendBindHost()) {
167+
const host = String(bindHost || "").trim();
168+
if (host === LAN_BACKEND_HOST || host === "::") return getLanAccessHost(DEFAULT_BACKEND_HOST);
169+
return host || DEFAULT_BACKEND_HOST;
170+
}
171+
172+
function getMcpAccessInfo(bindHost = getBackendBindHost(), port = getBackendPort()) {
173+
const accessHost = getMcpAccessHost(bindHost);
174+
const origin = `http://${formatHostForUrl(accessHost)}:${port}`;
175+
return {
176+
accessHost,
177+
mcpEndpoint: `${origin}/mcp`,
178+
skillBundleUrl: `${origin}/mcp/skill/bundle`,
179+
skillMarkdownUrl: `${origin}/mcp/skill`,
180+
};
181+
}
182+
104183
function getBackendPort() {
105184
const envPort = parsePort(process.env.WECHAT_TOOL_PORT);
106185
if (envPort != null) return envPort;
@@ -2340,19 +2419,24 @@ function registerWindowIpc() {
23402419

23412420
ipcMain.handle("backend:getMcpLanAccess", () => {
23422421
try {
2422+
const host = getBackendBindHost();
2423+
const port = getBackendPort();
23432424
return {
23442425
enabled: getMcpLanAccessEnabled(),
2345-
host: getBackendBindHost(),
2346-
port: getBackendPort(),
2426+
host,
2427+
port,
23472428
uiUrl: getDesktopUiUrl(),
2429+
...getMcpAccessInfo(host, port),
23482430
};
23492431
} catch (err) {
23502432
logMain(`[main] backend:getMcpLanAccess failed: ${err?.message || err}`);
2433+
const port = DEFAULT_BACKEND_PORT;
23512434
return {
23522435
enabled: false,
23532436
host: DEFAULT_BACKEND_HOST,
2354-
port: DEFAULT_BACKEND_PORT,
2437+
port,
23552438
uiUrl: getDesktopUiUrl(),
2439+
...getMcpAccessInfo(DEFAULT_BACKEND_HOST, port),
23562440
};
23572441
}
23582442
});
@@ -2363,13 +2447,16 @@ function registerWindowIpc() {
23632447
const nextEnabled = !!enabled;
23642448
const prevEnabled = getMcpLanAccessEnabled();
23652449
if (nextEnabled === prevEnabled) {
2450+
const host = getBackendBindHost();
2451+
const port = getBackendPort();
23662452
return {
23672453
success: true,
23682454
changed: false,
23692455
enabled: prevEnabled,
2370-
host: getBackendBindHost(),
2371-
port: getBackendPort(),
2456+
host,
2457+
port,
23722458
uiUrl: getDesktopUiUrl(),
2459+
...getMcpAccessInfo(host, port),
23732460
};
23742461
}
23752462

@@ -2396,6 +2483,7 @@ function registerWindowIpc() {
23962483
host: getBackendBindHost(),
23972484
port: getBackendPort(),
23982485
uiUrl,
2486+
...getMcpAccessInfo(),
23992487
};
24002488
} finally {
24012489
backendPortChangeInProgress = false;

frontend/components/SettingsDialog.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@
269269
<div class="min-w-0 flex-1">
270270
<div class="text-[13px] font-medium text-[#222]">允许手机局域网接入 MCP</div>
271271
<div class="mt-0.5 text-[11px] leading-relaxed text-[#909090]">开启后后端监听 0.0.0.0,手机可通过接入提示词中的地址接入。</div>
272+
<div class="mt-0.5 text-[11px] leading-relaxed text-[#909090] break-all">当前地址:{{ mcpEndpoint }}</div>
272273
<div v-if="mcpLanAccessMessage" class="mt-1 text-[11px] leading-relaxed text-[#1b6b43]">{{ mcpLanAccessMessage }}</div>
273274
<div v-if="mcpLanAccessError" class="mt-1 text-[11px] leading-relaxed text-red-600">{{ mcpLanAccessError }}</div>
274275
</div>
@@ -608,6 +609,8 @@ const mcpSkillBundleText = ref('')
608609
const mcpSkillBundleLoading = ref(false)
609610
const mcpSkillBundleError = ref('')
610611
const mcpCopiedKey = ref('')
612+
const mcpAccessHost = ref('')
613+
const mcpAccessEndpoint = ref('')
611614
let mcpCopiedTimer = null
612615
613616
const mcpPortText = computed(() => {
@@ -617,6 +620,10 @@ const mcpPortText = computed(() => {
617620
})
618621
619622
const mcpEndpoint = computed(() => {
623+
const reported = String(mcpAccessEndpoint.value || '').trim()
624+
if (/^https?:\/\//i.test(reported)) return reported
625+
const reportedHost = String(mcpAccessHost.value || '').trim()
626+
if (reportedHost) return `http://${reportedHost}:${mcpPortText.value}/mcp`
620627
if (!process.client || typeof window === 'undefined') return `http://127.0.0.1:${mcpPortText.value}/mcp`
621628
const apiBase = useApiBase()
622629
if (/^https?:\/\//i.test(apiBase)) {
@@ -630,6 +637,14 @@ const mcpEndpoint = computed(() => {
630637
return `${protocol}//${host}:${mcpPortText.value}/mcp`
631638
})
632639
640+
const applyMcpAccessInfo = (resp) => {
641+
if (!resp || typeof resp !== 'object') return
642+
const accessHost = String(resp.accessHost || resp.access_host || '').trim()
643+
const endpoint = String(resp.mcpEndpoint || resp.mcp_endpoint || '').trim()
644+
if (accessHost) mcpAccessHost.value = accessHost
645+
if (/^https?:\/\//i.test(endpoint)) mcpAccessEndpoint.value = endpoint
646+
}
647+
633648
const mcpSkillFallback = [
634649
'# WeChat MCP Copilot',
635650
'',
@@ -800,10 +815,12 @@ const refreshMcpLanAccess = async () => {
800815
if (window.wechatDesktop?.getMcpLanAccess) {
801816
const resp = await window.wechatDesktop.getMcpLanAccess()
802817
mcpLanAccessEnabled.value = !!resp?.enabled
818+
applyMcpAccessInfo(resp)
803819
return
804820
}
805821
const resp = await fetchAdminEndpoint('/admin/mcp-access')
806822
mcpLanAccessEnabled.value = !!resp?.enabled
823+
applyMcpAccessInfo(resp)
807824
} catch (e) {
808825
mcpLanAccessError.value = e?.message || '读取 MCP 接入状态失败'
809826
} finally {
@@ -881,7 +898,9 @@ const setMcpLanAccess = async (enabled) => {
881898
if (window.wechatDesktop?.setMcpLanAccess) {
882899
const resp = await window.wechatDesktop.setMcpLanAccess(!!enabled)
883900
mcpLanAccessEnabled.value = !!resp?.enabled
901+
applyMcpAccessInfo(resp)
884902
mcpLanAccessMessage.value = resp?.changed ? 'MCP 局域网接入已更新,后端已重启。' : 'MCP 局域网接入状态未变化。'
903+
await refreshMcpSkillBundle()
885904
return
886905
}
887906
@@ -890,11 +909,14 @@ const setMcpLanAccess = async (enabled) => {
890909
body: { enabled: !!enabled },
891910
})
892911
mcpLanAccessEnabled.value = !!resp?.enabled
912+
applyMcpAccessInfo(resp)
893913
mcpLanAccessMessage.value = resp?.changed ? 'MCP 局域网接入已更新,正在等待后端重启。' : 'MCP 局域网接入状态未变化。'
894914
if (resp?.changed) {
895915
await waitForBackendHealth(30_000)
916+
await refreshMcpLanAccess()
896917
mcpLanAccessMessage.value = 'MCP 局域网接入已更新,后端已恢复。'
897918
}
919+
await refreshMcpSkillBundle()
898920
} catch (e) {
899921
mcpLanAccessEnabled.value = previous
900922
mcpLanAccessError.value = e?.message || '设置 MCP 接入状态失败'

main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@
1111
import uvicorn
1212
import os
1313
from pathlib import Path
14+
from wechat_decrypt_tool.network_access import get_lan_access_host
1415
from wechat_decrypt_tool.runtime_settings import read_effective_backend_host, read_effective_backend_port
1516

1617
def main():
1718
"""启动微信解密工具API服务"""
1819
host, host_source = read_effective_backend_host(default="127.0.0.1")
1920
port, port_source = read_effective_backend_port(default=10392)
2021
access_host = "127.0.0.1" if host in {"0.0.0.0", "::"} else host
22+
lan_access_host = get_lan_access_host(default="127.0.0.1") if host in {"0.0.0.0", "::"} else access_host
2123

2224
print("=" * 60)
2325
print("微信解密工具 API 服务")
@@ -38,6 +40,8 @@ def main():
3840
print(f"监听地址: {host}")
3941
print(f"API文档: http://{access_host}:{port}/docs")
4042
print(f"健康检查: http://{access_host}:{port}/api/health")
43+
if lan_access_host != access_host:
44+
print(f"局域网 MCP: http://{lan_access_host}:{port}/mcp")
4145
print("按 Ctrl+C 停止服务")
4246
print("=" * 60)
4347

0 commit comments

Comments
 (0)