Article · 2024-01-01

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:

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:

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:

Working server:

Simplified workflow:

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:

© 2026 Yuxu Ge ·