Skip to content

feat(testing): public ASGI app builder for in-process test harnesses #618

@bokelley

Description

@bokelley

Problem

In-process test harnesses (httpx.ASGITransport, Starlette TestClient) need the unified MCP+A2A+admin ASGI app without binding a uvicorn socket. serve() binds a socket as part of its contract; it doesn't return the app handle.

We currently import private symbols to assemble the app ourselves:

from adcp.decisioning.serve import create_adcp_server_from_platform
from adcp.server.serve import _apply_asgi_middleware, _build_mcp_and_a2a_app

handler, _executor, _registry = create_adcp_server_from_platform(router, ...)
app = _build_mcp_and_a2a_app(handler, **build_kwargs)
return _apply_asgi_middleware(app, asgi_middleware)

This is fragile: any rename / signature change to _build_mcp_and_a2a_app or _apply_asgi_middleware (both leading-underscore private) silently breaks every test that drives the app via httpx.ASGITransport.

Proposed SDK shape

from adcp.testing import build_asgi_app

app = build_asgi_app(
    router,
    name=\"my-test-server\",
    auth=BearerTokenAuth(validate_token=...),
    asgi_middleware=[(MyMiddleware, {})],
    context_factory=auth_context_factory,
    enable_dns_rebinding_protection=False,
    streaming_responses=False,
)
# app is an ASGI handler ready for httpx.ASGITransport(app=app)

Same kwargs serve() accepts (auth, middleware, context_factory, etc.) minus the uvicorn-binding ones (port, host, debug endpoints). Returns the unified app handle.

Why this matters for adopters beyond us

  • Any adopter writing high-fidelity transport tests (assert exact wire shapes, headers, error envelopes) needs this — mocking the dispatcher doesn't catch transport-layer bugs.
  • The closed #549 / PR #554 shipped build_test_client(platform) for HTTP-level testing. This is the same intent at a lower layer: hand back the ASGI app, let me drive it however I want (httpx, Starlette TestClient, hypercorn for load testing, etc.).

Files

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions