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.
HTTP endpoints
REST-style routes generated from [HttpEndpoint] — verbs, route binding, and RFC 7807 errors.
Request-driven streaming
A cold, lazy response to one request over SSE or gRPC — exports, token output, and tail queries.
OpenAPI
An OpenAPI document for the [HttpEndpoint] routes — schema and client parity with JSON-RPC.
JSON-RPC
Operation-shaped methods from [Handler], with a build-time schema and a typed TypeScript client.
gRPC
Explicit unary and request-driven server-streaming protobuf mappings over the standard ASP.NET Core grpc-dotnet host.
MCP
The same [Handler] operations exposed to AI agents as Model Context Protocol tools.
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 bus — HandlerDispatcher — 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.
Serialization
One canonical JsonSerializerOptions that every Elarion subsystem reads, composed from per-module source-generated JSON contexts for AOT-friendly serialization.
HTTP endpoints
Mark a handler with [HttpEndpoint] and Elarion generates the minimal-API MapGet/MapPost mapping — unwrapping the Query/Command and mapping AppError to RFC 7807 status codes.