One Decorator vs. the Plumbing Tax: FastMCP, and What It Taught Me About Agent Tooling

By Prahlad Menon 4 min read

Every few weeks a thread goes viral claiming you can turn a Python function into an AI tool with “one line of code.” Usually it’s hype. This time the hype is mostly earned — but not for the reason the thread says. Let me show you the real before/after, then something the thread misses entirely: how this compares to the way I already run agents.

The plumbing tax

Building an MCP server by hand means writing all of this before your actual logic:

  • Transport (stdio, HTTP, SSE)
  • Authentication
  • JSON schemas for every tool
  • Input validation
  • Protocol lifecycle (initialize, list, call, shutdown)
  • Error handling in the shape the protocol expects

None of that is your business logic. It’s the tax you pay to make a function callable by an LLM over a standard protocol.

The before

Here’s a lead-lookup function — the kind of thing I have all over projects like LotPay (a BHPH dealer platform). Raw, this is what “expose it as a tool” looks like conceptually:

# Hand-rolled: you own the schema, validation, dispatch, errors
TOOLS = {
    "lookup_lead": {
        "name": "lookup_lead",
        "description": "Look up a lead by phone number",
        "inputSchema": {
            "type": "object",
            "properties": {"phone": {"type": "string"}},
            "required": ["phone"],
        },
    }
}

async def handle_call(name, arguments):
    if name != "lookup_lead":
        raise ProtocolError(f"unknown tool: {name}")
    phone = arguments.get("phone")
    if not isinstance(phone, str):
        raise ValidationError("phone must be a string")
    # ...and you still haven't written the query
    return await db.leads.find_by_phone(phone)

Multiply that by every tool. Then wire up transport and auth.

The after

from fastmcp import FastMCP

mcp = FastMCP("Dealer Tools 🚗")

@mcp.tool
def lookup_lead(phone: str) -> dict:
    """Look up a lead by phone number."""
    return db.leads.find_by_phone(phone)

if __name__ == "__main__":
    mcp.run()

The type hints become the schema. The docstring becomes the description. Validation and the protocol lifecycle are handled. That’s the honest version of “one decorator” — and it’s genuinely a big deal for anyone shipping tools.

A correction worth making: the viral thread says FastMCP “powers a huge share of MCP servers.” The real, verifiable claim from the project is sharper — FastMCP 1.0 was incorporated into the official MCP Python SDK in 2024, and some version of FastMCP powers ~70% of MCP servers across all languages. (The actively maintained project now lives under PrefectHQ, not the original author’s namespace.) Cite the number, not the vibe.

The part the thread skips: is the protocol always worth it?

Here’s what nobody selling you MCP will say out loud: if your tool only ever runs inside one process, MCP is overhead. A decorator is cheap, but the protocol — transport, handshake, serialization — isn’t free.

MCP earns its keep when you cross a boundary:

  • Remote tools — the tool runs somewhere your agent isn’t.
  • Cross-vendor composition — one agent, tools from five different teams/languages.
  • Reuse across agents — write once, any MCP client can call it.

If none of those apply, calling the function directly is faster and simpler. MCP is connective tissue; you only need tissue where two things connect.

How this compares to how I actually run agents

I run agents on OpenClaw, which has its own tools-and-skills model — and putting it next to MCP is the most useful thing I can add to the conversation, because they solve overlapping problems differently.

FastMCP toolOpenClaw skill
UnitA typed Python functionA SKILL.md + scripts, loaded on demand
InterfaceJSON-RPC over a transportInstructions the agent reads, then acts
DiscoveryClient lists tools at runtimeAgent scans skill descriptions, opens the relevant one
Best forDeterministic calls across a boundaryProcedural know-how (“how to do X here”)
OverheadProtocol handshake per connectionA file read

They’re not competitors — they’re different altitudes. An MCP tool is a capability: call lookup_lead, get data. A skill is a procedure: “when the user wants a blog post, here’s the workflow, the repo, the frontmatter format.” One is a verb the agent invokes; the other is a playbook the agent follows — often to decide which verbs to invoke.

The interesting design question — the one I’m actually chewing on — is where the line sits. A skill can wrap MCP tools. An MCP server can expose something a skill would otherwise describe by hand. The best agent stacks will probably use both: MCP for capabilities that cross boundaries, skills for the judgment about when and how to use them.

Bottom line

FastMCP is worth the hype for the boring reason: it deletes the plumbing tax so you can ship the logic. Use it when you’re crossing a process, vendor, or agent boundary. Skip the protocol when you aren’t. And if you’re building agents seriously, don’t think “tools or skills” — think capabilities and the procedures that orchestrate them.

Python function → @mcp.tool → AI-ready capability. Just remember the capability is the easy part. Knowing when to reach for it is the real work.