Skip to main content

Framework-agnostic agent service SDK for A2A delegation with Keycard authentication. Supports CrewAI, LangChain, and custom agents.

This project has been archived.

The maintainers of this project have marked this project as archived. No new releases are expected.

Project description

KeycardAI Agents

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

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

keycardai_agents-0.1.1.tar.gz (31.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

keycardai_agents-0.1.1-py3-none-any.whl (34.0 kB view details)

Uploaded Python 3

File details

Details for the file keycardai_agents-0.1.1.tar.gz.

File metadata

  • Download URL: keycardai_agents-0.1.1.tar.gz
  • Upload date:
  • Size: 31.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.22 {"installer":{"name":"uv","version":"0.9.22","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}

File hashes

Hashes for keycardai_agents-0.1.1.tar.gz
Algorithm Hash digest
SHA256 fadb89cdff4e872db20a51fa93dc42e005cfa88839fb7b4b779df9b826e83bac
MD5 0711b96e58b16c512bc26e9adcc2bb20
BLAKE2b-256 c17515861e463adcef96da8f696c1d5d6989d9e752d39190a55e3698d04aa3bf

See more details on using hashes here.

File details

Details for the file keycardai_agents-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: keycardai_agents-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 34.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.22 {"installer":{"name":"uv","version":"0.9.22","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}

File hashes

Hashes for keycardai_agents-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 26149c4c085a35e22e06fd6c5cb79d4b9f5e4ec4aabf46e8edaded67e2a85da3
MD5 7bde64d4cc938a19767e09111c7ccce5
BLAKE2b-256 10470a7c6af6936963e0a9eeded5f1bef8c085ee5ee2a65088baee70905628d0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page