6.13. The interface as data

-h is written for a person. --json-help is the same information written for a program: a complete, machine-readable description of every command, its arguments, their types, and what it returns.

$ ./sift --json-help

The top of the document records the compiler version, names the program, and lists its command groups:

{
...
  "program": {
    "name": "sift",
    "description": [
      "Search notes and count what turns up"
    ]
  },
  "groups": []
}

Each command follows. summarize is the simplest one in sift:

{
  "name": "summarize",
  "kind": "remote",
  "group": null,
  "description": [
    "Count the hits in each file"
  ],
  "arguments": [
    {
      "name": "arg0",
      "role": "positional",
      "position": 0,
      "metavar": null,
      "required": true,
      "variadic": false,
      "stdin": false,
      "quoted": false,
      "default": null,
      "description": [
        "Hits produced by an earlier search"
      ],
      "type": {
        "morloc": "[Hit]",
        "wire": "am34paths4linej4texts",
        "structure": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "path": { "type": "string" },
              "line": { "type": "integer" },
              "text": { "type": "string" }
            },
            "required": ["path", "line", "text"],
            "additionalProperties": false
          }
        }
      },
      "named_type_kind": null,
      "input": {
        "source": "auto",
        "form": "auto",
        "checks": [],
        "list_source": "inline",
        "list_form": "auto",
        "list_checks": [],
        "format": null
      }
    }
  ],
  "return": {
    "description": [],
    "streaming": false,
    "type": {
      "morloc": "[(Str, Int)]",
      "wire": "at2sj",
      "structure": {
        "type": "array",
        "items": {
          "type": "array",
          "prefixItems": [
            { "type": "string" },
            { "type": "integer" }
          ],
          "minItems": 2,
          "maxItems": 2
        }
      }
    }
  },
  "terminals": []
}

Every type appears three ways, because three different readers want it: morloc is the type as written, wire is the serialization schema, and structure is JSON Schema, which a validator or a form generator can consume directly. The wire schema is the general one: the concrete schema the runtime dispatches on names the container the pool’s language builds, which would make the published contract move whenever the implementation language did. The input block carries the shape directives of Input shape, so a caller can tell that an argument wants a path rather than a value without parsing prose.

return.streaming says whether the command writes a stream to standard output rather than returning a value; when it does, return.type describes the batch that reaches stdout rather than the () the function returns.

terminals lists a command’s output actions, each with the type its flag puts on the wire. scan has two:

[
  {
    "short": "c",
    "long": "count",
    "description": "Report the number of matches instead of the matches",
    "render": false,
    "default": false,
    "type": {
      "morloc": "U64",
      "wire": "u8",
      "structure": {
        "type": "integer"
      }
    }
  },
  {
    "short": "p",
    "long": "plain",
    "description": "Print one `path:line:text` record per line",
    "render": true,
    "default": false,
    "type": {
      "morloc": "Str",
      "wire": "s",
      "structure": {
        "type": "string"
      }
    }
  }
]

None of this is written by hand or kept in a sidecar file. It is derived from the same types and docstrings as the help text, on the same build, which is what makes it worth trusting: a description that can go stale is a description you have to verify, and this one cannot. Point a script at a directory of Morloc programs and you can build an accurate inventory of every command in it, with argument types, without knowing anything about any of them.

6.13.1. Other views of the same commands

Two more flags render the same information for model clients:

Flag Output

--mcp-tools

An MCP tools/list definition: one entry per command, with a JSON Schema inputSchema and outputSchema.

--mcp-config

A client MCP server config — an mcpServers JSON entry using the stdio transport — so the program can be registered with a model client by redirecting one command into a config file.

Commands whose types cannot cross the MCP boundary are excluded, and the reason is printed on standard error rather than left for you to discover:

$ ./sift --mcp-tools > tools.json
morloc mcp: excluding command 'total' from the tool surface (reads from @stdin)

The MCP surface is covered in Model Context Protocol (MCP), and the same module served over HTTP, TCP, and Unix sockets in Building API interfaces. They are worth reading together with this section, because they are the same point from three directions: the command line is one view of a typed library, not the thing the library is built on. A CLI, an HTTP endpoint, and an MCP tool are three renderings of one set of functions, and none of them is written by hand.