From Typer

Move from type-hint CLI ergonomics to Milo's CLI/MCP/llms.txt contract.

2 min read 310 words

Typer and Milo both make Python type hints central to CLI authoring. The main migration question is not syntax; it is contract surface. Milo adds MCP tools/list, MCP tools/call, llms.txt discovery, structured error data, and milo verifyto the same typed function.

Official references: Typer first steps and Typer command arguments.

Before

import typer

app = typer.Typer()


@app.command()
def greet(name: str, loud: bool = False):
    message = f"Hello, {name}!"
    print(message.upper() if loud else message)


if __name__ == "__main__":
    app()

After

from milo import CLI

cli = CLI(name="greeter", description="Greeting commands")


@cli.command("greet", description="Return a greeting")
def greet(name: str, loud: bool = False) -> str:
    """Greet someone.

    Args:
        name: Person to greet.
        loud: If true, SHOUT.
    """
    message = f"Hello, {name}!"
    return message.upper() if loud else message


if __name__ == "__main__":
    cli.run()

Mapping

Typer concept Milo equivalent
typer.run(main) cli = CLI(...); @cli.command(...); cli.run()
app = typer.Typer() cli = CLI(...)
@app.command() @cli.command("name", description="...")
Required argument fromname: str Required schema field fromname: str
Option fromflag: bool = False Optional flag fromflag: bool = False
Function docstring help Function docstring plusArgs:parameter descriptions

Add The Agent Contract

After migrating a Typer command, add these checks:

uv run python app.py --llms-txt
uv run milo verify app.py
uv run pytest tests/ -q

And test MCP dispatch directly:

from milo.mcp import _call_tool


def test_mcp_dispatch():
    result = _call_tool(cli, {"name": "greet", "arguments": {"name": "Agent"}})
    assert result["content"][0]["text"] == "Hello, Agent!"

What To Watch

  • Replace output-first handlers with return values when agents need structured data.
  • Document every public parameter.milo verifywarns when schema fields lack descriptions.
  • Keep interactive behavior behindctx.is_interactiveso MCP calls remain deterministic.