One capability, many hosts

The directory API is a handful of Endpoints bound once. JVM, Node, and Native run that bind.

HTTP, OpenAPI, MCP Streamable HTTP, and stdio are hosts of that bind. JVM, Node, and Native run the same Server.install. They are not three implementations. CLI is the same OpArgs shape, not shipped yet.

capability compiler
BoundOp

getShow

Endpoint + In => ZIO[R, E, Out]. Written once.

GET/shows/{id}.mcpReadOnly
CLI

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.

Write the capability once

getUser is an Endpoint. .mcp promotes it as a tool. Api.bind is the function agents and HTTP both run.

HTTP

api.routes is Routes. A GET is an ordinary Request. Humans hit this from a browser or Swagger. Systems hit it from Client. The same Server.install runs that Routes on JVM, Node, and Native. Do not grow a second Scala.js Api.

MCP Streamable HTTP

Mcp.from(api) speaks JSON-RPC on POST /mcp. Native is 2026-07-28 (server/discover). The same endpoint answers 2025-11-25 initialize so older hosts can list and call. tools/list is the promoted set. tools/call with get_show runs the same function as GET /shows/1.

stdio

mcp.stdio() is the same engine on a pipe: one JSON-RPC line in, one line out. Native lines carry _meta; a 2025 host may send initialize first. Local agent runtimes spawn sbt "example/run -- --mcp-stdio" (or a published main) and speak that framing.

Toggle the host

The same get-user operation as HTTP, OpenAPI, tools/call, and a stdio line. Payloads match the examples above.

same BoundOp · get_show
same BoundOp
get_showReadOnly
GET /shows/1 HTTP/1.1

HTTP/1.1 200 OK
content-type: application/json

{"id":1,"name":"Evening bill"}

HTTP, OpenAPI, MCP, and stdio are hosts. They are not copies.