不用切换到终端:让 Claude 网页通过 MCP 读写 Mac 本地文件
我平时会在 Claude 网页里讨论代码、整理文档。但讨论结束后,往往还要把它生成的内容复制到本地文件,再把修改结果贴回聊天。
能不能让 Claude 网页直接读取项目、创建文件,并把修改写回 Mac?
可以。我的做法是:在 Mac 上运行一个受限的文件 MCP 服务,再用 Cloudflare Tunnel 把它接到 Claude 的远程连接器。
这篇文章记录实际搭建过程,也解释其中容易混淆的几个问题:为什么网页能访问本机、是否需要公网 IP、cloudflared 是不是 SSH、TUN 能不能开,以及换 Claude 账号后要做什么。
本文环境为 macOS,使用 Node.js 文件 MCP 服务、Cloudflare Tunnel 和 Claude 网页自定义连接器。界面和功能入口以 2026 年 11 月实际使用情况为参考,Claude 账号是否支持远程自定义连接器及自定义认证头,需要以当前账号界面为准。
local-files-mcp是本文自建的项目名称,不是一个安装后就具备全部功能的同名公共软件包。文中的setup、service:install、manager:install等 npm 命令属于该项目的脚本。本文给出架构、实现要点与部署步骤,不包含完整应用源码;如果使用其他 MCP 文件服务,需要按它的文档替换启动和配置命令。
一、搭完以后可以做什么
连接成功后,可以直接在 Claude 网页对话中提出这样的要求:
先读取项目里的
docs/intro.md,把使用说明整理得更清晰。修改之前先说明准备改哪些地方,确认后写回原文件。
或者:
在
mcp-demo目录里创建一份hello.txt,写入一段测试文字,然后读取回来确认。
我的文件服务提供以下六个工具:
| 工具 | 用途 |
|---|---|
list_directory | 列出目录中的可见文件和子目录 |
read_file | 读取文本,同时返回文件版本指纹 |
search_text | 在允许的范围内搜索文本 |
create_directory | 创建目录 |
write_file | 创建或替换文本文件 |
edit_file | 替换文件中的一处文本 |
这套连接器没有提供终端执行、删除文件、Git 提交或部署工具。修改代码后,不会自动运行测试、安装依赖或者发布项目。
二、整体架构:网页的请求怎样到达 Mac
请求经过四个环节:
Claude 网页发起工具调用
↓
Claude 云端请求远程 MCP 地址
↓ HTTPS
Cloudflare 边缘节点
↓ 已建立的加密隧道
Mac 上的 cloudflared
↓ HTTP,回环地址
127.0.0.1:8787 上的 MCP 服务
↓ 认证、路径和文件版本检查
授权目录中的文件
这里有两个独立程序:
- cloudflared:Cloudflare 官方的隧道客户端,负责网络转发。
- 本地 MCP 服务:负责提供工具、检查权限,并真正执行文件读写。
cloudflared 本身不会根据 Claude 的要求修改文件。它把请求送到文件服务,文件服务检查通过后才操作磁盘。
MCP 负责什么
MCP 可以理解为模型调用外部工具的一套协议。
文件服务通过 MCP 告诉 Claude:有哪些工具、每个工具接受什么参数、调用之后如何返回结果。Claude 根据用户要求选择工具,例如先调用 read_file,再调用 edit_file。
这次使用官方 TypeScript SDK,在 JavaScript 项目中实现 Streamable HTTP 接口,供 Claude 云端访问。仅支持标准输入输出(stdio)的文件 MCP 服务,不能原样填入网页的远程连接器 URL;需要支持 HTTP 的服务或合适的转换层。
Cloudflare Tunnel 负责什么
家里的 Mac 通常没有可供外网访问的固定公网地址,而且处在路由器之后。
cloudflared 会从 Mac 主动向 Cloudflare 建立连接,并保持在线。Claude 请求公网域名时,Cloudflare 通过已有连接把请求传回 Mac。
它不是让 Mac 反复轮询“有没有新任务”,传来的也不只是通知,而是完整的 HTTP 请求与响应。
隧道通常使用基于 UDP 的 QUIC,也支持基于 TCP 的 HTTP/2。两者都不是 SSH,不过在“通过本地主动建立的通道访问本地服务”这一点上,可以类比 SSH 反向隧道。
因此,这套方案不需要:
- 给 Mac 配置固定公网 IP。
- 在路由器上做入站端口映射。
- 在 Mac 上直接暴露文件服务端口。
但网络必须允许 cloudflared 连接 Cloudflare 的隧道节点。
DevSpace 在这里起了什么作用
本次最终部署没有使用 DevSpace。
最初我研究过它是否能帮助完成这件事,但最终需要的是“HTTP MCP 文件服务 + 公网隧道”,直接用 Node.js 和 cloudflared 就能完成。
如果指的是 devspace.sh 对应的 DevSpace,它主要面向 Kubernetes 开发环境的部署、同步和调试。本次在 Mac 上直接运行服务,没有引入 Kubernetes,也不依赖 DevSpace。
三、准备条件与目录规划
准备以下条件:
- 一台可以联网的 Mac。
- Node.js 22 或以上版本与 npm。
- Cloudflare 账号,以及一个可以管理 DNS 的域名。
- Claude 网页账号中有可用的远程自定义连接器入口。
- 一个支持 HTTP、认证和目录边界检查的文件 MCP 项目。
本次本地服务使用的主要依赖是 @modelcontextprotocol/sdk、Express 和 Zod。
为了区分源码、配置和业务文件,可以按下面的方式放置:
~/code/local-files-mcp/ 自建 MCP 项目的源码
~/code/claude/ 固定配置与管理入口
├── .env 含认证密钥,不公开
├── restart.sh 重启文件服务
├── 打开管理页面.command 打开管理页面
├── index.html
├── app.js
└── style.css
~/code/29do/ 允许 Claude 操作的工作目录
~/.cloudflared/ 隧道配置与凭据,不公开
这些目录不是协议要求,只是方便维护的组织方式。源码移动后,后台任务中的程序路径需要同步更新。
本文用 mcp.example.com 表示公网域名。实际操作时,必须在本地服务配置、Cloudflare 配置、DNS 与 Claude 连接器中统一替换成自己的域名,不能直接使用示例地址。
四、先把本地 MCP 文件服务跑通
1. 明确工具与权限边界
不要把整个用户主目录直接交给模型。先从单个测试项目开始,确认流程后再调整范围。
本次实现做了以下检查:
- 工具只接受授权根目录内的相对路径,拒绝
../等越界访问。 - 拒绝隐藏路径、常见凭据和数据库备份、依赖及构建目录。
- 拒绝符号链接、硬链接以及 MCP 服务自身目录。
- 文本限定为 UTF-8,单文件最大 1 MiB。
- 读写都要求认证;对允许的 Host 和 Origin 做检查。
- 修改已有文件时校验 SHA-256 版本,避免直接覆盖较新的内容。
这些是本次文件服务的实现规则,并不是所有 MCP 服务默认都有的能力。
目录校验属于应用层限制,不能当作 macOS 系统沙箱。允许范围内的普通源码也可能包含秘密,文件名过滤无法自动识别所有敏感内容。
2. 初始化项目配置
在已经准备好的配套项目中执行:
cd ~/code/local-files-mcp
npm ci --ignore-scripts
mkdir -p ~/code/29do
npm run setup
本项目的 setup 会创建 ~/code/claude/.env,生成随机认证密钥,并保留已经存在的配置。
配置结构如下,实际密钥不要写成这里的占位文字:
MCP_ROOT="/Users/YOUR_MAC_USER/code/29do"
MCP_PUBLIC_URL=https://mcp.example.com
MCP_PORT=8787
MCP_TOKEN=YOUR_RANDOM_SECRET
其中:
| 配置项 | 含义 |
|---|---|
MCP_ROOT | 文件访问根目录,使用已存在的绝对路径 |
MCP_PUBLIC_URL | 公网 HTTPS 来源地址,不带 /mcp |
MCP_PORT | 本地文件服务端口,本例为 8787 |
MCP_TOKEN | 足够长的随机认证密钥,本项目至少要求 32 个字符 |
本次安装脚本默认写入了作者自己的域名,复用源码时要在启动前将 MCP_PUBLIC_URL 改成自己的域名。
保护配置文件:
chmod 700 ~/code/claude
chmod 600 ~/code/claude/.env
不要把 .env、密钥、Cloudflare 凭据提交到 Git,也不要贴进聊天截图或博客代码块。
3. 启动并验证本地服务
npm test
npm start
另开终端检查:
curl -fsS http://127.0.0.1:8787/health
预期返回:
{"status":"ok"}
再检查未认证的接口:
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8787/mcp
预期是 401。这是认证保护正常工作的表现。
本地服务只监听 127.0.0.1。暂时不要绑定到 0.0.0.0 来绕过网络问题。
五、配置 Cloudflare 域名与固定隧道
1. 将域名接入 Cloudflare
在 Cloudflare 添加自己的域名,按界面要求导入或补齐现有 DNS 记录,再到域名注册商或其代理管理平台更换 nameserver。
这里有一个容易混淆的地方:
- 修改 A/CNAME 记录,是在当前 DNS 托管平台操作。
- 更换权威 DNS 服务器(NS),通常要在域名注册管理平台操作。
两者可能不是同一个网站。域名目前在阿里云解析,不代表注册管理入口一定也在阿里云。
对普通完整域名接入方式,按 Cloudflare 分配给当前域名的两条 NS 设置即可。不要复制别人文章里的 NS。更换前核对现有网站、邮件等记录,避免遗漏。
等 Cloudflare 显示域名已激活,并确认边缘 HTTPS 证书可用后,再继续验证公网访问。不同账户方案也可能支持其他接入方式,本文记录的是完整域名接入的过程。
2. 安装 cloudflared
在已经安装 Homebrew 的 Mac 上执行:
brew install cloudflared
cloudflared --version
cloudflared 是一个命令行客户端,没有必须一直开着的软件窗口。
3. 登录并创建命名隧道
cloudflared tunnel login
命令会引导浏览器打开 Cloudflare,完成登录并授权目标域名。生成的账号证书保留在本机。
创建隧道:
cloudflared tunnel create 29do-files-mcp
记录命令返回的隧道 UUID 和凭据文件位置。下面示例中的 YOUR_TUNNEL_UUID 必须换成真实 UUID。
4. 编写本地转发配置
创建文件 ~/.cloudflared/29do-files-mcp.yml:
tunnel: YOUR_TUNNEL_UUID
credentials-file: /Users/YOUR_MAC_USER/.cloudflared/YOUR_TUNNEL_UUID.json
ingress:
- hostname: mcp.example.com
service: http://127.0.0.1:8787
- service: http_status:404
注意 credentials-file 使用实际绝对路径,不要假设 YAML 中的 ~ 一定会被展开。
转发规则的含义是:
mcp.example.com的请求发给本地 8787 端口。- 不匹配的域名返回 404。
- URL 路径会继续传递,所以公网
/mcp对应本地/mcp。
校验配置:
cloudflared tunnel --config ~/.cloudflared/29do-files-mcp.yml ingress validate
5. 将域名关联到隧道
cloudflared tunnel route dns 29do-files-mcp mcp.example.com
这一步通常会在 Cloudflare 创建指向隧道目标的 CNAME。它关联的是隧道身份,不是家里当前的公网 IP,因此网络地址变化时不需要手工更新 A 记录。
如果这个子域名已经有冲突记录,需要先核对用途,再在 DNS 控制台处理,不要直接覆盖其他服务。
6. 启动隧道
cloudflared tunnel --config ~/.cloudflared/29do-files-mcp.yml run 29do-files-mcp
保持本地 MCP 服务也在运行。看到隧道连接成功注册后,验证公网:
curl -fsS --max-time 20 https://mcp.example.com/health
curl -sS --max-time 20 -o /dev/null -w '%{http_code}\n' https://mcp.example.com/mcp
预期分别是健康 JSON 和 401。
Cloudflare 负责公网 HTTPS 证书,Mac 与 Cloudflare 之间使用加密隧道,本机最后一段 HTTP 只走回环地址。Mac 无需再单独配置公网 HTTPS 证书。
也不要在这个端点前加上需要人工网页登录的 Cloudflare Access 页面,否则 Claude 云端的机器请求可能被登录页面挡住。本例由 MCP 服务本身校验 Bearer 密钥。
六、Clash Verge 开着 TUN,隧道能不能用
可以,但需要让 cloudflared 的网络路径正确。
我的搭建过程中曾遇到:关闭 TUN 后隧道恢复,开启后连接失败。一开始尝试更换代理节点,没解决根本问题。
最终在当前订阅的扩展规则里,添加了优先执行的规则:
PROCESS-NAME,cloudflared,DIRECT
操作方式是:
- 在 Clash Verge 的“订阅”页找到当前正在使用的订阅。
- 打开它的“编辑规则”。
- 规则类型选择“匹配进程名称(PROCESS-NAME)”。
- 内容填写
cloudflared,策略选择DIRECT。 - 添加为前置规则并保存;必要时重新激活订阅。
- 确认处于“规则”代理模式。
- 在“连接”页筛选
cloudflared,检查实际连接命中DIRECT。 - 重启隧道后,再检查公网健康接口。
最后我实际验证了:TUN 开启、系统代理开启,隧道连接命中 ProcessName(cloudflared) → DIRECT,公网健康检查返回 200。
不过,DIRECT 只是让进程绕过代理,不保证任何网络环境都能直连成功。仍要确认到 Cloudflare 隧道节点的 TCP/UDP 7844 可达,以及 DNS 正常。
这条规则如果保存在某个订阅的扩展规则里,切换其他订阅时需要重新检查。不要只修改软件生成的临时运行配置,否则下次加载可能丢失。
命令行 curl 也不一定继承 macOS 的系统代理。若要通过本机代理检查公网,可以显式使用:
curl --proxy http://127.0.0.1:7897 \
--max-time 20 https://mcp.example.com/health
这里的 7897 是示例本机代理端口,按实际设置修改。这个命令只决定 curl 怎么访问公网,不会把 cloudflared 改成代理连接。
七、在 Claude 网页添加连接器
进入 Claude 的设置,找到连接器或自定义连接器入口。按钮名称可能随着版本变化。
填写:
| 项目 | 内容 |
|---|---|
| 名称 | 29do 本地文件,可自行命名 |
| Remote MCP server URL | https://mcp.example.com/mcp |
| 认证 | 本次使用固定请求头方式,不配置 OAuth |
| 请求头名称 | Authorization |
| 请求头值 | Bearer 加本地随机密钥 |
注意 Bearer 和密钥之间有一个空格。
本项目提供了复制脚本:
cd ~/code/local-files-mcp
npm run copy-token
它把完整的请求头值复制到本机剪贴板,不在终端打印。接着直接粘贴到目标账号的连接器认证字段中,不要粘贴进聊天消息。
如果当前 Claude 界面没有固定请求头配置能力,不要删除服务器认证来强行连接。先确认当前账号支持的认证方式;必要时采用符合其要求的 OAuth 实现,这不属于本文这版服务已经实现的功能。
保存并连接后,应该能看到文件工具列表。开始普通对话时,还需要启用这个连接器和需要的工具。Claude 可能对具体写操作再要求批准。
换账号是否需要重新配置
| 场景 | 处理方式 |
|---|---|
| 退出后重新登录同一个账号 | 通常保留配置 |
| 换浏览器登录同一个账号 | 通常保留配置 |
| 登录另一个 Claude 账号 | 需要在新账号添加连接器 |
新账号接入不需要在 Mac 上再部署一套服务,可以使用相同的端点。
但如果共享同一密钥,它们就具有相同的文件访问权限。退出旧账号不会自动撤销凭据。要让旧账号失效,需要轮换服务器密钥,并更新其他仍需使用该服务的账号。当前实现没有按 Claude 账号单独划分授权目录。
八、不要只看“连接成功”,实际做一次文件测试
先准备一个独立测试目录,不要拿业务配置文件做第一次测试。
可以在 Claude 中逐步提出要求:
使用“29do 本地文件”连接器:
1. 列出根目录,确认当前可访问范围。
2. 创建一个尚不存在的 mcp-demo-test 目录。
3. 在里面新建 hello.txt,写入 MCP_TEST_CREATED。
4. 读取该文件并返回内容。
5. 使用刚读取的文件版本,将内容改为 MCP_TEST_EDITED。
6. 再次读取文件,确认修改结果。
如果测试目录或文件已存在,先停止,不覆盖已有内容。
然后在 Mac 上检查磁盘结果:
cat ~/code/29do/mcp-demo-test/hello.txt
只有看到实际文件和最终内容,才能证明工具完成了写入。网页断流不一定表示工具没有执行,应先检查文件,再决定是否重试,避免重复创建。
我的这次搭建已验证 Claude 网页实际创建本地文件成功;服务端的创建、读取和编辑集成测试通过。网页完整的“编辑后再读取”验收曾受对话断流影响,不能把这一项写成已经全部完成。上面的流程是建议读者执行的完整验收方法。
为什么要带 SHA-256
假设 Claude 读取文件后,你又用编辑器修改了它。
如果 Claude 无条件覆盖,就可能把你刚写的内容抹掉。为此,read_file 返回文件的 SHA-256,修改时提交 expected_sha256,服务端确认版本没变再写入。
新文件使用 expected_sha256: null,目标已存在就拒绝创建。edit_file 则要求待替换文本恰好出现一次。
这种机制可以减少误覆盖,但不能把应用层读写变成操作系统级的事务,也不能完全消除其他本地进程同时修改路径或文件的竞态。
九、让服务在后台运行,登录后自动启动
如果一直依赖两个终端窗口运行,关闭终端就可能断开服务。因此我把文件服务、隧道和管理页面都交给 macOS 的 launchd 管理。
本项目的安装命令是:
cd ~/code/local-files-mcp
npm run service:install
npm run tunnel:install-service
npm run manager:install
执行前,先停止之前在终端里前台运行的同一服务,避免争用端口。
三个后台任务的 Label 是本项目自定义名称,不是 Cloudflare 的固定要求:
| 服务 | Label |
|---|---|
| 文件 MCP | com.wandoumama.local-files-mcp |
| 隧道 | com.wandoumama.local-files-tunnel |
| 本机管理页面 | com.wandoumama.local-files-manager |
安装器在 ~/Library/LaunchAgents/ 写入 plist,关键配置包括:
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
RunAtLoad 表示任务加载时启动;KeepAlive 表示进程退出后由 launchd 尝试重新拉起。cloudflared 本身也会尝试重连网络。
“开机启动”需要说准确
这次用的是用户级 LaunchAgent,因此是:
登录 macOS 账号后自动启动,随后在后台运行。
它不是机器刚开机、还停留在登录界面时就已经运行的系统级 LaunchDaemon。
关闭终端、关闭 Claude 或浏览器不会停止已经托管的服务。锁屏通常不影响运行,但电脑休眠、断网或退出 macOS 账号会影响连接。
不要为了方便就给所有进程授予完全磁盘访问权限。是否需要额外的 macOS 文件权限,取决于授权目录和实际报错;先从普通项目目录开始验证。
检查任务是否已加载:
launchctl print "gui/$(id -u)/com.wandoumama.local-files-mcp"
launchctl print "gui/$(id -u)/com.wandoumama.local-files-tunnel"
launchctl print "gui/$(id -u)/com.wandoumama.local-files-manager"
手动重启:
launchctl kickstart -k "gui/$(id -u)/com.wandoumama.local-files-mcp"
launchctl kickstart -k "gui/$(id -u)/com.wandoumama.local-files-tunnel"
启动任务不等于公网已经连接,重启后仍要检查健康接口。
十、用本机管理页面更换授权目录
我还给这套服务加了一个本机管理页面:
http://127.0.0.1:8788
它和公网 MCP 端点是两个不同的服务:
| 地址 | 用途 |
|---|---|
http://127.0.0.1:8788 | 本人管理配置与后台任务 |
https://mcp.example.com/mcp | Claude 调用文件工具 |
管理页面可以查看服务状态、点击“选择文件夹”打开原生 macOS 目录窗口、修改路径、保存配置并重启文件服务,也能单独重启隧道。
使用顺序是:
- 打开管理页面。
- 点击“选择文件夹”,或输入已经存在的绝对路径。
- 检查路径是否正确。
- 点击“保存并重启”。
- 等待文件服务恢复,在 Claude 中按新根目录使用相对路径。
选择文件夹只填入路径,保存才真正改变访问范围。
更换根目录不用修改公网域名、隧道或 Claude 的连接器地址。固定的 ~/code/claude/restart.sh 也能用于重启文件服务。
这个页面只监听回环地址,不通过 Cloudflare 发布。接口还会检查 Host、Origin 和自定义请求头,避免其他网站直接诱导浏览器修改本机配置。页面不显示认证密钥。
十一、遇到问题,按链路逐层查
不要一上来就重装或重建隧道。按下面顺序定位,通常更容易找到原因。
| 现象 | 优先检查 |
|---|---|
| 管理页面打不开 | 管理服务是否启动,8788 端口是否可访问 |
本地 /health 不通 | MCP 服务、根目录配置、端口占用与启动日志 |
| 本地正常,公网失败 | 隧道连接注册、Clash 路由、DNS 与 Cloudflare 域名状态 |
| 公网出现 1033 | 是否没有可用隧道连接;刚重启时可短暂出现,等待一次重连后复查 |
/mcp 返回 401 | 不带认证时正常;连接器也报错时检查 Bearer 值是否一致 |
| 域名访问返回 403 | Host/Origin 校验、Cloudflare 规则,以及是否有其他访问控制拦截 |
| Claude 看不到文件工具 | 账号配置、端点路径 /mcp、认证方式、对话是否启用连接器 |
| 文件拒绝访问 | 当前根目录、相对路径、文件类型及服务端限制 |
| 修改提示版本冲突 | 重新读取文件并取得新的 SHA-256,再决定如何合并 |
| 网页断流但不确定是否写入 | 先查看本机文件,避免直接重复执行 |
本项目日志在源码目录下的 .runtime/。文件服务、隧道和管理器各有独立日志。
查看或向别人提供日志时,先检查是否含有密钥、cookie、文件内容或其他私人信息,不要直接上传完整配置目录。
十二、使用这套方案时,我会保留的几条边界
它让网页调用本地工具变得方便,也把一部分文件操作能力交给了远端模型。我的做法是:
- 只开放实际需要的目录,从测试项目开始。
- 保留 Bearer 认证、目录边界和版本检查。
- 重要修改先让 Claude 说明计划,再批准写入。
- 用 Git 或其他备份方式保留恢复路径。
- 不因为一次文件访问报错,就把主目录或敏感配置全部开放。
- 不在博客截图里展示密钥、Cloudflare 凭据、账号邮箱和真实业务内容。
MCP 负责工具协议,Cloudflare Tunnel 负责把请求送到本机,文件服务负责权限与落盘。把这三层职责分清,之后无论是排查故障,还是换域名、换账号、换工作目录,都会容易很多。
