StackA2A
generaltypescript

A2A Playground

36

by alis-exchange

Local CLI with Vue UI for testing A2A agents in your browser. Supports gRPC and JSON-RPC transports

Updated 2026-02-20Apache-2.0
Quality Score36/100
Community
0
Freshness
85
Official
30
Skills
10
Protocol
40
🔒 Security
20

Getting Started

1Clone the repository
$ git clone https://github.com/alis-exchange/a2a-playground
2Navigate to the project
$ cd a2a-playground
3Install dependencies
$ npm install
4Run the agent
$ npm start

Or connect to the hosted endpoint: https://github.com/alis-exchange/a2a-playground

README

a2a-playground

A local CLI that serves a Vue UI and proxies to an A2A (Agent-to-Agent) agent. Use it to test and interact with A2A agents in your browser. Supports both gRPC and JSON-RPC transports.

Quick start

With an A2A agent running (e.g. on port 8080), install and run:

go install github.com/alis-exchange/a2a-playground/cmd/a2a-playground@latest
a2a-playground --agent-url=localhost:8080

The browser opens at http://localhost:3000. Start chatting with your agent.

Installation

From release

No dependencies. Installs the binary for your platform:

curl -fsSL https://raw.githubusercontent.com/alis-exchange/a2a-playground/main/install.sh | bash

Or a specific version:

./install.sh -v v1.0.0

With Go

Requires Go 1.25+:

go install github.com/alis-exchange/a2a-playground/cmd/a2a-playground@latest

Or a specific version:

go install github.com/alis-exchange/a2a-playground/cmd/a2a-playground@v1.0.0

Run without installing (fetches and builds on first use):

# gRPC
go run github.com/alis-exchange/a2a-playground/cmd/a2a-playground@latest --agent-url=localhost:8080

# JSON-RPC
go run github.com/alis-exchange/a2a-playground/cmd/a2a-playground@latest --agent-url=http://localhost:8080/jsonrpc --jsonrpc

Note: go run and go install with a version (e.g. @v1.0.0) only work for releases where the embedded frontend was committed. Use the install script or build from source if a version fails with "no matching files found".

From source

Requires Go 1.25+, Node.js 20+, pnpm, and buf CLI:

make build

This generates protobuf code, builds the frontend, embeds it, and compiles the a2a-playground binary.

Usage

gRPC (default)

a2a-playground --agent-url=localhost:8080

JSON-RPC

a2a-playground --agent-url=http://localhost:8080/jsonrpc --jsonrpc

Flags

Flag Default Description
--agent-url localhost:8080 Agent endpoint. For gRPC: host:port. For JSON-RPC: full URL (e.g. http://localhost:8080/jsonrpc)
--grpc (when no protocol flag) Use gRPC transport (default)
--jsonrpc Use JSON-RPC over HTTP transport
--port 3000 HTTP port for the BFF
--no-open false Do not open the browser on start
--dev false Serve from app/dist on disk instead of embedded files

Authentication and headers

Use the Playground sidebar (right side) to configure:

  • Connection settings – Agent URL and transport (gRPC or JSON-RPC).
  • Authentication – Custom headers (e.g. Authorization, X-API-Key, X-Tenant-ID) sent with every request. These are persisted and forwarded by the BFF to the agent.
  • OAuth 2.0 – Client ID, Client Secret, Authorization URL, Token URL, and scope. Click Authorize Connection to sign in via a popup; the access token is then sent as Authorization: Bearer <token> on all A2A requests. The BFF forwards the incoming Authorization header to the agent.

OAuth presets – You can save the current OAuth config under a nickname and load it later from the Saved configs dropdown in the OAuth section. Presets are stored on disk in ~/.a2a-playground/oauth/<nickname>.json. Client secrets in these files are stored unencrypted; keep the directory permissions restrictive and do not commit the folder to version control. The folder includes a .gitignore so its contents are ignored by git. Nicknames may only contain letters, numbers, underscore, dot, and hyphen.

Architecture

┌─────────┐     ┌─────────────────────────────────────┐     ┌────────────────────────────┐
│ Browser │────▶│ BFF (static SPA + Connect proxy)    │────▶│ gRPC or JSON-RPC A2A agent  │
│         │     │ serves Vue app, proxies A2A RPC      │     │ (e.g. localhost:8080)      │
└─────────┘     └─────────────────────────────────────┘     └────────────────────────────┘

The BFF (Backend-for-Frontend) serves the static Vue SPA and proxies Connect-RPC requests to your A2A agent. You can use gRPC (default) or JSON-RPC depending on what your agent exposes.

Message flow (Frontend → BFF → Agent)

┌─────────────────────────────────────────────────────────────────────────────────────┐
 Frontend (Vue)                                                                      
  messages.ts  createA2AClient().sendStreamingMessage(req)                          
  a2aClient.ts: agentConfigInterceptor (X-A2A-Agent-URL, X-A2A-Agent-Protocol)       
                agentOAuthBearerInterceptor (Authorization: Bearer <token>)           
                agentHeadersInterceptor (X-A2A-Agent-Headers from Pinia store)        
└─────────────────────────────────────────────────────────────────────────────────────┘
                                        
                                         HTTP POST (Connect) to /a2a.v1.A2AService/*
                                         Headers: Authorization, X-A2A-Agent-Headers, etc.
                                        
┌─────────────────────────────────────────────────────────────────────────────────────┐
 BFF                                                                                  
  server.go: ExtractAgentHeaders(r)  WithAgentHeaders(ctx)  proxy handler          
  Protocol switch: cfg.Protocol ? NewJSONRPCProxy : NewGrpcProxy                     
└─────────────────────────────────────────────────────────────────────────────────────┘
                                                        
        ┌───────────┴───────────┐            ┌────────────┴────────────┐
         grpcProxy                           jsonrpcProxy             
         metadata.Append(...)                agentHeadersInterceptor  
          gRPC agent                        req.Meta.Append(...)     
         localhost:8080                       HTTP JSON-RPC agent    
        └───────────────────────┘             http://.../jsonrpc       
                                              └─────────────────────────┘

How it works

1. Frontend

  • User input flows through PlaygroundView into the messages store (app/src/pages/playground/store/messages.ts), which builds a SendMessageRequest and calls createA2AClient().sendStreamingMessage(request).
  • The Connect client (app/src/clients/a2aClient.ts) uses same-origin requests, so all A2A calls go to the BFF (e.g. http://localhost:3000).
  • Before every request, the Connect client adds: (1) agent config (X-A2A-Agent-URL, X-A2A-Agent-Protocol) from agentConnection store; (2) Authorization: Bearer <token> from agentOAuth store when the user has completed OAuth; (3) custom headers as X-A2A-Agent-Headers (JSON) from agentHeaders store. These are configured in the Playground sidebar and (for headers) can be persisted to localStorage.

2. BFF routing and headers

  • The BFF (internal/bff/server.go) mounts two handler groups: the A2A Connect proxy at /a2a.v1.A2AService/ and the static SPA at /.
  • All A2A requests pass through middleware that builds the agent headers from the incoming request (internal/bff/headers.go): it parses X-A2A-Agent-Headers (JSON) and merges in the request’s Authorization header so OAuth Bearer tokens are forwarded. The result is put into the request context; the chosen proxy sends these headers to the agent.

3. Protocol switching

  • The CLI (cmd/a2a-playground/main.go) sets --jsonrpc or defaults to gRPC. That value is passed into ServerConfig.Protocol.
  • NewServer checks cfg.Protocol: if JSON-RPC, it creates NewJSONRPCProxy(cfg.AgentURL); otherwise NewGrpcProxy(cfg.AgentURL). Both proxies implement A2AServiceHandler, so routing stays the same; only the outbound transport differs.

4. gRPC proxy

  • internal/bff/proxy.go: The gRPC proxy uses go.alis.build/client to connect to the agent. Before each call, withAgentHeaders(ctx) reads headers from context and adds them to metadata.NewOutgoingContext. Those metadata entries become gRPC headers on the agent call. The proxy forwards Connect requests as native gRPC calls.

5. JSON-RPC proxy

  • internal/bff/jsonrpc_proxy.go: The JSON-RPC proxy uses a2aclient from a2a-go with WithJSONRPCTransport. It converts Connect/protobuf requests to JSON-RPC using pbconv.FromProto* and pbconv.ToProto*. The proxy is built with WithInterceptors(&agentHeadersInterceptor{}), which reads headers from context and appends them to req.Meta; the a2a-go client sends Meta as HTTP headers to the agent's JSON-RPC endpoint. ListTasks is not supported over JSON-RPC per the A2A spec and returns CodeUnimplemented.

Development

Run in dev mode

Uses app/dist on disk instead of embedded files (no rebuild needed after frontend changes):

make run

This runs with --dev --agent-url=localhost:8080.

Generate protobuf only

make generate

Runs buf generate in packages/a2a (generates TS into app/packages/a2a/protobuf and Go into gen/go).

Frontend development

cd app && pnpm dev

Build frontend for production:

cd app && pnpm build

Releasing

The binary embeds app/dist at build time. For go run and go install with a version to work, the built frontend must be committed before tagging:

make generate build-frontend
git add app/dist
git commit -m "chore: embed frontend for v1.0.0"
git tag v1.0.0
git push origin main --tags

The release workflow then builds the cross-platform binaries and attaches them to the GitHub release.

Project structure

Directory Description
cmd/a2a-playground/ CLI entrypoint
internal/bff/ BFF server, static serving, Connect proxy (gRPC or JSON-RPC)
packages/a2a/ Proto definitions and buf config (A2A canonical)
app/ Vue 3 + Vuetify SPA
gen/go/ Generated Connect handlers (uses a2a-go/a2apb)

Contributing

  1. Fork and open a PR
  2. Use conventional commits
  3. Run tests: go test ./...

License

See LICENSE.

Capabilities

StreamingPush NotificationsMulti-TurnAuth: none
a2a-agenta2a-client

Related Articles

View on GitHub