Commit 2affd00
fix(ci): unblock storyboard runner — poll, MCP outputSchema, ref seller boot (#443)
* fix(ci): correct broken curl readiness check in storyboard poll loop
When the agent isn't yet listening, ``curl -w "%{http_code}"`` writes
"000" to stdout AND exits non-zero, so ``... || echo "000"`` appended
a second "000" — making HTTP_CODE the string "000000". The
``"$HTTP_CODE" != "000"`` comparison then succeeded on the first
iteration, falsely declaring the agent ready before it had started.
Both storyboard jobs (seller_agent.py and v3_reference_seller) failed
on every run with overall_status=unreachable as a result.
Move ``||`` onto the command-substitution assignment so the fallback
overwrites instead of concatenates.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(server): outputSchema must declare type:object per MCP spec
MCP requires ``outputSchema`` to define top-level ``type: "object"``
because it describes ``CallToolResult.structuredContent``, which is
always a JSON object. Pydantic's ``TypeAdapter.json_schema`` for
discriminated-union response types (``CreateMediaBuyResponse``,
``AcquireRightsResponse``, etc.) emits ``{"anyOf": [...]}`` with no
``type`` field — Zod-validated MCP clients reject these tools at
``tools/list``. The storyboard runner's capability discovery flagged
five such tools as ``invalid_value`` on ``outputSchema.type``, taking
the agent to ``overall_status: unreachable``.
Every variant inside the union is itself a Pydantic model rendered as
``type: "object"``, so adding root-level ``type: "object"`` alongside
``anyOf`` is semantically equivalent (must be an object AND match a
variant) and MCP-spec-conformant. Existing union-shape assertions in
``tests/test_tools_list_output_schema.py`` continue to pass — the
``anyOf`` is preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(v3-ref-seller): disable F12 auto-emit gate to unblock boot
The v3 reference seller's platform claims the ``sales-non-guaranteed``
specialism (which exposes ``create_media_buy``, ``sync_creatives``,
``update_media_buy``) but doesn't wire a ``WebhookSender`` or
``WebhookDeliverySupervisor`` — server-boot
``validate_webhook_sender_for_platform`` raises
``AdcpError[INVALID_REQUEST]`` and the process dies before it
listens, taking storyboard CI to ``overall_status: unreachable``.
Pass ``auto_emit_completion_webhooks=False`` to ``serve()``. The
reference platform doesn't emit completion webhooks, which matches
the supported "I handle webhooks manually" code path the validator
calls out. Adopters whose platforms need webhook delivery wire a
``WebhookSender`` (or ``InMemoryWebhookDeliverySupervisor``) and drop
the kwarg — see the webhook_supervisor module for the wiring pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(v3-ref-seller): dispose engine after schema bootstrap
``asyncio.run(_bootstrap_schema(engine))`` opens asyncpg connections
on a transient event loop that closes when ``asyncio.run`` returns.
The connections stay in the pool, but asyncpg binds connection-
internal Future objects to the loop they were opened on. uvicorn
then runs on its own loop, and the first request raises
``RuntimeError: got Future attached to a different loop`` —
returning HTTP 500 on the readiness GET and taking storyboard CI
to ``overall_status: unreachable``.
Dispose the engine inside the bootstrap coroutine so the pool is
emptied while the bootstrap loop is still alive. uvicorn opens
fresh connections on its own loop on first use.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(seller-agent): filter invalid channels from seeded products
Storyboard fixtures from @adcp/sdk's ``delivery_reporting.yaml`` ship
``channels: ["video"]`` for ``outdoor_video_q2`` (and similar legacy
names elsewhere). ``"video"`` isn't in the canonical
``MediaChannelSchema`` enum from schemas/cache/enums/channels.json
(the enum has ``olv``, ``ctv``, ``linear_tv``, etc. — bare
``"video"`` was never a valid value), so the SDK's strict response
validator rejects ``get_products`` and storyboard CI reports the
agent as ``mcp_error: VALIDATION_ERROR[/products/N/channels/0]``.
Filter incoming fixture ``channels`` against the spec enum in
``seed_product`` and drop the field if no values remain. The static
``PRODUCTS`` block doesn't declare ``channels`` either, so the seller
behaves consistently across static and seeded products.
The upstream fixture is genuinely buggy and should be fixed in
@adcp/sdk; this is a defensive normalization on the demo seller side
so storyboard CI keeps moving while that gets sorted upstream.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(v3-ref-seller): normalize host before tenant lookup
``SubdomainTenantMiddleware`` passes the raw Host header to
``router.resolve()``. RFC 7230 makes the header case-insensitive and
lets the client include ``:port``; the Protocol docstring is explicit
that implementations strip the port suffix as needed. The CI
readiness probe sends ``Host: acme.localhost:3001``, but
``SqlSubdomainTenantRouter.resolve`` ran the string verbatim through
``WHERE host == :host`` and never matched the seeded
``acme.localhost`` row — every request 404'd as ``unknown-host``,
storyboard CI reported the agent as ``unreachable``.
Lower-case and strip the port suffix before the cache lookup AND the
DB query so ``ACME.localhost:3001`` resolves the same row as
``acme.localhost``. Adds a regression test that captures the SQL bind
to prove the literal port-suffixed host doesn't reach the WHERE
clause.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(server): expose DNS-rebinding controls on serve()
FastMCP's TransportSecurityMiddleware enforces a strict default
``allowed_hosts`` (loopback only — ``127.0.0.1:*``, ``localhost:*``,
``[::1]:*``). Adopters serving multi-tenant subdomain hosts
(``acme.example.com``, ``acme.localhost``) get ``421 Misdirected
Request`` on every MCP request — the storyboard runner reports the
agent as ``unreachable`` because capability discovery never
completes.
Surface the underlying knobs on ``adcp.server.serve.serve`` and
``create_mcp_server``:
* ``allowed_hosts``: extends the FastMCP default (loopback probes
still work alongside adopter-specified tenant hosts).
* ``allowed_origins``: symmetric, for the Origin header check.
* ``enable_dns_rebinding_protection``: turns the MCP-layer check off
entirely — the right move for adopters whose outer ASGI middleware
(e.g. :class:`SubdomainTenantMiddleware`) already validates the
Host header against a tenant table, so duplicating the check
against a static allow-list adds operational overhead without a
security benefit.
Threaded through ``_serve_mcp``, ``_serve_mcp_and_a2a``, and
``_build_mcp_and_a2a_app`` so every transport sees the same wiring.
``adcp.decisioning.serve`` already forwards via ``**serve_kwargs``,
so adopters using the decisioning wrapper pick this up for free.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(v3-ref-seller): disable MCP DNS-rebinding check
``SubdomainTenantMiddleware`` (wired via ``asgi_middleware``) already
validates the Host header against the seeded tenant table — that's
the load-bearing host check for this seller. Without further config,
FastMCP's TransportSecurityMiddleware also rejects any non-loopback
Host (``acme.localhost:3001`` → ``421 Misdirected Request``), and
the storyboard runner reports the agent as ``unreachable`` because
MCP discovery never completes.
Pass ``enable_dns_rebinding_protection=False`` to ``serve()`` so the
MCP-layer check is off and the SubdomainTenantMiddleware stays the
single host-validation point. Adopters that don't run a tenant-aware
ASGI middleware leave the kwarg unset to keep the FastMCP defaults
active.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: switch storyboard runner from @adcp/client to @adcp/sdk
``@adcp/client`` was renamed to ``@adcp/sdk`` at v6.0; the old name is
deprecated and ``@adcp/client@latest`` still resolves to 5.25.1, where
the ``mock-server`` subcommand the v3 storyboard upstream relies on
doesn't exist — the runner falls back to interpreting ``mock-server``
as an unknown agent alias and the upstream readiness loop times out.
Switch all three ``npx`` invocations to ``@adcp/sdk``:
* seller_agent.py storyboard runner: ``@adcp/sdk@latest`` (track latest
to surface protocol drift, same posture as before).
* v3 reference seller storyboard runner: ditto.
* Upstream mock-server boot (translator pattern): pin floor at
``@adcp/sdk@>=6.7.0`` — that's the version that introduced the
``adcp mock-server <specialism>`` subcommand. Leaving it as
``@latest`` would work today but a downgrade-to-6.6 SDK release
would silently break this step.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 0af6098 commit 2affd00
8 files changed
Lines changed: 272 additions & 3 deletions
File tree
- .github/workflows
- examples
- v3_reference_seller
- src
- tests
- src/adcp/server
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
396 | 396 | | |
397 | 397 | | |
398 | 398 | | |
| 399 | + | |
| 400 | + | |
399 | 401 | | |
400 | | - | |
| 402 | + | |
401 | 403 | | |
402 | 404 | | |
403 | 405 | | |
| |||
554 | 556 | | |
555 | 557 | | |
556 | 558 | | |
| 559 | + | |
| 560 | + | |
557 | 561 | | |
558 | | - | |
| 562 | + | |
559 | 563 | | |
560 | 564 | | |
561 | 565 | | |
| |||
589 | 593 | | |
590 | 594 | | |
591 | 595 | | |
| 596 | + | |
| 597 | + | |
592 | 598 | | |
593 | | - | |
| 599 | + | |
594 | 600 | | |
595 | 601 | | |
596 | 602 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
46 | 78 | | |
47 | 79 | | |
48 | 80 | | |
| |||
769 | 801 | | |
770 | 802 | | |
771 | 803 | | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
772 | 815 | | |
773 | 816 | | |
774 | 817 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
93 | 101 | | |
94 | 102 | | |
95 | 103 | | |
| |||
170 | 178 | | |
171 | 179 | | |
172 | 180 | | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
173 | 202 | | |
174 | 203 | | |
175 | 204 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
52 | 59 | | |
53 | 60 | | |
54 | 61 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
105 | 140 | | |
106 | 141 | | |
107 | 142 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1553 | 1553 | | |
1554 | 1554 | | |
1555 | 1555 | | |
| 1556 | + | |
| 1557 | + | |
| 1558 | + | |
| 1559 | + | |
| 1560 | + | |
| 1561 | + | |
| 1562 | + | |
| 1563 | + | |
| 1564 | + | |
1556 | 1565 | | |
1557 | 1566 | | |
1558 | 1567 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
444 | 444 | | |
445 | 445 | | |
446 | 446 | | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
447 | 450 | | |
448 | 451 | | |
449 | 452 | | |
| |||
691 | 694 | | |
692 | 695 | | |
693 | 696 | | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
694 | 700 | | |
695 | 701 | | |
696 | 702 | | |
| |||
713 | 719 | | |
714 | 720 | | |
715 | 721 | | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
716 | 725 | | |
717 | 726 | | |
718 | 727 | | |
| |||
996 | 1005 | | |
997 | 1006 | | |
998 | 1007 | | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
999 | 1011 | | |
1000 | 1012 | | |
1001 | 1013 | | |
| |||
1010 | 1022 | | |
1011 | 1023 | | |
1012 | 1024 | | |
| 1025 | + | |
| 1026 | + | |
| 1027 | + | |
1013 | 1028 | | |
1014 | 1029 | | |
1015 | 1030 | | |
| |||
1199 | 1214 | | |
1200 | 1215 | | |
1201 | 1216 | | |
| 1217 | + | |
| 1218 | + | |
| 1219 | + | |
1202 | 1220 | | |
1203 | 1221 | | |
1204 | 1222 | | |
| |||
1233 | 1251 | | |
1234 | 1252 | | |
1235 | 1253 | | |
| 1254 | + | |
| 1255 | + | |
| 1256 | + | |
1236 | 1257 | | |
1237 | 1258 | | |
1238 | 1259 | | |
| |||
1336 | 1357 | | |
1337 | 1358 | | |
1338 | 1359 | | |
| 1360 | + | |
| 1361 | + | |
| 1362 | + | |
1339 | 1363 | | |
1340 | 1364 | | |
1341 | 1365 | | |
| |||
1377 | 1401 | | |
1378 | 1402 | | |
1379 | 1403 | | |
| 1404 | + | |
| 1405 | + | |
| 1406 | + | |
1380 | 1407 | | |
1381 | 1408 | | |
1382 | 1409 | | |
| |||
1411 | 1438 | | |
1412 | 1439 | | |
1413 | 1440 | | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
1414 | 1444 | | |
1415 | 1445 | | |
1416 | 1446 | | |
| |||
1526 | 1556 | | |
1527 | 1557 | | |
1528 | 1558 | | |
| 1559 | + | |
| 1560 | + | |
| 1561 | + | |
| 1562 | + | |
| 1563 | + | |
| 1564 | + | |
| 1565 | + | |
| 1566 | + | |
| 1567 | + | |
| 1568 | + | |
| 1569 | + | |
| 1570 | + | |
| 1571 | + | |
| 1572 | + | |
| 1573 | + | |
| 1574 | + | |
| 1575 | + | |
| 1576 | + | |
| 1577 | + | |
| 1578 | + | |
| 1579 | + | |
| 1580 | + | |
| 1581 | + | |
| 1582 | + | |
| 1583 | + | |
| 1584 | + | |
1529 | 1585 | | |
1530 | 1586 | | |
1531 | 1587 | | |
| |||
0 commit comments