本文仅用于本地开发学习,MCP服务拥有本地文件读写权限,存在敏感文件泄露、路径穿越风险,请严格遵守以下安全规范:

  1. 仅授权AI访问非隐私、无密钥、无配置文件的项目目录,禁止直接扫描系统根目录、用户桌面、数据库密钥文件夹;
  2. 代码内置目录白名单校验,生产环境必须开启路径限制,防止../路径穿越攻击;
  3. stdio模式仅本地客户端使用,streamable-http HTTP模式禁止公网暴露,仅本地localhost访问;
  4. 本教程技术仅供个人开发调试,请勿用于第三方远程文件读取、批量扫描他人设备。

一、前言

日常使用Cursor、Claude Desktop、VS Code MCP客户端时,AI只能处理对话内粘贴的代码片段,无法直接读取本地项目目录、批量检索源码、统计文件结构、预览日志文件。
核心原因:AI客户端默认无本地文件系统操作权限,而 MCP(Model Context Protocol 模型上下文协议) 是Anthropic推出的标准化AI本地工具通信协议,相当于AI的通用USB接口,一次开发MCP服务,所有支持MCP的AI客户端均可无缝调用本地文件能力。

本文基于Python FastMCP SDK,从零实现文件搜索、目录统计、文件预览三大工具,完整兼容Cursor、Claude Desktop,附带安全权限校验、客户端全平台配置教程。

适用场景

  • 本地项目批量检索py/json配置文件
  • 代码目录结构统计、文件大小汇总
  • 日志、源码快速预览,无需手动打开文件
  • AI自动分析本地项目工程结构

二、MCP协议通俗原理

把MCP理解为AI客户端的标准化外设接口:

类比事物 MCP对应概念
电脑主机 Claude/Cursor/VS Code AI客户端
U盘/外设 自定义Python本地工具服务
USB通信标准 MCP(JSON-RPC/Streamable HTTP/stdio)
即插即用配置 客户端mcpServers配置项

传统方案:不同AI工具需要单独开发插件、适配各自私有API;
MCP标准化后:一套MCP服务,全平台AI客户端通用,大幅降低本地工具开发成本。

三、环境部署

1. Python版本要求

Python ≥ 3.10

# 查看版本
python3 --version

2. 安装依赖库

# MCP官方SDK + HTTP服务框架
pip install mcp fastapi uvicorn

依赖说明:

  • mcp:FastMCP官方Python开发库,快速注册AI可调用工具
  • fastapi+uvicorn:提供Streamable HTTP服务传输通道

3. 项目初始化

# 创建项目文件夹
mkdir -p ~/my-file-mcp && cd ~/my-file-mcp

四、完整MCP服务代码(带安全白名单+详细注释)

新建 file_scanner.py,增加目录白名单防护,规避路径穿越漏洞:

"""
本地文件扫描MCP Server(安全加固版)
功能:文件检索、目录统计、文件内容预览
安全限制:仅允许访问预设白名单目录,防止路径穿越攻击
"""
import os
from pathlib import Path
from mcp.server.fastmcp import FastMCP

# ====================== 安全配置:允许访问的目录白名单(自行修改) ======================
ALLOW_DIRS = [
    Path("~/projects").expanduser(),
    Path("~/code").expanduser()
]
# 转换为绝对路径,用于校验
ALLOW_ABS = [p.resolve() for p in ALLOW_DIRS]

# 初始化MCP服务
mcp = FastMCP(
    "File Scanner",
    description="本地文件系统安全扫描工具,仅读取白名单内目录,支持文件检索、目录统计、源码预览",
    port=8765
)

def is_path_safe(target_path: str) -> bool:
    """路径安全校验:仅允许访问白名单目录,拦截../路径穿越"""
    target = Path(target_path).resolve()
    # 判断目标路径是否在任一允许目录下
    for safe_dir in ALLOW_ABS:
        if str(target).startswith(str(safe_dir)):
            return True
    return False

@mcp.tool()
def search_files(directory: str, pattern: str = "*.py", max_depth: int = 3) -> str:
    """
    递归搜索指定目录下匹配后缀的文件
    Args:
        directory: 目标检索目录(必须在白名单内)
        pattern: 文件匹配规则,如 *.py、*.json
        max_depth: 最大递归深度,避免遍历过多文件
    Returns:
        匹配到的文件路径列表
    """
    # 安全校验
    if not is_path_safe(directory):
        return f"权限拒绝:{directory} 不在允许访问目录列表中,请修改ALLOW_DIRS配置"

    base = Path(directory)
    if not base.exists():
        return f"错误:目录不存在 —— {directory}"

    results = []
    try:
        for depth in range(max_depth + 1):
            glob_rule = "**/*" + pattern if depth > 0 else pattern
            for file_path in base.glob(glob_rule):
                if file_path.is_file() and len(results) < 50:
                    results.append(str(file_path))
    except Exception as e:
        return f"文件搜索异常:{str(e)}"

    if not results:
        return f"在 {directory} 未匹配到 {pattern} 文件(最大深度{max_depth})"
    return f"共找到 {len(results)} 个文件:\n" + "\n".join(results)

@mcp.tool()
def dir_stats(path: str) -> str:
    """
    统计目录:文件总数、总大小、文件类型分布
    Args:
        path: 目标统计目录
    Returns:
        结构化目录统计报告
    """
    if not is_path_safe(path):
        return f"权限拒绝:{path} 不在允许访问目录列表中"

    p = Path(path)
    if not p.exists():
        return f"错误:路径不存在 —— {path}"

    total_files = 0
    total_size = 0
    ext_count = {}

    for item in p.rglob("*"):
        if item.is_file():
            total_files += 1
            size = item.stat().st_size
            total_size += size
            suffix = item.suffix if item.suffix else "(无后缀)"
            ext_count[suffix] = ext_count.get(suffix, 0) + 1

    # 按文件数量降序排序
    sorted_ext = sorted(ext_count.items(), key=lambda x: -x[1])
    lines = [
        f"📁 统计目录:{path}",
        f"📄 文件总数量:{total_files}",
        f"💾 目录总大小:{total_size / 1024:.1f} KB",
        "",
        "前10类文件分布:"
    ]
    for ext, num in sorted_ext[:10]:
        lines.append(f"  {ext}{num} 个")
    return "\n".join(lines)

@mcp.tool()
def preview_file(path: str, max_lines: int = 30) -> str:
    """
    预览文件前N行内容,避免一次性读取超大文件占满上下文
    Args:
        path: 文件完整路径
        max_lines: 最多预览行数,默认30行
    Returns:
        文件元信息 + 头部内容预览
    """
    if not is_path_safe(path):
        return f"权限拒绝:{path} 不在允许访问目录列表中"

    p = Path(path)
    if not p.exists() or not p.is_file():
        return f"错误:文件不存在或非文件 —— {path}"

    try:
        with open(p, "r", encoding="utf-8", errors="replace") as f:
            all_lines = f.readlines()
            preview = all_lines[:max_lines]
    except Exception as e:
        return f"文件读取失败:{str(e)}"

    file_size = p.stat().st_size
    total_line_num = len(all_lines)
    header = (
        f"📄 文件路径:{path}\n"
        f"文件大小:{file_size/1024:.1f} KB | 总行数:{total_line_num}\n"
        f"预览前 {min(max_lines, total_line_num)} 行内容:\n\n"
    )
    return header + "".join(preview)

if __name__ == "__main__":
    # Streamable HTTP 本地服务(仅localhost访问)
    mcp.run(transport="streamable-http")

启动服务命令

python file_scanner.py

正常输出示例:

INFO: Uvicorn running on http://0.0.0.0:8765
INFO: Application startup complete.

五、AI客户端接入配置(区分Mac/Windows)

5.1 Claude Desktop 配置

  1. Mac配置文件路径:~/Library/Application Support/Claude/claude_desktop_config.json
  2. Windows配置文件路径:%APPDATA%\Claude\claude_desktop_config.json

配置JSON(stdio传输模式,推荐本地使用):

{
  "mcpServers": {
    "file-scanner": {
      "command": "python",
      "args": ["~/my-file-mcp/file_scanner.py"],
      "transport": "stdio"
    }
  }
}

修改完成后重启Claude Desktop,自动加载MCP服务。

5.2 Cursor 编辑器配置

  1. 打开Cursor → Settings → MCP → Add new MCP Server
  2. 配置参数:
    | 配置项 | 填写内容 |
    | Name | file-scanner |
    | Command | python |
    | Arguments | ~/my-file-mcp/file_scanner.py |
    | Transport | stdio |

保存后刷新MCP列表,显示服务在线即连接成功。

六、实操调用示例

客户端连接成功后,直接对话发送指令即可自动调用文件工具:

  1. 检索项目所有Python文件
调用file-scanner,扫描~/projects目录,找出所有*.py文件,最大深度3
  1. 统计目录文件结构
统计~/projects目录总文件数量、总大小,展示各类文件占比
  1. 预览源码文件
读取~/projects/utils.py前20行代码并展示文件信息

七、进阶扩展工具模板

基于@mcp.tool()装饰器可快速扩展更多本地自动化能力,推荐开发:

工具函数 实现功能
git_log 读取Git仓库最近提交记录
log_tail 实时读取日志末尾内容
dir_diff 对比两个目录文件差异
docker_list 列出本地运行容器
cron_check 读取系统定时任务配置

新增工具仅需定义函数、增加参数注解,MCP会自动生成工具描述供AI识别。

八、安全加固&生产优化方案

  1. 严格目录白名单:代码内置ALLOW_DIRS拦截路径穿越,禁止开放系统根目录;
  2. 限制文件读取行数preview_file限制最大预览行数,防止超大文件填满LLM上下文;
  3. HTTP模式禁止公网暴露:streamable-http仅本地localhost访问,不开放0.0.0.0给局域网;
  4. 增加文件大小校验:可新增代码判断文件体积,拒绝读取超过1MB大文件;
  5. 日志记录所有文件操作:添加本地日志,审计AI全部文件访问行为。

九、常见踩坑FAQ

Q1:客户端识别不到MCP服务?
A:检查Python路径是否正确、配置文件路径区分Mac/Windows,修改配置后重启客户端;确认mcp库版本≥1.0。

Q2:调用文件工具提示权限拒绝?
A:将目标目录添加到代码顶部ALLOW_DIRS白名单,重启MCP服务生效。

Q3:启动服务报端口占用?
A:修改FastMCP初始化时port=xxxx更换端口,同步修改客户端配置。

Q4:读取中文文件乱码?
A:代码已使用encoding="utf-8", errors="replace",特殊编码文件可自行调整编码参数。

十、全文总结

  1. MCP协议统一AI本地工具标准,一份服务兼容Cursor、Claude、VS Code多客户端;
  2. 本文实现安全版文件扫描MCP服务,内置路径白名单规避文件读取安全漏洞;
  3. 通过@mcp.tool()装饰器极简开发自定义本地工具,轻松扩展Git、容器、日志自动化能力;
  4. 核心价值:打破AI只能操作对话文本的限制,让AI直接接管本地项目分析、批量文件处理工作。

本教程基于Python MCP SDK v1.x编写,代码可直接本地运行,仅用于个人开发学习。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐