Repository navigation
feat: add broker-mode ServiceX client factory (--broker-url) - #3
Conversation
Adds BrokerServiceXClientFactory and _BrokerServiceXAdapter, mirroring BearerTokenClientFactory's shape but authenticating clients via an AF Broker Identity Token (forwarded by af-mcp-broker's aggregator instead of a ServiceX personal refresh token. The actual ServiceX access token is redeemed lazily from the broker's POST /v1/credentials/servicex/redeem via a ServiceXRedeemer protocol, satisfied by af-credentials' ProxyClient once its kind=servicex support ships (maniaclab/af-credentials#9). Closes #2 Assisted-by: Claude Sonnet 5 <noreply@anthropic.com> EOF )
af-credentials' published conda-forge build depends on python>=3.11 (verified via `pixi search`), which conflicted with this project's py310 pixi environment once af-credentials became a hard runtime dependency for broker mode (server.py imports it unconditionally at module level, so the whole package -- not just broker mode -- was unimportable under 3.10). Raise the floor to python>=3.11 everywhere (pyproject.toml classifiers, mypy/pylint target versions, pixi.toml's py310 feature/environment, CI matrix) rather than special-casing the import, since af-credentials is now a first-class dependency for this project's HTTP transport, not an optional extra. Also fixes two lint issues surfaced by re-locking with a synced dev environment: the mypy pre-commit hook's additional_dependencies was missing af-credentials entirely (mypy was silently treating the whole module as untyped and flagging the deliberate type: ignore as unused), and ruff's UP017 rule now fires under the 3.11 floor (datetime.UTC alias). Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
|
Update: dropped python 3.10 support project-wide (
All environments (py311/py312/py313) green: 297 tests each, mypy/ruff/pylint(10.00/10)/pre-commit all clean. |
af-credentials v0.3.1 (kind="servicex"/access_token() support, maniaclab/af-credentials#9) is now built on conda-forge, so pixi.lock picks it up directly. Tighten the version constraint to >=0.3.1 in pyproject.toml and pixi.toml (0.3.0 predates the feature entirely -- the previous >=0.3.0 pin would have let a fresh install resolve a version missing it). Drop the cast()/type: ignore workaround at server.py's ProxyClient construction site now that af-credentials is genuinely resolvable by mypy: it surfaced a real structural mismatch, not just the stale "unreleased interface" placeholder -- af-credentials' ServiceXAccessToken is a frozen dataclass, but the ServiceXRedeemer protocol's _RedeemedAccessToken declared access_token as a plain (read-write) attribute, which PEP 544 protocols require write access for by default. Redeclared it as a read-only @Property, which is all this code actually needs and what frozen dataclasses can satisfy. Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
|
Update: af-credentials 0.3.1 is now built on conda-forge, so That workaround turned out to be catching a real bug, not just a placeholder for an unreleased interface: All environments (py311/py312/py313) still green: 297 tests each, mypy/ruff/pylint(10.00/10)/pre-commit all clean against the real, released interface (no more structural-typing workaround needed). |
Summary
Adds
--broker-urland a second HTTP-mode auth factory,BrokerServiceXClientFactory, alongside the existing bridge-modeBearerTokenClientFactory.--broker-urlCLI flag (cli.py): when set,--transport httpruns in broker mode — clients authenticate with an AF Broker Identity Token (forwarded byaf-mcp-broker's aggregator), not a ServiceX personal refresh token._BrokerServiceXAdapter(auth/factory.py): aServiceXAdaptersubclass that overrides only_get_token()to redeem the access token from the broker instead of exchanging a refresh token via ServiceX's own/token/refresh._get_authorization()'s existing JWT-expiry-aware caching is untouched, so this re-redeems exactly as lazily as the stock path re-refreshes.BrokerServiceXClientFactory: mirrorsBearerTokenClientFactory's per-session caching exactly; no redeem call happens inget_client()itself — it's deferred to the adapter's own lazy_get_token().ServiceXRedeemerprotocol: the redeem call is injected via structural typing rather than a hard import ofaf_credentials.proxy.ProxyClient, soauth/factory.py's tests stay independent of af-credentials' release status. The one concreteProxyClient(broker_url, kind="servicex")construction lives inserver.py.classify_error(tools/_helpers.py): two new branches distinguishing "no ServiceX credential linked at the broker" (ProxyNotAvailableError) from "broker/servicex-token-service infra failure" (ProxyRedeemError) — these propagate automatically through every existing tool call'sexcept Exception as exc: return classify_error(exc), no per-tool changes needed._make_broker_http_mcp(server.py): broker mode registers no OAuth 2.1 authorization server of its own and no/bridgeroutes — the broker's aggregator already authenticated the caller.resource_url-derived transport security (DNS-rebinding protection) still applies uniformly viaserve_http.Depends on / coordinate with
POST /v1/credentials/servicex/redeem(no ServiceX identity-provider type exists yet).ProxyClient'skind="servicex"+access_token()support. This repo has noaf-credentialsdependency today; this PR adds it (af-credentials>=0.3.0, the current released version). The specific interface this PR calls (access_token(),kind="servicex") isn't in that version yet — the one construction site inserver.pycarries# type: ignore[arg-type]with a tracking comment, and a version bump will be needed once af-credentials#9 ships. Everything is written and tested against the interface as specified in that issue.Known follow-up
pixi.lockcould not be regenerated in this environment: this workspace'sexclude-newer = "7d"policy excludesaf-credentials 0.3.0(uploaded within the last 7 days as of this PR).pyproject.toml/pixi.tomlcorrectly declare the dependency;pixi.lockwill needpixi lock/pixi installre-run once that window passes (orexclude-neweris bumped).Test plan
_BrokerServiceXAdapter,build_broker_servicex_client,BrokerServiceXClientFactory, the two newclassify_errorbranches,--broker-urlCLI wiring, and_make_broker_http_mcp/serve_httpbroker-mode dispatch.integration/live tests) green.mypy,ruff check,pylint(10.00/10),pydocstyleall clean.af-credentials==0.3.0actually installed (realProxyClient/ProxyNotAvailableError/ProxyRedeemErrorimports, not mocks) sincepixi installis blocked by the lockfile issue above.🤖 Generated with Claude Code