从零用 Python 写一个 MCP Server:本地文件与数据库实战
如果你已经开始接触 MCP,下一步最值得做的事情,不是继续背协议名词,而是亲手写一个自己的 MCP Server。
原因很简单:只要你真正把“本地文件”和“数据库查询”接进来,你就会马上明白 MCP 到底解决了什么问题——它不是在替代你的业务逻辑,而是在给 AI Agent 提供一套稳定、统一、可复用的工具接入方式。
这篇文章我们不讲概念堆砌,直接用 Python 做一个最小可用、能落地的 MCP Server:
- 能读本地文本文件
- 能列出目录内容
- 能查询 SQLite 数据库
- 能被 Claude Desktop 直接调用
一、为什么要自己写 MCP Server
很多人第一次接触 MCP 时,会觉得“现成的 Server 已经很多了,为什么还要自己写”。
答案是:你几乎一定会有自己的私有数据源和内部规则。
现成的 MCP Server 很适合通用工具,比如 GitHub、Notion、文件系统;但一旦你想接入:
- 本地知识库
- 团队共享文档
- 内网数据库
- 项目专属脚本
- 只允许读不允许写的业务数据
你就会发现,自己写一个 Server 反而更稳。
自己写 Server 的三个价值
可控
- 你决定暴露什么工具
- 你决定哪些路径能读,哪些不能读
- 你决定返回的数据粒度
可复用
- 写一次,Claude Desktop、你自己的 Agent、后续脚本都能用
更贴近业务
- 不是“给 AI 一个数据库”,而是“给 AI 一个符合你团队规则的数据库视图”
如果你已经看过《Claude Code 完整使用指南:从入门到自动化工作流》,你会更容易理解这件事:Claude Code 解决的是“怎么让 AI 进入工作流”,MCP 解决的是“怎么让 AI 接上真实工具”。
二、先搭一个最小可运行的 Python MCP Server
我们用 Python 官方/主流 MCP SDK 的写法来做一个简单服务器。下面这个示例重点是思路清楚、结构简单、能直接扩展。
1. 安装依赖
python -m venv .venv
source .venv/bin/activate
pip install mcp
如果你在公司环境里有额外规范,也可以把 sqlite3、pathlib 这些标准库直接用起来,不额外引入复杂依赖。
2. 创建 server.py
下面这个版本包含三个工具:
read_text_file:读取文本文件list_dir:列出目录内容query_sqlite:查询 SQLite 数据库
from pathlib import Path
import sqlite3
from typing import Any
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("local-tools")
BASE_DIR = Path.home() / "Documents"
def _safe_path(path: str) -> Path:
"""限制只能访问 BASE_DIR 下的文件,避免越权读取。"""
target = (BASE_DIR / path).resolve()
if BASE_DIR not in target.parents and target != BASE_DIR:
raise ValueError(f"Path not allowed: {path}")
return target
@mcp.tool()
def read_text_file(path: str) -> str:
"""读取 BASE_DIR 下的文本文件。"""
file_path = _safe_path(path)
if not file_path.exists():
return f"File not found: {path}"
if not file_path.is_file():
return f"Not a file: {path}"
return file_path.read_text(encoding="utf-8")
@mcp.tool()
def list_dir(path: str = ".") -> list[str]:
"""列出目录内容。"""
dir_path = _safe_path(path)
if not dir_path.exists():
return [f"Directory not found: {path}"]
if not dir_path.is_dir():
return [f"Not a directory: {path}"]
return sorted([p.name for p in dir_path.iterdir()])
@mcp.tool()
def query_sqlite(db_file: str, sql: str) -> list[dict[str, Any]]:
"""只读查询 SQLite 数据库。"""
db_path = _safe_path(db_file)
if not db_path.exists():
return [{"error": f"Database not found: {db_file}"}]
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
try:
cur = conn.execute(sql)
rows = cur.fetchall()
return [dict(row) for row in rows]
finally:
conn.close()
if __name__ == "__main__":
mcp.run()
3. 这段代码做了什么
别被代码吓到,它本质上只有三个关键点:
FastMCP("local-tools"):创建 MCP Server@mcp.tool():把普通 Python 函数暴露成工具mcp.run():启动服务
真正值得注意的,是我在 _safe_path() 里加了一个路径白名单限制。
这一步很重要,因为 MCP Server 一旦连上了 AI,就不是“给程序读文件”,而是“给智能体发能力”。你必须明确控制它能读哪里、不能读哪里。
三、把这个 Server 接到 Claude Desktop
写完 Server 之后,下一步是让 Claude Desktop 知道它的存在。
通常你需要在 Claude Desktop 的配置里加入一个 MCP Server 配置项。不同版本字段可能略有差异,但思路都一样:
- 指定启动命令
- 指定工作目录
- 如有需要,传入环境变量
下面是一个典型写法示例:
{
"mcpServers": {
"local-tools": {
"command": "python",
"args": ["/Users/yourname/projects/local-tools/server.py"],
"env": {
"PYTHONUNBUFFERED": "1"
}
}
}
}
配置完成后,你就可以在 Claude Desktop 里这样问:
- “帮我看看 Documents 目录下有哪些文件”
- “读取 notes.md,帮我总结今天的待办”
- “查一下这个 SQLite 里最近 10 条记录”
这时 AI 不是在“猜”,而是在调你自己暴露出来的工具。
四、把文件读取做成真正好用的工具
很多人写的第一版 MCP Server 有一个问题:工具能跑,但不好用。
原因通常不是代码错,而是工具设计太粗。
推荐的设计原则
1. 工具要小而精
不要把“读文件、搜文件、改文件、写文件”全揉成一个超级工具。
更好的做法是:
read_text_filelist_dirsearch_filequery_sqlite
每个工具只负责一件事,AI 更容易选,也更容易调。
2. 返回值要结构清晰
AI 最怕“看起来像人写的长段解释”。
例如目录查询,最好返回:
[
"notes.md",
"todo.md",
"archive/"
]
而不是一大段混杂的描述。
3. 限制可访问范围
尤其是文件工具,务必要做目录白名单。
一旦你把整个磁盘都开放给 AI,风险就不再是“工具是否好用”,而是“你是否真的想把这个权限交出去”。
五、把 SQLite 接入后,MCP 才真正有生产力
单纯读文件已经有用了,但真正让 MCP 变强的,通常是把本地数据库接进来。
这意味着 AI 不只是看文本,而是可以直接查结构化信息。
一个非常实用的场景
你有一个本地 SQLite 数据库,存着:
- 文章草稿
- 任务列表
- 本地知识库
- 研究记录
你可以让 AI 直接查:
- 最近 7 天的任务完成情况
- 还没发布的文章草稿
- 某个关键词相关的历史记录
这比把所有内容复制进提示词里高效得多。
一个查询示例
@mcp.tool()
def latest_articles(db_file: str, limit: int = 10) -> list[dict[str, Any]]:
"""查询最近文章列表。"""
db_path = _safe_path(db_file)
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
try:
cur = conn.execute(
"""
SELECT title, published_at, tags
FROM articles
ORDER BY published_at DESC
LIMIT ?
""",
(limit,),
)
return [dict(row) for row in cur.fetchall()]
finally:
conn.close()
如果你把这个工具接到 Claude Desktop,AI 就能直接问:
“帮我找出最近未发布但和 MCP 相关的文章草稿。”
这就是 MCP 真正开始像“智能工作台”的时刻。
六、常见坑:为什么你的 MCP Server 看起来能跑,但不好用
坑 1:工具太多
工具越多,AI 越容易选错。
建议先从 3 到 5 个高频工具开始,跑顺了再扩展。
坑 2:权限太大
如果你的工具默认能读写整个系统,迟早会出问题。
建议:
- 默认只读
- 写操作单独拆工具
- 对路径、SQL、URL 做白名单/黑名单限制
坑 3:返回值不结构化
AI 喜欢结构化返回。
简单、稳定、可预测,比“写得像文档”更重要。
坑 4:没有错误提示
如果文件不存在、数据库表不存在、SQL 语法错了,要尽量返回明确错误。
不要只回一个空字符串。
七、适合你直接落地的 3 个 MCP Server 小项目
如果你想从这篇文章立刻开始,建议做下面三种小项目之一:
1. 本地笔记查询器
工具:
list_notesread_notesearch_note
适合:Obsidian、Markdown 知识库、研究笔记。
2. 文章草稿检索器
工具:
latest_draftssearch_drafts_by_keywordget_draft_status
适合:内容团队、博客作者、公众号运营。
3. SQLite 运营面板
工具:
latest_articlescount_by_tagfind_unpublished
适合:把自己的内容和任务系统结构化起来。
这三种都不复杂,但都足够让你真正理解 MCP 的价值。
八、总结
Python 写 MCP Server 的核心意义,不是“我会不会又多学一个框架”,而是你终于可以把自己的本地数据、团队数据、业务规则,统一接进一个标准化的 Agent 接口里。
它让 AI 从“会回答”变成“会操作”,而且这些操作不是散乱的脚本,而是可复用、可治理、可逐步扩展的工具能力。
如果你今天只想记住一句话,那就是:
MCP 不是让 AI 更聪明,而是让 AI 能真正调用你自己的世界。




