# Configuration (/aiway/a2a/configuration)



The A2A connector is **enabled by default** on the [shared HTTP server](/connectors/concepts/shared-http-server) — start kubemq-server and `/a2a/*` is live with no flag to set. Configuration tunes agent liveness, request timeouts, concurrency, and response limits; you only set values that differ from the defaults below.

## Overview [#overview]

A2A settings live under `connectors.a2a` in the kubemq-server configuration (the Go `A2aConfig` struct). Every field has a working default, so a minimal deployment needs no A2A configuration at all. You can override any field through a YAML/TOML config file, an environment variable, or a Docker `-e` flag — they map one-to-one.

The connector listens on the shared HTTP server's port (`9090` by default, inherited from `Rest.Port`) and is **on unless you disable it**. There is no `=true` flag — older KubeMQ builds were off-by-default, but current builds ship all three connectors enabled.

## Config fields [#config-fields]

Defaults are taken verbatim from the `A2aConfig` struct in kubemq-server.

| Field                   | Type       | Default    | Description                                                                                                                                                  |
| ----------------------- | ---------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Enable`                | `bool`     | `true`     | Whether the A2A connector is served. Set to `false` to disable `/a2a/*` and the registry.                                                                    |
| `AgentTTLSeconds`       | `int`      | `300`      | Liveness window for a registered agent. An agent that does not heartbeat within this window is considered stale. Must be positive.                           |
| `DefaultTimeoutSeconds` | `int`      | `300`      | Timeout applied to an agent request when the caller does not set `params.configuration.timeout`. Must be positive.                                           |
| `MaxTimeoutSeconds`     | `int`      | `3600`     | Upper bound for caller-specified timeouts. Larger values are capped to this. Must be `>= DefaultTimeoutSeconds`.                                             |
| `MaxAgents`             | `int`      | `0`        | Maximum number of registered agents. `0` means unlimited. Cannot be negative.                                                                                |
| `MaxSSEIdleSeconds`     | `int`      | `300`      | Idle timeout for an SSE stream. When it fires, a `task.error` with code `-32001` is sent and the agent is asked to cancel. Must be positive.                 |
| `TrustedOrigins`        | `[]string` | `["auto"]` | Origins allowed by Origin validation. `auto` matches localhost and the bind address; `*` allows all. See [Authentication](/aiway/a2a/guides/authentication). |
| `AgentMaxResponseBytes` | `int64`    | `10485760` | Maximum agent response size in bytes (10 MB). Oversized responses are rejected with `-32603`. `0` means unlimited. Cannot be negative.                       |
| `AgentTLSSkipVerify`    | `bool`     | `false`    | When agents are registered with `https://` URLs, skip TLS certificate verification. Leave `false` in production.                                             |
| `AgentMaxConcurrency`   | `int`      | `100`      | Maximum simultaneous in-flight requests per agent. The 101st concurrent request is rejected with `-32603`. A non-positive value resets to `100`.             |

<Callout type="info">
  **Validation.** kubemq-server rejects the configuration at startup if `AgentTTLSeconds`, `DefaultTimeoutSeconds`, or `MaxSSEIdleSeconds` is not positive, if `MaxTimeoutSeconds < DefaultTimeoutSeconds`, or if `MaxAgents` / `AgentMaxResponseBytes` is negative.
</Callout>

## Enable / disable [#enable--disable]

The A2A connector is enabled by default. To **disable** it, set its enable variable to `false`:

<RunKubeMQ variant="disable" env="{ CONNECTORSA2_A_ENABLE: 'false' }" />

<Callout type="warn">
  **The enable variable name is irregular by design.** `Connectors.A2A.Enable&#x60; becomes &#x2A;*`CONNECTORSA2_A_ENABLE`** — the `2`-to-`A` boundary inside `A2A` splits into `A2_A`. kubemq-server derives every env var by snake-casing the dotted config path with two regexes, stripping dots, and uppercasing. Do not "fix" the name to `CONNECTORS_A2A_ENABLE`; that string is not bound and has no effect. The full algorithm and the matching MCP/CE names are documented in [Shared HTTP server](/connectors/concepts/shared-http-server#enable-model-on-by-default).
</Callout>

## Environment variables [#environment-variables]

Each field maps to one environment variable via the same transform. These are the A2A variables in full:

| Variable                                  | Config field                           | Default    |
| ----------------------------------------- | -------------------------------------- | ---------- |
| `CONNECTORSA2_A_ENABLE`                   | `Connectors.A2A.Enable`                | `true`     |
| `CONNECTORSA2_A_AGENT_TTL_SECONDS`        | `Connectors.A2A.AgentTTLSeconds`       | `300`      |
| `CONNECTORSA2_A_DEFAULT_TIMEOUT_SECONDS`  | `Connectors.A2A.DefaultTimeoutSeconds` | `300`      |
| `CONNECTORSA2_A_MAX_TIMEOUT_SECONDS`      | `Connectors.A2A.MaxTimeoutSeconds`     | `3600`     |
| `CONNECTORSA2_A_MAX_AGENTS`               | `Connectors.A2A.MaxAgents`             | `0`        |
| `CONNECTORSA2_A_MAX_SSE_IDLE_SECONDS`     | `Connectors.A2A.MaxSSEIdleSeconds`     | `300`      |
| `CONNECTORSA2_A_TRUSTED_ORIGINS`          | `Connectors.A2A.TrustedOrigins`        | `auto`     |
| `CONNECTORSA2_A_AGENT_MAX_RESPONSE_BYTES` | `Connectors.A2A.AgentMaxResponseBytes` | `10485760` |
| `CONNECTORSA2_A_AGENT_TLS_SKIP_VERIFY`    | `Connectors.A2A.AgentTLSSkipVerify`    | `false`    |
| `CONNECTORSA2_A_AGENT_MAX_CONCURRENCY`    | `Connectors.A2A.AgentMaxConcurrency`   | `100`      |

## Configuration file [#configuration-file]

kubemq-server loads a YAML or TOML file (auto-detected) from the `--config` flag or the `CONFIG` environment variable. The A2A connector sits under `connectors.a2a`, alongside the shared `http` block. This example pins every A2A field to its default and shows the shared HTTP/CORS context:

```yaml title="config.yaml"
connectors:
  rest:
    enable: true
    port: "9090"
  http:
    readtimeout: 60
    bodylimit: "100M"
    cors:
      alloworigins: ["*"]
      allowmethods: ["GET", "POST", "DELETE", "OPTIONS"]
      allowheaders: ["Authorization", "Content-Type", "MCP-Protocol-Version", "MCP-Session-Id", "Last-Event-ID", "Accept"]
  a2a:
    enable: true
    agentttlseconds: 300
    defaulttimeoutseconds: 300
    maxtimeoutseconds: 3600
    maxagents: 0
    maxsseidleseconds: 300
    trustedorigins: ["auto"]
    agentmaxresponsebytes: 10485760
    agenttlsskipverify: false
    agentmaxconcurrency: 100
```

The shared HTTP server inherits its port from `connectors.rest.port`. To run the connectors on a separate port from REST, set `connectors.http.port` explicitly. See [Shared HTTP server](/connectors/concepts/shared-http-server) for the port-inheritance and middleware details.

## Tuning the limits [#tuning-the-limits]

The concurrency and response-size caps are the two limits you are most likely to hit under load. The `.kb` examples exercise both against a live server — the curl call below shows the wire behavior, and the language tabs run the full client that fires past the limit and reports the rejection.

### Per-agent concurrency [#per-agent-concurrency]

`AgentMaxConcurrency` (default `100`) bounds in-flight requests per agent. The 101st concurrent request to a single agent is rejected with JSON-RPC error `-32603` ("internal error") while the first 100 succeed.

<Tabs groupId="language" items="['curl','C#','Go','Java','Python','TypeScript']">
  <Tab value="curl">
    ```bash
    # Each of these is a normal request; firing more than AgentMaxConcurrency
    # of them at once against one agent makes the overflow return -32603.
    curl -X POST http://localhost:9090/a2a/echo-agent-01 \
      -H 'Content-Type: application/json' \
      -d '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "message/send",
        "params": { "message": { "parts": [{ "text": "hello" }] } }
      }'
    ```
  </Tab>

  <Tab value="C#">
    ```csharp
    // .kb/integration-a2a/examples/csharp/limits/concurrency-limit/Client.cs
    using System.Net.Http.Json;

    var kubemqUrl = Environment.GetEnvironmentVariable("KUBEMQ_URL") ?? "http://localhost:9090";
    var agentId = "concurrency-agent-01";
    const int total = 101; // limit is 100; 1 request will be rejected

    using var http = new HttpClient();

    async Task<(bool ok, int? code)> SendOne(int i)
    {
        var payload = new
        {
            jsonrpc = "2.0",
            id = i,
            method = "message/send",
            @params = new { message = new { parts = new[] { new { text = $"req-{i}" } } } }
        };
        var resp = await http.PostAsJsonAsync($"{kubemqUrl}/a2a/{agentId}", payload);
        var body = await resp.Content.ReadFromJsonAsync<JsonRpcResponse>();
        if (body?.Error is not null)
            return (false, body.Error.Code);
        return (true, null);
    }

    var results = await Task.WhenAll(Enumerable.Range(0, total).Select(SendOne));
    var rejected = results.Count(r => r.code == -32603);
    Console.WriteLine($"Successes: {results.Count(r => r.ok)}  Rejected (-32603): {rejected}");
    ```
  </Tab>

  <Tab value="Go">
    ```go
    // .kb/integration-a2a/examples/go/limits/concurrency-limit/client.go
    package main

    import (
        "bytes"
        "encoding/json"
        "fmt"
        "net/http"
        "os"
        "sync"
        "sync/atomic"
    )

    func main() {
        kubemqURL := os.Getenv("KUBEMQ_URL")
        if kubemqURL == "" {
            kubemqURL = "http://localhost:9090"
        }
        agentID := "concurrency-agent-01"
        const total = 101 // limit is 100; 1 request is rejected

        var success, rejected int64
        var wg sync.WaitGroup
        for i := 0; i < total; i++ {
            wg.Add(1)
            go func(i int) {
                defer wg.Done()
                body, _ := json.Marshal(map[string]any{
                    "jsonrpc": "2.0", "id": i, "method": "message/send",
                    "params": map[string]any{"message": map[string]any{
                        "parts": []map[string]string{{"text": fmt.Sprintf("req-%d", i)}}}},
                })
                resp, err := http.Post(kubemqURL+"/a2a/"+agentID, "application/json", bytes.NewReader(body))
                if err != nil {
                    return
                }
                defer resp.Body.Close()
                var out struct {
                    Error *struct {
                        Code int `json:"code"`
                    } `json:"error"`
                }
                _ = json.NewDecoder(resp.Body).Decode(&out)
                if out.Error != nil && out.Error.Code == -32603 {
                    atomic.AddInt64(&rejected, 1)
                } else {
                    atomic.AddInt64(&success, 1)
                }
            }(i)
        }
        wg.Wait()
        fmt.Printf("Successes: %d  Rejected (-32603): %d\n", success, rejected)
    }
    ```
  </Tab>

  <Tab value="Java">
    ```java
    // .kb/integration-a2a/examples/java/limits/concurrency-limit/Client.java
    import java.net.URI;
    import java.net.http.*;
    import java.util.concurrent.*;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.stream.IntStream;

    public class Client {
        public static void main(String[] args) throws Exception {
            String kubemqUrl = System.getenv().getOrDefault("KUBEMQ_URL", "http://localhost:9090");
            String agentId = "concurrency-agent-01";
            int total = 101; // limit is 100; 1 request is rejected

            HttpClient http = HttpClient.newHttpClient();
            AtomicInteger success = new AtomicInteger();
            AtomicInteger rejected = new AtomicInteger();
            ExecutorService pool = Executors.newFixedThreadPool(total);

            var tasks = IntStream.range(0, total).<Callable<Void>>mapToObj(i -> () -> {
                String payload = """
                    {"jsonrpc":"2.0","id":%d,"method":"message/send",
                     "params":{"message":{"parts":[{"text":"req-%d"}]}}}""".formatted(i, i);
                HttpRequest req = HttpRequest.newBuilder()
                    .uri(URI.create(kubemqUrl + "/a2a/" + agentId))
                    .header("Content-Type", "application/json")
                    .POST(HttpRequest.BodyPublishers.ofString(payload))
                    .build();
                String body = http.send(req, HttpResponse.BodyHandlers.ofString()).body();
                if (body.contains("-32603")) rejected.incrementAndGet();
                else success.incrementAndGet();
                return null;
            }).toList();

            pool.invokeAll(tasks);
            pool.shutdown();
            System.out.printf("Successes: %d  Rejected (-32603): %d%n", success.get(), rejected.get());
        }
    }
    ```
  </Tab>

  <Tab value="Python">
    ```python
    # .kb/integration-a2a/examples/python/limits/concurrency-limit/client.py
    import asyncio
    import os

    import httpx

    KUBEMQ_URL = os.getenv("KUBEMQ_URL", "http://localhost:9090")
    AGENT_ID = "concurrency-agent-01"
    TOTAL = 101  # limit is 100; 1 request is rejected


    async def send_one(client: httpx.AsyncClient, i: int) -> int | None:
        payload = {
            "jsonrpc": "2.0",
            "id": i,
            "method": "message/send",
            "params": {"message": {"parts": [{"text": f"req-{i}"}]}},
        }
        resp = await client.post(f"{KUBEMQ_URL}/a2a/{AGENT_ID}", json=payload)
        error = resp.json().get("error")
        return error["code"] if error else None


    async def main() -> None:
        async with httpx.AsyncClient(timeout=30) as client:
            codes = await asyncio.gather(*(send_one(client, i) for i in range(TOTAL)))
        rejected = sum(1 for c in codes if c == -32603)
        successes = sum(1 for c in codes if c is None)
        print(f"Successes: {successes}  Rejected (-32603): {rejected}")


    if __name__ == "__main__":
        asyncio.run(main())
    ```
  </Tab>

  <Tab value="TypeScript">
    ```typescript
    // .kb/integration-a2a/examples/typescript/limits/concurrency-limit/client.ts
    const kubemqUrl = process.env.KUBEMQ_URL ?? "http://localhost:9090";
    const agentId = "concurrency-agent-01";
    const total = 101; // limit is 100; 1 request is rejected

    async function sendOne(i: number): Promise<number | null> {
      const resp = await fetch(`${kubemqUrl}/a2a/${agentId}`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          jsonrpc: "2.0",
          id: i,
          method: "message/send",
          params: { message: { parts: [{ text: `req-${i}` }] } },
        }),
      });
      const body = (await resp.json()) as { error?: { code: number } };
      return body.error?.code ?? null;
    }

    const codes = await Promise.all(
      Array.from({ length: total }, (_, i) => sendOne(i)),
    );
    const rejected = codes.filter((c) => c === -32603).length;
    const successes = codes.filter((c) => c === null).length;
    console.log(`Successes: ${successes}  Rejected (-32603): ${rejected}`);
    ```
  </Tab>
</Tabs>

### Response size [#response-size]

`AgentMaxResponseBytes` (default `10485760`, 10 MB) caps the size of an agent's reply. A larger response is rejected before it reaches the caller, who receives `-32603` with the message `internal error: response too large`.

<Tabs groupId="language" items="['curl','C#','Go','Java','Python','TypeScript']">
  <Tab value="curl">
    ```bash
    # A normal request; the cap is hit only when the agent's reply exceeds
    # AgentMaxResponseBytes, in which case the response carries error -32603.
    curl -X POST http://localhost:9090/a2a/oversize-agent-01 \
      -H 'Content-Type: application/json' \
      -d '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "message/send",
        "params": { "message": { "parts": [{ "text": "return a big payload" }] } }
      }'
    ```
  </Tab>

  <Tab value="C#">
    ```csharp
    // .kb/integration-a2a/examples/csharp/limits/response-size/Client.cs
    using System.Net.Http.Json;

    var kubemqUrl = Environment.GetEnvironmentVariable("KUBEMQ_URL") ?? "http://localhost:9090";
    var agentId = "oversize-agent-01";

    using var http = new HttpClient();
    var payload = new
    {
        jsonrpc = "2.0",
        id = 1,
        method = "message/send",
        @params = new { message = new { parts = new[] { new { text = "return a big payload" } } } }
    };

    var resp = await http.PostAsJsonAsync($"{kubemqUrl}/a2a/{agentId}", payload);
    var body = await resp.Content.ReadFromJsonAsync<JsonRpcResponse>();
    if (body?.Error is not null)
        Console.WriteLine($"Error code: {body.Error.Code}  Message: {body.Error.Message}");
    else
        Console.WriteLine("Response within the size limit.");
    ```
  </Tab>

  <Tab value="Go">
    ```go
    // .kb/integration-a2a/examples/go/limits/response-size/client.go
    package main

    import (
        "bytes"
        "encoding/json"
        "fmt"
        "net/http"
        "os"
    )

    func main() {
        kubemqURL := os.Getenv("KUBEMQ_URL")
        if kubemqURL == "" {
            kubemqURL = "http://localhost:9090"
        }
        agentID := "oversize-agent-01"

        body, _ := json.Marshal(map[string]any{
            "jsonrpc": "2.0", "id": 1, "method": "message/send",
            "params": map[string]any{"message": map[string]any{
                "parts": []map[string]string{{"text": "return a big payload"}}}},
        })
        resp, err := http.Post(kubemqURL+"/a2a/"+agentID, "application/json", bytes.NewReader(body))
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        var out struct {
            Error *struct {
                Code    int    `json:"code"`
                Message string `json:"message"`
            } `json:"error"`
        }
        _ = json.NewDecoder(resp.Body).Decode(&out)
        if out.Error != nil {
            fmt.Printf("Error code: %d  Message: %s\n", out.Error.Code, out.Error.Message)
        } else {
            fmt.Println("Response within the size limit.")
        }
    }
    ```
  </Tab>

  <Tab value="Java">
    ```java
    // .kb/integration-a2a/examples/java/limits/response-size/Client.java
    import java.net.URI;
    import java.net.http.*;

    public class Client {
        public static void main(String[] args) throws Exception {
            String kubemqUrl = System.getenv().getOrDefault("KUBEMQ_URL", "http://localhost:9090");
            String agentId = "oversize-agent-01";

            String payload = """
                {"jsonrpc":"2.0","id":1,"method":"message/send",
                 "params":{"message":{"parts":[{"text":"return a big payload"}]}}}""";

            HttpClient http = HttpClient.newHttpClient();
            HttpRequest req = HttpRequest.newBuilder()
                .uri(URI.create(kubemqUrl + "/a2a/" + agentId))
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(payload))
                .build();

            String body = http.send(req, HttpResponse.BodyHandlers.ofString()).body();
            if (body.contains("-32603"))
                System.out.println("Error code: -32603  (response too large)");
            else
                System.out.println("Response within the size limit.");
        }
    }
    ```
  </Tab>

  <Tab value="Python">
    ```python
    # .kb/integration-a2a/examples/python/limits/response-size/client.py
    import os

    import httpx

    KUBEMQ_URL = os.getenv("KUBEMQ_URL", "http://localhost:9090")
    AGENT_ID = "oversize-agent-01"


    def main() -> None:
        payload = {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "message/send",
            "params": {"message": {"parts": [{"text": "return a big payload"}]}},
        }
        resp = httpx.post(f"{KUBEMQ_URL}/a2a/{AGENT_ID}", json=payload, timeout=30)
        error = resp.json().get("error")
        if error:
            print(f"Error code: {error['code']}  Message: {error['message']}")
        else:
            print("Response within the size limit.")


    if __name__ == "__main__":
        main()
    ```
  </Tab>

  <Tab value="TypeScript">
    ```typescript
    // .kb/integration-a2a/examples/typescript/limits/response-size/client.ts
    const kubemqUrl = process.env.KUBEMQ_URL ?? "http://localhost:9090";
    const agentId = "oversize-agent-01";

    const resp = await fetch(`${kubemqUrl}/a2a/${agentId}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        jsonrpc: "2.0",
        id: 1,
        method: "message/send",
        params: { message: { parts: [{ text: "return a big payload" }] } },
      }),
    });

    const body = (await resp.json()) as {
      error?: { code: number; message: string };
    };
    if (body.error) {
      console.log(`Error code: ${body.error.code}  Message: ${body.error.message}`);
    } else {
      console.log("Response within the size limit.");
    }
    ```
  </Tab>
</Tabs>

<Callout type="info">
  Timeouts compound. The gateway adds `GatewayTimeoutBuffer` (10s) on top of the effective request timeout before timing out the proxied call to the agent, so the agent always has a slightly longer window than the caller-facing timeout. See [Concurrency & limits](/aiway/a2a/guides/concurrency).
</Callout>

## Related [#related]

<Cards>
  <Card title="Shared HTTP server" href="/connectors/concepts/shared-http-server" description="Port 9090, the middleware chain, and the enable-variable algorithm." />

  <Card title="Concurrency & limits" href="/aiway/a2a/guides/concurrency" description="How the concurrency and response-size caps behave under load." />

  <Card title="Authentication" href="/aiway/a2a/guides/authentication" description="JWT auth, trusted origins, and agent ownership." />

  <Card title="Reference" href="/aiway/a2a/reference" description="Full config field, endpoint, and error-code tables." />
</Cards>
