Typer and FastMCP are strong, mature choices for their primary surfaces. The real alternative to Milo is often not either project alone; it is a Typer CLI and a FastMCP server composed around the same operation. This page compares that composition at the contract boundary.
| If your product is primarily... | Start with |
|---|---|
| An MCP server, client, remote service, or conversational app | FastMCP |
| A polished human CLI | Typer |
| One capability that must stay aligned across CLI, Python, MCP, and llms.txt | Milo |
This is not a claim that Milo replaces either framework. It is an accounting of the glue a team owns when both surfaces must describe the same capability.
Same Deploy App, Complete Entrypoints
The repository contains two runnable fixtures for adeployoperation:
milo_app.pyregisters one typed function. Milo projects it to the CLI, local stdio MCP, direct Python dispatch, and llms.txt.typer_fastmcp_app.pyuses FastMCP for the tool and a Typer wrapper for human output, then owns the--mcp/CLI process switch.
The composed fixture was executed and inspected with Typer 0.26.8 and FastMCP 3.4.3. Reproduction commands pin those versions so a future framework change cannot silently rewrite this evidence.
The metric counts every nonblank, non-comment physical source line in the complete entrypoint. Imports, docstrings, registration, human presentation, and process dispatch count. Tests recalculate these totals so the page cannot drift from the fixtures.
| Complete same-app entrypoint | Source lines |
|---|---|
| Milo | 24 |
| Typer + FastMCP | 44 |
The 20-line difference is a small composition-cost illustration, not a general productivity or performance benchmark. A real project may factor wrappers into shared modules, add richer Typer presentation, or use FastMCP's broader server features. The fixture deliberately keeps the business operation identical and counts the plumbing needed for both runnable entrypoints.
Reproduction commands and the counting rule live beside the
comparison fixtures.
Compared at the Contract Boundary
| Concern | Milo | Typer + FastMCP |
|---|---|---|
| Primary ownership | One registered command owns CLI, direct call, MCP, and llms.txt | Typer owns the CLI; FastMCP owns MCP; the application owns their composition |
| Typed function projection | argparse command, programmatic call, MCP schema/call, llms.txt | Click command/help plus FastMCP tool schema/call |
| Human output | Built-in terminal renderer can present structured results | Typer wrapper prints or renders the domain result |
| Agent schema | function_to_schema()is shared by Milo's dispatch paths |
FastMCP generates and validates the MCP tool schema |
| Verification | One ten-check run covers import, registration, schema, in-process MCP, discovery, MCP Apps, gateway projection, and subprocess transport | Typer documentsCliRunner; FastMCP provides server inspection and MCP-format metadata; the application supplies cross-surface parity tests |
| Discovery | Built-in llms.txt and MCPtools/list |
MCP discovery through FastMCP; any separate llms.txt artifact is application-owned |
| Remote MCP breadth | Local stdio today; streamable HTTP is tracked separately | Remote transports, auth, deployment, clients, apps, providers, and transforms |
| Runtime dependency posture | One runtime dependency,kida-templates |
Typer/Click and FastMCP's MCP/validation stack |
The Verification Difference
FastMCP's official fastmcp inspect
command is useful: it loads a local server and emits either FastMCP metadata or
the MCP protocol view. Typer's official
CliRunnertesting guide
shows how to invoke a CLI and assert its exit code and output. Those are real
verification tools, and a composed application should use them.
What the composition does not add by itself is one conformance command for the relationship between those independently registered surfaces. The application must decide how to prove that CLI defaults, tool schema, visibility, errors, and results still agree.
milo verify app.pyowns that relationship. Its ten checks cover:
- target import;
- CLI discovery;
- command registration;
- schema generation and documentation warnings;
- in-process MCP tool listing;
- protocol discovery;
- in-process MCP Apps links and resources;
- gateway MCP Apps projection;
- subprocess JSON-RPC discovery, initialization, and tool listing; and
- subprocess MCP Apps negotiation and resource reads.
The list is defined in
src/milo/verify.py
and exercised by
tests/test_verify.py,
not maintained as a marketing-only checklist.
What FastMCP and Typer Do Better
Choose FastMCP when MCP itself is the product. Its current official documentation covers remote transports, authentication, deployment, clients, interactive apps, providers, transforms, tool versioning, rich Pydantic-backed types, and output schemas. FastMCP also runs synchronous tools in a thread pool by default. See the FastMCP overview and tool documentation.
Choose Typer when the human CLI is the product. It offers automatic help, subcommands, shell completion, prompts, progress bars, and the wider Click ecosystem. See Typer and its features.
Choose Milo when drift between the CLI and agent surface is the problem you want the framework to own. Milo is not currently the broadest remote MCP platform or the largest CLI ecosystem.
Scoped Performance Receipts
Milo's BASELINE.md
records local, workload-specific medians rather than competitor speed claims.
Examples include a233ns MCP initialize router dispatch and a 125ns
cachedget_env()lookup on the stated Apple Silicon / CPython 3.14 baseline.
It also records a13.1µsfull local MCP round trip and approximately
15.8µsper command for uncached tool-schema generation.
These numbers explain Milo's own hot paths. They do not compare Milo with
Typer or FastMCP, and environment changes require a new baseline before making
a speed claim. The machine-readable
public-claims.json
keeps this evidence classified as a scoped snapshot.
Parallel HTTP Proof Is Still Pending
Milo does not yet publish the planned free-threaded parallel HTTP tool-call
claim. That evidence depends on the streamable HTTP work tracked in
#106 and the shared workload
from pounce
#229. The claims ledger marks it
pending; this page will link the resulting artifact instead of restating an
unverified headline.
Migration Is Incremental
An existing Typer or argparse application does not need a flag-day rewrite.
Start with one command whose agent and human contracts must agree, prove it with
schema/direct/MCP tests andmilo verify, then move the next command. See the
[migration guides] for concrete
patterns.
Comparison checked against the linked official documentation on July 8, 2026.