The Problem Nobody Talks About
You write an API. Your framework forces you to think in HTTP from day one: paths, verbs, request objects, response objects. Your business logic is buried inside framework-specific handlers.
Then someone asks: "Can we expose this via CLI too?" Or: "Can the MCP bridge call these methods directly?" Or: "Can I test this without spinning up a server?"
And you realize your "API" is not an API — it's an HTTP handler collection glued to a framework.
Genro Routes separates what your code does from how it's exposed. Your handlers are plain Python methods. The transport layer (HTTP, CLI, WebSocket, MCP) is a thin adapter on top. This makes everything more natural for GraphQL, tRPC, MCP, direct Python calls, and AI agents.
When does this actually matter?
One codebase, many frontends
HTTP, MCP, CLI, WebSocket — same handlers, zero duplication. The transport adapter is a thin, replaceable layer.
Test without infrastructure
router.node("create_order")(payload) — no HTTP client, no server, no mock. Just call the method.
Runtime introspection
router.nodes() returns every handler with metadata, auth rules, capabilities. Build admin UIs or let AI agents discover your API.
Compose services like objects
Attach/detach child services at runtime. Build complex apps from simple, independent modules that know nothing about each other.
Plugins without middleware spaghetti
Auth, validation, logging, OpenAPI — plugins that attach to routers and propagate to children automatically.
Evolve without rewriting
Started as CLI? Add HTTP. Need AI? Add MCP. Your routing layer stays exactly the same.
One name per operation
Each handler has a unique name that is the operation. Not a resource acted upon by a verb. This is how GraphQL, gRPC, tRPC, MCP, and every modern AI agent protocol works. The HTTP verb is inferred automatically when needed.
| Traditional REST | Genro Routes |
|---|---|
GET /users | list_users |
POST /users | create_user |
POST /users/123/suspend | suspend_user |