上期回顾:MCP 实战系列(Day 1)- 什么是 MCP?
在上期文章中,我们详细介绍了 Model Context Protocol(MCP)的基本概念和应用场景。本节将带领大家开发一个简易的 Filesystem MCP Server,通过代码示例逐步讲解实现细节。
本期目标:掌握 MCP Server 开发的基础框架,实现一个支持基本文件系统操作的服务器。
开源地址:https://github.com/jinzcdev/mcp-demos
开发环境准备
官方提供了 Python、Node、Java、Kotlin、C# 五种语言的 MCP 服务器 SDK。本文以 Python 为例进行开发,其他语言的实现方式类似。读者可根据实际项目需求选择熟悉的开发语言。
MacOS/Linux 系统
- 安装 uv 工具
curl -LsSf https://astral.sh/uv/install.sh | sh
- 创建项目目录并安装依赖
# 创建项目目录并进入
uv init simple-filesystem-server-python
cd simple-filesystem-server-python
# 创建虚拟环境并激活
uv venv
source .venv/bin/activate
# 安装 MCP 相关依赖
uv add "mcp[cli]"
# 创建 MCP Server 文件
touch simple_filesystem.py
说明:uv 是一个高效的 Python 包管理工具,功能类似 pip。如果更熟悉 pip,也可以使用 pip install "mcp[cli]"
安装依赖或创建虚拟环境。mcp[cli]
中的 cli 表示额外安装 typer 和 python-dotenv 两个库,前者用于创建命令行界面,后者用于加载环境变量(可通过查看 mcp 依赖库源代码中的 project.optional-dependencies
配置项找到这两个额外依赖)。最后创建的 simple_filesystem.py
文件是定义 MCP 服务的核心源文件,我们将在其中创建相关工具。
编写 MCP Server 代码
我们将实现一个简易的 Filesystem MCP Server,支持在指定目录中进行文件查找、读写等操作。
值得一提的是,Claude 官方已经提供了一个功能完备的 Node.js 版本 Filesystem MCP Server,包含了丰富的文件系统操作工具。本文选择使用 Python 重新实现,目的是帮助读者更清晰地理解 MCP Server 的 开发流程 和 工具设计思路,而非重复造轮子。
接下来,让我们逐步实现这个服务器。
1. 导入必要模块并初始化服务器
完成前面的准备步骤后,我们首先在 simple_filesystem.py
中导入所需模块,并通过命令行参数指定允许访问的目录:
from mcp.server.fastmcp import FastMCP
import os
import os.path as osp
import argparse
parser = argparse.ArgumentParser(description="Simple Filesystem MCP Server")
parser.add_argument("allowed_dirs", nargs="+", help="List of allowed directories for file operations")
args = parser.parse_args()
allowed_directories = [osp.abspath(dir_path) for dir_path in args.allowed_dirs]
mcp = FastMCP("SimpleFileSystemMCPServer", log_level="ERROR")
说明:使用 argparse
解析命令行参数,在 MCP 服务启动时指定可访问的目录(防止 LLM 访问系统中的任意目录)。这里 FastMCP 初始化了一个名为 Simple Filesystem MCP Server 的服务器实例,日志级别设为 ERROR(测试时发现 Cline 客户端因为服务运行时输出的 INFO 日志导致无法列举服务工具,但不影响工具调用)。
2. 实现工具逻辑
使用 @mcp.tool
装饰器定义以下功能工具,这里的 @mcp.tool
可以理解为在 MCP Server 中注册一个工具,不定义 name 的情况下,工具名默认为函数名称:
(1) list_directory - 查找文件和目录
@mcp.tool(
description="获取指定路径下的所有文件和目录的详细列表。返回结果会通过 [FILE] 和 [DIR] 前缀明确区分文件类型。该工具对于了解目录结构及查找特定文件非常实用。仅限在允许访问的目录中操作。"
)
async def list_directory(dir_path) -> str:
if not osp.exists(dir_path) or not osp.isdir(dir_path):
raise ValueError(f"{dir_path} is not a valid directory")
entries = os.listdir(dir_path)
formatted = []
for entry in entries:
entry_path = osp.join(dir_path, entry)
if osp.isdir(entry_path):
formatted.append(f"[DIR] {entry}")
else:
formatted.append(f"[FILE] {entry}")
return "\n".join(formatted)
该工具返回指定目录下的文件和子目录列表,使用 [DIR] 和 [FILE] 前缀区分。当目录不存在时抛出异常,帮助模型理解当前文件系统状态。
最初定义工具时,我采用了 os.walk
递归调用的方式来实现目录遍历,但目录中可能包含大量依赖文件(如 node_modules
),浪费 Token 数,所以我们只需要返回第一层的文件和目录列表即可,必要时让模型继续向下搜索即可。
(2) read_file - 读取文件内容
@mcp.tool(
description="读取文件系统中的完整文件内容。支持处理多种文本编码格式,若读取失败将返回详细错误信息。适用于需要检查单个文件内容的场景。仅可在允许访问的目录中操作。"
)
async def read_file(file_path) -> str:
if not osp.exists(file_path) or not osp.isfile(file_path):
raise ValueError(f"{file_path} is not a valid file")
with open(file_path, "r") as file:
content = file.read()
return content
该工具读取单个文件内容,当文件不存在或无效时抛出异常。
注意:如果文件内容过大也可能导致 Token 数溢出,实际开发中可设置字符数限制,超过则截断或做其他处理。
(3) write_file - 写入文件
@mcp.tool(
description="创建新文件或完全覆盖现有文件内容。使用时需谨慎,此操作将直接覆盖目标文件且无警告提示。支持正确处理文本编码,仅限在允许的目录内执行。"
)
async def write_file(file_path, content) -> str:
if not osp.exists(osp.dirname(file_path)):
raise ValueError(f"Directory for {file_path} does not exist")
with open(file_path, "w") as file:
file.write(content)
return f"Successfully wrote to {file_path}"
该工具实现文件写入功能,在目标目录不存在时抛出异常,成功则返回确认信息。
注意:写文件是危险操作,建议在 MCP 工具调用时确认是否执行该操作,避免误操作导致数据丢失。
(4) read_multiple_files - 批量读取文件
@mcp.tool(
description="同时读取多个文件的内容。当需要分析或比较多个文件时,这种方式比逐个读取更高效。每个文件的内容将与其路径一并返回,便于追溯。即使个别文件读取失败,也不会中断整体操作。仅限在允许的目录内执行。"
)
async def read_multiple_files(file_paths) -> str:
results = []
for file_path in file_paths:
try:
if not osp.exists(file_path) or not osp.isfile(file_path):
results.append(f"{file_path}: Error - Not a valid file")
continue
with open(file_path, "r") as file:
content = file.read()
results.append(f"{file_path}:\n{content}")
except Exception as e:
results.append(f"{file_path}: Error - {str(e)}")
return "\n---\n".join(results)
该工具支持批量文件读取操作,使用分隔符区分不同文件内容(保持数据结构化便于模型理解)。为保证健壮性,单文件读取失败不会中断整个操作,并将错误信息返回给模型处理。
说明:为了便于读者理解,我在上述 4 个工具的 description
部分使用了中文描述,在实际测试中,发现中英文描述均不影响模型理解工具含义。
3. 启动服务器
在文件末尾添加启动代码:
if __name__ == "__main__":
mcp.run(transport="stdio")
最后,在项目根目录下,运行命令启动 MCP 服务器:
uv run simple_filesystem.py /path/to/dir1 /path/to/dir2
服务启动后不会有输出,这属于正常现象。下面我们将使用 MCP 客户端测试服务器。
测试 Filesystem MCP Server
常用的 MCP 客户端包括 Claude for Desktop、Cursor、Cline、GitHub Copilot(现已支持 MCP)、Cheery Studio 等。由于 Claude 客户端可能存在国内网络访问问题,而 Cline 与 GitHub Copilot 都内置了文件读取工具,不便测试,因此我们选择 Cherry Studio 进行测试。
安装并配置客户端
从官网 https://cherry-ai.com/
下载最新版 Cherry Studio。
配置服务器
1、打开 Cherry Studio 的 设置,点击 MCP 服务器
2、手动 添加服务器 或点击 编辑 MCP 配置,将以下配置添加到配置文件中:
{
"mcpServers": {
"simplefilesystem": {
"isActive": true,
"name": "Filesystem MCP Server",
"type": "stdio",
"description": "用于获取本地目录列表、读写本地文件的 MCP Server",
"registryUrl": "https://pypi.tuna.tsinghua.edu.cn/simple",
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/simple-filesystem-server-python",
"run",
"simple_filesystem.py",
"/path/to/dir1",
"/path/to/dir2"
]
}
}
}
注意:上述配置中的命令行参数 args
,需修改为真实的项目地址与允许服务访问的目录。
3、在 设置-模型服务 中选择模型并输入 API Key(需开启函数调用功能),这里选择了 DeepSeek V3:
4、返回聊天页面,在输入框下启用 MCP 服务
功能验证
通过多次工具调用,模型成功读取了本地文件并执行了内容总结。
完整代码已经上传到我的 GitHub 仓库,并封装了更多文件操作的工具,可通过以下命令获取源代码:
git clone https://github.com/jinzcdev/mcp-demos.git
总结
本文详细介绍了如何使用 Python 开发一个简易的 Filesystem MCP Server,实现了四个核心功能工具:
list_directory
- 目录内容列表read_file
- 单文件读取write_file
- 文件写入read_multiple_files
- 批量文件读取
通过 Cherry Studio 客户端的测试验证了 Filesystem MCP Server 的功能。开发过程中我总结了以下创建 MCP Server Tools 的核心经验:
- 详尽的工具描述:清晰说明工具功能和使用场景,提升模型调用准确率
- 明确的错误反馈:工具调用失败时返回具体错误信息,辅助模型调整策略
- 保证工具健壮性:局部错误不要中断整体操作,返回部分成功结果
本文是 《从原理到实战:掌握 MCP》 系列的第二篇文章,间隔时间较长了。如果文章对你有帮助欢迎收藏、转发,您的关注是我更新的最大动力。如有疑问欢迎在评论区留言。
往期推荐: