Proxy A2A agents through Kong AI Gateway

Deployment Platform
Tags
Minimum Version
Kong Gateway - 3.14
TL;DR

Create a service pointing to your A2A agent, add a route, and enable the AI A2A Proxy plugin. Kong proxies A2A JSON-RPC traffic and can export A2A metrics and payloads as OpenTelemetry span attributes.

Prerequisites

Set the following OTel tracing variables before you configure the Data Plane:

export KONG_TRACING_INSTRUMENTATIONS=all
export KONG_TRACING_SAMPLING_RATE=1.0

This is a Konnect tutorial and requires a Konnect personal access token.

  1. Create a new personal access token by opening the Konnect PAT page and selecting Generate Token.

  2. Export your token to an environment variable:

     export KONNECT_TOKEN='YOUR_KONNECT_PAT'
    
  3. Run the quickstart script to automatically provision a Control Plane and Data Plane, and configure your environment:

     curl -Ls https://get.konghq.com/quickstart | bash -s -- -k $KONNECT_TOKEN -e KONG_TRACING_INSTRUMENTATIONS -e KONG_TRACING_SAMPLING_RATE --deck-output
    

    This sets up a Konnect Control Plane named quickstart, provisions a local Data Plane, and prints out the following environment variable exports:

     export DECK_KONNECT_TOKEN=$KONNECT_TOKEN
     export DECK_KONNECT_CONTROL_PLANE_NAME=quickstart
     export KONNECT_CONTROL_PLANE_URL=https://us.api.konghq.com
     export KONNECT_PROXY_URL='http://localhost:8000'
    

    Copy and paste these into your terminal to configure your session.

This tutorial requires Kong Gateway Enterprise. If you don’t have Kong Gateway set up yet, you can use the quickstart script with an enterprise license to get an instance of Kong Gateway running almost instantly.

  1. Export your license to an environment variable:

     export KONG_LICENSE_DATA='LICENSE-CONTENTS-GO-HERE'
    
  2. Run the quickstart script:

    curl -Ls https://get.konghq.com/quickstart | bash -s -- -e KONG_LICENSE_DATA \
         -e KONG_TRACING_INSTRUMENTATIONS \
         -e KONG_TRACING_SAMPLING_RATE
    

    Once Kong Gateway is ready, you will see the following message:

     Kong Gateway Ready
    

decK is a CLI tool for managing Kong Gateway declaratively with state files. To complete this tutorial, install decK version 1.43 or later.

This guide uses deck gateway apply, which directly applies entity configuration to your Gateway instance. We recommend upgrading your decK installation to take advantage of this tool.

You can check your current decK version with deck version.

For this tutorial, you’ll need Kong Gateway entities, like Gateway Services and Routes, pre-configured. These entities are essential for Kong Gateway to function but installing them isn’t the focus of this guide. Follow these steps to pre-configure them:

  1. Run the following command:

    echo '
    _format_version: "3.0"
    services:
      - name: a2a-currency-agent
        url: http://host.docker.internal:10000
    routes:
      - name: a2a-route
        paths:
        - "/a2a"
        strip_path: true
        service:
          name: a2a-currency-agent
        protocols:
        - http
        - https
    ' | deck gateway apply -
    

To learn more about entities, you can read our entities documentation.

This tutorial uses OpenAI:

  1. Create an OpenAI account.
  2. Get an API key.
  3. Create a decK variable with the API key:

    export DECK_OPENAI_API_KEY='YOUR OPENAI API KEY'
    

In this tutorial, we’ll collect data in OpenTelemetry Collector. Use the following command to launch a Collector instance with default configuration that listens on port 4318 and writes its output to a text file:

docker run \
  --name otel-collector \
  -p 127.0.0.1:4319:4318 \
  otel/opentelemetry-collector:0.141.0 \
  2>&1 | tee collector-output.txt

In a new terminal, export the OTEL Collector host. In this example, use the following host:

export DECK_OTEL_HOST=host.docker.internal

You need a running A2A-compliant agent. This guide uses a sample currency conversion agent from the A2A project.

Create a docker-compose.yaml file:

cat <<'EOF' > docker-compose.yaml
services:
  a2a-agent:
    container_name: a2a-currency-agent
    build:
      context: .
      dockerfile_inline: |
        FROM python:3.12-slim
        WORKDIR /app
        RUN pip install uv && apt-get update && apt-get install -y git
        RUN git clone --depth 1 https://github.com/a2aproject/a2a-samples.git /tmp/a2a && \
            cp -r /tmp/a2a/samples/python/agents/langgraph/* . && \
            rm -rf /tmp/a2a
        ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
        RUN uv sync --frozen --no-dev
        EXPOSE 10000
        CMD ["uv", "run", "app", "--host", "0.0.0.0"]
    environment:
      - model_source=openai
      - API_KEY=${DECK_OPENAI_API_KEY}
      - TOOL_LLM_URL=https://api.openai.com/v1
      - TOOL_LLM_NAME=gpt-5.1
    ports:
      - "10000:10000"
EOF

Export your OpenAI API key and start the agent:

export DECK_OPENAI_API_KEY='your-openai-key'
docker compose up --build -d

The agent listens on port 10000 and uses the A2A JSON-RPC protocol to handle currency conversion queries. In this guide, the gateway service points to host.docker.internal:10000 instead of the container name because Kong Gateway runs in its own container with a separate DNS resolver.

Enable the AI A2A Proxy plugin

The AI A2A Proxy plugin parses A2A JSON-RPC requests and proxies them to the upstream agent. With logging enabled, the plugin records A2A metrics and payloads as OpenTelemetry span attributes.

echo '
_format_version: "3.0"
plugins:
  - name: ai-a2a-proxy
    config:
      max_request_body_size: 0
      logging:
        log_statistics: true
        log_payloads: true
' | deck gateway apply -

log_statistics adds A2A metrics to Kong log plugin output. log_payloads records request and response bodies, and requires log_statistics to be enabled. See the AI A2A Proxy plugin reference for all available parameters.

Retrieve the Agent Card

A2A agents expose their capabilities through an Agent Card at the /.well-known/agent-card.json endpoint. Retrieve it through the gateway:

curl -X GET "$KONNECT_PROXY_URL/a2a/.well-known/agent-card.json" \
     --no-progress-meter --fail-with-body 

curl -X GET "http://localhost:8000/a2a/.well-known/agent-card.json" \
     --no-progress-meter --fail-with-body 

You should see the following response:

{"capabilities":{"pushNotifications":true,"streaming":true},"defaultInputModes":["text","text/plain"],"defaultOutputModes":["text","text/plain"],"description":"Helps with exchange rates for currencies","name":"Currency Agent","preferredTransport":"JSONRPC","protocolVersion":"0.3.0","skills":[{"description":"Helps with exchange values between various currencies","examples":["What is exchange rate between USD and GBP?"],"id":"convert_currency","name":"Currency Exchange Rates Tool","tags":["currency conversion","currency exchange"]}],"url":"http://0.0.0.0:10000/","version":"1.0.0"}%

Enable the OpenTelemetry plugin

The OpenTelemetry plugin exports distributed traces for each A2A request to your Jaeger instance. Combined with the logging configuration on the AI A2A Proxy plugin, traces include A2A-specific span attributes.

echo '
_format_version: "3.0"
plugins:
  - name: opentelemetry
    config:
      traces_endpoint: http://${{ env "DECK_OTEL_HOST" }}:4319/v1/traces
      metrics:
        endpoint: http://${otel.host}:4319/v1/metrics
        enable_ai_metrics: true
      resource_attributes:
        service.name: kong-a2a
' | deck gateway apply -

The traces_endpoint points to the OpenTelemetry Collector’s OTLP HTTP receiver on port 4318. The service.name attribute identifies this AI Gateway instance in the collector output.

Send an A2A request

Send a message/send JSON-RPC request to the gateway route:

curl -X POST "$KONNECT_PROXY_URL/a2a" \
     --no-progress-meter --fail-with-body  \
     -H "Content-Type: application/json" \
     --json '{
       "jsonrpc": "2.0",
       "id": "1",
       "method": "message/send",
       "params": {
         "message": {
           "kind": "message",
           "messageId": "msg-001",
           "role": "user",
           "parts": [
             {
               "kind": "text",
               "text": "How much is 100 USD in EUR?"
             }
           ]
         }
       }
     }'

curl -X POST "http://localhost:8000/a2a" \
     --no-progress-meter --fail-with-body  \
     -H "Content-Type: application/json" \
     --json '{
       "jsonrpc": "2.0",
       "id": "1",
       "method": "message/send",
       "params": {
         "message": {
           "kind": "message",
           "messageId": "msg-001",
           "role": "user",
           "parts": [
             {
               "kind": "text",
               "text": "How much is 100 USD in EUR?"
             }
           ]
         }
       }
     }'

Kong Gateway proxies the request to the A2A agent and returns the agent’s JSON-RPC response. A successful response contains either a completed task with artifacts, or a task in input-required state if the agent needs more information.

Validate traces

You should see data in your OpenTelemetry Collector terminal. You can also search for kong-a2a in the collector-output.txt output file. You should see the following data:

ResourceSpans #0
Resource SchemaURL: 
Resource attributes:
     -> service.instance.id: Str(9c214152-1621-456a-8b42-6f1309dac551)
     -> service.name: Str(kong-a2a)
     -> service.version: Str(3.14.0.0)
ScopeSpans #0
ScopeSpans SchemaURL: 
InstrumentationScope kong-internal 0.1.0
Span #0
    Trace ID       : 1bfc19e17dd9121769882cd9b8bf5de1
    Parent ID      : 
    ID             : 779db508077de69f
    Name           : kong
    Kind           : Server
    Start time     : 2026-04-03 06:48:41.446000128 +0000 UTC
    End time       : 2026-04-03 06:48:47.139977728 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> http.flavor: Str(1.1)
     -> http.route: Str(/a2a)
     -> http.url: Str(http://localhost/a2a)
     -> http.scheme: Str(http)
     -> http.client_ip: Str(192.168.65.1)
     -> http.method: Str(POST)
     -> net.peer.ip: Str(192.168.65.1)
     -> http.status_code: Int(200)
     -> http.host: Str(localhost)
     -> kong.request.id: Str(8221291c2cac1842d7c77118ca409e6a)
Span #1
    Trace ID       : 1bfc19e17dd9121769882cd9b8bf5de1
    Parent ID      : 779db508077de69f
    ID             : a3b699c33700feee
    Name           : kong.router
    Kind           : Internal
    Start time     : 2026-04-03 06:48:41.446752256 +0000 UTC
    End time       : 2026-04-03 06:48:41.44679424 +0000 UTC
    Status code    : Unset
    Status message : 
Span #2
    Trace ID       : 1bfc19e17dd9121769882cd9b8bf5de1
    Parent ID      : 779db508077de69f
    ID             : de4e6ed2c16a2dd3
    Name           : kong.access.plugin.ai-a2a-proxy
    Kind           : Internal
    Start time     : 2026-04-03 06:48:41.446919936 +0000 UTC
    End time       : 2026-04-03 06:48:41.447105024 +0000 UTC
    Status code    : Unset
    Status message : 
Span #3
    Trace ID       : 1bfc19e17dd9121769882cd9b8bf5de1
    Parent ID      : de4e6ed2c16a2dd3
    ID             : 240b2b9ac3ac9e38
    Name           : kong.a2a
    Kind           : Internal
    Start time     : 2026-04-03 06:48:41.44707456 +0000 UTC
    End time       : 2026-04-03 06:48:47.140356608 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> kong.a2a.protocol.version: Str(unknown)
     -> rpc.system: Str(jsonrpc)
     -> rpc.method: Str(message/send)
     -> kong.a2a.task.id: Str(8a98bbbf-7d09-4336-b3aa-afe73e3a38d3)
     -> kong.a2a.task.state: Str(completed)
     -> kong.a2a.context.id: Str(df2e34aa-27ce-44ee-b5d3-3130b4f10985)
     -> kong.a2a.operation: Str(message/send)
Span #4
    Trace ID       : 1bfc19e17dd9121769882cd9b8bf5de1
    Parent ID      : 779db508077de69f
    ID             : c1573adfe53ae258
    Name           : kong.access.plugin.opentelemetry
    Kind           : Internal
    Start time     : 2026-04-03 06:48:41.447129088 +0000 UTC
    End time       : 2026-04-03 06:48:41.447464448 +0000 UTC
    Status code    : Unset
    Status message : 
Span #5
    Trace ID       : 1bfc19e17dd9121769882cd9b8bf5de1
    Parent ID      : 779db508077de69f
    ID             : 1c44c62490a4dc00
    Name           : kong.dns
    Kind           : Client
    Start time     : 2026-04-03 06:48:41.44754304 +0000 UTC
    End time       : 2026-04-03 06:48:41.447862272 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> dns.record.port: Double(10000)
     -> dns.record.ip: Str(172.18.0.2)
     -> dns.record.domain: Str(a2a-currency-agent)
Span #6
    Trace ID       : 1bfc19e17dd9121769882cd9b8bf5de1
    Parent ID      : 779db508077de69f
    ID             : 811a109d1908068d
    Name           : kong.header_filter.plugin.ai-a2a-proxy
    Kind           : Internal
    Start time     : 2026-04-03 06:48:47.139697664 +0000 UTC
    End time       : 2026-04-03 06:48:47.139731712 +0000 UTC
    Status code    : Unset
    Status message : 
Span #7
    Trace ID       : 1bfc19e17dd9121769882cd9b8bf5de1
    Parent ID      : 779db508077de69f
    ID             : ff3f295f3b8cf464
    Name           : kong.header_filter.plugin.opentelemetry
    Kind           : Internal
    Start time     : 2026-04-03 06:48:47.139753728 +0000 UTC
    End time       : 2026-04-03 06:48:47.1397632 +0000 UTC
    Status code    : Unset
    Status message : 
Span #8
    Trace ID       : 1bfc19e17dd9121769882cd9b8bf5de1
    Parent ID      : 779db508077de69f
    ID             : f8718c5342d3bc70
    Name           : kong.balancer
    Kind           : Client
    Start time     : 2026-04-03 06:48:41.447897088 +0000 UTC
    End time       : 2026-04-03 06:48:47.139977728 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> net.peer.ip: Str(172.18.0.2)
     -> net.peer.port: Double(10000)
     -> net.peer.name: Str(a2a-currency-agent)
     -> try_count: Double(1)
     -> peer.service: Str(a2a-currency-agent)

Validate metrics

You should also see metrics data in the OpenTelemetry Collector output. Search for kong.gen_ai.a2a in the collector-output.txt file. You should see the following data:

ResourceMetrics #0
Resource SchemaURL: 
Resource attributes:
     -> service.instance.id: Str(9c214152-1621-456a-8b42-6f1309dac551)
     -> service.name: Str(kong-a2a)
     -> service.version: Str(3.14.0.0)
ScopeMetrics #0
ScopeMetrics SchemaURL: 
InstrumentationScope kong-internal 0.1.0
Metric #0
Descriptor:
     -> Name: kong.gen_ai.a2a.request.duration
     -> Description: Measures A2A request duration in seconds.
     -> Unit: s
     -> DataType: Histogram
     -> AggregationTemporality: Cumulative
HistogramDataPoints #0
Data point attributes:
     -> kong.service.name: Str(a2a-currency-agent)
     -> kong.route.name: Str(a2a-route)
     -> kong.gen_ai.a2a.method: Str(message/send)
     -> kong.workspace.name: Str(default)
     -> kong.gen_ai.a2a.binding: Str(jsonrpc)
StartTimestamp: 2026-04-03 06:40:44.823196672 +0000 UTC
Timestamp: 2026-04-03 06:48:47.141009664 +0000 UTC
Count: 3
Sum: 20.365000
Min: 5.692000
Max: 8.950000
Metric #1
Descriptor:
     -> Name: kong.gen_ai.a2a.response.size
     -> Description: Measures A2A response body size in bytes.
     -> Unit: By
     -> DataType: Histogram
     -> AggregationTemporality: Cumulative
HistogramDataPoints #0
Data point attributes:
     -> kong.service.name: Str(a2a-currency-agent)
     -> kong.route.name: Str(a2a-route)
     -> kong.gen_ai.a2a.method: Str(message/send)
     -> kong.workspace.name: Str(default)
     -> kong.gen_ai.a2a.binding: Str(jsonrpc)
StartTimestamp: 2026-04-03 06:40:44.823648 +0000 UTC
Timestamp: 2026-04-03 06:48:47.141217024 +0000 UTC
Count: 3
Sum: 3994.000000
Min: 1304.000000
Max: 1345.000000
Metric #2
Descriptor:
     -> Name: kong.gen_ai.a2a.request.count
     -> Description: Counts A2A requests.
     -> Unit: {request}
     -> DataType: Sum
     -> IsMonotonic: true
     -> AggregationTemporality: Cumulative
NumberDataPoints #0
Data point attributes:
     -> kong.service.name: Str(a2a-currency-agent)
     -> kong.route.name: Str(a2a-route)
     -> kong.gen_ai.a2a.method: Str(message/send)
     -> kong.workspace.name: Str(default)
     -> kong.gen_ai.a2a.binding: Str(jsonrpc)
StartTimestamp: 2026-04-03 06:40:44.822096128 +0000 UTC
Timestamp: 2026-04-03 06:48:47.14095616 +0000 UTC
Value: 3
Metric #3
Descriptor:
     -> Name: kong.gen_ai.a2a.task.state.count
     -> Description: Counts A2A task state transitions.
     -> Unit: {state}
     -> DataType: Sum
     -> IsMonotonic: true
     -> AggregationTemporality: Cumulative
NumberDataPoints #0
Data point attributes:
     -> kong.workspace.name: Str(default)
     -> kong.service.name: Str(a2a-currency-agent)
     -> kong.route.name: Str(a2a-route)
     -> kong.gen_ai.a2a.task.state: Str(completed)
StartTimestamp: 2026-04-03 06:40:44.824023552 +0000 UTC
Timestamp: 2026-04-03 06:48:47.141275648 +0000 UTC
Value: 3

Cleanup

If you created a new control plane and want to conserve your free trial credits or avoid unnecessary charges, delete the new control plane used in this tutorial.

curl -Ls https://get.konghq.com/quickstart | bash -s -- -d

Stop and remove the sample A2A agent and OpenTelemetry Collector containers:

docker compose down
docker rm -f otel-collector

FAQs

The Agent2Agent (A2A) protocol is an open standard originally developed by Google that defines how AI agents communicate with each other. It uses JSON-RPC over HTTP and supports capability discovery through Agent Cards, task lifecycle management, multi-turn conversations, and streaming responses. See the A2A protocol documentation for the full specification.

MCP (Model Context Protocol) standardizes how agents connect to tools, APIs, and data sources. A2A standardizes how agents communicate with other agents. They are complementary: use MCP for agent-to-tool communication and A2A for agent-to-agent communication.

Yes. Apply any Kong Gateway authentication plugin (Key Auth, OAuth2, JWT, etc.) to the same service or route. The AI A2A Proxy plugin handles A2A protocol concerns independently of authentication.

Help us make these docs great!

Kong Developer docs are open source. If you find these useful and want to make them better, contribute today!