aws / aws/bedrock-agentcore-sdk-typescript
[FEATURE] A2A protocol support in the runtime module (parity with Python SDK's serve_a2a)
- Dominant language
- TypeScript
- Stars
- 91
- Forks
- 31
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 5
Description
**Is your feature request related to a problem? Please describe.**
AgentCore Runtime supports hosting agents on the A2A protocol path (`serverProtocol: A2A`), and the Python SDK ships first-class support for it: `serve_a2a`, `build_a2a_app`, `build_runtime_url`, and `BedrockCallContextBuilder` in `bedrock_agentcore.runtime.a2a`. The TypeScript SDK has nothing equivalent — `BedrockAgentCoreApp` covers only the HTTP protocol path.
TypeScript teams building multi-agent systems on AgentCore today have to hand-roll the entire A2A container contract themselves: the JSON-RPC endpoint at `POST /`, the agent card at `/.well-known/agent-card.json` (including v0.3/v1.0 protocol-version compatibility), the `GET /ping` health check, extraction of the AgentCore-injected headers (session id, request id, `WorkloadAccessToken`, `OAuth2CallbackUrl`), the runtime header allowlist, and the `InvokeAgentRuntime` URL construction on the client side.
A further consequence of the gap: the identity wrappers (`withAccessToken` / `withApiKey`) rely on `getContext()` for the ambient `workloadAccessToken`, which only `BedrockAgentCoreApp` populates — so today they cannot work inside an A2A agent at all.
**Describe the solution you'd like**
A `bedrock-agentcore/runtime/a2a` subpath export that implements the AgentCore Runtime A2A container contract around the official `@a2a-js/sdk` (v1.x) server handlers, mirroring the Python SDK's surface and defaults:
- `serveA2A(options)` / `buildA2AApp(options)` — analogous to `serve_a2a` / `build_a2a_app`: JSON-RPC 2.0 at `POST /`, agent card with v0.3 legacy compat, `/ping` with an optional custom handler (`'Healthy' | 'HealthyBusy'`, falling back to `Healthy` if the handler throws), injectable `TaskStore` and `ServerCallContextBuilder`, port from `PORT` env or 9000, host auto-detection (`0.0.0.0` in containers, loopback otherwise), auto-built agent card when none is provided, and `AGENTCORE_RUNTIME_URL`-based card URL rewriting.
- **Context propagation into the existing runtime context** — the AgentCore-injected headers flow into the same `runWithContext`/`getContext` used by the HTTP path (the analog of Python's `BedrockAgentCoreContext`), so the identity wrappers work unchanged inside A2A executors, plus a `ServerCallContext.state` mirror for executors that prefer `requestContext.context.state` (the analog of `BedrockCallContextBuilder`).
- `isForwardableHeader` / header allowlist — the port of `is_forwardable_header` and `RESTRICTED_HEADERS` from the Python SDK's `runtime/models.py` (notably letting `traceparent`/`baggage` through for trace propagation).
- `buildRuntimeUrl(runtimeArn, region?)` — the analog of `build_runtime_url` for A2A clients calling deployed agents through `InvokeAgentRuntime`.
- `buildAgentCard` / `withJsonRpcUrl` — card construction helpers (Python builds its card internally; exposing these fits TS ergonomics).
`@a2a-js/sdk` and `express` would be **optional peerDependencies**, following the existing `playwright`/`@strands-agents/sdk` precedent, so the main package surface is unaffected for non-A2A users.
**Proposed API**
```typescript
import { serveA2A, buildAgentCard, getContext } from 'bedrock-agentcore/runtime/a2a'
import type { AgentExecutor } from '@a2a-js/sdk/server'
class MyExecutor implements AgentExecutor {
async execute(requestContext, eventBus) {
const ctx = getContext() // sessionId, requestId, workloadAccessToken, headers
// ... run any agent framework, publish A2A task events
}
async cancelTask(taskId, eventBus) {}
}
await serveA2A({
executor: new MyExecutor(),
agentCard: buildAgentCard({
name: 'research-agent',
description: 'Deep research specialist',
skills: [{ id: 'main', name: 'research', description: 'Research a topic' }],
}),
pingHandler: () => 'Healthy',
})
```
**Describe alternatives you've considered**
- *Fastify-native implementation instead of the `@a2a-js/sdk` Express adapters.* The HTTP path uses Fastify, so this would avoid adding `express` as an (optional) peer. `@a2a-js/sdk` exposes a transport-agnostic `JsonRpcTransportHandler`, so a Fastify mount is feasible — at the cost of hand-maintaining the card endpoint, v0.3 legacy compat, and JSON-RPC error mapping that the official Express adapters already provide. The Python SDK has the same duality precedent (its `serve_a2a` builds its own Starlette app alongside the HTTP path). We'd propose starting with the official adapters as optional peers, but we're open to either direction — flagging it here explicitly for maintainer input before the PR.
- *Publishing this as a separate community package.* Works, but the Python SDK ships A2A in the SDK itself, and context propagation into the identity wrappers genuinely belongs in the SDK (it needs the internal `runWithContext`).
**Use Case**
Multi-agent systems in TypeScript on AgentCore: a lead agent on the HTTP path delegating to specialist worker agents on the A2A path over SigV4-signed `InvokeAgentRuntime` calls. We built exactly this (with the Claude Agent SDK as the agent framework) and extracted the A2A hosting layer described here from it.
**Additional context**
We have a working implementation at feature parity with the Python SDK's `runtime/a2a.py`, developed against this repo's AGENTS.md/TESTING.md conventions (co-located tests, no `any`, TSDoc on all exports, ~96% line coverage on the new module, `npm run check` green). It has been validated end to end against a live AgentCore Runtime (eu-central-1): agent card discovery, blocking `message/send`, streaming `message/stream`, and CloudWatch-verified context propagation (session/request ids visible via `getContext()` inside the executor).
Branch: `krausexb/bedrock-agentcore-sdk-typescript@feat/a2a-runtime-support`
Deliberate deviations from Python we'd document in the PR (details there): camelCase `ServerCallContext.state` keys; `AGENTCORE_RUNTIME_URL` normalized to a trailing slash on advertised cards (Python passes it through verbatim — the slashless value breaks relative agent-card resolution for standards-compliant A2A clients); no executor introspection for the auto-card name (Python reads `executor.agent`, a Strands-specific convention).
**Would you be willing to contribute this feature?**
- [x] Yes, I would like to contribute this feature
- [ ] No, I'm just suggesting the idea
Contributor guide
Research direction
Start by reading the existing BedrockAgentCoreApp context path and the proposed runtime/a2a entry point, along with AGENTS.md and TESTING.md. Compare the requested APIs and context propagation with the Python SDK, then run npm run check; done requires the stated A2A flows, context behavior, and test coverage to pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- express, node.js, typescript
- Domain
- api, backend, backend-api-design
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100