Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion examples/hello_seller.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,10 @@ def _get_packages(req: Any) -> list[dict[str, Any]]:
# registry, validates the platform at boot, and starts the MCP
# server. Default port 3001 over streamable-http; override via
# ``serve(seller, port=...)``.
serve(HelloSeller(), name="hello-seller")
#
# ``auto_emit_completion_webhooks=False`` opts out of the F12
# sync-completion webhook auto-emit so the example boots without
# a ``webhook_sender``. Wire ``webhook_sender=`` in production so
# buyers who register ``push_notification_config.url`` get
# notifications.
serve(HelloSeller(), name="hello-seller", auto_emit_completion_webhooks=False)
10 changes: 9 additions & 1 deletion examples/hello_seller_async_handoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,4 +285,12 @@ def _echo_packages(req: Any) -> list[dict[str, Any]]:
# for local dev. In production, set
# ADCP_DECISIONING_ALLOW_INMEMORY_TASKS=1 (single-process pilot)
# OR pass registry= a durable impl (Postgres-backed v6.1).
serve(HelloSellerHybrid(), name="hello-seller-hybrid")
serve(
HelloSellerHybrid(),
name="hello-seller-hybrid",
# Opt out of F12 auto-emit so the example boots without a
# ``webhook_sender``. Production sellers wire ``webhook_sender=``
# so buyers who register ``push_notification_config.url`` get
# completion notifications when their TaskHandoff finishes.
auto_emit_completion_webhooks=False,
)
69 changes: 69 additions & 0 deletions examples/hello_seller_audience.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Hello-seller-audience — minimal AudiencePlatform adopter.

The smallest possible ``audience-sync`` seller. One method:
``sync_audiences`` accepts buyer-supplied first-party audiences with
delta upsert.

Run::

uv run python examples/hello_seller_audience.py
"""

from __future__ import annotations

from typing import Any

from adcp.decisioning import (
DecisioningCapabilities,
DecisioningPlatform,
RequestContext,
SingletonAccounts,
serve,
)


class HelloAudienceSeller(DecisioningPlatform):
"""The canonical minimal ``audience-sync`` adopter.

Note the platform method signature: ``sync_audiences(audiences,
ctx)`` — the wire shape carries ``audiences[]`` on the request
body, but the framework's arg-projector unpacks it so the platform
method receives the list directly. Cleaner than reaching through
``req.audiences``.
"""

capabilities = DecisioningCapabilities(specialisms=["audience-sync"])
accounts = SingletonAccounts(account_id="hello-audience")

def sync_audiences(
self,
audiences: list[Any],
ctx: RequestContext[Any],
) -> list[dict[str, Any]]:
"""Upsert each audience and return per-row sync status. Returning
a list (rather than the full ``SyncAudiencesSuccessResponse``)
is the ergonomic arm — the framework wraps to
``{audiences: [...]}`` on the wire."""
return [
{
"audience_id": getattr(a, "audience_id", str(i)),
"status": "synced",
"match_rate": 0.85,
"estimated_size": 100_000,
}
for i, a in enumerate(audiences)
]


def main() -> None:
"""Boot the seller on http://localhost:3001/mcp.

``auto_emit_completion_webhooks=False`` opts out so this example
boots without a ``webhook_sender``. In production, wire
``webhook_sender=`` for buyer notification.
"""
serve(HelloAudienceSeller(), auto_emit_completion_webhooks=False)


if __name__ == "__main__":
main()
98 changes: 98 additions & 0 deletions examples/hello_seller_brand_rights.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Hello-seller-brand-rights — minimal BrandRightsPlatform adopter.

The smallest possible ``brand-rights`` seller. Three required methods:
``get_brand_identity``, ``get_rights``, ``acquire_rights``.

The ``acquire_rights`` method has a 4-arm discriminated success union
(acquired / pending / rejected / error) — rejection-as-data per the
Protocol; the buyer doesn't need to disambiguate exception types.

Run::

uv run python examples/hello_seller_brand_rights.py
"""

from __future__ import annotations

from typing import Any

from adcp.decisioning import (
DecisioningCapabilities,
DecisioningPlatform,
RequestContext,
SingletonAccounts,
serve,
)


class HelloBrandRightsSeller(DecisioningPlatform):
"""The canonical minimal ``brand-rights`` adopter."""

capabilities = DecisioningCapabilities(specialisms=["brand-rights"])
accounts = SingletonAccounts(account_id="hello-rights")

def get_brand_identity(
self,
req: Any,
ctx: RequestContext[Any],
) -> dict[str, Any]:
"""Read brand identity record."""
return {
"brand": {
"brand_id": "example-brand",
"name": "Example Brand",
"asset_pack_url": "https://cdn.example.com/brand-pack/example.zip",
}
}

def get_rights(
self,
req: Any,
ctx: RequestContext[Any],
) -> dict[str, Any]:
"""List rights matching brand + use query."""
return {
"rights": [
{
"rights_id": "right-1",
"use": "display_advertising",
"term_start": "2026-01-01",
"term_end": "2026-12-31",
"status": "available",
}
]
}

def acquire_rights(
self,
req: Any,
ctx: RequestContext[Any],
) -> dict[str, Any]:
"""Acquire rights — 4-arm discriminated success union.

Adopters pick one shape per call:
* ``{"status": "acquired", ...}`` — rights granted
* ``{"status": "pending", ...}`` — needs human approval
* ``{"status": "rejected", "reason": ...}`` — denied as data
* ``{"status": "error", "message": ...}`` — rights system
failure the buyer can retry against
"""
return {
"status": "acquired",
"rights_id": "right-1",
"acquisition_id": "acq-1",
}


def main() -> None:
"""Boot the seller on http://localhost:3001/mcp.

``auto_emit_completion_webhooks=False`` opts out so this example
boots without a ``webhook_sender``. In production, wire
``webhook_sender=`` for buyer notification.
"""
serve(HelloBrandRightsSeller(), auto_emit_completion_webhooks=False)


if __name__ == "__main__":
main()
80 changes: 80 additions & 0 deletions examples/hello_seller_collection_lists.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""Hello-seller-collection-lists — minimal CollectionListsPlatform adopter.

The smallest possible ``collection-lists`` seller. Five-method CRUD
plus fetch-token issuance. Pattern-mirrors ``property-lists`` —
adopters typically implement both side-by-side.

Run::

uv run python examples/hello_seller_collection_lists.py
"""

from __future__ import annotations

import secrets
from typing import Any

from adcp.decisioning import (
DecisioningCapabilities,
DecisioningPlatform,
RequestContext,
SingletonAccounts,
serve,
)


class HelloCollectionListsSeller(DecisioningPlatform):
"""The canonical minimal ``collection-lists`` adopter."""

capabilities = DecisioningCapabilities(specialisms=["collection-lists"])
accounts = SingletonAccounts(account_id="hello-collection-lists")

def __init__(self) -> None:
super().__init__()
self._lists: dict[str, dict[str, Any]] = {}

def create_collection_list(self, req: Any, ctx: RequestContext[Any]) -> dict[str, Any]:
list_id = f"cl-{secrets.token_urlsafe(8)}"
token = secrets.token_urlsafe(24)
self._lists[list_id] = {
"list_id": list_id,
"name": getattr(req, "name", "untitled"),
"fetch_token": token,
"items": getattr(req, "items", []),
}
return {
"list_id": list_id,
"fetch_url": f"https://example.com/lists/{list_id}",
"fetch_token": token,
}

def update_collection_list(self, req: Any, ctx: RequestContext[Any]) -> dict[str, Any]:
list_id = getattr(req, "list_id", None)
if list_id and list_id in self._lists:
self._lists[list_id]["items"] = getattr(req, "items", [])
return {"list_id": list_id}

def get_collection_list(self, req: Any, ctx: RequestContext[Any]) -> dict[str, Any]:
list_id = getattr(req, "list_id", None)
return self._lists.get(list_id, {"list_id": list_id, "items": []})

def list_collection_lists(self, req: Any, ctx: RequestContext[Any]) -> dict[str, Any]:
return {"collection_lists": list(self._lists.values())}

def delete_collection_list(self, req: Any, ctx: RequestContext[Any]) -> dict[str, Any]:
"""Security-critical: revokes the fetch_token. See
``hello_seller_property_lists.delete_property_list`` for the
same security contract."""
list_id = getattr(req, "list_id", None)
if list_id:
self._lists.pop(list_id, None)
return {"list_id": list_id, "deleted": True}


def main() -> None:
"""Boot the seller on http://localhost:3001/mcp."""
serve(HelloCollectionListsSeller(), auto_emit_completion_webhooks=False)


if __name__ == "__main__":
main()
75 changes: 75 additions & 0 deletions examples/hello_seller_content_standards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Hello-seller-content-standards — minimal ContentStandardsPlatform adopter.

The smallest possible ``content-standards`` seller. Six required CRUD +
calibration + validation methods. Two optional analyzer reads
(``get_media_buy_artifacts``, ``get_creative_features``) are omitted —
the framework's UNSUPPORTED_FEATURE gate surfaces them as such to
buyers who call them.

Run::

uv run python examples/hello_seller_content_standards.py
"""

from __future__ import annotations

from typing import Any

from adcp.decisioning import (
DecisioningCapabilities,
DecisioningPlatform,
RequestContext,
SingletonAccounts,
serve,
)


class HelloContentStandardsSeller(DecisioningPlatform):
"""The canonical minimal ``content-standards`` adopter."""

capabilities = DecisioningCapabilities(specialisms=["content-standards"])
accounts = SingletonAccounts(account_id="hello-standards")

def list_content_standards(self, req: Any, ctx: RequestContext[Any]) -> dict[str, Any]:
return {
"content_standards": [
{
"standards_id": "std-default",
"name": "Default Content Standards",
"version": "1.0.0",
}
]
}

def get_content_standards(self, req: Any, ctx: RequestContext[Any]) -> dict[str, Any]:
return {
"content_standards": {
"standards_id": "std-default",
"name": "Default Content Standards",
"version": "1.0.0",
"policies": [],
}
}

def create_content_standards(self, req: Any, ctx: RequestContext[Any]) -> dict[str, Any]:
return {"standards_id": "std-new"}

def update_content_standards(self, req: Any, ctx: RequestContext[Any]) -> dict[str, Any]:
return {"standards_id": getattr(req, "standards_id", "std-default")}

def calibrate_content(self, req: Any, ctx: RequestContext[Any]) -> dict[str, Any]:
"""Score content against published standards."""
return {"score": 0.92, "violations": []}

def validate_content_delivery(self, req: Any, ctx: RequestContext[Any]) -> dict[str, Any]:
"""Post-flight conformance check."""
return {"passed": True, "violations": []}


def main() -> None:
"""Boot the seller on http://localhost:3001/mcp."""
serve(HelloContentStandardsSeller(), auto_emit_completion_webhooks=False)


if __name__ == "__main__":
main()
Loading
Loading