Install

One bind on the JVM, on Node, and on Scala Native. MCP, OAuth, and brotli are optional artifacts on the same version. JSON is zio-json on core. JVM is JDK 21+. heddle and heddle-mcp publish for all three runtimes (%%%). heddle-oauth is JVM and JS. HeddleApp is JVM-only.

libraryDependencies += "rocks.earlyeffect" %% "heddle" % "0.4.0"
libraryDependencies += "rocks.earlyeffect" %% "heddle-mcp" % "0.4.0"

import heddle.* is the facade. SSE, WebSocket, and Datastar stay in their own packages.

A server in one file

This is already a service. You can stop here. The hub appears when you promote selected routes to Endpoint / Api. That is the next page, not a rewrite of this one.

val routes = Routes(
  Method.GET / "health"            -> Handler.text("ok"),
  Method.GET / "users" / int("id") -> { (id: Int) =>
    ZIO.succeed(Response.text(id.toString))
  },
)
val app = routes @@ (Middleware.requestId() ++ Middleware.cors())

To bind a port:

object Hello extends HeddleApp:
  def routes = app

HeddleApp serves routes and exits 0 on Ctrl-C under sbt 2 (JVM). Server.serve(app).provide(Server.Config.defaults) is the same bind without the trait, and that is what JS and Native use.

Run the example hub

This repo's example is one process with HTTP, Swagger, MCP, an embedded OpenID provider, and a directory UI:

sbt example/run                      # http://localhost:8080/docs  /preview  POST /mcp
sbt "example/run -- --mcp-stdio"     # same Api, stdio JSON-RPC

Seed user ada / ada. Machine client machine / secret.

Then walk Domain is data to see how that hub is assembled.