实战:文件系统服务器

让 AI 读写你的文件,是最直观也最危险的 MCP 用例。本章用 FastMCP 封装一个"安全版"文件系统服务器:只允许在某个白名单根目录内操作,其余一概拒绝。官方仓库也有现成 filesystem 服务器可参考,但自己写一遍能真正理解安全边界。

安全边界设计

两个原则贯穿始终:白名单根目录 + 防目录穿越。

  1. 根目录由环境变量 MCP_FS_ROOT 指定,服务器启动时解析为绝对路径;
  2. 任何用户传入的路径都先与根目录拼接、再 resolve 规范化,然后校验结果仍在根目录内——这样 ../ 穿越、绝对路径、指向外部的符号链接都会被拦下;
  3. 只暴露最小工具集: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 读改指定目录"的完整链路。
笔记加载中…