🪟 Windows TippsThe Gemini desktop app is now available for Windows(11.09.2026 um 17:06 Uhr)
🪟 Windows TippsSetting up live captions stuck in Windows 11(14.09.2026 um 16:31 Uhr)
🕵️ SicherheitslückenBurn Out, Or Fade Away(14.09.2026 um 14:25 Uhr)
🪟 Windows TippsWindows 11's latest update broke my speakers, and I'm not alone(14.09.2026 um 18:54 Uhr)
🪟 Windows TippsThe Gemini desktop app is now available for Windows(11.09.2026 um 17:06 Uhr)
🪟 Windows TippsSetting up live captions stuck in Windows 11(14.09.2026 um 16:31 Uhr)
🕵️ SicherheitslückenBurn Out, Or Fade Away(14.09.2026 um 14:25 Uhr)
🪟 Windows TippsWindows 11's latest update broke my speakers, and I'm not alone(14.09.2026 um 18:54 Uhr)

🔧 Programmierung 🕛 vor 1 Monat 6 Min Lesezeit
0

I Almost Hand-Rolled JSON-RPC for an MCP Server. Eight Tools Later I'm Glad I Didn't.

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

When I built the MCP server for this project — it combines GitHub and DEV.to into a set of tools an agent can call — I had a decision to make before writing a single tool: talk to the low-level MCP protocol directly, or use FastMCP's decorator API. I've seen a few "your first MCP server" writeups lately walk through the low-level path because it's more "honest" about what MCP actually is under the hood — JSON-RPC over stdio, a capabilities handshake, typed request/response schemas. That's true, and it's a reasonable thing to want to understand. But I want to write about the other side: what it actually costs you in practice once you have more than one or two tools, because I went through both and the difference showed up fast.






what the low-level path actually asks you to write



Strip away the decorator and MCP is a JSON-RPC server. For every tool you add, you're responsible for:




  • Registering the tool's name, description, and a JSON Schema for its inputs in a list_tools handler

  • Writing a call_tool dispatcher that matches on tool name and unpacks arguments by hand

  • Serializing the return value into the TextContent/ImageContent wrapper types MCP expects

  • Keeping the schema you wrote in step 1 in sync with the arguments you actually read in step 2, by hand, forever



None of that is hard in isolation. The problem is it's boilerplate that scales linearly with tool count and has zero connection to the actual logic of the tool. My server has 8 tools. Hand-rolled, that's 8 schema blocks plus a dispatcher if/elif chain plus 8 response-wrapping calls, all of which exist purely to satisfy the protocol, not to do anything a GitHub or DEV.to API call needs.






what it looks like with FastMCP



Here's an actual tool from server.py, unedited:




CODE
@mcp.tool()
def get_repo_stats(repo: str) -> dict:
"""Get stars, forks, watchers, open issues for enjoykumawat/<repo>."""
r = _gh(f"/repos/{GITHUB_USERNAME}/{repo}")
return {
"name": r["name"],
"stars": r["stargazers_count"],
"forks": r["forks_count"],
"watchers": r["watchers_count"],
"open_issues": r["open_issues_count"],
"language": r.get("language"),
"description": r.get("description"),
}






The type hint (repo: str) becomes the JSON Schema. The docstring becomes the tool description the agent sees when deciding whether to call it. The return type becomes the response shape. There's no schema to keep in sync by hand — it's derived from the same signature Python already type-checks against. Adding a ninth tool is: write a function, put @mcp.tool() above it, done. No dispatcher to update, no TextContent wrapping, no protocol-layer code touched at all.



Our ADR from when I made this call is blunt about the tradeoff:




Decision: Use FastMCP from mcp.server.fastmcp with @mcp.tool() decorators. HTTP calls via stdlib urllib.

Alternatives Considered: Low-level MCP server → rejected (unnecessary complexity for this use case).

Consequences: mcp[cli] is the only dependency. Server is runnable via python server.py or mcp dev server.py.




"Unnecessary complexity for this use case" is doing a lot of work in that sentence, and I think it's the right lens: the low-level API exists because someone needs to write MCP client and server libraries, or needs protocol-level control (streaming partial results, custom capability negotiation, non-standard transports). If you're exposing 8 REST-API wrappers to an agent, you are not that someone, and the protocol-layer code you'd write has no relationship to your actual problem.






where the decorator approach did bite me



It's not free. Two real snags from actually running this server:



Return type coercion is implicit and easy to get subtly wrong. list_articles returns a list[dict] built by a list comprehension over the DEV.to API response. FastMCP serializes it fine — but the first version of that function forgot to cap per_page, and a caller passing per_page=1000 would have silently gotten whatever DEV.to's API actually returns for an out-of-range value (turns out it clamps, but I had to check, because FastMCP's schema generation from per_page: int = 10 doesn't enforce an upper bound just because the docstring says one exists):




CODE
@mcp.tool()
def list_articles(per_page: int = 10) -> list:
"""List your published DEV.to articles."""
articles = _dev(f"/articles/me?per_page={min(per_page, 30)}")






The min(per_page, 30) is doing real validation work that the type hint alone doesn't give you. The decorator gets you schema generation for free; it does not get you argument validation for free. Those are different things and it's easy to assume the first implies the second.



Errors inside a tool function become opaque to the agent unless you're deliberate about them. If _gh() raises urllib.error.HTTPError because a repo doesn't exist, FastMCP will catch it and turn it into a tool-call error the agent sees — but the agent sees "an error occurred," not "404, that repo name is wrong." I had to explicitly catch and reformat the interesting HTTP status codes in a couple of tools to get error messages an agent could actually act on instead of retry-blindly against.



Neither of those is a reason to go back to hand-rolled JSON-RPC — they're both things you'd still have to handle yourself at the low level, just with more code around them, not less. But "the decorator handles it" is only true for the schema-and-transport layer. Validation and error-message quality are still your job either way.






the actual takeaway



If you're deciding between the low-level MCP SDK and FastMCP for a server that's fundamentally "wrap some REST APIs as tools," the decorator API isn't the beginner's shortcut that graduates to the "real" low-level API once you're serious — it's the correct tool for that shape of problem, full stop. The complexity the low-level API buys you (protocol control, custom transports, non-standard capability negotiation) is complexity you pay for whether or not you need it. I needed 8 tools that call two REST APIs. FastMCP got me there with schema generation I didn't write and didn't have to keep in sync — and the two real gaps I hit (argument validation, error-message shaping) were mine to solve regardless of which API I'd started from.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
The Gemini desktop app is now available for Windows
1 Quelle
Setting up live captions stuck in Windows 11
1 Quelle
Burn Out, Or Fade Away
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten I Almost Hand-Rolled JSON-RPC for an MCP Server. Eight Tools Later I'm Glad I Didn't.

Thematisch verwandte Begriffe: Almost, HandRolled, JSONRPC, Server · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...