Interface Generators
Normalize follows a library-first design: the core is NormalizeAPI in src/normalize/normalize_api.py, and all interfaces (CLI, HTTP, MCP, LSP, TUI, gRPC) are generated from it.
Overview
NormalizeAPI (normalize_api.py)
│
▼
introspect_api() → list[SubAPI]
│
├─► MCPGenerator → MCP tools (mcp_server.py)
├─► HTTPGenerator → FastAPI routes (server/app.py)
├─► CLIGenerator → argparse commands
├─► LSPGenerator → workspace commands
├─► TUIGenerator → Textual UI screens
└─► GRPCGenerator → Protocol Buffers + servicerHow It Works
1. Introspection (gen/introspect.py)
The introspect_api() function analyzes NormalizeAPI to extract:
- SubAPIs: Classes like
SkeletonAPI,HealthAPI,RAGAPI - Methods: Public methods with their signatures
- Parameters: Names, types, defaults, docstrings
- Return types: For serialization hints
from normalize.gen import introspect_api
sub_apis = introspect_api()
# Returns: [SubAPI(name="skeleton", methods=[...]), SubAPI(name="health", ...)]2. Adding a New Tool
To add a new MCP/HTTP/CLI tool:
Add API class to
normalize_api.py:python@dataclass class MyNewAPI: """API for doing something useful.""" root: Path def my_method(self, arg: str) -> str: """Do the thing. Args: arg: The input argument Returns: The result string """ return f"Result: {arg}"Add accessor to
NormalizeAPI:python@property def my_new(self) -> MyNewAPI: """Access my new functionality.""" return MyNewAPI(root=self.root)Register in introspect.py:
python# In sub_apis dict: "my_new": (MyNewAPI, "my_new"),Regenerate interfaces:
bashnormalize gen --target=mcp # Regenerate MCP server normalize gen --target=http # Regenerate HTTP routes normalize gen --target=all # Regenerate everything
3. Generators
MCP Generator (gen/mcp.py)
Generates MCP tool definitions for the Model Context Protocol:
from normalize.gen import generate_mcp_definitions
tools = generate_mcp_definitions()
# Returns: list of MCP Tool objects with schemasThe MCP server in mcp_server.py uses these definitions directly.
HTTP Generator (gen/http.py)
Generates FastAPI routes:
from normalize.gen import generate_http, generate_openapi
routes = generate_http() # FastAPI router
openapi_spec = generate_openapi() # OpenAPI JSONCLI Generator (gen/cli.py)
Generates argparse command structure:
from normalize.gen import generate_cli
parser = generate_cli()TUI Generator (gen/tui.py)
Generates Textual terminal UI:
from normalize.gen import run_tui
run_tui() # Launches interactive TUILSP Generator (gen/lsp.py)
Generates Language Server Protocol workspace commands:
from normalize.gen import generate_lsp_commands
commands = generate_lsp_commands()gRPC Generator (gen/grpc.py)
Generates Protocol Buffers and Python servicer:
from normalize.gen import generate_proto, generate_servicer_code
proto_content = generate_proto()
servicer_code = generate_servicer_code()Serialization (gen/serialize.py)
All generators use shared serialization for consistent output:
to_compact(): Token-efficient single-line formatto_dict(): JSON-serializable dictionaryto_markdown(): Human-readable markdown
Drift Detection
The CI checks that generated specs match committed versions:
# Check for drift
python scripts/check_gen_drift.py
# Auto-update specs
normalize gen --target=mcp --output=specs/mcp_tools.json
normalize gen --target=openapi --output=specs/openapi.jsonPre-commit hooks automatically update specs when API changes.
Design Principles
- Single Source of Truth:
NormalizeAPIis canonical; interfaces derive from it - Docstrings are Documentation: Method docstrings become tool descriptions
- Type Hints are Schemas: Python types become JSON schemas
- Consistent Serialization: All outputs use the same formatting
- No Manual Sync: Regenerate, don't manually update interface code