“本地部署一个微信公众号文章搜索 MCP 服务,还要在 Linux 服务器上让局域网或公网的其他设备也能访问”——这个标题听起来像是一个一次性配置任务,但我自己完整跑下来之后发现,真正花时间的不是装服务本身,而是搞清楚 MCP 服务“从本机到外部可访问”这条链路上每一层都在发生什么。把 weixin_search_mcp 这类基于网页检索的搜索工具接到 AI 客户端里,和普通 Web 服务不一样,它对网络绑定、协议路径、客户端注册方式都有讲究。这篇文章不打算复述官方 README,而是把我在 Linux 上从零部署、验证、开放访问、接入 MCP 客户端的完整过程拆开来讲。适合两类人看:一类是已经在用 Claude、Cherry Studio 或类似支持 MCP 的工具、想把公众号文章搜索能力接进去的人;另一类是刚接触 MCP Server、想在 Linux 上部署一个工具型服务练手的开发者。如果你只需要在本机跑,那第十二节的“最小配置”就够了;如果你希望手机或另一台电脑上的 AI 客户端能连过来,重点看监听地址、防火墙和鉴权这三件事。
1. 先搞清楚:weixin_search_mcp 到底暴露给 AI 什么能力
1.1 MCP 服务不是爬虫脚本,是给模型用的“外接工具协议”
很多人第一次接触 MCP 时会把它理解成一个普通的 HTTP 接口,其实不太一样。Model Context Protocol 解决的核心问题是:模型本身只擅长文本推理,它没法自己去微信公众号里检索文章,但如果通过 MCP 协议把你本地的搜索服务“挂”到模型能调用的工具列表里,模型在回答问题时就可以主动调用搜索工具去拿真实结果,再基于结果组织回答。
这个过程很像你把一个计算器递给一个心算很厉害但偶尔也会算错的人。计算能力不是模型提供的,是你提供的,模型只负责决定“什么时候该按计算器、按完之后怎么解读数字”。weixin_search_mcp 扮演的就是那个计算器,只不过它算的不是加减乘除,而是“根据关键词找微信公众号文章”。
从架构上看,一个 MCP Server 通常要暴露三类资源:Tools(工具,比如 search_weixin_article)、Resources(可读取的数据资源)、Prompts(可复用的提示词模板)。weixin_search_mcp 这种搜索类服务,最核心的实际只有 Tools。它接收一个查询词和几个可选参数,返回一串文章列表。返回结果里一般包含标题、来源公众号、摘要、发布时间和文章链接。模型拿到这些信息之后,可以做摘要、二次筛选、对比,或者把链接交给阅读工具去抓全文。
1.2 它的搜索边界决定了你能拿它做什么
微信公众号文章不存在像百度那样完整开放的全文搜索引擎,目前最常用的检索入口是搜狗微信搜索。weixin_search_mcp 这类服务大多就是把这个网页检索能力封装成 MCP 工具。这也意味着它的能力边界很清晰:
- 它搜的是已被搜狗微信索引到的公众号文章,不是你指定某个公众号的私有内容库;
- 它返回的是文章元信息(标题、摘要、链接),不是文章正文;
- 不同实现方式提供的结果字段、排序策略、是否支持按公众号筛选,会有差异;
- 网页检索会受到频率限制和验证机制影响,不能当无限量的爬虫用。
我在部署之前先想明白了这一点,所以没有对它产生不切实际的期待。它的正确用法是给 AI 客户端补充“公众号文章发现”的能力,而不是做全文数据库。比如你问 Clude“最近有没有关于 MCP 落地实践的文章”,它就可以调用 weixin_search_mcp 获取真实链接和摘要,再给你一个有出处的回答。如果你想做的是对已抓取文章做定期全文备份,那应该另做数据管道,而不是靠这类服务反复请求。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 部署前要做好的准备:目录规划、运行环境、依赖安装
2.1 系统环境清单与目录结构
我这次用的是 Ubuntu 22.04 LTS 的服务器,纯净系统,没有装图形界面。weixin_search_mcp 如果以 Python 实现为例,整个服务对系统资源的要求很低,512MB 内存的云主机跑起来也没有压力。部署前先把基础环境确认一遍:
- 操作系统:Debian/Ubuntu 系,或 CentOS/RHEL/Fedora 系均可,关键是 systemd 可用
- Python 版本:Python 3.10 及以上,如果项目依赖 pydantic-core、lxml 这类编译型包,Python 3.12 以下会省去一些麻烦
- 网络:服务器能正常访问外网,以便安装依赖和发起微信搜索请求
- 客户端机器:准备一台有 MCP 客户端的设备,后面验证要用
我习惯把第三方独立服务统一放在 /opt 目录下,而不是塞进普通用户的家目录。这样做的原因很实际:服务需要开机自启时,systemd 的 ExecStart 路径写 /opt/whatever/venv/bin/python 比写 /home/xxx/... 更稳定;日志、环境变量文件也能和代码本身放在一起,换机器时不容易漏掉东西。目录结构里主要包含源码、虚拟环境和配置文件:
text复制/opt/weixin_search_mcp/
├── main.py # 服务启动入口,以实际项目为准
├── requirements.txt # Python 依赖清单
├── weixin_search_mcp/ # 源码包
├── .env # 环境变量配置,不要提交到 git
└── .venv/ # 独立的 Python 虚拟环境
2.2 拿到项目文件,确认启动方式
获取项目文件的方式无非两种:一种是从 git 仓库直接 clone 到服务器,另一种是在本机下载压缩包再通过 scp 传到服务器。我在内网服务器上更常用后面这种方式,因为很多服务器不会给 github 仓库开很激进的缓存策略,直接 clone 可能很慢。
bash复制sudo mkdir -p /opt/weixin_search_mcp
sudo chown $(whoami):$(whoami) /opt/weixin_search_mcp
# 方式一:直接 clone
git clone <你的项目仓库地址> /opt/weixin_search_mcp
# 方式二:本机下载后上传
scp -r ./weixin_search_mcp <user>@<server-ip>:/opt/weixin_search_mcp
拿到代码后不要急着装依赖,先翻一下 README 和仓库根目录的结构,确认三件事:启动入口是哪个文件、支持哪些命令行参数、配置项是通过环境变量还是 config 文件读取。不同的实现差别会比较大。有的版本提供 python main.py --host 0.0.0.0 --port 8900 这种标准参数,有的则把所有配置都固化在 .env 文件里。从 README 里找到准确的启动方式,比盲目执行 python main.py 然后面对一堆报错要高效得多。
如果项目里面使用了 uv 这类新一代 Python 包管理器,你会发现它创建虚拟环境和安装依赖的速度非常快。我的经验是:不管项目推荐 pip 还是 uv,只要 requirements.txt 存在,都可以用一套标准流程跑通:
bash复制cd /opt/weixin_search_mcp
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
2.3 依赖编译报错的处理
如果服务器是最小化安装,缺少编译工具链时 pip 安装 pydantic-core、lxml 这类包含 Rust/C 扩展的包会直接报错。看到 Failed building wheel 不要先怀疑代码,先检查是不是缺系统级依赖。Debian/Ubuntu 系统下我通常会把这几样一次性装齐:
bash复制sudo apt update
sudo apt install -y python3-dev build-essential libffi-dev libssl-dev
装完之后再重新执行 pip install,绝大多数编译问题都能解决。如果你不需要在服务器上编译依赖,也可以找一个和服务器同样架构和同样 Python 版本的本机环境,把依赖装好后把 .venv 整个目录打包传上去。但要提醒一句:.venv 目录里有绝对路径信息,打包换机器后如果有问题,删掉重建是更干净的做法。我试过复制虚拟环境到另一台机器,十个里有八个能用,剩下的总会出一些莫名其妙的第三方库加载问题,所以生产环境里还是老老实实现场装依赖更稳妥。
3. 本地跑通:配置项、启动命令与联通性验证
3.1 配置环境变量,区分必填项和可选参数
weixin_search_mcp 这种工具型服务,配置项通常不多,我建议用 .env 文件管理,而不是改代码里的默认值。以我部署的版本为例,重点关注这些参数:
| 配置项 | 作用 | 我的推荐值 | 说明 |
|---|---|---|---|
| HOST | 服务监听地址 | 先用 127.0.0.1 |
本地验证阶段别急着绑 0.0.0.0 |
| PORT | 服务监听端口 | 8900 | 避开 8000、8080 等容易被扫描的默认端口 |
| REQUEST_TIMEOUT | 请求微信搜索的超时时间 | 15 秒 | 网络波动时给检索请求留点余地 |
| MAX_RESULTS | 单次搜索返回的文章条数 | 10 | 返回太多条对模型 token 消耗大 |
| SOGOU_COOKIE | 可选,用于绕过风控 | 不填 | 首次部署不需要,遇到风控再处理 |
| LOG_LEVEL | 日志级别 | INFO | 排错时临时改成 DEBUG |
配置文件的具体字段名以你拿到的项目为准,但你不需要记死每一个键,关键是理解这些参数背后的作用:监听地址决定谁能访问到这个服务;超时时间决定搜索请求最多等多久;返回条数决定 MCP 输出大小会不会把模型上下文塞爆。
我见过不少人在本地验证阶段就把 HOST 设置成 0.0.0.0,其实这没必要,还增加了暴露风险。正确的节奏是:先用 127.0.0.1 把服务跑通,验证整体功能正常之后,再考虑对外监听。这样一来,如果后面外部访问连不上,排错范围可以清楚地收窄到监听地址、防火墙、安全组这几个环节。
3.2 用最小配置完成第一次启动
第一阶段我通常不急着加任何高级参数,直接用文件里的默认配置启动。假设项目入口是 main.py:
bash复制cd /opt/weixin_search_mcp
source .venv/bin/activate
python main.py
# 或者如果项目定义了 CLI 入口,形如:
# weixin-search-mcp --host 127.0.0.1 --port 8900
当终端日志里出现类似 MCP server running on http://127.0.0.1:8900 或 listening on 127.0.0.1:8900 的输出,说明服务已经起来了。这时候我习惯立刻开一个终端窗口做验证,不让服务占着当前终端。可以用 nohup 临时放后台,也可以直接按 Ctrl+Z 后用 bg,但这些都是权宜之计,后面会用 systemd 做正式的常驻方案。
本地验证服务是否正常,不建议直接拿浏览器访问根路径。MCP 服务本身不是给浏览器展示页面的,根路径设计上可能返回 404 或者一个协议说明页。我从实际经验中总结出一套更准确的检查方法:
bash复制# 1. 确认端口进入了监听状态
ss -lntp | grep 8900
# 2. 用 curl 探测端口是否响应 TCP 握手
curl -v --connect-timeout 3 http://127.0.0.1:8900/
# 3. 查看 MCP 服务实际记录的访问日志
tail -f /opt/weixin_search_mcp/logs/*.log
如果你拿到的项目使用新版 MCP Streamable HTTP 协议,可能要求请求带上特定的 JSON-RPC payload;如果使用 SSE 模式,则通常会有 /sse 这样的独立路径。盲目用 curl http://127.0.0.1:8900/ 看到 404 不代表服务有问题,反而说明 HTTP 层已经能通了。最可靠的本地验证方式其实是直接把它接入一个 MCP 客户端去调用一次搜索工具,这个问题我会在第五节详细展开。
3.3 首次调用搜索工具时的预期表现
如果你的 MCP 客户端支持手动调用工具,第一次调用 search_weixin_articles 之类的方法时,传入一个简单的关键词,比如“大模型”,正常会返回一个包含多篇文章的 JSON 结构。每篇文章一般包含标题、公众号、链接、发布时间、摘要。这里有一个经验要分享:搜索响应耗时通常比普通 API 慢一些,因为网页检索本身要加载目标页面、解析结构,中间还需要做简单的去重和字段清洗。如果 15 秒内没有响应,优先考虑搜索入口是否触发了验证,而不是服务假死。
4. 外部访问的完整链路:监听地址、防火墙、systemd 自启
4.1 理解“监听地址”这个第一道闸门
服务启动后能不能被其他机器访问,第一个决定性因素是它监听的 IP 地址。Python 的 127.0.0.1 是回环地址,只有本机能访问;0.0.0.0 表示监听本机所有网卡接口,局域网里其他机器才能通过这台机器的内网 IP 访问到它。
所以从“本机运行”切换到“外部可访问”,第一步就是修改监听地址:
bash复制python main.py --host 0.0.0.0 --port 8900
如果你用的是 .env 文件配置,把 HOST=127.0.0.1 改成 HOST=0.0.0.0,然后重启服务。这一步看起来简单,但非常容易被忽略。很多人折腾了半天外部访问不通,最后发现进程还在监听 127.0.0.1,因为启动脚本里写死了参数,改配置文件根本不起作用。所以我通常建议:启动命令和配置文件必须二选一来管理监听地址,不要两处都写,否则排查时你根本不知道哪一个在生效。
4.2 防火墙放行:服务器上有两道关卡
监听地址改对了,外部还是连不上,下一个要检查的是防火墙。Linux 服务器上常见的是两套防火墙体系:Ubuntu 默认用 ufw,CentOS/RHEL 系列用 firewalld。放行端口的命令如下。
如果使用 ufw:
bash复制sudo ufw allow 8900/tcp
sudo ufw reload
sudo ufw status
如果使用 firewalld:
bash复制sudo firewall-cmd --permanent --add-port=8900/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports
这里要重点提醒:如果你用的是云服务商提供的服务器,系统内部防火墙之外还会有一层云平台安全组。安全组没有放行对应端口时,即使你在系统里把防火墙完全关掉,外部流量也进不来。我遇到过太多次这种情况:系统防火墙已经放行、ss 也显示监听在 0.0.0.0,但外部就是不通,最后登录云控制台,发现安全组的入站规则里根本没有这条端口。所以排查顺序必须固定:本机 curl 确认服务活着 → 本机或局域网另一台机器用 IP 访问确认监听正确 → 再检查云平台安全组。不要一上来就怀疑代码。
4.3 局域网与公网两种“外部访问”场景
“外部访问”在不同部署环境里含义不同,我在文章开头说的两句话现在可以展开:
如果你是把服务部署在家里或办公室的 Linux 机器上,外部访问通常指“同一局域网内其他设备访问”。手机或另一台电脑的 MCP 客户端配置服务地址时,填写的是这台 Linux 机器的局域网 IP,比如 http://192.168.31.110:8900,前提是手机和它在同一个 WiFi 下。
如果你是云服务器,外部访问就指公网访问。那就不需要在路由器上做任何设置,只要系统防火墙和云安全组都放行了 8900 端口,服务地址填 http://公网IP:8900 或 http://你的域名:8900 即可。
如果你的 Linux 机器在家庭宽带内网里,又想从外网访问,那么需要在路由器上配置端口映射,并把运营商分配的公网 IP 地址或动态域名填到客户端。家庭宽带的运营商网络环境差异较大,这部分不展开,先看局域网访问是否通,这是最容易被验证的一步。
我建议初始阶段不要直接对公网开放。先在局域网环境下把整条链路跑通,确认 MCP 客户端能正常搜索、返回结果,再决定是否要公网开放。这样即使公网被扫描器盯上,临时关掉端口也来得及。
4.4 用 systemd 把服务变成开机自启的常驻进程
前面用 python main.py 启动的服务有一个缺点:关闭终端或连接断开时服务就会挂掉。在 Linux 服务器上部署服务,正确姿势是配置 systemd unit。这一步我在多台机器上反复确认过,配置文件写成下面这样基本能适配绝大多数 MCP 服务:
ini复制[Unit]
Description=weixin_search_mcp MCP Server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=weixin-mcp
Group=weixin-mcp
WorkingDirectory=/opt/weixin_search_mcp
EnvironmentFile=/opt/weixin_search_mcp/.env
ExecStart=/opt/weixin_search_mcp/.venv/bin/python /opt/weixin_search_mcp/main.py --host 0.0.0.0 --port 8900
Restart=on-failure
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
几个值得解释的点:
EnvironmentFile用来加载.env配置,服务起来后会自动读取文件中的 HOST、PORT、超时等参数;ExecStart写的是.venv/bin/python的绝对路径,而不是python,这可以保证 systemd 启动时用的是虚拟环境里的解释器,避免系统默认 Python 版本不对导致依赖加载失败;Environment=PYTHONUNBUFFERED=1很重要。没有它的话,Python 的输出会先进入缓冲区,崩溃前的日志可能来不及写入 journald,排错时会发现日志缺失。Restart=on-failure让服务在异常退出后自动拉起,对于对外提供搜索的服务来说,自动恢复比人工干预及时得多。
把文件写入 systemd 管理目录后,依次执行:
bash复制sudo useradd -r -s /sbin/nologin weixin-mcp
sudo chown -R weixin-mcp:weixin-mcp /opt/weixin_search_mcp
sudo systemctl daemon-reload
sudo systemctl enable --now weixin_search_mcp
sudo systemctl status weixin_search_mcp
查看运行日志则是这个命令:
bash复制sudo journalctl -u weixin_search_mcp -f
如果按照普通用户部署,不需要单独创建系统用户,但生产服务器上我倾向于让服务以一个权限受限的系统账户运行,避免万一代码被远程执行时获得过高的系统权限。
5. MCP 客户端接入:从本机命令到远端 URL 的完整配置
5.1 MCP 客户端的两种连接模型
现在服务已经可以对外监听了,但要让 Claude Desktop、Cherry Studio 这类 MCP 客户端真正用上它,还需要理解两种连接模型之间的差异,否则配置一定会出错。
- stdio 模式:客户端在本机启动一个子进程,通过标准输入输出与 MCP Server 通信。这种模式适合服务端和客户端在同一台机器上的场景,配置文件里往往是指定命令和参数,而不是 URL。
- HTTP/SSE 模式:客户端通过 HTTP 请求连接远端 MCP Server。这是外部访问场景必然走的方式,配置时需要填写服务地址。有些实现走 SSE(Server-Sent Events),有些实现走新版 Streamable HTTP,两者的端点路径和消息格式不完全一样。
weixin_search_mcp 如果设计为网络服务,那么外部客户端接入用的一定是第二种。但这里有一个很容易踩的坑:项目的 README 默认给出的配置样例很可能是 stdio 模式的,因为开发者调试时基本都用本机进程。如果你把 stdio 配置里的 command 和 args 照搬到远程客户端,它会在客户端那台机器上尝试寻找不存在的解释器和程序路径,必然报错。正确的做法是找到 README 里关于网络模式 / SSE 模式 / HTTP 模式的启动参数说明,看这个服务启动后暴露的协议路径是什么。
5.2 Claude Desktop 配置示例
Claude Desktop 的 MCP 配置一般位于配置文件里。如果服务以 stdio 模式运行在同一个 Linux 账号下,配置类似这样:
json复制{
"mcpServers": {
"weixin_search_mcp": {
"command": "/opt/weixin_search_mcp/.venv/bin/python",
"args": ["/opt/weixin_search_mcp/main.py"]
}
}
}
如果服务运行在 Linux 服务器上,而 Claude Desktop 运行在另一台电脑上,通常不能用 stdio 模式,得看具体客户端是否支持添加 HTTP/SSE 类型的 MCP Server。Cherry Studio 的 MCP 设置页面允许直接添加一个 URL 地址,这是远程访问最便捷的方式。添加时把 URL 填成 http://<Linux机器IP>:8900 或者带具体路径的 /sse 端点即可,前提是 Linux 服务已经正确启动。
我在实际接入时发现一个规律:远程连接失败时,客户端报的错误往往很笼统,比如“无法连接 MCP Server”“MCP 握手失败”。这时候第一时间到服务器上看 journald 日志,如果服务确实收到了连接请求,日志里会显示客户端的 IP 和请求路径;如果日志里什么都没有,说明请求根本没有到达服务端,那问题几乎可以确定在网络层(防火墙、安全组、地址写错),而不是 MCP 配置问题。
5.3 Cherry Studio 等通用 MCP 客户端的字段含义
如果你用的是 Cherry Studio 这类支持 MCP 的界面化客户端,添加服务器时会看到几个字段:名称、地址/URL、可能是 enabled、header 等。其中最容易出问题的字段是 URL 路径。不同实现暴露的 MCP 端点路径可能不同,有的根路径就是端点,有的是 /mcp,有的是 /sse。填错路径时,TCP 连接会成功但 MCP 握手会失败。
这里我给你一个可以直接照做的检查路径:启动服务后先在 Linux 机器上用 curl 带上客户端会发的头信息去请求这个路径,看服务返回的是协议握手内容而不是 HTML 错误页。如果返回 JSON-RPC 或 SSE 相关内容,说明路径正确;如果返回 404,说明路径不对。不要用浏览器访问根路径来猜,MCP 服务的路径设计不是按网页习惯来的。
6. 实际运行中我踩过的坑,以及一步步排查的过程
6.1 连外部设备时最典型的排查链路
我在开放外部访问后遇到过一次最诡异的情况:Linux 服务器本机访问 MCP 服务一切正常,局域网里另一台电脑用 curl http://192.168.31.110:8900 也通,但手机上的 MCP 客户端就是连不上。
排查顺序是这样的:
- 先在手机浏览器里访问
http://192.168.31.110:8900,如果浏览器也打不开,说明网络层有问题;如果浏览器能打开但客户端不行,说明是客户端配置问题——但注意,MCP 服务根路径返回 404 是正常的,所以“打不开”需要用开发者模式看响应状态码,不能只看页面空白。 - 确认手机和服务器在同一个局域网网段,并且没有启用客户端网络隔离 / 访客模式。
- 在服务器上执行
sudo tcpdump -n port 8900或简单的sudo ss -tnp | grep 8900,观察是否有来自手机 IP 的 TCP 连接。如果有连接记录但应用层无响应,问题在服务进程;如果连 SYN 包都没有,问题在网络路径。 - 最后检查云平台安全组,把来源 IP 限制从“仅允许自己的公网 IP”临时改为“允许局域网网段”,重启客户端再试。
那次问题最后出在手机连接的是访客 WiFi,访客 WiFi 默认开启了 AP 隔离,设备之间不能互访。这个坑和 Linux 本身无关,但如果你在办公室或家里部署,这个问题出现的概率不低。所以排查要按链路从上到下走一遍,不要在一个位置反复试。
6.2 搜索接口返回零结果的几种情况
服务部署好后,第一次调用搜索工具返回了零篇文章,这个问题比连接失败更隐蔽。我遇到过三种场景:
第一种是请求参数问题。有的搜索实现要求必须传完整的关键词,关键词太短、太宽泛时,网页索引端会认为你是垃圾请求而返回空结果。可以尝试改用更具体的长尾词,比如把“AI”改成“AI Agent 工具部署”。
第二种是频率风控。连续搜索多次后,搜索索引入口会弹出验证页面,MCP 服务解析不到真正结果,表现为返回空列表或超时。这种情况下等几分钟再试通常就恢复了。我这边实际测试下来,两次搜索之间至少间隔 1 到 2 秒比较安全,如果连续高频调用,触发验证的可能性会明显上升。
第三种是依赖环境里的 Cookie 过期。某些搜索入口对没有登录态或会话标识的请求会比较严格,需要在配置中填上有效的 Cookie。如果你发现服务刚部署时能搜索,运行一段时间后开始频繁返回空结果,优先检查是不是请求头需要重新配置会话信息。
6.3 服务重启后配置“丢”了的真相
还有一次让我印象很深:服务在 systemd 下运行正常,我修改了 .env 文件里的端口号,然后执行 sudo systemctl restart weixin_search_mcp,结果服务依然跑在旧端口上。一开始以为 systemd 没重新加载配置,执行了 daemon-reload,仍然没用。
后来仔细检查才发现,项目启动入口代码里对端口参数的处理优先级高于环境变量:代码里存在 --port 参数默认值,而 systemd 的 ExecStart 行里没有显式传 --port 8900,服务就自动使用了代码内嵌默认端口。换句话说,系统里同时存在“环境变量控制的端口”和“代码默认端口”两套逻辑,修改 .env 并不会覆盖代码里的默认值。
这个问题的通用解法是:让 systemd 的 ExecStart 显式写出所有关键参数,同时把环境变量文件里的重复项移除,只保留代码没有暴露为命令行参数的配置。也就是说,端口和监听地址用命令行参数管理,其余敏感信息用 .env 管理,避免两边同时管同一个配置项。
6.4 Python 版本跨越版本带来的编译地雷
如果你是直接在 CentOS 7 这类老系统上部署,系统自带的 Python 3.6 或 3.8 往往无法满足新版 pydantic 或 FastMCP 的最低版本要求。遇到 ModuleNotFoundError: No module named 'pydantic_core' 这类错误,多半是 Python 版本太老或依赖二进制包没有匹配当前系统架构。
解决方式是明确用 Python 3.10+,并打开虚拟环境检查解释器版本:
bash复制python3 --version
source /opt/weixin_search_mcp/.venv/bin/activate
python --version
pip list
如果源码编译 Rust 扩展报错,可以设置 PIP_USE_PEP517=1 强制使用 PEP 517 流程,也可以考虑直接用 uv 安装依赖,它在处理复杂依赖树时通常比 pip 更顺滑。但如果你只是为了跑通项目,最省力的路径还是换一台 Python 3.10 以上的现代发行版系统,不要在编译上恋战。
7. 部署完之后,安全与日常维护的原样经验
7.1 公网直接暴露不等于可以裸奔
服务公网可访问后,有一件事必须认真对待:MCP 工具是完全没有界面保护的,任何知道地址的人都能调用你的搜索接口,进而消耗你的服务器资源、微信搜索配额,甚至通过工具输出把敏感查询词发给外部检索入口。所以我的建议是:
- 如果没有强需求,端口只对局域网或固定来源 IP 开放;
- 云服务器安全组里不要写
0.0.0.0/0加全部端口,写0.0.0.0/0加指定端口已经是底线,能指定来源 IP 就指定; - 如果必须在公网开放,优先考虑在服务前面加一层带访问控制的 Web 服务,只允许携带正确 Header 或 Token 的请求转发到后端 MCP 进程,不要直接把服务端口暴露到公网。
很多 MCP Server 在新版本里已经内置了 Bearer Token 鉴权,配置时可以查一下 README 里有没有 AUTH_TOKEN、API_TOKEN 这类字段。如果没有,那至少要依赖网络层白名单。
7.2 日志与更新:小服务也要养成习惯
这类工具型服务部署完不是一劳永逸的。我一般会做三件日常维护动作:
- 每周看一次
journalctl -u weixin_search_mcp --since today,确认没有持续报错; - 在服务端目录建一个简单的健康检查脚本,每隔几分钟请求一次本地端口,不通就自动重启服务;
- 项目更新后先备份
.env和代码目录,再拉取新版本,不要直接覆盖旧目录。
这里还要注意一点:微信搜索索引的页面结构会不定期调整,如果某一天发现搜索结果的字段全都解析不出来,而你没有改动任何代码,那很可能不是你的问题,而是网页结构变了。这类工具的代码需要关注上游更新,不要装完就永远不管。我自己常用的一个做法是每月固定时间刷新一次项目仓库,有版本发布就对比 changelog,再做小范围回归测试。
最后想单独说一下我部署这一路下来的体会:整个过程中最难的不是执行命令,而是建立“分链路排错”的意识。MCP 服务从本机到外部访问会经过至少四个环节:进程监听、系统防火墙、云安全组、客户端协议路径。每一层都用自己的规则,踩坑时绝大部分问题都是层与层之间的配置不一致导致的,而不是核心代码本身有什么深奥的问题。如果以后再部署其他 MCP Server,我也建议你把同样的顺序重新走一遍:先在 127.0.0.1 跑通,再开放对外监听,再用最小权限接入客户端。链路清晰,部署就是一件可以复用的事。
