Skills

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-item skipped/errors list.
@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}
Read the whole SKILL.md on GitHub

More in Engineering