Skip to main content
Archived

This project has been archived by its maintainers, and is no longer receiving any updates.

KeycardAI Agents

Framework-agnostic agent service SDK for A2A (Agent-to-Agent) delegation with Keycard OAuth authentication.

Requirements

  • Python 3.10 or greater
  • Virtual environment (recommended)

Features

  • 🔐 Built-in OAuth: Automatic JWKS validation, token exchange, delegation chains
  • 🌐 Dual Protocol Support: A2A JSONRPC + custom REST endpoints (same executor powers both)
  • 🔧 Framework Agnostic: Supports CrewAI, LangChain, custom via AgentExecutor protocol
  • 🔄 Service Delegation: RFC 8693 token exchange preserves user context
  • 👤 User Auth: PKCE OAuth flow with browser-based login

A2A Protocol Integration

We use a2a-python SDK for protocol compliance while adding production-ready authentication:

  • Full A2A JSONRPC support - Standards-compliant /a2a/jsonrpc endpoint
  • Plus simpler REST endpoint - Custom /invoke for easier integration
  • Production OAuth layer - BearerAuthMiddleware, JWKS, token exchange (A2A SDK has none)
  • Delegation chain tracking - JWT-based audit trail for service-to-service calls
  • Dual protocol support - Same executor powers both JSONRPC and REST endpoints

Result: A2A standards compliance + Keycard security + flexible APIs = Best of both worlds

Installation

pip install keycardai-agents

# With CrewAI support
pip install 'keycardai-agents[crewai]'

Quick Start

CrewAI Service

import os
from crewai import Agent, Crew, Task
from keycardai.agents import AgentServiceConfig
from keycardai.agents.integrations.crewai import CrewAIExecutor
from keycardai.agents.server import serve_agent

def create_my_crew():
    agent = Agent(role="Assistant", goal="Help users", backstory="AI helper")
    task = Task(description="{task}", agent=agent, expected_output="Response")
    return Crew(agents=[agent], tasks=[task])

config = AgentServiceConfig(
    service_name="My Service",
    client_id=os.getenv("CLIENT_ID"),
    client_secret=os.getenv("CLIENT_SECRET"),
    identity_url="http://localhost:8000",
    zone_id=os.getenv("ZONE_ID"),
    agent_executor=CrewAIExecutor(create_my_crew),  # Framework adapter
    capabilities=["assistance"],
)

serve_agent(config)  # Starts server with OAuth middleware

Custom Executor

from keycardai.agents.server import LambdaExecutor

def my_logic(task, inputs):
    return f"Processed: {task}"

config = AgentServiceConfig(
    # ... same config as above
    agent_executor=LambdaExecutor(my_logic),  # Simple function wrapper
)

Advanced: Custom Executor Class

from keycardai.agents.server import AgentExecutor

class MyFrameworkExecutor:
    """Implement AgentExecutor protocol for any framework."""

    def execute(self, task, inputs):
        # Your framework logic here
        result = my_framework.run(task, inputs)
        return result

    def set_token_for_delegation(self, access_token):
        # Optional: handle delegation token
        self.context.set_auth(access_token)

config = AgentServiceConfig(
    # ...
    agent_executor=MyFrameworkExecutor(),
)

Client Usage

User Authentication (PKCE)

from keycardai.agents.client import AgentClient

async with AgentClient(config) as client:
    # Automatically: OAuth discovery → Browser login → Token exchange
    result = await client.invoke("https://service.com", task="Hello")

Service-to-Service (Token Exchange)

from keycardai.agents.server import DelegationClient

client = DelegationClient(service_config)

# Get delegation token (RFC 8693) - preserves user context
token = await client.get_delegation_token(
    "https://target.com",
    subject_token="user_token"
)

# Invoke with token
result = await client.invoke_service(
    "https://target.com",
    task="Process data",
    token=token
)
# Result includes delegation_chain: ["service_a", "service_b"]

Architecture

Server

Your Agent
  ↓
AgentExecutor.execute(task, inputs)
  ↓
AgentServer (keycardai-agents)
  ├─ OAuth Middleware (BearerAuthMiddleware)
  │  ├─ JWKS validation
  │  ├─ Token audience check
  │  └─ Delegation chain extraction
  ├─ /invoke (protected, REST-like)
  ├─ /a2a/jsonrpc (protected, A2A JSONRPC)
  │  ├─ message/send
  │  ├─ message/stream
  │  └─ tasks/* (get, cancel, list)
  ├─ /.well-known/agent-card.json (A2A format)
  ├─ /.well-known/oauth-protected-resource
  └─ /status

Dual Protocol Support

The SDK provides two ways to invoke agents:

  1. A2A JSONRPC (/a2a/jsonrpc) - Standards-compliant

    • Use when: Integrating with A2A ecosystem, need standard protocol
    • Methods: message/send, message/stream, tasks/get, etc.
    • Bridge: KeycardToA2AExecutorBridge adapts your executor to A2A protocol
  2. Custom REST (/invoke) - Simpler API

    • Use when: Direct service calls, simpler integration
    • Format: {"task": "...", "inputs": {...}}
    • Direct executor invocation

Both endpoints share the same underlying executor - write once, support both protocols.

OAuth Flow

User → OAuth Login (PKCE)
  ↓
User Token → Service A
  ↓
Service A → Token Exchange (RFC 8693) → Service B Token
  ↓
Service A → Calls Service B with Service B Token
  ↓
Service B validates token (JWKS)
Service B updates delegation_chain

A2A Protocol Compliance

Agent Card

Services expose A2A-compliant agent cards at /.well-known/agent-card.json:

{
  "name": "My Service",
  "url": "https://my-service.com",
  "version": "1.0.0",
  "protocolVersion": "0.3.0",
  "skills": [
    {
      "id": "assistance",
      "name": "Assistance",
      "description": "assistance capability",
      "tags": ["assistance"]
    }
  ],
  "capabilities": {
    "streaming": false,
    "multiTurn": true
  },
  "additionalInterfaces": [
    {
      "url": "https://my-service.com/invoke",
      "transport": "http+json"
    }
  ],
  "securitySchemes": {
    "oauth2": {
      "type": "oauth2",
      "flows": {
        "authorizationCode": {
          "authorizationUrl": "https://zone.keycard.cloud/oauth/authorize",
          "tokenUrl": "https://zone.keycard.cloud/oauth/token"
        }
      }
    }
  }
}

Endpoints

A2A JSONRPC Endpoint (Standards-Compliant)

POST /a2a/jsonrpc
Authorization: Bearer <token>
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "method": "message/send",
  "params": {
    "message": {
      "role": "user",
      "parts": [{"text": "Do something"}]
    }
  },
  "id": 1
}

Response:

{
  "jsonrpc": "2.0",
  "result": {
    "task": {
      "taskId": "task-123",
      "state": "completed",
      "result": {...}
    }
  },
  "id": 1
}

Supported methods:

  • message/send - Send message to agent
  • message/stream - Stream agent responses
  • tasks/get - Get task status
  • tasks/cancel - Cancel running task
  • tasks/list - List all tasks

Custom REST Endpoint (Simpler API)

POST /invoke
Authorization: Bearer <token>

{
  "task": "Do something",
  "inputs": {"key": "value"}
}

Response:

{
  "result": "Done",
  "delegation_chain": ["service_a", "service_b"]
}

Use /invoke for: Direct service calls, easier integration, delegation chain tracking.

Use /a2a/jsonrpc for: A2A ecosystem integration, standard protocol compliance, task management.

Framework Support

CrewAI

from keycardai.agents.integrations.crewai import CrewAIExecutor

executor = CrewAIExecutor(lambda: create_my_crew())

Features:

  • Automatic delegation token context
  • Supports CrewAI tools
  • Handles crew.kickoff() execution

LangChain, AutoGen, Custom

Implement the AgentExecutor protocol:

class MyExecutor:
    def execute(self, task, inputs):
        # Your logic
        return result

API Reference

AgentServiceConfig

@dataclass
class AgentServiceConfig:
    service_name: str              # Human-readable name
    client_id: str                 # Keycard Application client ID
    client_secret: str             # Keycard Application secret
    identity_url: str              # Public URL
    zone_id: str                   # Keycard zone ID
    agent_executor: AgentExecutor  # REQUIRED: Executor instance

    # Optional
    authorization_server_url: str | None = None
    port: int = 8000
    host: str = "0.0.0.0"
    description: str = ""
    capabilities: list[str] = []

AgentExecutor Protocol

class AgentExecutor(Protocol):
    def execute(
        self,
        task: dict[str, Any] | str,
        inputs: dict[str, Any] | None = None,
    ) -> Any:
        """Execute agent task."""
        ...

    def set_token_for_delegation(self, access_token: str) -> None:
        """Optional: Set token for delegation."""
        ...

KeycardToA2AExecutorBridge

Bridge adapter that makes your executor work with A2A JSONRPC protocol:

from keycardai.agents.server import KeycardToA2AExecutorBridge, SimpleExecutor

# Your executor
executor = SimpleExecutor()

# Wrap for A2A JSONRPC support
a2a_executor = KeycardToA2AExecutorBridge(executor)

# Now works with A2A DefaultRequestHandler
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore

handler = DefaultRequestHandler(
    agent_executor=a2a_executor,
    task_store=InMemoryTaskStore()
)

What it does:

  • Converts A2A RequestContext → Keycard task/inputs format
  • Calls your synchronous executor
  • Publishes result as A2A Task events
  • Handles delegation tokens

Note: This bridge is automatically configured when using serve_agent() - you don't need to use it directly unless building custom A2A integrations.

serve_agent()

Start an agent service (blocking):

serve_agent(config: AgentServiceConfig) -> None

AgentClient

User authentication with PKCE OAuth:

from keycardai.agents.client import AgentClient

async with AgentClient(service_config) as client:
    result = await client.invoke(service_url, task, inputs)
    agent_card = await client.discover_service(service_url)

DelegationClient

Service-to-service with token exchange:

from keycardai.agents.server import DelegationClient

client = DelegationClient(service_config)
token = await client.get_delegation_token(target_url, subject_token)
result = await client.invoke_service(url, task, token)

Service Delegation

Pattern

# In Service A (orchestrator)
from keycardai.agents.server import DelegationClient

client = DelegationClient(service_a_config)

# Discover Service B
card = await client.discover_service("https://service-b.com")

# Get token with user context
token = await client.get_delegation_token(
    "https://service-b.com",
    subject_token=user_access_token
)

# Call Service B
result = await client.invoke_service(
    "https://service-b.com",
    task="Process data",
    token=token
)

# Result includes delegation chain for audit
print(result["delegation_chain"])
# ["user_service", "service_a", "service_b"]

Delegation Chain Tracking

  1. User authenticates → Token with empty delegation_chain
  2. User calls Service A → Service A adds itself to chain
  3. Service A calls Service B → Token exchange preserves chain
  4. Service B adds itself → Full chain in response for audit

Production Deployment

Environment Variables

# Required
export KEYCARD_ZONE_ID="your_zone_id"
export KEYCARD_CLIENT_ID="service_client_id"
export KEYCARD_CLIENT_SECRET="client_secret"
export SERVICE_URL="https://your-service.com"

# Optional
export PORT="8000"
export HOST="0.0.0.0"

Health Checks

# Liveness
curl https://your-service.com/status

# Agent card
curl https://your-service.com/.well-known/agent-card.json

Security

  • Token Validation: JWKS-based JWT signature verification
  • Audience Check: Token aud must match service URL
  • Issuer Validation: Token iss from Keycard zone
  • Delegation Chain: Preserved for audit trail

Examples

See examples/ directory:

  • oauth_client_usage.py - PKCE user authentication

FAQ

Q: Why not use the A2A SDK server?

A: The A2A SDK has no authentication layer. We'd have to rebuild all OAuth infrastructure.

Q: Can I use LangChain/AutoGen?

A: Yes! Implement the AgentExecutor protocol or use LambdaExecutor for simple functions.

Q: What's the difference between AgentClient and DelegationClient?

A:

  • AgentClient: User authentication with PKCE (browser-based login)
  • DelegationClient: Service-to-service with token exchange (RFC 8693)

Q: Do I need CrewAI?

A: No! Use any framework or write custom logic. Just implement AgentExecutor.

Support

License

MIT

Release files for keycardai-agents 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for keycardai-agents 0.2.0
File Size Uploaded
keycardai_agents-0.2.0.tar.gz 32.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for keycardai-agents 0.2.0
File Interpreter ABI Platform
keycardai_agents-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 66.8 kB

Release files / keycardai_agents-0.2.0.tar.gz

Download URL keycardai_agents-0.2.0.tar.gz
Size 32.8 kB
Tags Source
SHA-256 checksum
How to use checksums
b52c2c5734781668ed9fcd1d82e54ee2f0a7ae162d5041a00bc4beeda8859d2b
BLAKE2b-256 checksum
How to use checksums
88a3aea6cabef8176b99bd831fe181bd3dd1b775f11e8a98fc37948ff35f1e64
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","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_agents-0.2.0-py3-none-any.whl

Download URL keycardai_agents-0.2.0-py3-none-any.whl
Size 34.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f621e32d9d5d8cf40bfa412176a801db2569e6590516225039a7dbd82720cea8
BLAKE2b-256 checksum
How to use checksums
7e4c96697b554d407273e6d903ec94d6e94cc4b68a4ef53312ab7e991d3e10cd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","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 history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page