实战:文件系统服务器
让 AI 读写你的文件,是最直观也最危险的 MCP 用例。本章用 FastMCP 封装一个"安全版"文件系统服务器:只允许在某个白名单根目录内操作,其余一概拒绝。官方仓库也有现成 filesystem 服务器可参考,但自己写一遍能真正理解安全边界。
安全边界设计
两个原则贯穿始终:白名单根目录 + 防目录穿越。
- 根目录由环境变量 MCP_FS_ROOT 指定,服务器启动时解析为绝对路径;
- 任何用户传入的路径都先与根目录拼接、再 resolve 规范化,然后校验结果仍在根目录内——这样 ../ 穿越、绝对路径、指向外部的符号链接都会被拦下;
- 只暴露最小工具集:list_dir、read_file、write_file,不做递归删除等高危操作。
完整服务器代码
# filesystem_server.py:白名单目录内的文件读写 MCP 服务器
import os
from pathlib import Path
from mcp.server.fastmcp import FastMCP
ROOT = Path(os.environ.get("MCP_FS_ROOT", ".")).resolve() # 白名单根目录
mcp = FastMCP("safe-fs")
def _safe(raw: str) -> Path:
"""把输入解析为白名单内的绝对路径,越界即抛错"""
p = (ROOT / raw).resolve() # ../ 在这里被消解
if p != ROOT and ROOT not in p.parents: # 必须落在 ROOT 之内
raise ValueError(f"路径越界:{raw}")
return p
@mcp.tool()
def list_dir(path: str = ".") -> str:
"""列出白名单内某目录下的条目"""
d = _safe(path)
if not d.is_dir():
raise ValueError(f"{path} 不是目录")
return "\n".join(sorted(os.listdir(d)))
@mcp.tool()
def read_file(path: str, max_chars: int = 10000) -> str:
"""读取白名单内的文本文件,最多返回 max_chars 字符"""
f = _safe(path)
if not f.is_file():
raise ValueError(f"{path} 不是文件")
return f.read_text(encoding="utf-8", errors="replace")[:max_chars]
@mcp.tool()
def write_file(path: str, content: str, append: bool = False) -> str:
"""向白名单内的文件写入内容;append=True 时追加"""
f = _safe(path)
f.parent.mkdir(parents=True, exist_ok=True) # 允许在白名单内新建子目录
if append:
with f.open("a", encoding="utf-8") as fh:
fh.write(content)
else:
f.write_text(content, encoding="utf-8")
return f"已写入 {len(content)} 字符"
if __name__ == "__main__":
mcp.run() # FastMCP 默认 stdio 运行
运行与测试
export MCP_FS_ROOT="$HOME/workspace/docs" # Windows: $env:MCP_FS_ROOT="D:\docs"
python filesystem_server.py
服务器会阻塞等待客户端。先用官方检查器验证再接入宿主:
pip install "mcp[cli]"
mcp dev filesystem_server.py
# 输出:Inspector 已启动:http://127.0.0.1:6274
在打开的页面选 Stdio 传输并连接,就能逐个调用 list_dir/read_file/write_file,看参数校验与返回;故意传 ../etc/passwd 应得到"路径越界"错误。
接入宿主后的对话效果
把服务器登记进 Claude Desktop/Cline(command 写 python 与脚本绝对路径,env 设 MCP_FS_ROOT,见第 13 章)后,典型对话流程:
- 用户:"看看 D:\docs 目录下都有什么" → 宿主调 list_dir 返回文件名列表;
- 用户:"把 报告.md 的开头 500 字读出来" → 调 read_file 取内容展示;
- 用户:"把标题改成《季度总结》" → 调 write_file 写入,写入前宿主通常会请求用户确认;
- 用户试图读白名单外文件时,模型收到"路径越界"错误后主动说明做不到。 工具描述会被模型读到,务必写清"只操作白名单目录、不做删除",避免模型产生不安全的调用意图。 小结:安全文件服务器 = 白名单根目录 + resolve 后校验路径仍在根内 + 最小工具集;用 mcp dev 起 Inspector 验证越界拦截,再接入宿主体验"AI 读改指定目录"的完整链路。