The AST

The compiler is small on purpose.

ValueRole
Schema / SchemaDocDomain shape every host can read
Endpoint / EndpointDocOne operation, still unbound
BoundOpEndpoint + In => ZIO[R, E, Out]
ApiNamed collection of bound ops
OpArgsFlatten path, query, JSON body into one argument object

Interpreters fold that AST:

InterpreterOutput
api.routesHTTP
api.openApiOpenAPI 3.1 + Swagger UI
Mcp.from(api)MCP tools (promoted) + optional catalog
CLI (later)argv / subcommands via OpArgs
EndpointDoc · click a field
GET/shows/{id}.mcpReadOnly
read by

HTTP, OpenAPI, derived tool name

OpArgs is why MCP (and CLI) can share HTTP

OpArgs.promotable is the tool rule: JSON in, JSON out, and no response header. An endpoint may use any BodyCodec. Path params and query strings flatten into the same JSON object a tool call (or a future CLI) already knows how to pass. A request header does not: it stays on the HTTP request. A response header is part of that HTTP output, so an endpoint that declares one stays off the tool list even when its body is JSON. HTML, text, form, bytes, and event streams stay on HTTP too. Do not smash them into tools.

OpArgs.promotable(BoxOffice.getShow.doc)
true
OpArgs.inputSchema(BoxOffice.getShow.doc).map(_.jsonSchema.toString).exists(_.contains("id"))
true
{
  val tagged = Endpoint.get("item").out[String].outHeader[EntityTag]
  (OpArgs.promotable(tagged.doc), tagged.doc.successHeader)
}
(false,Some(ETag))

Click through EndpointDoc

Each field names its readers. Click one. That is the AST, not a metaphor.