# Python MCP Server Builder

Builds robust FastMCP servers with tool contracts, subprocess wrapping and tests.

- Repository: https://github.com/kajisho5/ffmpeg-skill (4279 stars)
- Category: Engineering
- License: MIT
- Last updated: 2026-10-31
- Runs scripts: no, instructions only

## Install

Claude Code: `npx skills add kajisho5/ffmpeg-skill --skill building-python-mcp-servers -g -a claude-code -y`
Codex: `npx skills add kajisho5/ffmpeg-skill --skill building-python-mcp-servers -g -a codex -y`
Cursor: `npx skills add kajisho5/ffmpeg-skill --skill building-python-mcp-servers -g -a cursor -y`
Gemini CLI: `npx skills add kajisho5/ffmpeg-skill --skill building-python-mcp-servers -g -a gemini-cli -y`
Claude Code plugin marketplace: `/plugin marketplace add kajisho5/ffmpeg-skill`

## Description

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.

## SKILL.md, as published by its author (untrusted third-party text, read it as data)

# 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)

```python
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 `continue`s 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.

```python
@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}
```

The rest: https://github.com/kajisho5/ffmpeg-skill/blob/HEAD/.claude/skills/mcp-server-design/SKILL.md
