
Python MCP Server Builder
Builds robust FastMCP servers with tool contracts, subprocess wrapping and tests.
Install
npx skills add kajisho5/ffmpeg-skill --skill building-python-mcp-servers -g -a claude-code -y- Repository
- kajisho5/ffmpeg-skill4.3k
- Category
- Engineering
- Updated
- 4d ago
- License
- MIT
- Context cost
- Mediumabout 3,000 tokens
- Runs scripts
- No, instructions only
What it does
Builds robust Python MCP (Model Context Protocol) servers with FastMCP, tool design, error contracts, event-loop-safe blocking work, subprocess/CLI wrapping, single-file vs packaged distribution, global-state-free testing, and prompt-injection awareness. Use when writing an MCP server, exposing a tool or CLI to an LLM client, debugging tool registration/packaging, or testing MCP tools.
Building Python MCP Servers
MCP servers expose tools to an LLM client (Claude Desktop, Claude Code, etc.). The LLM is the caller, so the failure modes differ from a normal library: errors must be machine-readable, every input is untrusted, and a green test suite often proves nothing about whether the tools actually work. This skill encodes the patterns that recur when these go wrong.
Quick Start (FastMCP)
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
@mcp.tool()
def read_config(path: str) -> dict:
"""Read a config file. `path` must be absolute."""
p = Path(path)
if not p.is_absolute():
return {"error": "path must be absolute"}
if not p.exists():
return {"error": f"no such file: {path}"}
return {"data": p.read_text()}
if __name__ == "__main__":
mcp.run()
Error Contract: return, don't raise, and stay consistent
An uncaught exception surfaces to the LLM as an opaque protocol error it can't reason about. Return a structured result with a predictable shape instead, and make callers check for it.
- Pick one error shape and use it everywhere. A dict with an
"error"key is the common convention. Document that callers must check for it. - Batch tools must report skips, not swallow them. The most common
inconsistency: a single-item tool collects per-item errors, but a sibling
"do this across a directory" tool silently
continues past files that fail to load. For automation that is invisible data loss. Every batch tool should return both results and a per-itemskipped/errorslist.
@mcp.tool()
def validate_dir(path: str) -> dict:
results, skipped = {}, []
for f in Path(path).glob("*.md"):
try:
results[f.name] = _validate(f)
except Exception as e:
skipped.append({"file": f.name, "error": str(e)}) # never silently continue
return {"results": results, "skipped": skipped}
