keycardai-a2a
Keycard auth primitives for a2a-sdk 1.x agent services. This package is glue, not a parallel server abstraction. Customers compose these primitives with a2a-sdk's standard route factories and request handler in their own Starlette / FastAPI app to get bearer token verification, OAuth metadata discovery, and OAuth 2.0 token exchange (RFC 8693) for downstream delegated calls.
What's in here
Server-side wiring:
KeycardServerCallContextBuilder: aServerCallContextBuildersubclass. Pass toa2a.server.routes.create_jsonrpc_routes. Propagates the verifiedKeycardUserontoServerCallContext.state.keycard_user(context): typed accessor executors call on theirRequestContextto get thatKeycardUserback (Nonewhen the request was unauthenticated), including theaccess_tokenfor delegated downstream calls.build_agent_card_from_config(config): produces a 1.x protobufAgentCard. Pass toa2a.server.routes.create_agent_card_routesanda2a.server.request_handlers.DefaultRequestHandler.
For the auth backend itself, use keycardai.starlette.KeycardAuthBackend(verifier, require_authentication=True) on the JSONRPC mount. The kwarg flips the default mixed-route behavior to "every path on this mount needs auth," which matches the JSONRPC dispatcher's lack of a per-route gate.
Outbound delegation:
DelegationClient,DelegationClientSync: server-to-server token exchange and JSONRPC invocation against another agent service.
Inbound discovery:
ServiceDiscovery: query a remote agent service's.well-known/agent-card.jsonwith caching.
Configuration:
AgentServiceConfig: service identity + Keycard credentials + agent card metadata.
Installation
pip install keycardai-a2a
This pulls in keycardai-oauth, keycardai-starlette, a2a-sdk[http-server]>=1.0.
Quick start
You already have an a2a-sdk server. Add the Keycard-protected A2A mount to your existing Starlette / FastAPI app:
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes
from a2a.server.tasks import InMemoryTaskStore
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.routing import Mount
from keycardai.a2a import (
AgentServiceConfig,
KeycardServerCallContextBuilder,
build_agent_card_from_config,
)
from keycardai.oauth.server.credentials import ClientSecret
from keycardai.starlette import AuthProvider, KeycardAuthBackend, keycard_on_error
from keycardai.starlette.routers.metadata import (
well_known_authorization_server_route,
well_known_protected_resource_route,
)
config = AgentServiceConfig(
service_name="My Agent",
client_id="...",
client_secret="...",
identity_url="https://my-agent.example.com",
zone_id="your-zone-id",
capabilities=["chat"],
)
auth_provider = AuthProvider(
zone_url=config.auth_server_url,
server_name=config.service_name,
server_url=config.identity_url,
# Bind accepted tokens to this service; leaving audience unset
# disables the audience check entirely.
audience=config.identity_url,
application_credential=ClientSecret((config.client_id, config.client_secret)),
)
verifier = auth_provider.get_token_verifier()
agent_card = build_agent_card_from_config(config)
request_handler = DefaultRequestHandler(
agent_executor=YourExecutor(), # subclass of a2a.server.agent_execution.AgentExecutor
task_store=InMemoryTaskStore(),
agent_card=agent_card,
)
# Add these routes to your existing Starlette / FastAPI app:
your_app.routes.extend(create_agent_card_routes(agent_card=agent_card))
your_app.routes.append(well_known_protected_resource_route(
issuer=config.auth_server_url,
resource="/.well-known/oauth-protected-resource{resource_path:path}",
))
your_app.routes.append(well_known_authorization_server_route(
issuer=config.auth_server_url,
resource="/.well-known/oauth-authorization-server{resource_path:path}",
))
your_app.routes.append(Mount(
"/a2a",
routes=create_jsonrpc_routes(
request_handler=request_handler,
rpc_url="/jsonrpc",
context_builder=KeycardServerCallContextBuilder(),
),
middleware=[
Middleware(
AuthenticationMiddleware,
backend=KeycardAuthBackend(verifier, require_authentication=True),
on_error=keycard_on_error,
),
],
))
Inside your AgentExecutor.execute(self, context, event_queue), read the verified caller with keycard_user(context) and use its access_token as the subject token in keycardai-oauth's TokenExchangeRequest for downstream API calls:
from keycardai.a2a import keycard_user
async def execute(self, context, event_queue):
caller = keycard_user(context)
if caller is None:
raise PermissionError("unauthenticated")
subject_token = caller.access_token # also caller.client_id, caller.scopes, caller.zone_id
The builder still writes the bare token under the legacy state["access_token"] key for executors written against keycardai-a2a 0.4.x.
For a runnable greenfield example (no existing app), see examples/keycard_protected_server/.
Protocol version
A server composed this way speaks A2A protocol 1.0: SendMessage under an A2A-Version: 1.0 header. All four Keycard SDKs send that generation, so no compatibility flag is needed between them:
- Python:
keycardai-a2a0.3.0 and later - TypeScript:
@keycardai/a2a0.4.0 and later - Go:
github.com/keycardai/go-sdkv0.22.0 and later (a2a.DelegationClient) - Ruby:
keycardai-a2a0.2.0 and later
A 0.3-generation caller (message/send with no A2A-Version header) is rejected by the a2a-sdk 1.x dispatcher with -32601 MethodNotFound. Such callers should upgrade; if one cannot, pass a2a-sdk's own enable_v0_3_compat=True to create_jsonrpc_routes yourself. This package no longer enables or recommends it.
Relationship to other Keycard packages
keycardai-oauth: OAuth 2.0 primitives used for token exchange and PKCE.keycardai-starlette: providesAuthenticationMiddleware+KeycardAuthBackendand the OAuth metadata route helpers used here.keycardai-mcp: sister package for MCP server protection. Same auth shape, different protocol.
History
This package was extracted from the original keycardai-agents package (KEP: Decompose keycardai-agents). The PKCE user-login client moved to keycardai-oauth. The keycardai-agents package itself is archived.
Metadata
Release files for keycardai-a2a 0.5.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| keycardai_a2a-0.5.1.tar.gz | 24.7 kB | Without provenance | Details for keycardai_a2a-0.5.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| keycardai_a2a-0.5.1-py3-none-any.whl | Python 3 | none | any | Details for keycardai_a2a-0.5.1-py3-none-any.whl |
Total release size: 42.0 kB
Release files / keycardai_a2a-0.5.1.tar.gz
| Download URL | keycardai_a2a-0.5.1.tar.gz |
|---|---|
| Size | 24.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f23b7e9e42c989105226046a2939cf9fdf7c5306df778523ed87b227c1e98659
|
|
BLAKE2b-256 checksum How to use checksums |
9120f8abb3fff0b9fa903d45b78d2f5cddde112d5fa3dd3170d000fed9c85d8b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / keycardai_a2a-0.5.1-py3-none-any.whl
| Download URL | keycardai_a2a-0.5.1-py3-none-any.whl |
|---|---|
| Size | 17.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eee8861ae201c0ebcff2be9d67bcfdb39845de7fb808419c75e6d1fd6431e98e
|
|
BLAKE2b-256 checksum How to use checksums |
962f6e943ab276ab113f377844e710e6933e10453c4c9024e275ab39c3b5c9a2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for keycardai-a2a 0.5.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.