vscode-a2a
README.md
# vscode-a2a
An [MCP](https://modelcontextprotocol.io) bridge that exposes [A2A](https://google.github.io/A2A) agents as tools in VS Code agent chat (GitHub Copilot, etc.).
Reads an `a2a.json` config, discovers each agent's card via `.well-known/agent-card.json`, and registers one MCP tool per agent. The LLM picks which agent to delegate to — multi-turn conversation is maintained automatically.
Compatible with A2A `protocolVersion` `0.3.0` and `1.0`, JSONRPC and REST transports, and SSE streaming.
## Debug Logging
`vscode-a2a` supports leveled debug logging through environment variables in `mcp.json`.
- `A2A_DEBUG_LEVEL`: integer `0` to `5`
Level guide:
1. High-level flow (`discovery-start`, `request-mode`, fallback success/error)
2. Request/response summaries (`http-request`, `http-response`, stream start/end)
3. Payload summaries and collected text
4. Event-shape diagnostics and SSE preview
5. Full raw event and buffer-churn logs (most verbose)
Example:
```jsonc
"vscode-a2a": {
"command": "node",
"args": ["${workspaceFolder}/vscode-a2a/dist/index.js"],
"type": "stdio",
"env": {
"A2A_DEBUG_LEVEL": "1"
}
}
```
## Installation
### Via npx (recommended — works in VS Code, Cursor, Windsurf, Claude Desktop)
Add to your `.vscode/mcp.json`:
```jsonc
{
"servers": {
"vscode-a2a": {
"command": "npx",
"args": ["-y", "vscode-a2a"],
"type": "stdio",
"env": {
"MY_AGENT_TOKEN": "${input:myAgentToken}"
}
}
},
"inputs": [
{
"id": "myAgentToken",
"type": "promptString",
"description": "Bearer token for your A2A agents",
"password": true
}
]
}
```
### From source
```bash
git clone https://github.com/kranthikirang/vscode-a2a
cd vscode-a2a && npm install && npm run build
```
Then point `mcp.json` at the built output:
```jsonc
"vscode-a2a": {
"command": "node",
"args": ["/path/to/vscode-a2a/dist/index.js"],
"type": "stdio"
}
```
## Configuration — `a2a.json`
Place `.vscode/a2a.json` (or `a2a.json` at project root) alongside your `mcp.json`. The bridge hot-reloads on save.
```jsonc
{
"agents": {
"my-agent": {
"url": "https://example.com/my-agent/",
// optional: override card discovery path or provide full card URL
"cardPath": ".well-known/agent-card.json"
}
}
}
```
### Agent card discovery
If `cardPath` is omitted the bridge tries in order:
1. `{url}/.well-known/agent-card.json`
2. `{url}/.well-known/agent.json`
If neither is reachable the agent is skipped (logged to stderr) and the bridge continues.
### Authentication
All secret values support `${env:VAR_NAME}` substitution — the variable is read from the process env injected by `mcp.json`.
#### `az_cli` — delegated user identity (recommended for developers)
No secrets required. Uses the user's existing `az login` session. The token carries the user's actual roles and groups, refreshes automatically.
```jsonc
// Shortest form — az_cli is the implicit default when no auth is specified
{
"agents": {
"my-agent": {
"url": "https://my-gateway.example.com/agw/my-agent/",
"resource": "api://your-azure-ad-app-client-id"
}
}
}
```
```jsonc
// Explicit form — same result, useful when mixing auth types across agents
"auth": {
"type": "az_cli",
"resource": "api://your-azure-ad-app-client-id"
}
```
`resource` is the Azure AD application (client) ID prefixed with `api://`. When omitted, the bridge attempts to discover it from the agent card's `securitySchemes`.
Prerequisite: `az login` must have been run at least once. VS Code terminals inherit this session automatically.
#### Bearer token
```jsonc
"auth": {
"type": "bearer",
"token": "${env:MY_AGENT_TOKEN}"
}
```
Pass the token via `mcp.json` env:
```jsonc
// .vscode/mcp.json
"env": { "MY_AGENT_TOKEN": "${input:myAgentToken}" }
```
#### API key
```jsonc
"auth": {
"type": "apikey",
"key": "${env:MY_API_KEY}",
"header": "X-API-Key" // optional, default: X-API-Key
}
```
#### OAuth2 client credentials (M2M / service principal)
```jsonc
"auth": {
"type": "oauth2",
"clientId": "your-client-id",
"clientSecret": "${env:MY_CLIENT_SECRET}",
"tokenUrl": "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token",
"scopes": ["api://your-app-id/.default"]
}
```
Tokens are cached and refreshed automatically 60 s before expiry.
### Full example — multiple agents, different auth per agent
```jsonc
// .vscode/a2a.json
{
"agents": {
// az_cli shorthand — uses developer's `az login` session
"prometheus-agent": {
"url": "https://my-gateway.example.com/agw/prometheus-agent/",
"resource": "api://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
// Same app registration, different agent — each gets its own resource
"k8s-agent": {
"url": "https://my-gateway.example.com/agw/k8s-agent/",
"resource": "api://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
// Different app registration (different environment or org)
"external-agent": {
"url": "https://partner.example.com/agw/external/",
"resource": "api://a1b2c3d4-0000-0000-0000-000000000000"
},
// M2M service principal (CI/CD, automation)
"ci-agent": {
"url": "https://my-gateway.example.com/agw/ci-agent/",
"auth": {
"type": "oauth2",
"clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"clientSecret": "${env:CI_CLIENT_SECRET}",
"tokenUrl": "https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token",
"scopes": ["api://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/.default"]
}
},
// Static bearer token (non-Azure endpoints)
"third-party-agent": {
"url": "https://api.thirdparty.example.com/agent/",
"auth": {
"type": "bearer",
"token": "${env:THIRD_PARTY_TOKEN}"
}
},
// API key authentication
"api-key-agent": {
"url": "https://api.example.com/agent/",
"auth": {
"type": "apikey",
"key": "${env:AGENT_API_KEY}",
"header": "X-API-Key"
}
}
}
}
```
## How it works
```
a2a.json → discover .well-known/agent-card.json
→ register one MCP tool per agent (name + all skills in description)
→ LLM calls tool with { message, start_fresh? }
→ bridge sends A2A message/stream to agent
→ collects streamed artifacts → returns final text to LLM
→ preserves task ID for multi-turn (input-required state)
```
### Stream fallback behavior
For some upstreams, the SDK streaming call can return `200 text/event-stream` with zero emitted events.
When that happens, the bridge automatically tries fallback paths:
1. SDK non-stream (`sendMessage`)
2. Raw legacy JSON-RPC stream (`message/stream`) with legacy payload shape
3. Raw legacy JSON-RPC non-stream (`message/send`) as last fallback
This keeps normal SDK behavior as primary, while recovering text from upstreams that only respond reliably to legacy stream payloads.
### Multi-turn
The bridge keeps the same A2A `contextId` per agent for the lifetime of the MCP process. When an agent enters `input-required` state, the bridge returns the agent's question to the LLM; the next tool call continues the same task. Pass `start_fresh: true` to start a new context.
### Live reload
Saving `a2a.json` triggers agent re-discovery and sends `notifications/tools/list_changed` to VS Code so new agents appear immediately without restarting.
## Versioning
This package follows [semver](https://semver.org). To cut a release:
```bash
npm version patch # 0.1.0 → 0.1.1 (bug fixes)
npm version minor # 0.1.0 → 0.2.0 (new features, backwards-compatible)
npm version major # 0.1.0 → 1.0.0 (breaking changes)
npm publish
```
`npm version` bumps `package.json`, commits, and creates a git tag automatically. `prepublishOnly` runs `npm run build` before every publish.
## License
[Apache-2.0](LICENSE)
Maintenance
ActivitySlowing
ResponsivenessNo issues