Elarion

Transports

Map a handler over HTTP, JSON-RPC, MCP, or explicit unary/server-streaming gRPC service methods while keeping application logic transport-neutral.

A handler is transport-neutral: it accepts an application request with no knowledge of how it was called. A unary handler returns Result<T>; an explicit server-stream handler returns a lazy response sequence. A transport is what puts that handler on the network. HTTP, JSON-RPC, and MCP are peer surfaces selected from one unary handler definition. gRPC is an additional typed adapter: its generated service override deliberately owns protobuf mapping while Elarion.Grpc preserves the same dispatch pipeline; Elarion.Grpc.AspNetCore supplies the conventional grpc-dotnet host composition.

The shared handler shape

Unary transports invoke IHandler<TRequest, Result<TResponse>>; the request and response types may be nested inside the handler or declared top-level — the nesting carries no meaning. The transport unwraps the success value from Result<T> and maps an AppError to its own failure representation centrally, so the handler never encodes protocol concerns. HTTP/SSE and gRPC can also explicitly invoke IStreamHandler<TRequest, TItem> for a cold request-driven response. gRPC adds explicit generated protobuf-to-application mapping in the service override; see gRPC.

JSON-RPC and MCP share a single identity. A handler opts into them with one [Handler("module.action")] attribute (the operation name is optional — [Handler] alone is valid and the name is inferred by convention), and the Transports flag (HandlerTransports.JsonRpc, HandlerTransports.Mcp, or HandlerTransports.All, the default) selects which of the two surface it. HTTP is a separate opt-in via [HttpEndpoint] because it needs route, verb, and parameter binding that do not fit a flags enum.

Under the hood, JSON-RPC and MCP are not two parallel stacks. There is one transport-neutral named request/reply busHandlerDispatcher — that maps an operation name to a handler and invokes it through the full decorator pipeline. JSON-RPC and MCP are thin adapters over that one bus: each serves only the operations flagged for its surface. The generator builds one registry; both adapters resolve it. So a handler is define once, choose surfaces via the Transports flag.

The bus is a transport seam, not an in-process call path: it exists because JSON-RPC and MCP carry an operation name on the wire. HTTP doesn't use it — a [HttpEndpoint] route resolves its handler typed-directly, since the route already pins the type. To call a handler from your own code, prefer the typed paths in Calling a handler from code.

Which transport should I use?

HTTP / REST

Choose when you need resource-shaped URLs, standard verbs and status codes, or you are serving a public or third-party API where REST conventions are expected.

JSON-RPC

Choose for internal, first-party APIs where one team owns both ends. A method maps one-to-one to a handler, and the generated typed client makes a frontend call feel like invoking the handler directly.

MCP

Choose to expose your operations to AI agents as tools, with names, descriptions, and input schemas generated from your handlers — no separate tool layer.

They are not exclusive. A handler can be a REST endpoint, a JSON-RPC method, and an MCP tool at once.

Wiring

HTTP, JSON-RPC, and MCP are module-scoped and feature-flag-gated when a host opts into [GenerateModuleBootstrapper]: a disabled module disappears from every transport at once. See Hosting & composition for how the generated bootstrapper maps each transport into the host.

On this page