我构建了一个自定义 MCP 服务器来发布我的博客

为什么开始这个项目

最近我一直在深入准备 AI 工程面试——部署了几个全栈 AI 项目,微调了一个开源权重 LLM,还在为求职刷数据结构与算法。期间,我对 MCP(Model Context Protocol,模型上下文协议)产生了兴趣,决定实际用它构建一些东西,而不仅仅是阅读相关内容。

想法很简单:如果 Claude 能读取我机器上的一篇粗糙的博客草稿,整理润色,然后直接发布到 Dev.to——完全不需要我触碰 Dev.to 的界面,会怎么样?

事实证明,这正是 MCP 的用途。

什么是 MCP(简要说明)

MCP 是一个协议,它允许像 Claude 这样的 AI 模型调用你定义的工具——读取文件、调用 API、运行脚本——而不仅仅是生成文本。你编写一个小型服务器,暴露 "工具"(带有文档字符串的普通 Python 函数),将其插入 Claude Desktop 的配置中,Claude 就能突然行动,而不仅仅是聊天。

我为此设置了两个服务器:

  1. filesystem —— 官方 MCP 服务器,允许 Claude 读写我授权的文件夹中的文件
  2. devto —— 我构建的自定义服务器,暴露一个 publish_blog_to_devto 工具,封装 Dev.to API

环境设置

我首先使用 uv 进行 Python 包管理——比普通的 pip/venv 快得多。

第一个小问题: 安装 uv 后,PowerShell 找不到它,因为终端会话在我安装程序更新 PATH 之前就已经打开了。关闭并重新打开终端立即解决了这个问题。这是个小问题,但如果你不知道原因,可能会感到恐慌。

第二个小问题 更有趣。我创建了一个新的 venv 并尝试 uv add "mcp[cli]",但它一直无法解决依赖关系——因为我的 pyproject.toml 中保留了 requires-python = ">=3.9",这是项目最初针对系统 Python 3.9 搭建时留下的,但实际的 MCP SDK 需要 3.10+。我通过明确固定版本解决了这个问题:

uv python pin 3.12
uv venv --python 3.12

进入全屏模式 退出全屏模式

然后我将 requires-python 编辑为 ">=3.10"pyproject.toml 中,安装顺利完成。

包名陷阱

这是真正困扰了我一段时间的问题。在安装 supposedly 成功后,导入 fastmcp 仍然抛出 ModuleNotFoundError: No module named 'mcp.server.fastmcp' —— 尽管 import mcp 工作正常,文件夹中明显有一个 server 目录。

事实证明,uv pip show mcp 显示安装的包版本是 2.0.0,依赖项包括 httpx2mcp-typespyjwtpywin32 —— 这些都不属于真正的 MCP SDK。PyPI 上有一个无关的包占用了 mcp 这个名字,被拉入了而不是真正的 modelcontextprotocol SDK。

解决方案是明确固定版本范围:

uv remove mcp
uv add "mcp[cli]>=1.2.0,<2.0.0"

进入全屏模式 退出全屏模式

这拉入了合法的 SDK(当时是 1.29.0),from mcp.server.fastmcp import FastMCP 终于可以工作了。

教训: 如果 uv add somepackage "成功" 但之后一切都不合理,请检查 uv pip show —— 不要假设 PyPI 上的名称就是你认为的项目。

连接到 Claude Desktop

Claude Desktop 从 claude_desktop_config.json 读取其 MCP 服务器列表,在顶级的 mcpServers 键下。这个文件现在有其他无关的应用程序偏好设置,所以很容易意外地将服务器添加为 mcpServers 的兄弟而不是嵌套在里面,这会默默地什么都不做。我通过艰苦的方式学到了这一点,盯着设置 → 开发者 → 本地 MCP 服务器,想知道为什么只有 filesystem 显示。

正确的结构:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "C:\\Technical\\mcp-code"]
    },
    "devto": {
      "command": "C:\\Users\\vdine\\.local\\bin\\uv.exe",
      "args": ["--directory", "C:\\Technical\\custom-mcp\\devto-mcp-server", "run", "dev-server.py"]
    }
  }
}

进入全屏模式 退出全屏模式

另一个 Windows 特定的陷阱:Claude Desktop 并不总是继承 shell 的 PATH,所以 "command": "uv" 有时无法启动。使用 where.exe uv 的完整路径永久修复了这个问题。

在完全退出并重新打开 Claude Desktop 后(不仅仅是关闭窗口——它会留在托盘中),两个服务器都显示为正在运行

使用 MCP Inspector 进行测试

在将任何东西连接到 Claude Desktop 之前,我使用以下命令独立测试了服务器:

uv run mcp dev src/mcp_server_demo/__init__.py

进入全屏模式 退出全屏模式

这会启动 MCP Inspector——一个本地 Web UI,你可以直接调用你的工具并查看原始请求/响应负载,完全不需要 Claude 参与。对于在通过聊天界面调试之前及早捕获错误非常有用。

发布博客

连接了两个服务器后,实际的发布步骤几乎是反高潮的——我只是正常地与 Claude 交谈:

"我在 [folder] 中有一个 blog.txt 文件,精炼其内容,然后将其作为草稿发布到 Dev.to,并添加相关标签。"

Claude 自己链式调用工具:

  1. 调用 read_text_file(filesystem 服务器)来获取原始草稿
  2. 内联重写和清理内容
  3. 使用标题、markdown 正文和标签调用 publish_blog_to_devto(我的自定义服务器)

无需复制粘贴到 Dev.to 的编辑器,无需手动格式化。我首先设置 published: false 作为草稿进行审查,然后再发布——这是防止发布半成品的廉价保险。

这实际上教会了我什么

这里真正的学习大部分不是关于 MCP 协议的——而是标准的环境调试:PATH 问题、Python 版本固定、被占用的包名和 JSON 嵌套错误。一旦环境正常,MCP 本身可能只是带有文档字符串的 20 行 Python 代码。

这可能是被低估的教训:代理工具只有在其下面的无聊管道可靠时才可靠。正确设置 venv、包版本和配置结构,"AI 做有趣的部分" 就会自己处理。

接下来,我计划在同一个服务器中添加更多工具——也许一个可以拉取我的 GitHub 提交历史来帮助自动起草 "本周我构建了什么" 帖子的工具。