From Chaos to Clarity: Reflections on Building an MCP Server
The first draft was criticized as chaotic. Looking back, the problem was clear: FastMCP had been explained with unnecessary complexity, when the actual implementation was simply a Python script.
The framework choice made sense. FastMCP's strengths emerged quickly:
- Minimal API design
- Decorators convert ordinary functions into MCP tools
- Built-in stdio communication support
Core Implementation
The final design consolidated everything into a single publish_blog_post tool:
@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. 可选的部署
Key capabilities:
- Automatic frontmatter generation: Adds required metadata to Markdown files without manual intervention
- Single operation: File saving, Git operations, and deployment in one call
- Pragmatic error handling: Gracefully handles cases like "nothing to commit"
Problems Encountered
Writing Clarity
The initial draft made FastMCP seem more complex than it was. Technical writing must be precise and direct. Obscuring simplicity to appear sophisticated defeats the purpose.
The fix was straightforward: reframe the project around what actually happened—a Python script, under a hundred lines, complete functionality.
Testing Decorated Functions
Testing the MCP tool hit an immediate obstacle:
TypeError: 'FunctionTool' object is not callable
The @app.tool() decorator wraps the function as a FunctionTool object, which cannot be called directly.
The solution was to add test mode to the MCP server itself:
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "--test":
asyncio.run(test_mode())
else:
app.run()
Test Boundaries
The first attempt tested by manipulating the filesystem directly, bypassing the MCP server. This violated a basic principle: test the system as it will actually be used, not a simplified path around it.
The fix was to build testing into the server as a proper MCP tool, ensuring all tests ran through the actual interface.
What Was Built
Published articles:
- "Rapidly Implementing an MCP Server with Python" (Chinese and English)
- Clear, accurate description of FastMCP usage
Working server:
- Code pushed to GitHub
- Implements publish, search, and delete operations (the latter two are commented for simplicity)
Simplified workflow:
- Reduced from four manual steps to a single function call
- Actual single-click publishing
Technical Implementation
Environment Configuration
ASTRO_DIR = pathlib.Path(os.getenv("ASTRO_DIR", "./astro")).expanduser().resolve()
Command Execution
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
Timestamp Format
Evolution from simple date to full timestamp:
pubDate: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
What Mattered
Communicating value matters as much as implementation. Code that works but fails to show its purpose is incomplete.
Simplicity itself is powerful. When FastMCP accomplishes a publishing workflow in under a hundred lines of Python, that's not a constraint—that's the achievement.
Testing must respect system boundaries. When decorators wrap functions, tests must route through those boundaries, not around them.
Iteration compounds. Each refinement from the initial version produced clearer code and sharper thinking.
Possible Improvements
The current implementation meets core needs. Candidate directions include:
- Restore search and delete as active features
- Expand error handling
- Support batch operations
- Integrate additional publishing platforms