从混乱到清晰:一次MCP服务器开发的反思与总结
初稿被批评"乱写一通"。回顾来看,问题很清楚:对FastMCP的解释显得过于复杂,而实际实现只是一个Python脚本。
选择Python和FastMCP的理由充分。该框架的优势很快显现:
- 极简的API设计
- 通过装饰器将普通函数转换为MCP工具
- 内置的stdio通信支持
核心实现
最终设计将一切整合为单一的publish_blog_post工具:
@app.tool()
async def publish_blog_post(
directory: str,
content: str,
filename: str,
commit_message: str = None,
deploy: bool = True
) -> str:
"""Save article, commit changes, and optionally deploy - all in one command."""
# 1. 保存文章(自动添加frontmatter)
# 2. Git提交
# 3. 可选的部署
关键特性:
- 自动生成frontmatter:无需手动干预,为Markdown文件添加必要的元数据
- 一站式操作:文件保存、Git操作和部署在一个调用中完成
- 实用的错误处理:优雅处理"nothing to commit"等情况
遇到的问题
技术写作的清晰性
初稿使FastMCP看起来比实际更复杂。技术写作必须准确、直接。把简单的事情复杂化以显得高深会适得其反。
解决方案很直接:围绕实际发生的情况重新框架——一个Python脚本,不到一百行代码,功能完整。
装饰器函数的测试
对MCP工具进行测试时立即遇到一个障碍:
TypeError: 'FunctionTool' object is not callable
@app.tool()装饰器将函数包装为FunctionTool对象,无法直接调用。
解决方案是在MCP服务器本身添加测试模式:
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "--test":
asyncio.run(test_mode())
else:
app.run()
测试边界
第一次尝试通过直接操作文件系统进行测试,绕过了MCP服务器。这违反了一个基本原则:应该按系统实际使用的方式进行测试,而不是通过简化的旁路。
解决方案是将测试构建到服务器中,作为适当的MCP工具,确保所有测试都通过实际接口运行。
最终成果
已发布的文章:
- 《用Python脚本快速实现MCP服务器》(中英文)
- FastMCP用法的清晰准确描述
可用的服务器:
- 代码已推送至 GitHub
- 实现了发布、搜索和删除功能(后两者因简洁性而注释)
简化的工作流:
- 从四个手动步骤简化为单一函数调用
- 真正的一键发布
技术实现细节
环境配置
ASTRO_DIR = pathlib.Path(os.getenv("ASTRO_DIR", "./astro")).expanduser().resolve()
命令执行
def _run(cmd: List[str]) -> str:
proc = subprocess.run(cmd, cwd=ASTRO_DIR, capture_output=True, text=True)
banner = f"$ {' '.join(cmd)}\n"
return banner + proc.stdout + proc.stderr
时间戳格式
从简单日期格式到完整时间戳的演进:
pubDate: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
关键收获
传达价值与实现代码同样重要。能工作但无法展示其用途的代码是不完整的。
简洁本身就很强大。当FastMCP用不到一百行Python代码完成发布工作流时,这不是约束——这是成就。
测试必须尊重系统边界。当装饰器包装函数时,测试必须通过这些边界,而不是绕过它们。
迭代会产生复合效应。从初始版本的每次改进都产生了更清晰的代码和更敏锐的思考。
可能的改进方向
当前实现满足核心需求。潜在的改进方向包括:
- 将搜索和删除恢复为活跃功能
- 扩展错误处理
- 支持批量操作
- 集成更多博客平台