如果你已经开始接触 MCP,下一步最值得做的事情,不是继续背协议名词,而是亲手写一个自己的 MCP Server

原因很简单:只要你真正把“本地文件”和“数据库查询”接进来,你就会马上明白 MCP 到底解决了什么问题——它不是在替代你的业务逻辑,而是在给 AI Agent 提供一套稳定、统一、可复用的工具接入方式。

这篇文章我们不讲概念堆砌,直接用 Python 做一个最小可用、能落地的 MCP Server:

  • 能读本地文本文件
  • 能列出目录内容
  • 能查询 SQLite 数据库
  • 能被 Claude Desktop 直接调用

一、为什么要自己写 MCP Server

很多人第一次接触 MCP 时,会觉得“现成的 Server 已经很多了,为什么还要自己写”。

答案是:你几乎一定会有自己的私有数据源和内部规则

现成的 MCP Server 很适合通用工具,比如 GitHub、Notion、文件系统;但一旦你想接入:

  • 本地知识库
  • 团队共享文档
  • 内网数据库
  • 项目专属脚本
  • 只允许读不允许写的业务数据

你就会发现,自己写一个 Server 反而更稳。

自己写 Server 的三个价值

  1. 可控

    • 你决定暴露什么工具
    • 你决定哪些路径能读,哪些不能读
    • 你决定返回的数据粒度
  2. 可复用

    • 写一次,Claude Desktop、你自己的 Agent、后续脚本都能用
  3. 更贴近业务

    • 不是“给 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

如果你在公司环境里有额外规范,也可以把 sqlite3pathlib 这些标准库直接用起来,不额外引入复杂依赖。

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_file
  • list_dir
  • search_file
  • query_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_notes
  • read_note
  • search_note

适合:Obsidian、Markdown 知识库、研究笔记。

2. 文章草稿检索器

工具:

  • latest_drafts
  • search_drafts_by_keyword
  • get_draft_status

适合:内容团队、博客作者、公众号运营。

3. SQLite 运营面板

工具:

  • latest_articles
  • count_by_tag
  • find_unpublished

适合:把自己的内容和任务系统结构化起来。

这三种都不复杂,但都足够让你真正理解 MCP 的价值。


八、总结

Python 写 MCP Server 的核心意义,不是“我会不会又多学一个框架”,而是你终于可以把自己的本地数据、团队数据、业务规则,统一接进一个标准化的 Agent 接口里。

它让 AI 从“会回答”变成“会操作”,而且这些操作不是散乱的脚本,而是可复用、可治理、可逐步扩展的工具能力。

如果你今天只想记住一句话,那就是:

MCP 不是让 AI 更聪明,而是让 AI 能真正调用你自己的世界。


延伸阅读