内网跑 Gitea,代码不出边界,这套模式在办公室里很舒服。一旦有人居家、出差,或者临时拉个外包进来,问题立刻冒头:网页打不开,git 命令连不上,想看个 PR 状态只能等回公司。把 3000 端口直接映射到公网是绝对不行的,扫描器和撞库每天都有,Token 一旦泄露就是整库风险。全员 VPN 又太重,权限粒度粗,运维审批链路长。
我们最后实现的思路不是”让人进内网”,而是”把查询结果递出来”。内网服务器上跑一个 MCP 服务,通过 Gitea REST API 取数据,再用内网穿透给它开一个公网 HTTPS 入口。外部只能做 API 级查询,碰不到 Gitea 本体,也 clone 不了仓库。
架构为什么这样切
整条链路是:外网开发机上的 Cursor 走 HTTPS/SSE 到公网端点,经加密隧道进内网,落到只监听 127.0.0.1:8004 的 MCP 服务,再由它回环调用 127.0.0.1:3000 的 Gitea API。
几个设计点必须守住。Gitea 不绑公网网卡,只监听回环或内网网卡;MCP 代理同样只绑本机,外部无法直连;公网流量全部走隧道,中间有安全策略过滤;全链路 HTTPS。
选 MCP 而不是暴露 Git 协议,核心在于攻击面。MCP 走 REST API 层,即使入口暴露,能做的也只是 API 级查询,拖不走整个仓库。Gitea 官方维护了 gitea-mcp,部署轻,还能配合 AI 工具用自然语言查仓库信息。

服务端准备与依赖版本
环境是 Ubuntu 22.04 LTS,Gitea 1.21.x 已在内网运行,客户端用 Cursor。
先装 ZeroNews 客户端并注册为系统服务:
curl -SLk https://download.zeronews.cc/linux/zeronews-install.sh | sudo sh -s install <你的AuthToken>
sudo zeronews service start
控制台看到客户端在线即可。

接着在 Gitea 里生成访问令牌。设置 → 应用 → 生成新令牌,权限按最小原则给:repository 读、issue 读、pull_request 读。只读场景不要给写权限,泄露后的影响面完全不同。

运行时依赖这块,原文用的是 Node.js 20.x。现在 Node.js 22 已是 LTS,装 22 也能跑,但要注意 mcp-proxy 的版本对齐:npx 拉取的是最新版,如果团队里有人本地缓存了旧版 mcp-proxy,SSE 端点路径和参数解析行为会有差异,表现为客户端连上却收不到工具列表。安全做法是固定版本,比如 npx -y mcp-proxy@<锁定版本>,避免不同机器行为漂移。
sudo apt-get update
sudo apt-get install -y ca-certificates curl tar
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v && npx -v
再装官方 gitea-mcp 二进制:
curl -fsSL -o /tmp/gitea-mcp.tgz "https://gitea.com/gitea/gitea-mcp/releases/download/v1.8.0/gitea-mcp_Linux_x86_64.tar.gz"
sudo tar -xzf /tmp/gitea-mcp.tgz -C /usr/local/bin gitea-mcp
sudo chmod +x /usr/local/bin/gitea-mcp
gitea-mcp --help
启动 MCP 并只绑回环
配置只读 Token 后用 mcp-proxy 在 8004 暴露 gitea-mcp:
export GITEA_ACCESS_TOKEN='你的只读Token'
npx -y mcp-proxy --host 127.0.0.1 --port 8004 -- gitea-mcp -transport stdio -host http://127.0.0.1:3000
这里踩过一次坑。最初图省事绑了 0.0.0.0,安全扫描直接扫到 8004。改成只监听回环、只走隧道之后就干净了。Token 权限同理,一开始给了全权限,后来收敛成只读。

隧道与安全策略
控制台里先加自定义域名,比如 giteamcp.你的域名.com,再新建一条 HTTPS 隧道:协议 HTTPS,内网 IP 填 127.0.0.1,内网端口 8004,绑定装了客户端的设备,公网域名选刚加的那个。


隧道通了之后,公网入口就是 https://giteamcp.你的域名.com/sse。不要再额外映射 Gitea Web 或 SSH。
公网暴露一个入口就必须有防护,三层建议都开。Basic 认证,所有连接先过用户名密码;IP 白名单,把公司出口 IP 和员工固定 IP 加进去,能挡掉绝大部分乱序扫描;区域访问控制,员工 IP 经常变不好维护白名单时,只放行境内 IP,过滤无效流量效果明显。



客户端接入与长连接稳定性
Cursor 里 Settings → MCPs → New MCP Server,填:
{"mcpServers": {"giteamcp": {"url": "https://giteamcp.你的域名.com/sse","type": "sse"}}}
列表里 giteamcp 变绿即连上。


上线一段时间后遇到一个典型故障:空闲十几分钟后客户端报连接断开,重新触发才恢复。根因是 SSE 是长连接,隧道或中间层对空闲连接有超时回收,而客户端不会自动重连。处理方式是让服务端定期发心跳事件,或在隧道侧把空闲超时调大。排查时可以直接用 curl 观察事件流是否持续输出:
curl -N -H "Authorization: Basic <base64>" https://giteamcp.你的域名.com/sse
如果几十秒后连接被对端关闭且没有心跳帧,基本可以判定是空闲超时。判定标准很简单:持续收到事件帧即正常,静默断开即需调整超时或补心跳。
实际能干什么,边界在哪
配好之后日常查询不用开网页。在 Agent 对话框里直接说”列一下所有仓库”、”order-service 的 main 分支最近 10 次提交都是谁改的”、”现在有多少个 open 的 PR”,AI 会自动调 MCP 接口整理结果。轻量场景效率明显高于开网页,而且不用连 VPN。




但边界要清楚。MCP 走 API,适合查询和轻量操作。想 clone 整个仓库、大批量 push,还是得走内网或正经 Git 通道。另外仓库数量多、PR 历史长的时候,API 分页和限流会让单次查询变慢甚至被截断,返回结果不完整。判断方法很直接:如果一次查询跨了几十个分页,或者短时间内连续触发多次工具调用,就该怀疑限流而非配置错误。这类场景老老实实回内网操作。
IP 白名单也要定期维护,员工家里 IP 变了、公司出口换了,不及时更新就是连不上,然后来问运维。
这套方案适合谁
中小团队、没有专职运维搞 VPN 体系的,收益最大。远程办公和居家协作频繁、外包临时项目多、需要精细控制权限、又不想把 Git 仓库暴露公网的,都合适。半小时能部署完,运维成本低。
代价是依赖支持 MCP 的 AI 工具,公网入口仍需认真做防护,重型 Git 操作不在覆盖范围内。对”人在外网想看个代码状态”这个痛点,它在安全和效率之间找了个不错的平衡点。
ID:7690