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
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1597,10 +1597,6 @@ See `examples/fetch_agent_authorizations.py` for complete examples.

## Request Signing (AdCP 3.0 optional, 4.0 required)

AdCP 3.3 identity support is available as an opt-in preview: see the
[Web Bot Auth and trust.json migration guide](docs/agent-identity-3-3-migration.md)
for signing, directory publishing, origin binding, demand grants, and preview types.

AdCP defines an optional transport-layer request-signing profile based on
[RFC 9421 HTTP Message Signatures](https://datatracker.ietf.org/doc/rfc9421/).
A valid signature proves the request came from the agent whose key signed it.
Expand Down
161 changes: 0 additions & 161 deletions docs/agent-identity-3-3-migration.md

This file was deleted.

4 changes: 0 additions & 4 deletions docs/request-signing-migration.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
# Migrating to signed AdCP requests

For the experimental AdCP 3.3 origin identity profile, see
[Web Bot Auth and trust.json migration](agent-identity-3-3-migration.md).
The guide below continues to cover the RFC 9421 profile supported through 3.x.

Rolling out RFC 9421 request signing against an existing AdCP integration is a two-track exercise: **bootstrap** once, then **enforce** in stages per operation. Key **rotation** follows the same pattern and is meant to be routine.

This guide covers the operator-facing mechanics. Spec reference: [Signed Requests (Transport Layer)](https://adcontextprotocol.org/docs/building/implementation/security#signed-requests-transport-layer).
Expand Down
4 changes: 0 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ dependencies = [
# httpx2 2.5.0 requires idna>=3.18. Upper bound at <4 because IDNA
# semantics across a major are not contracted.
"idna>=3.18,<4",
"http-sfv>=0.9.9,<1.0",
]

[project.scripts]
Expand Down Expand Up @@ -229,8 +228,6 @@ namespaces = true

[tool.setuptools.package-data]
adcp = [
"signing/_identity_schemas/**/*.json",
"signing/_identity_schemas/*.json",
"py.typed",
"types/*.pyi",
"types/versioned_bases/*.pyi",
Expand Down Expand Up @@ -296,7 +293,6 @@ extend-exclude = "/(tasks|_generated|registry)\\.py$|/types/domains/|/types/v3[0
line-length = 100
target-version = "py310"
extend-exclude = [
"src/adcp/types/_identity_preview.py",
"src/adcp/types/_generated.py",
# datamodel-code-generator output; regenerate with
# scripts/vendor_reporting_status_preview.py rather than hand-editing.
Expand Down
145 changes: 0 additions & 145 deletions scripts/generate_identity_preview.py

This file was deleted.

13 changes: 1 addition & 12 deletions src/adcp/_notification_envelope.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ def validate_notification_body(
idempotency_key: str | None = None,
notification_type: str | None = None,
account_id: str | None = None,
experimental_agent_identity: bool = False,
) -> dict[str, Any]:
"""Reject malformed envelopes before committing and again before HTTP.

Expand Down Expand Up @@ -77,17 +76,7 @@ def validate_notification_body(
validator = get_named_validator(NOTIFICATION_SCHEMAS[kind])
if validator is None:
raise ValueError("bundled notification schema is unavailable")
schema_payload = payload
if (
experimental_agent_identity
and kind == "principal.changed"
and isinstance(payload.get("reason"), str)
and payload.get("reason") in {"relationship_changed", "relationship_expiring"}
):
# The pinned draft adds two enum values; all other wire constraints
# still come from the stable offline schema. Preserve delivery bytes.
schema_payload = {**payload, "reason": "other"}
error = next(validator.iter_errors(schema_payload), None)
error = next(validator.iter_errors(payload), None)
if error is not None:
raise ValueError(f"notification envelope failed schema validation ({error.validator})")
if kind.startswith(("product.", "signal.")) or kind == "wholesale_feed.bulk_change":
Expand Down
33 changes: 2 additions & 31 deletions src/adcp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@
from adcp.signing.autosign import (
SigningConfig,
operation_needs_signing,
require_signing_profile,
signing_profile_for_adcp_version,
)
from adcp.signing.autosign import (
Expand Down Expand Up @@ -720,7 +719,6 @@ def __init__(
account_policy: AccountPolicy | None = None,
account_storage: AccountStorage | None = None,
raise_account_errors: bool | None = None,
experimental_agent_identity: bool = False,
product_property_policy: ProductPropertyPolicy | None = None,
):
"""
Expand All @@ -731,8 +729,6 @@ def __init__(
product_property_policy: Optional buyer-side property-list check;
filters or flags seller products and reports unjudgeable inventory
in TaskResult.metadata. Disabled by default.
experimental_agent_identity: Parse the pinned AdCP 3.3 relationship and
onboarding response types. WBA signing enables this automatically.
webhook_url_template: Template for webhook URLs with {agent_id},
{task_type}, {operation_id}
webhook_secret: Shared secret for the deprecated HMAC-SHA256 webhook
Expand Down Expand Up @@ -920,9 +916,6 @@ def __init__(
self.validate_features = validate_features
self.strict_idempotency = strict_idempotency
self.signing = signing
self.experimental_agent_identity = experimental_agent_identity or (
signing is not None and signing.agent_origin is not None
)
self.product_property_policy = product_property_policy
if httpx_client_factory is not None and not callable(httpx_client_factory):
raise TypeError("httpx_client_factory must be callable")
Expand Down Expand Up @@ -1489,7 +1482,6 @@ async def _sign_outgoing_request(self, request: httpx.Request) -> None:
decision = operation_needs_signing(req_signing, operation)
if decision == "skip":
return
require_signing_profile(req_signing, self.signing)

covers_policy: str | None = None
if req_signing is not None and req_signing.covers_content_digest is not None:
Expand All @@ -1516,7 +1508,6 @@ async def _sign_outgoing_request(self, request: httpx.Request) -> None:
alg=self.signing.alg,
cover_content_digest=cover_digest,
tag=self.signing.tag,
agent_origin=self.signing.agent_origin,
signing_profile_version=signing_profile_version,
)
# pop-then-set ensures our signed values are authoritative even if
Expand Down Expand Up @@ -2239,7 +2230,6 @@ async def _execute_typed_task(
response_type: type[BaseModel] | Any,
) -> TaskResult[Any]:
"""Execute and parse one typed AdCP task with activity events."""
response_type = self._identity_response_type(response_type)
operation_id = self._task_operation_id()
params = request.model_dump(mode="json", exclude_none=True)
# AdCP 3.2 mutation inputs are three-state: omitted leaves the stored
Expand Down Expand Up @@ -2273,7 +2263,7 @@ async def _execute_typed_task(
timestamp=datetime.now(timezone.utc).isoformat(),
)
)
parsed_result: TaskResult[Any] = self.adapter._parse_response(raw_result, response_type)
parsed_result = self.adapter._parse_response(raw_result, response_type)
if task_type == "refine_proposals" and isinstance(raw_result.data, Mapping):
metadata = dict(parsed_result.metadata or {})
metadata[WIRE_RESPONSE_METADATA_KEY] = raw_result.data
Expand Down Expand Up @@ -3995,20 +3985,7 @@ async def get_adcp_capabilities(
)
)

return self.adapter._parse_response(
raw_result, self._identity_response_type(GetAdcpCapabilitiesResponse)
)

def _identity_response_type(self, response_type: type[R]) -> type[R]:
"""Use the pinned draft response wrappers only for opted-in clients."""
if self.experimental_agent_identity:
from adcp.types import experimental_identity

if response_type is GetPrincipalResponse:
return cast(type[R], experimental_identity.GetPrincipalResponse)
if response_type is GetAdcpCapabilitiesResponse:
return cast(type[R], experimental_identity.GetAdcpCapabilitiesResponse)
return response_type
return self.adapter._parse_response(raw_result, GetAdcpCapabilitiesResponse)

@_task_options_method
async def sync_agent_notification_configs(
Expand Down Expand Up @@ -6179,7 +6156,6 @@ def _parse_webhook_result(
return cast(TaskResult[AdcpAsyncResponseData], projected)
response_type = response_type_map.get(task_type)
if response_type:
response_type = self._identity_response_type(response_type)
try:
parsed_result: Any = parse_json_or_text(result, response_type)
metadata: dict[str, Any] = {
Expand Down Expand Up @@ -6746,7 +6722,6 @@ def __init__(
canonical_format_legacy_resolver: CanonicalFormatLegacyResolver | None = None,
allow_unauthenticated_webhooks: bool | Mapping[str, bool] = False,
httpx_client_factory: MCPHttpxClientFactory | None = None,
experimental_agent_identity: bool = False,
product_property_policy: ProductPropertyPolicy | None = None,
):
"""
Expand All @@ -6756,8 +6731,6 @@ def __init__(
agents: List of agent configurations
product_property_policy: Optional property-list policy forwarded to
each per-agent client.
experimental_agent_identity: Enable draft identity response types for
every per-agent client; WBA signing also enables them.
webhook_url_template: Template for webhook URLs
webhook_secret: Shared secret for the deprecated HMAC-SHA256 webhook
fallback. Configure only for registrations that explicitly
Expand Down Expand Up @@ -6835,7 +6808,6 @@ def __init__(
webhook_secret=webhook_secret,
on_activity=on_activity,
signing=signing,
experimental_agent_identity=experimental_agent_identity,
product_property_policy=product_property_policy,
httpx_client_factory=(
httpx_client_factory if agent.protocol == Protocol.MCP else None
Expand All @@ -6857,7 +6829,6 @@ def __init__(
webhook_secret=webhook_secret,
on_activity=on_activity,
signing=signing,
experimental_agent_identity=experimental_agent_identity,
product_property_policy=product_property_policy,
httpx_client_factory=(
httpx_client_factory if agent.protocol == Protocol.MCP else None
Expand Down
Loading
Loading