Agents fall out

MCP is a host protocol over BoundOp, not a second tool DSL. Api.job promotes the ops agents should see. Mcp.from(api) fails on duplicate tool names or non-promotable shapes. Native protocol is 2026-07-28. HTTP and stdio also answer initialize, so Grok Build and Cursor url / command configs work. The authoring rule is The tool is the job.

harness · Grok Build
Grok  →  tools/list
hub   ←  get_show        GET /shows/{id}  ReadOnly
         list_shows      GET /shows       ReadOnly
         seat_the_party  POST /parties

create_hold is not promoted. Catalog can still find it.

Same bind the tests run. JVM, Node, or Native.

Promote, don't auto-export

JSON in and JSON out are required (OpArgs.promotable). Form, bytes, and SSE stay on HTTP.

tools/list is promoted-only. withCatalog adds search_operations and invoke so agents can still find the rest of the Api.

promote, don't auto-export
tools/list  (promoted only)
- get_show          GET /shows/{id}   ReadOnly
- list_shows        GET /shows        ReadOnly
- seat_the_party    POST /parties

create_hold has no .mcp.

Call the same function

tools/call with get_show is GET /shows/{id}. Click call on the harness above.

Point Grok Build at the hub

With sbt example/run listening:

[mcp_servers.heddle-example]
url = "http://localhost:8080/mcp"

Same Api as a local process:

[mcp_servers.heddle-example]
command = "sbt"
args = ["example/run", "--", "--mcp-stdio"]

Ask Grok to list tools, then seat a party of 2 on the Evening bill. You should see seat_the_party and a pickup code. That is the same function Swagger just called.

stdio cold-start under sbt is slow. A packaged main is what you ship. The protocol is identical.

Next

A web app is another client.