Skip to content

feat(toolchain): add agent-native developer toolchain (headless CLI, Lit render engine, FastMCP server, host profiles) - #2665

Open
weichunglow2024 wants to merge 4 commits into
a2ui-project:mainfrom
weichunglow2024:pr/3-fastmcp-perception-action-server
Open

weichunglow2024 wants to merge 4 commits into
a2ui-project:mainfrom
weichunglow2024:pr/3-fastmcp-perception-action-server

Conversation

@weichunglow2024

Copy link
Copy Markdown

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:

  1. Headless Developer CLI (a2ui):
    • a2ui check <file>: Full 3-tier validation (JSON Schema Draft 2020-12, component reference graph integrity, and topological recursion checks) with difflib fuzzy 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.
  2. Hermetic Headless Render Engine (a2ui render --png):
    • Compiles declarative JSON payloads to pixel-perfect PNG images in <200ms using Playwright and an offline, pre-compiled standalone @a2ui/lit bundle (413.5 KB).
    • Zero web server or network connection required; loads directly via file://.
    • Multi-pass whenSettled reactive lifecycle settling loop to prevent visual flakiness.
  3. FastMCP Perception-Action Server (a2ui-mcp):
    • Built on fastmcp (stdio transport) exposing 4 core tools: a2ui_list_components, a2ui_validate, a2ui_render (native MCP image streaming), and in-memory a2ui_simulate_action.
  4. Declarative Host Target Profiles (--target <profile>):
    • Portable YAML schema enforcing wire protocol versions, required MIME types, composite catalog schemas, and host quirks (gemini-enterprise.yaml).

Test Coverage & Verification

  • 216 / 216 automated tests passing (0 failures, 0 skips):
    • agent_sdks/python/a2ui_agent/tests/cli/: 38 unit tests passing
    • agent_sdks/python/a2ui_agent/tests/render/: 30 render and visual fidelity tests passing
    • tools/a2ui_mcp/tests/: 69 FastMCP unit and adversarial tests passing
    • tests/e2e/: 79 opaque-box end-to-end tests passing
  • Independent Model Verification:
    • Full programmatic audit executed by Anthropic Claude Sonnet 5 (claude-sonnet-5 on Vertex AI) confirming all acceptance criteria with OVERALL AUDIT DISPOSITION: PASS (documented in AUDIT_REPORT.md).
  • Hygiene & Standards:
    • Formatted cleanly via pyink (0 files reformatted across 360 files).
    • Apache 2.0 / Google LLC copyright headers on all new files.

- 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)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +316 to +330
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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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):

Comment thread tools/a2ui_mcp/server.py
node = graph.get_or_create_node(component_id, "/")

action_callable = None
props = node.props.value or {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
props = node.props.value or {}
props = node.props.value if isinstance(node.props.value, dict) else {}
References
  1. 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 prevent TypeError crashes.


# 7. Deduplicate identical error messages
unique_errors: List[Dict[str, Any]] = []
seen_messages: Set[str] = set()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
seen_messages: Set[str] = set()
seen_messages: set[str] = set()


context = None
page = None
effective_timeout_sec = max(0.05, float(timeout_seconds))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The variable effective_timeout_sec is calculated but never used in the render method. Consider removing it to keep the code clean.

References
  1. When refactoring or fixing issues in code, prefer completely removing dead code or redundant checks rather than modifying them with more complex conditional logic.

@weichunglow2024 weichunglow2024 changed the title feat(toolchain): add agent-native developer toolchain (headless CLI, Lit render engine, FastMCP server, host profiles)Pr/3 fastmcp perception action server feat(toolchain): add agent-native developer toolchain (headless CLI, Lit render engine, FastMCP server, host profiles) Sep 15, 2026
@github-actions github-actions Bot added the status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs label Sep 15, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant