fix: use correct PYPY_API_TOKEN secret for PyPI publishing#8
Merged
Conversation
Previous revert commit didn't actually change the secret name. The correct secret name in the repository is PYPY_API_TOKEN. This should fix the 403 Forbidden error when publishing to PyPI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Document key learnings from building the Python AdCP SDK: **Type Safety & Code Generation:** - Auto-generate Pydantic models from upstream schemas - Handle missing schema types with documented type aliases - Use TYPE_CHECKING for optional dependencies - Use cast() for JSON deserialization type safety **Testing Strategy:** - Mock at the right level (_get_client() not httpx class) - httpx response.json() is SYNCHRONOUS not async - Test the API as it exists, not as we wish it existed **CI/CD & Release:** - Verify secret names before changing them (PYPY_API_TOKEN not PYPI_API_TOKEN) - Release Please automates version bumps and PyPI publishing - Entry points in pyproject.toml enable uvx usage **Python Patterns:** - String escaping order matters (backslash first, then quotes) - Atomic file operations for config files - Connection pooling for HTTP clients - Python 3.10+ required for | union syntax 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This was referenced Apr 20, 2026
bokelley
added a commit
that referenced
this pull request
Apr 20, 2026
Closes 5 items from salesagent's feedback on adopting adcp.server in one cohesive server/transport surface change. SkillMiddleware parity across transports (#7) --------------------------------------------- The A2A executor's per-skill middleware (PR #233) is now available on MCP too. Same SkillMiddleware type alias, same composition semantics (outermost-first, _step recursion), same call_next contract — a middleware list written against one transport works unchanged on the other. - src/adcp/server/serve.py: new module-level _dispatch_with_middleware that A2A's _dispatch_with_middleware delegates to. - create_mcp_server, _register_handler_tools, _register_tool accept middleware=[SkillMiddleware]; _register_tool wraps caller in the chain between context build and handler invocation. - serve() already exposed the kwarg for A2A; now forwards to MCP too. BearerTokenAuthMiddleware in adcp.server.auth (#1) -------------------------------------------------- The pattern in examples/mcp_with_auth_middleware.py was four security-critical concerns (ContextVar carrier, constant-time compare, discovery bypass, reset-in-finally); every downstream copy-pasted it. Now shipped as a class. - src/adcp/server/auth.py: BearerTokenAuthMiddleware, Principal (frozen dataclass), TokenValidator, auth_context_factory, constant_time_token_match. Seller supplies validate_token; framework owns the ContextVar plumbing, RFC 7235 scheme parsing (case- insensitive + whitespace-folded), discovery bypass, peek_jsonrpc with explicit request._body cache, fail-closed validator exception handling, principal metadata that can't shadow SDK audit keys. - examples/mcp_with_auth_middleware.py shrunk 243 → 89 lines. A2A message_parser hook (#3) ---------------------------- ADCPAgentExecutor._parse_request was hardcoded to DataPart({'skill': ..., 'parameters': ...}). Sellers fronting JSON-RPC or vendor-specific shapes had to subclass privately. - src/adcp/server/a2a_server.py: new MessageParser type alias, message_parser= kwarg on ADCPAgentExecutor, create_a2a_server, _serve_a2a, serve(). Default = _default_parse_request (was inline). Startup advertised-tools log (#9) --------------------------------- - src/adcp/server/serve.py: _log_advertised_tools() runs from _register_handler_tools (MCP) and create_a2a_server (A2A). INFO: 'X of Y tools advertised'; DEBUG: list of unadvertised. Custom tools doc (#8) --------------------- docs/handler-authoring.md: new section covering the @mcp.tool() passthrough on create_mcp_server's return value. Expert-review followups (security + code review) ------------------------------------------------- - _parse_bearer_header: case-insensitive scheme, folded whitespace. - validator exceptions → 401 (no stack-trace leak). - principal metadata can't shadow SDK-owned keys (tool_name, transport). - explicit request._body = body after peek. - tests use regex to match log messages (not positional tokens). - Python 3.10 skipif on two new A2A create_a2a_server tests (a2a-sdk starlette integration requires 3.11+; matches pre-existing skip). Tests ----- +53 tests across three new/modified test files. 1990 tests passing, mypy clean. Closes #224, #225, #226, #240, #241 salesagent feedback items #1, #3, #7, #8, #9. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
Apr 30, 2026
…design
Round-4 review pass synthesizes (a) the TS team's review of the parallel
@adcp/client port (PR #1005, EmmaLouise2018), (b) the TS team's
decisioning-platform-python-port-v2.md RFC, and (c) Yahoo's ask for
typed framework-owned state threading on RequestContext.
Guiding principle ported from the TS port: "make it impossible for an
implementer to screw up via typing." Python can't match TS's
compile-time RequiredPlatformsFor<S> gate, but per-method typed
surfaces, runtime validate_platform fail-fast, and Protocol structural
matching close most of the gap.
Highlights:
- D15 NEW: typed RequestContext sub-readers (state + resolve).
- StateReader (sync) — find_by_object, find_proposal_by_id,
governance_context, workflow_steps. Lets platforms read prior
workflow context without re-querying their own DB.
- ResourceResolver (async) — property_list, collection_list,
creative_format. Framework-mediated cache + validation.
- Surface ships in v6.0 with no-op stub backings; impls fill in
for v6.1 (same gating as TS side). Locks the typed contract so
adopters write the right shape from day one.
- Round-4 changelog covers 8 cross-language items applied:
- D14 enum coverage (Emma #6)
- D7+serve() prod gate on InMemoryTaskRegistry (Emma #8)
- Dispatch AdcpError projection consistency (Emma #10)
- D6 sync-handoff register-before-cleanup race (Emma #11)
- validate_platform catches validator throws (Emma #16)
- Per-server status-change bus, not module-level singleton (Emma #17)
- AdcpError ACCOUNT_NOT_FOUND semantic narrowing (Emma #18)
- CI lint: examples can't reach into src/ (Emma #5)
- Bugs structurally avoided in our hybrid SalesResult[T] design
documented (Emma #2, #3, #13, design concern #14) — worth calling
out in foundation PR description; the framework-design choice gets
the credit.
- File plan additions: state.py, resolve.py, context.py extensions for
D15; four new test files for Round-4 regressions. Foundation PR
total grew from ~2475 to ~2965 lines.
- Items deferred to follow-up PRs: ErrorCode Literal codegen (Emma #19),
workflow-step/proposal/governance backing store (D15 v6.1),
tasks/get wire surface.
- TS-only items (no Python equivalent) explicitly enumerated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
Apr 30, 2026
Stage 3 final piece. Two public entry points wire the foundation layers together: - create_adcp_server_from_platform(platform, ...) → (handler, executor, registry) 3-tuple. Adopters wanting full control over MCP/A2A wiring use this seam. - serve(platform, ...) → one-call wrapper that builds the handler and starts the MCP server via adcp.server.serve. Most adopters use this. Forwards host/port/transport/etc. via **serve_kwargs. Wires per the dispatch design doc: - D5 ThreadPoolExecutor configurability: * executor= (BYO operator-vetted pool — operator owns lifecycle) * thread_pool_size= (size the framework-allocated default) * default min(32, cpu+4) with thread_name_prefix="adcp-decisioning-" * executor= and thread_pool_size= are mutually exclusive - Emma #8 production-mode gate on InMemoryTaskRegistry: * Reads ADCP_ENV (case-insensitive {"prod", "production"} — same convention as adcp.validation.client_hooks._default_response_mode) * Refuses to start in production with InMemoryTaskRegistry unless ADCP_DECISIONING_ALLOW_INMEMORY_TASKS=1 explicitly set * Custom durable registry bypasses the gate - D15 state_reader / resource_resolver kwargs plumbed through to PlatformHandler. - validate_platform called before handler construction; failure surfaces as AdcpError to the caller. 24 tests in test_decisioning_serve.py covering all the above scenarios. Foundation tests: 133 (+24). Full suite: 2513 passed, 17 skipped, 1 xfailed. ruff + mypy clean. Stage 3 complete. Stage 4 next: examples/hello_seller.py + integration tests + ruff lint rule banning examples reaching into src/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The PyPI publish step in Release Please workflow is failing with 403 Forbidden because it's using the wrong secret name.
Root Cause
Previous PRs (#6 and #7) changed the secret name from
PYPY_API_TOKENtoPYPI_API_TOKENand then attempted to revert it, but the revert didn't actually work.Solution
Change
PYPI_API_TOKENback toPYPY_API_TOKENwhich is the actual secret name in the repository.Testing
After merging, the next release will use the correct secret and should publish successfully to PyPI.
🤖 Generated with Claude Code
Co-Authored-By: Claude noreply@anthropic.com