The hub
Heddle is a capability compiler.
You write a service idea once: domain types, operations, and an effect that runs them. That value is the AST. Everything a human, a system, or an agent uses is an interpreter of it.
- Humans get HTTP, OpenAPI / Swagger, and a web UI.
- Systems get the same HTTP, plus
Client. - Agents get MCP 2026-07-28 over Streamable HTTP or a stdio process, plus the 2025 handshake for hosts that still send it.
- CLI is a later interpreter of the same
OpArgs. It is not a second product.
That shape is a human-centric AI service hub. Agents do not get a shadow API. They get the same BoundOp, marked with .mcp.
getShow
Endpoint + In => ZIO[R, E, Out]. Written once.
Same OpArgs. Not shipped. The AST is already this shape.
GET /shows/1 HTTP/1.1
HTTP/1.1 200 OK
content-type: application/json
{"id":1,"name":"Evening bill"}Hosts are interpreters. Do not grow a second tool DSL.
Progressive, not a conversion
Day one is Routes and HeddleApp. That is already a service. You can stop there.
Endpoint + Api.bind is how documentation and the other hosts appear. OpenAPI is a projection. MCP is a host protocol. stdio is the same engine on a pipe. Do not grow a second tool DSL.
The effect is the body
A handler is Request => ZIO[R, E, Response]. Api.bind is In => ZIO[R, E, Out]. Accept, read, and write are ordinary ZIO on the JVM (Loom by default), on Node, and on Native. Interrupt a connection and the fiber stops. Typed errors stay in E until a host maps them to HTTP or MCP.
The AST is the contract. The effect is the work. Hosts are projections.
Three runtimes
Server.install is the same bind on the JVM, on Node, and on Scala Native. Loom is the JVM default scheduler, not a requirement to bind. HTTP/2 is the JVM bind. JS and Native serve HTTP/1.1. docs-js mounts Hub widgets. It is not a second Api.
Where to go
- Install if you want a server in one file.
- One capability, many hosts if you want the thesis in one demo.
- the landing to click Evening bill and flip HTTP / OpenAPI / MCP on the same id.
- Domain is data to build a hub end to end.
A server is still a server
This is the only snippet on this page that is meant to be copied. The rest is the hub, not a code sample.
val routes = Routes(Method.GET / "health" -> Handler.text("ok"))