feat(toolchain): add agent-native developer toolchain (headless CLI, Lit render engine, FastMCP server, host profiles) - #2665
Conversation
- Add headless a2ui CLI entry point: check, catalog describe, catalog diff - Implement fuzzy difflib suggestions for misspelled components and properties - Add HostTargetProfile specification and gemini-enterprise target profile - Enforce Gemini Enterprise wire constraints (v0.9, application/json+a2ui, canvas root quirks) - Add comprehensive CLI unit tests (38/38 passing)
…elines - Add pre-compiled standalone Lit/WebCore bundle (bundle.js, 413.5 KB) and harness.html - Implement Playwright Chromium headless render engine with multi-pass whenSettled lifecycle settling loop - Integrate CLI subcommand a2ui render <payload.json> --png <out.png> - Add 6 committed visual baseline fixtures with automated parity and fidelity tests (30/30 passed)
- Implement FastMCP server in tools/a2ui_mcp/server.py - Expose a2ui_list_components with catalog schema and reference topology introspection - Expose a2ui_validate with fuzzy property suggestions and host profile target checks - Expose a2ui_render emitting native MCP Image objects for multimodal coding agents - Expose a2ui_simulate_action for in-memory action resolution via SurfaceModel without browser - Add unit and adversarial test suites (69/69 passed) - Add complete opaque-box E2E test suite (79/79 passed)
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive developer toolchain CLI and a FastMCP server for A2UI, enabling payload validation, catalog inspection/comparison, headless rendering via Playwright, and action simulation. Feedback on the changes highlights a potential type mismatch bug when validating component IDs in the target profile schema, a potential AttributeError in the MCP server's action simulation if node properties are not a dictionary, an unused variable in the rendering engine, and a minor type-hinting style improvement.
| root_comps = [ | ||
| c | ||
| for c in components | ||
| if isinstance(c, dict) and c.get("id") not in children_ids | ||
| ] | ||
|
|
||
| # Quirk check 1: Canvas must not be a child component | ||
| for cid in canvas_ids: | ||
| if cid in children_ids: | ||
| errors.append( | ||
| "Quirk 'side-panel-requires-canvas-root' violated: Component" | ||
| f" 'Canvas' (id: '{cid}') is nested as a child component in" | ||
| f" surface '{surface_id}'; Canvas must be the root component." | ||
| ) | ||
| elif root_comps and not any(r.get("id") == cid for r in root_comps): |
There was a problem hiding this comment.
There is a potential type mismatch bug when validating component IDs. canvas_ids contains string-coerced IDs (via str(cid)), but root_comps and children_ids use raw IDs (which could be int or other types). Comparing them directly (e.g., r.get("id") == cid) will fail if one is an integer and the other is a string. Coercing all IDs to strings during these checks ensures robust validation.
| root_comps = [ | |
| c | |
| for c in components | |
| if isinstance(c, dict) and c.get("id") not in children_ids | |
| ] | |
| # Quirk check 1: Canvas must not be a child component | |
| for cid in canvas_ids: | |
| if cid in children_ids: | |
| errors.append( | |
| "Quirk 'side-panel-requires-canvas-root' violated: Component" | |
| f" 'Canvas' (id: '{cid}') is nested as a child component in" | |
| f" surface '{surface_id}'; Canvas must be the root component." | |
| ) | |
| elif root_comps and not any(r.get("id") == cid for r in root_comps): | |
| root_comps = [ | |
| c | |
| for c in components | |
| if isinstance(c, dict) and str(c.get("id")) not in children_ids | |
| ] | |
| # Quirk check 1: Canvas must not be a child component | |
| for cid in canvas_ids: | |
| if cid in children_ids: | |
| errors.append( | |
| "Quirk 'side-panel-requires-canvas-root' violated: Component" | |
| f" 'Canvas' (id: '{cid}') is nested as a child component in" | |
| f" surface '{surface_id}'; Canvas must be the root component." | |
| ) | |
| elif root_comps and not any(str(r.get("id")) == cid for r in root_comps): |
| node = graph.get_or_create_node(component_id, "/") | ||
|
|
||
| action_callable = None | ||
| props = node.props.value or {} |
There was a problem hiding this comment.
The variable props is assigned node.props.value or {}, but there is no type verification to ensure it is actually a dictionary. If node.props.value is a non-dictionary type, calling props.get or props.items later will raise an AttributeError and crash the server. It is safer to verify the type first.
| props = node.props.value or {} | |
| props = node.props.value if isinstance(node.props.value, dict) else {} |
References
- When validation checks or function arguments can be represented as either a dictionary (named) or a list (positional), always verify the type (e.g., using
isinstance(args, dict)) before performing key-based lookups or assignments to preventTypeErrorcrashes.
|
|
||
| # 7. Deduplicate identical error messages | ||
| unique_errors: List[Dict[str, Any]] = [] | ||
| seen_messages: Set[str] = set() |
There was a problem hiding this comment.
The type hint Set is used here but is not imported from typing. Since from __future__ import annotations is enabled, you can use the standard lowercase set[str] instead of Set[str], which is more idiomatic in modern Python (3.9+) and does not require any imports.
| seen_messages: Set[str] = set() | |
| seen_messages: set[str] = set() |
|
|
||
| context = None | ||
| page = None | ||
| effective_timeout_sec = max(0.05, float(timeout_seconds)) |
There was a problem hiding this comment.
The variable effective_timeout_sec is calculated but never used in the render method. Consider removing it to keep the code clean.
References
- When refactoring or fixing issues in code, prefer completely removing dead code or redundant checks rather than modifying them with more complex conditional logic.
Summary of Contribution
This pull request introduces the complete Agent-Native Developer Toolchain for A2UI, bridging the critical developer experience (DX) gap between declarative UI protocol authoring and real-world agent/CI workflows.
It equips human developers and AI coding agents (Claude, Gemini, Cursor, Copilot) with an offline, ground-truth perception-action loop:
a2ui):a2ui check <file>: Full 3-tier validation (JSON Schema Draft 2020-12, component reference graph integrity, and topological recursion checks) withdifflibfuzzy suggestions for misspelled components or properties.a2ui catalog describe <catalog>: Formatted tables and JSON schema introspection for component inventories, required props, and reference hierarchy.a2ui catalog diff <left> <right>: Structural delta auditing between catalogs or versions.a2ui render --png):@a2ui/litbundle (413.5 KB).file://.whenSettledreactive lifecycle settling loop to prevent visual flakiness.a2ui-mcp):fastmcp(stdio transport) exposing 4 core tools:a2ui_list_components,a2ui_validate,a2ui_render(native MCP image streaming), and in-memorya2ui_simulate_action.--target <profile>):gemini-enterprise.yaml).Test Coverage & Verification
agent_sdks/python/a2ui_agent/tests/cli/: 38 unit tests passingagent_sdks/python/a2ui_agent/tests/render/: 30 render and visual fidelity tests passingtools/a2ui_mcp/tests/: 69 FastMCP unit and adversarial tests passingtests/e2e/: 79 opaque-box end-to-end tests passingclaude-sonnet-5on Vertex AI) confirming all acceptance criteria withOVERALL AUDIT DISPOSITION: PASS(documented inAUDIT_REPORT.md).pyink(0 files reformatted across 360 files).