Skip to content

Commit dfec179

Browse files
bokelleyclaude
andauthored
feat: add V3 protocol support (Governance, Content Standards, SI, CLI) (#117)
* feat: add V3 protocol support (Governance, Content Standards, SI, CLI) Add comprehensive V3 ADCP protocol support including: Server Framework: - Add GovernanceHandler for property list management - Add ContentStandardsHandler for content calibration - Add SponsoredIntelligenceHandler for conversational AI sponsorship - Add governance method stubs to all protocol handlers - Add MCP tool definitions for all V3 operations Client: - Add typed client methods for all V3 operations - Export all V3 types from adcp.types Protocol Adapters: - Add governance methods to protocol base class - Add governance implementations to MCP and A2A adapters CLI: - Add all V3 operations to command-line client: - Protocol Discovery: get_adcp_capabilities - Content Standards: 7 operations - Sponsored Intelligence: 4 operations - Governance: 5 operations Types: - Sync schemas with ADCP 2.6+ spec - Generate types for V3 domains Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: resolve ruff linting errors - Fix import block sorting (I001) across multiple files - Remove unused import ContentStandards from content_standards.py - Update Callable import to collections.abc in mcp_tools.py - Fix line length (E501) in server/base.py Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: sync with ADCP 2.6 schema changes - Sync schemas from upstream adcontextprotocol.org - Consolidate pricing options (CpmAuction/CpmFixed -> CpmPricingOption, VcpmAuction/VcpmFixed -> VcpmPricingOption) - Consolidate PreviewRender (UrlPreviewRender/HtmlPreviewRender/BothPreviewRender -> unified PreviewRender) - Update type exports and aliases for backwards compatibility - Fix preview_cache.py to work with unified PreviewRender (no longer RootModel) - Update tests to reflect schema changes BREAKING CHANGE: CpmAuctionPricingOption, CpmFixedRatePricingOption, VcpmAuctionPricingOption, VcpmFixedRatePricingOption have been consolidated into CpmPricingOption and VcpmPricingOption respectively. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: resolve ruff import sorting error in _ergonomic.py Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: sync schemas with upstream and fix type generation - Fix generate_types.py to convert absolute /schemas/latest/ $ref paths to relative paths for proper schema resolution - Sync all schemas from upstream (ADCP package 2.6) - PreviewRender is now a discriminated union by output_format: - PreviewRender1 (output_format='url') with preview_url - PreviewRender2 (output_format='html') with preview_html - PreviewRender3 (output_format='both') with both - Update aliases: UrlPreviewRender, HtmlPreviewRender, BothPreviewRender now point to their respective variant types - Fix preview_cache.py to access RootModel.root for PreviewRender - Update tests for discriminated union types This fixes the "Validate schemas are up-to-date" CI failure. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: support authoritative_location redirects in fetch_adagents - Implements authoritative_location redirect following per AdCP spec - When adagents.json contains authoritative_location instead of authorized_agents, fetches the referenced URL for actual data - Includes loop detection and max depth limit (5) for security - Validates HTTPS requirement for authoritative_location URLs - Tests for happy path, HTTPS validation, loop detection, max depth Cherry-picked from PR #118 (bokelley/issue-114) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review feedback for V3 migration Handler Method Signatures: - Refactored GovernanceHandler, ContentStandardsHandler, and SponsoredIntelligenceHandler to fix Liskov Substitution Principle violation - Public methods now accept params: dict[str, Any] matching base class - Added Pydantic validation with proper error responses - Abstract handle_* methods accept typed requests for type safety CLI Dispatch Table: - Replaced mutable global TOOL_DISPATCH with cached _get_dispatch_table() - Fails fast with clear error message if types can't be imported - Eliminates late import error surprises Version Interop Tests: - Added test_version_interop.py to verify V3/V2 compatibility - Tests V3-only tools are in dispatch table - Tests V2 core tools remain available - Tests base handler returns 'not supported' for unimplemented V3 methods Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: rename summary() to model_summary() to avoid field collision The generated POC types have `summary` fields that conflicted with the AdCPBaseModel.summary() method, causing mypy errors. Renamed to model_summary() to follow Pydantic's naming convention (model_dump, model_dump_json, etc.) and avoid the collision. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add tests documenting version negotiation design Version negotiation is intentionally delegated to consumers. The SDK provides primitives (get_adcp_capabilities, TaskResult wrapping), not policy. Added TestVersionNegotiationDesign class with tests that serve as executable documentation for: - TaskResult error wrapping pattern - Capabilities response structure - Recommended version detection pattern - Recommended feature detection pattern Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: update pricing option examples to current schema format Updated test fixtures and documentation examples from deprecated format: {"model": "cpm_fixed_rate", "is_fixed": true, "cpm": 5.50} To current unified CpmPricingOption schema format: { "pricing_model": "cpm", "pricing_option_id": "po-1", "currency": "USD", "fixed_price": 5.50 } The old format used separate types with is_fixed discriminator. The new schema uses optional fixed_price/floor_price fields to distinguish fixed vs auction pricing. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: upgrade to ADCP 3.0.0-beta.1 schemas - Update ADCP_VERSION from 'latest' to '3.0.0-beta.1' - Sync schemas from https://adcontextprotocol.org/schemas/3.0.0-beta.1/ - Update generate_types.py to handle versioned schema paths (previously only handled /schemas/latest/, now handles /schemas/<version>/) - Regenerate Pydantic models from new schemas - All 518 tests pass - mypy passes with no issues Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * style: fix import sorting in _ergonomic.py Reorder imports to satisfy ruff I001 (isort) rules. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * ci: skip schema sync for pre-release versions Pre-release schema versions (alpha, beta, rc) may not be publicly accessible from CI environments. Skip the download/sync steps for these versions and rely on committed schemas instead. Validation steps (syntax check, import test, code generation tests) still run to ensure the committed schemas work correctly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5eca428 commit dfec179

471 files changed

Lines changed: 16076 additions & 3914 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,30 @@ jobs:
106106
python -m pip install --upgrade pip
107107
pip install -e ".[dev]"
108108
109+
- name: Check ADCP version
110+
id: version-check
111+
run: |
112+
VERSION=$(cat src/adcp/ADCP_VERSION)
113+
echo "ADCP_VERSION=$VERSION"
114+
# Check if version contains pre-release identifiers (alpha, beta, rc)
115+
if echo "$VERSION" | grep -qE '(alpha|beta|rc)'; then
116+
echo "is_prerelease=true" >> $GITHUB_OUTPUT
117+
echo "Pre-release version detected - will skip schema sync (may not be publicly accessible)"
118+
else
119+
echo "is_prerelease=false" >> $GITHUB_OUTPUT
120+
echo "Stable version - will sync schemas from upstream"
121+
fi
122+
109123
- name: Download latest schemas
124+
if: steps.version-check.outputs.is_prerelease != 'true'
110125
run: python scripts/sync_schemas.py
111126

112127
- name: Fix schema references
128+
if: steps.version-check.outputs.is_prerelease != 'true'
113129
run: python scripts/fix_schema_refs.py
114130

115131
- name: Generate models
132+
if: steps.version-check.outputs.is_prerelease != 'true'
116133
run: python scripts/generate_types.py
117134

118135
- name: Validate generated code syntax
@@ -132,6 +149,7 @@ jobs:
132149
pytest tests/test_code_generation.py -v --tb=short
133150
134151
- name: Check for schema drift
152+
if: steps.version-check.outputs.is_prerelease != 'true'
135153
run: |
136154
# Check if only generation timestamp changed (expected when CI regenerates)
137155
if git diff --exit-code src/adcp/types/_generated.py schemas/cache/ | grep -v "^[-+]Generation date:"; then

docs/examples/testing_patterns.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,10 @@ def test_get_products_response_deserializes_from_protocol_json(self):
6868
],
6969
"pricing_options": [
7070
{
71-
"model": "cpm_fixed_rate",
72-
"is_fixed": true,
73-
"cpm": 5.50
71+
"pricing_model": "cpm",
72+
"pricing_option_id": "po-premium-1",
73+
"currency": "USD",
74+
"fixed_price": 5.50
7475
}
7576
]
7677
}
@@ -182,7 +183,14 @@ async def test_buyer_discovers_products_for_coffee_campaign(self, mocker):
182183
"property_tags": ["morning", "lifestyle"],
183184
}
184185
],
185-
"pricing_options": [{"model": "cpm_fixed_rate", "is_fixed": True, "cpm": 4.50}],
186+
"pricing_options": [
187+
{
188+
"pricing_model": "cpm",
189+
"pricing_option_id": "po-breakfast-1",
190+
"currency": "USD",
191+
"fixed_price": 4.50,
192+
}
193+
],
186194
}
187195
]
188196
}
@@ -207,7 +215,9 @@ async def test_buyer_discovers_products_for_coffee_campaign(self, mocker):
207215

208216
# Assert: Can plan budget from pricing
209217
pricing = product.pricing_options[0]
210-
assert pricing.model in ["cpm_fixed_rate", "cpm_auction"]
218+
assert pricing.pricing_model == "cpm"
219+
# Fixed pricing has fixed_price, auction pricing has floor_price
220+
assert pricing.fixed_price is not None or pricing.floor_price is not None
211221

212222
@pytest.mark.asyncio
213223
async def test_buyer_handles_no_products_available(self, mocker):
@@ -412,7 +422,14 @@ def test_anti_pattern_importing_generated_poc(self):
412422
"property_ids": ["site1"],
413423
}
414424
],
415-
"pricing_options": [{"model": "cpm_fixed_rate", "is_fixed": True, "cpm": 5.0}],
425+
"pricing_options": [
426+
{
427+
"pricing_model": "cpm",
428+
"pricing_option_id": "po-test-1",
429+
"currency": "USD",
430+
"fixed_price": 5.0,
431+
}
432+
],
416433
}
417434

418435
product = Product.model_validate(product_json)
@@ -510,7 +527,14 @@ def sample_product_json():
510527
"property_ids": ["homepage", "mobile_app"],
511528
}
512529
],
513-
"pricing_options": [{"model": "cpm_fixed_rate", "is_fixed": True, "cpm": 5.50}],
530+
"pricing_options": [
531+
{
532+
"pricing_model": "cpm",
533+
"pricing_option_id": "po-premium-1",
534+
"currency": "USD",
535+
"fixed_price": 5.50,
536+
}
537+
],
514538
}
515539

516540

docs/testing-guide.md

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,10 @@ def test_get_products_response_deserializes_from_protocol_json():
7676
],
7777
"pricing_options": [
7878
{
79-
"model": "cpm_fixed_rate",
80-
"is_fixed": true,
81-
"cpm": 5.50
79+
"pricing_model": "cpm",
80+
"pricing_option_id": "po-premium-1",
81+
"currency": "USD",
82+
"fixed_price": 5.50
8283
}
8384
]
8485
}
@@ -191,7 +192,12 @@ async def test_buyer_discovers_products_for_coffee_campaign(mocker):
191192
}
192193
],
193194
"pricing_options": [
194-
{"model": "cpm_fixed_rate", "is_fixed": True, "cpm": 4.50}
195+
{
196+
"pricing_model": "cpm",
197+
"pricing_option_id": "po-breakfast-1",
198+
"currency": "USD",
199+
"fixed_price": 4.50,
200+
}
195201
],
196202
}
197203
]
@@ -221,7 +227,9 @@ async def test_buyer_discovers_products_for_coffee_campaign(mocker):
221227

222228
# Assert: Can plan budget from pricing
223229
pricing = product.pricing_options[0]
224-
assert pricing.model in ["cpm_fixed_rate", "cpm_auction"]
230+
assert pricing.pricing_model == "cpm"
231+
# Fixed pricing has fixed_price, auction pricing has floor_price
232+
assert pricing.fixed_price is not None or pricing.floor_price is not None
225233
```
226234

227235
### Example: Handle Empty Results
@@ -410,7 +418,12 @@ def sample_product_json():
410418
}
411419
],
412420
"pricing_options": [
413-
{"model": "cpm_fixed_rate", "is_fixed": True, "cpm": 5.50}
421+
{
422+
"pricing_model": "cpm",
423+
"pricing_option_id": "po-premium-1",
424+
"currency": "USD",
425+
"fixed_price": 5.50,
426+
}
414427
],
415428
}
416429
```
@@ -440,7 +453,12 @@ product_json = {
440453
"property_ids": ["site1"],
441454
}],
442455
"pricing_options": [
443-
{"model": "cpm_fixed_rate", "is_fixed": True, "cpm": 5.0}
456+
{
457+
"pricing_model": "cpm",
458+
"pricing_option_id": "po-test-1",
459+
"currency": "USD",
460+
"fixed_price": 5.0,
461+
}
444462
],
445463
}
446464

0 commit comments

Comments
 (0)