aws / aws/bedrock-agentcore-sdk-typescript

[FEATURE] A2A protocol support in the runtime module (parity with Python SDK's serve_a2a)

Open
#228 0 comments 0 reactions 0 assignees View on GitHub
enhancement
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.