Skip to main content
Glama
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)