Feature: Explorer UI¶
| Field | Value |
|---|---|
| Feature ID | F-10 |
| Name | explorer |
| Priority | P2 |
| SRS Refs | FR-EXP-001 |
| Tech Design | §4.8 Explorer Module |
| Depends On | F-03 (server-core — ExecutionRouter, agent_card) |
| Blocks | None |
Purpose¶
Browser-based interactive UI for exploring and testing an A2A agent. Mounted as a Starlette sub-application at a configurable URL prefix (default: /explorer). Delivered as a single self-contained HTML file — no external CDN dependencies.
Component: create_explorer_mount() — explorer/__init__.py¶
def create_explorer_mount(
agent_card: dict,
router: object, # duck-typed: DefaultRequestHandler or ExecutionRouter
*,
explorer_prefix: str = "/explorer",
authenticator: object | None = None,
) -> Mount:
"""Create a Starlette Mount for the Explorer UI.
Args:
agent_card: Pre-computed Agent Card dict (served to Explorer JS).
router: Request handler for proxying test requests (duck-typed).
explorer_prefix: URL prefix at which the Explorer is mounted.
authenticator: Optional Authenticator for Explorer POST routes.
Returns:
A Starlette Mount that can be added to the parent app's routes.
"""
Mount routes:
| Method | Path | Handler | Auth Required |
|---|---|---|---|
GET |
{explorer_prefix}/ |
Serve index.html |
No |
GET |
{explorer_prefix}/agent-card |
Return agent card JSON | No |
Explorer GET endpoints are exempt from JWT authentication even when auth is configured. This is enforced by adding explorer_prefix to AuthMiddleware.exempt_prefixes.
Explorer UI Features (index.html)¶
The single-file UI provides:
Agent Card Panel¶
- Agent name, description, version, URL
- Capabilities: streaming, push notifications, state history
- Security schemes listing
Skills Browser¶
- List all skills with: ID, name, description, tags
- Expand each skill to view:
- Input/output MIME modes
- Examples (up to 10)
- apcore annotations (
readonly,destructive,idempotent,requires_approval) - Input schema (formatted JSON)
Message Composer¶
- Skill selector dropdown (populated from agent card)
- Message editor (text input or JSON DataPart)
- Context ID field (optional, pre-filled with generated UUID)
- Send button →
message/sendJSON-RPC call → display Task result - Stream button →
message/stream→ live SSE event log
SSE Stream Viewer¶
- Live-rendered event list
- Event type badges (status / artifact)
- State transitions highlighted
- Artifact parts displayed inline (text rendered, JSON pretty-printed)
Task State Viewer¶
- Input task ID → fetch via
tasks/get - Display current state, timestamp, history timeline
- Cancel button →
tasks/cancel
Implementation Notes¶
Single HTML File¶
src/apcore_a2a/explorer/index.html
- No external CDN (fonts, JS libs) — fully self-contained.
- Agent Card fetched from
GET {explorer_prefix}/agent-cardon page load. - JSON-RPC calls made via
fetch()to the parent server'sPOST /. - SSE stream via
EventSourceAPI pointed at parent server'sPOST /. - Auth token (if configured) stored in sessionStorage, sent as
Authorization: Bearerheader.
Starlette Mount¶
from starlette.routing import Mount, Route
from starlette.responses import HTMLResponse
from starlette.staticfiles import StaticFiles
def create_explorer_mount(agent_card, router, *, explorer_prefix="/explorer", authenticator=None):
html_path = Path(__file__).parent / "index.html"
async def serve_index(request):
return HTMLResponse(html_path.read_text())
async def serve_agent_card(request):
return JSONResponse(agent_card)
return Mount(explorer_prefix, routes=[
Route("/", endpoint=serve_index),
Route("/agent-card", endpoint=serve_agent_card),
])
Auth Exemption¶
When A2AServerFactory mounts the Explorer, it passes explorer_prefix to AuthMiddleware:
# In A2AServerFactory.create():
if explorer:
explorer_mount = create_explorer_mount(agent_card, router, explorer_prefix=explorer_prefix)
routes.append(explorer_mount)
exempt_prefixes.add(explorer_prefix)
This means any GET {explorer_prefix}/* request bypasses JWT validation.
File Structure¶
src/apcore_a2a/explorer/
__init__.py # create_explorer_mount()
index.html # Self-contained Explorer UI
Key Invariants¶
- Explorer is mounted only when
explorer=Trueinserve()/A2AServerFactory.create() - All Explorer routes are exempt from JWT authentication (public dev tool)
index.htmlis self-contained — no external network requests from the UI- Agent Card data served at
{explorer_prefix}/agent-card(not re-fetching/.well-known/agent.jsonto avoid CORS) create_explorer_mount()has no dependency on auth or storage modules
Test Module¶
tests/explorer/test_explorer.py