DEV Community

xbill for Google Developer Experts

Posted on • Originally published at xbill999.Medium on

Building a Rust MCP/A2A Currency Agent with AG-UI and Antigravity CLI

Building an Agent with Rust, A2A, MCP, and AG-UI

This tutorial aims to extend and test a currency Agent using A2A and MCP protocols with the AG-UI protocol for custom presentation.

Reduce, Re-Use, Re-Cycle!

This paper is a re-visiting of the original currency Agent Codelab:

Getting Started with MCP, ADK and A2A | Google Codelabs

and a GitHub Repo:

GitHub - jackwotherspoon/currency-agent: A sample agent demonstrating A2A + ADK + MCP working together.

In this updated version, the Antigravity CLI is used to add support for AG-UI and extend the existing user interface.

But, wait! There is more!

A minimal working AG-UI proof of concept was built out here:

Extending a MCP/A2A Currency Agent with AG-UI and Antigravity CLI

and a pure Rust A2-UI demo was built out here:

Extending a Rust MCP/A2A Currency Agent with A2UI

This demo/article takes it to the next level and builds a fully working AG-UI demo in Rust!

What is the A2A protocol?

The Agent2Agent (A2A) protocol, an open communication standard for AI agents, was initially introduced by Google in April 2025. It is specifically engineered to facilitate seamless interoperability within multi-agent systems, enabling AI agents developed by diverse providers or built upon disparate AI agent frameworks to communicate and collaborate effectively.

A good overview of the A2A protocol can be found here:

A2A Protocol

Language Support For the A2A Protocol

The official ADK for Python, GO, and Java provide built-in support for working with the A2A protocol. For other programming languages like JS, Rust, and .NET — 3rd party libraries are available to add support for the protocol.

The main source for A2A Language support is the GitHub repo:

GitHub - a2aproject/A2A: An open protocol enabling communication and interoperability between opaque agentic applications.

AG-UI

AG-UI (Agent-User Interaction) is an open-source protocol that standardizes how AI agents communicate with frontend web applications. It acts as a universal adapter between backend AI logic (like LangGraph or CrewAI) and user interfaces, handling streaming responses, tool executions, and state synchronization out of the box. [1, 2, 3, 4]

More details are available here:

AG-UI Overview - Agent User Interaction Protocol

A2UI

A2UI (Agent-to-User Interface) is an open-source protocol that allows AI agents to dynamically generate and stream rich, interactive user interfaces in real-time. [1, 2]

Instead of an AI relying on pre-built screens or just returning plain text in a chat window, A2UI enables the agent to instantly build and display tailored components like interactive charts, date-pickers, or approval forms. [1, 2]

What is A2UI? - A2UI

More Word Salad Protocols — What about A2A-XYZ?

This article provides a good overview of how the various protocols fit together:

A2A, MCP, AG-UI, A2UI: The Essential 2026 AI Agent Protocol Stack

Confused yet?

How Does A2UI Compare? - A2UI

and this:

AG-UI and A2UI: Understanding the Differences | CopilotKit

Antigravity CLI

Antigravity CLI is the follow-on successor to Gemini CLI- the terminal driven, agent assisted coding tool.

Full details on installing Antigravity CLI are here:

Getting Started with Antigravity CLI

Testing the Antigravity CLI Environment

Once you have all the tools in place- you can test the startup of Antigravity CLI.

You will need to authenticate with a Google Cloud Project or your Google Account:

agy
Enter fullscreen mode Exit fullscreen mode

This will start the interface:

Checking the Developer Environment

Verify that all the prerequisite packages and compilers are installed — and clone the sample Github repo:

git clone https://github.com/xbill9/currency-agent-rust-agui
cd currency-agent-rust-agui
Enter fullscreen mode Exit fullscreen mode

Once you have your Google Cloud Project and preferred authentication method — run the init.sh script to validate the setup:

xbill@penguin:~/currency-agent-rust-agui$ source init.sh

[environment: Development]
Updated property [core/project].
ADC is valid.
Environment setup
GOOGLE_GENAI_USE_VERTEXAI=1
GOOGLE_CLOUD_PROJECT=comglitn
GOOGLE_CLOUD_LOCATION=us-central1
IMAGEN_MODEL="imagen-3.0-fast-generate-001"
GENAI_MODEL="gemini-2.5-flash"
Cloud Login
  Credentialed Accounts
ACTIVE ACCOUNT
* xbill@glitnir.com
Enter fullscreen mode Exit fullscreen mode

The set_env.sh script is provided to set common ADK environment variables:

xbill@penguin:~/currency-agent-rust-agui$ source set_env.sh 
Current Environment
GOOGLE_GENAI_USE_VERTEXAI=false
GOOGLE_CLOUD_PROJECT=comglitn
GOOGLE_CLOUD_LOCATION=us-central1
GENAI_MODEL="gemini-2.5-flash"
Enter fullscreen mode Exit fullscreen mode

Debugging API Permission Errors

If your application default credentials expires or your Google Cloud Authentication expires you will get an error. The workaround is to re-authenticate:

gcloud auth login
gcloud auth application-default login
Enter fullscreen mode Exit fullscreen mode

Another common error is that the environment variables are not set correctly. Go the the root directory and re-run the set_env.sh to set the variables:

cd ~/currency-agent-rust-agui
source set_env.sh
Enter fullscreen mode Exit fullscreen mode

A2A Debugging Tools -A2A Inspector

The A2A Inspector is a standalone tool that provides low level visibility into the A2A protocol. The GitHub is available here:

GitHub - a2aproject/a2a-inspector: Validation Tools for A2A Agents

A summary of the features of the A2A inspector can be found here:

A2A Protocol Documentation

To install the A2A Inspector:

cd ~
git clone https://github.com/a2aproject/a2a-inspector
Enter fullscreen mode Exit fullscreen mode

Then follow the build instructions — you need uv , and a recent version of node :

a2a-inspector/README.md at main · a2aproject/a2a-inspector

Staring the A2A Inspector

Once the A2A inspector has been installed- you can validate the installation by using this URL:

http://127.0.0.1:5001/

Local A2A Validation with A2A Inspector

The local A2A agent is checked with the A2A Inspector:

> which port is the a2a server listening on

The A2A server is listening on port 10000 .

This is configured when starting the service (e.g., via uvicorn currency_agent.agent:a2a_app --port 10000 ).
Enter fullscreen mode Exit fullscreen mode

and then the Agent card is retrieved:

and the Agent skill cross checked:

Antigravity CLI with MCP Connection

The Master Agent starts a FastMCP server interface. This allows Antigravity CLI to be used as a MCP client:

xbill@penguin:~/currency-agent-rust-agui/.agents$ more mcp_config.json

"currency_mcp": {
      "serverUrl": "http://localhost:8080/mcp"
    }
Enter fullscreen mode Exit fullscreen mode

The MCP tools can be reviewed:

MCP Servers

Plugins (~/.gemini/antigravity-cli/plugins)
   ✓ currency_mcp Tools: get_exchange_rate
Enter fullscreen mode Exit fullscreen mode

The MCP tool functionality can be checked with Antigravity:

> what does the currency-mcp server do?
39.2k in | 421 out | 225 thinking
● Read(/home/xbill/currency-agent-rust-agui/mcp-server/src/main.rs) (ctrl+o to expand)

  The Currency MCP Server (implemented in mcp-server/src/main.rs ) is a Model Context Protocol (MCP) server built with Rust
  and the Axum web framework. Its primary purpose is to expose currency-related capabilities as tools that can be consumed
  by LLMs.

  Here is a summary of what it does:

  ### 1. Exposes the get_exchange_rate Tool

  The server registers and provides a standard tool named get_exchange_rate with the following parameters:

  • currency_from (string, default: "USD" ): The base currency to convert from (e.g., "USD" , "GBP" ).
  • currency_to (string, default: "EUR" ): The target currency to convert to (e.g., "EUR" , "JPY" ).
  • currency_date (string, default: "latest" ): The date for the exchange rate or "latest" for real-time rates.

  ### 2. Fetches Live Exchange Rates

  When the tool is called, the server forwards the request to the high-performance Frankfurter API (
  https://api.frankfurter.dev/ ) to retrieve exchange rates, handles any API failures or validation errors, and returns the
  structured rates back to the agent.

  ### 3. Standards-Compliant MCP Protocol Over SSE

  • It serves as a JSON-RPC 2.0 endpoint over an HTTP Server-Sent Events (SSE) transport layer.
  • It listens on port 8080 (customizable via MCP_PORT ).
  • It manages persistent client connections and communication sessions using mcp-session-id headers.
Enter fullscreen mode Exit fullscreen mode

What About the Rust A2A Server?

Ok we got MCP.

What exactly does the Rust A2A server do in this demo?

> what does the Rust A2A server running on port 10000 do in this demo?
29.7k in | 967 out | 405 thinking
● Read(/home/xbill/currency-agent-rust-agui/src/main.rs) (ctrl+o to expand)

  The Rust server running on port 10000 is the Core Agent Engine & Gateway for the Currency Agent. It acts as the bridge
  between user frontends, the Gemini model, the MCP tool server, and the UI layout engine.

  Here is exactly what it does in this demo:
  ### 1. Unified A2A & AG-UI Protocol Support

  The server handles incoming HTTP POST / traffic using two distinct protocol streams:

  • Modern AG-UI (Agent-to-User Interface) Protocol: Handles real-time interactive chat sessions. It receives conversation
  histories, triggers the LLM, and streams back responses in chunks using Server-Sent Events (SSE) (sending standard events
  like RUN_STARTED , STEP_STARTED , TEXT_CHUNK , and RUN_COMPLETED ).
  • Legacy JSON-RPC A2A (Agent-to-Agent) Protocol: Supports legacy clients using asynchronous JSON-RPC calls with methods
  such as message/send and tasks/get .

  ### 2. Live Dynamic UI Component Generation

  The server equips the Gemini model ( gemini-2.5-flash or gemini-3.5-flash ) with a system instruction that contains a
  full A2UI Schema.

  • When users request comparisons, charts, or historical trends, the LLM is instructed to output visual design structures
  (such as LineChart , BarChart , Table , or Card ) wrapped inside special <a2ui-json> tags.
  • The frontend then reads these tags to draw premium, interactive, responsive dashboards.

  ### 3. Service Orchestration & Tool Calling

  • It processes user prompts and coordinates tool calling with the MCP Server (running on port 8080 ).
  • It integrates the live rates returned by the Frankfurter API into the LLM context, which then shapes the final visual
  charts or conversion tables.

  ### 4. Well-Known Agent Discovery

  It hosts discovery endpoints for client-side integration:

  • GET /.well-known/agent-card.json : Exposes the formal Agent Card metadata detailing the agent's identity, capabilities,
  and system schemas.
  • GET /health : Exposes a standard health-check endpoint ( {"status": "ok"} ) endpoint "ok" ) check.json) state. 

Enter fullscreen mode Exit fullscreen mode

Where Does AG-UI come in?

The first version used a Vanilla TS interface to the backend agent system. AG-UI provides a standard way to look at the components:

So What is all this Doing?

All of the setup and configuration allows you to start debugging and visualizing Agent flows using the A2A protocol. The currency Agent has been validated using the A2A Inspector and the MCP interface via Antigravity CLI.

The make help provides the main project targets:

> make help

2.7k in | 204 out | 135 thinking
● Bash(make help) (ctrl+o to expand)

  Here is the output from running make help in the project root directory:

    Usage: make <target>

    Services:
      start - Start background Rust services (MCP + Agent)
      stop - Stop background Rust & Frontend services
      status - Check status of running services
      mcp - Start the Rust MCP Server (foreground)
      agent - Start the Rust Currency Agent (foreground)

    Vanilla TS Frontend (Port 4173):
      frontend-install - Install dependencies for Vanilla TS Frontend
      frontend-build - Build production assets for Vanilla TS Frontend
      frontend - Build and preview the Vanilla TS Frontend

    React Frontend (Port 3000):
      react-install - Install dependencies for React Frontend
      react-ui - Start React Frontend UI

    Development & Quality:
      install - Install all Rust & Frontend dependencies
      test - Run Rust end-to-end tests (MCP + Agent + Test Client)
      test-rust-e2e - Cleanly compile, launch, and run end-to-end client tests
      lint - Run cargo clippy and format checks
      format - Auto-format Rust code
      clean - Remove logs and caches

    Cloud Deployment:
      deploy - Deploy to Cloud Run using Cloud Build
      logs - Read logs from Cloud Run
      endpoint - Get the Cloud Run service endpoint
      remote-status - Check the status of the remote endpoint

Enter fullscreen mode Exit fullscreen mode

The Makefile provides targets to build and manage the project:

> make install

3.7k in | 174 out | 104 thinking
● Bash(make install) (ctrl+o to expand)

  The installation completed successfully! Here is a summary of what was done:

  1. Vanilla TS Frontend ( frontend/frontend/ ): Installed all Node.js dependencies ( npm install ).
  2. React Frontend ( frontend-react/ ): Installed all React/Next.js/CopilotKit dependencies ( npm install ).
  3. Rust Projects: Cleanly compiled the workspace binaries:
      • Built the main Rust Currency Agent ( cargo build ).
      • Built the Rust MCP Server ( cargo build --manifest-path mcp-server/Cargo.toml ).
Enter fullscreen mode Exit fullscreen mode

Lint:

> make lint

● Bash(make lint) (ctrl+o to expand)

  The lints passed cleanly with zero warnings or formatting issues!

  The check verified:

  • Cargo Clippy on both the main Agent codebase and the MCP Server codebase (with -D warnings enforced).
  • Cargo Fmt checks on all source files across both projects to ensure standard formatting is ensuring correct, idiomatic
  Rust code formatting.
Enter fullscreen mode Exit fullscreen mode

Test:

● Bash(make test) (ctrl+o to expand)

  I have started the end-to-end tests via make test as a background task. I will monitor its progress and notify you as
  soon as the test suite completes.

  The end-to-end test suite ( make test ) completed successfully!

  Here are the key validation details from the run:

  1. Service Startup: Both the background Rust services (the MCP Server on 8080 and the Currency Agent Server on 10000 )
  successfully booted.
  2. End-to-End Query Test:
      • Simulated user query: "how much is 100 USD in CAD?"
      • The Rust A2A server successfully invoked the Gemini model and the Frankfurter MCP tool.
      • A2UI Rendering: It correctly returned a schema-validated, premium visual payload ( <a2ui-json> ) containing
      structured components ( Card , Column , LineChart , Table elements) along with the textual response: "100 USD is
      equal to 141.82 CAD".
  3. Multi-Turn Integration Test:
      • Simulated user query: "how much is 100 USD?"
      • The agent successfully completed the turn by proactively clarifying the target currency: "To which currency would
      you like to convert 100 USD to?"
  4. Service Cleanliness: The test client cleanly wrapped up and invoked make stop to shutdown all active background
  services.
Enter fullscreen mode Exit fullscreen mode

Time to Start some Currency Arbitrage!

The servers are started:

xbill@penguin:~/currency-agent-rust-agui$ make start
Building Rust projects...
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s
Starting Rust MCP Server in background...
Waiting for MCP Server to initialize...
Starting Rust Currency Agent in background...
Services started. Logs: mcp.log, agent.log

xbill@penguin:~/currency-agent-rust-agui$ make status
Checking status of services...
  MCP Server: Running on port 8080 (PID 225534 )
  Currency Agent: Running on port 10000 (PID 225564 )
  Vite Preview: Running on port 4173 (PID 225593 225594 )
  React Frontend: Running on port 3000 (PID 218673 218674 225597 225598 )
Enter fullscreen mode Exit fullscreen mode

and checked end-to-end:


xbill@penguin:~/currency-agent-rust-agui$ make test-rust-e2e
Running Rust end-to-end test...
make stop
make[1]: Entering directory '/home/xbill/currency-agent-rust-agui'
Stopping servers...
make[1]: Leaving directory '/home/xbill/currency-agent-rust-agui'
Starting Rust MCP Server (port 8080)...
Starting Rust Currency Agent (port 10000)...
Running Rust Test Client...
cargo run --bin test_client
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s
     Running `target/debug/test_client`
--- 🔄 Connecting to agent at http://127.0.0.1:10000... ---
--- ✅ Connection successful. Agent Card retrieved: ---
Agent Name: currency_agent

--- 🚀 First turn completed, no further input required for this test case. ---
make stop
make[1]: Entering directory '/home/xbill/currency-agent-rust-agui'
Stopping servers...
make[1]: Leaving directory '/home/xbill/currency-agent-rust-agui'
xbill@penguin:~/currency-agent-rust-agui$ 
Enter fullscreen mode Exit fullscreen mode

Start the Currency Session

Once all the agents have been started — the react interface on port 3000 is connected with a standard web browser:

The new AG-UI tools are presented:

And in plain text:

250 USD is equal to 219.28 EUR based on the exchange rate of 1 USD = 0.87712 EUR as of 2026-06-26.
Enter fullscreen mode Exit fullscreen mode

The Co-Pilot chat-style box allows for more complex queries:

Data can be summarized in tables:

bar charts:

even complex dashboards can be built:

JSON Payloads

The key to AG-UI working with A2UI is the clean JSON payload generated by the A2A Agent. The Agent generates the raw data to display and leaves the actual presentation to the AG-UI client.

The JSON will look something like:

    <a2ui-json>
    {
      "version": "v0.9",
      "updateComponents": {
        "surfaceId": "currency_agent",
        "components": [
          {
            "id": "root",
            "component": "Card",
            "props": {
              "title": "USD to EUR Currency Conversion",
              "children": ["rate_headline", "actions_row"]
            }
          },
          {
            "id": "rate_headline",
            "component": "Text",
            "props": {
              "value": "250.00 USD is currently equivalent to 229.50 EUR (Rate: 0.9180).",
              "variant": "h3"
            }
          },
          {
            "id": "actions_row",
            "component": "Row",
            "props": {
              "children": ["btn_reverse", "btn_trends"]
            }
          },
          {
            "id": "btn_reverse",
            "component": "Button",
            "props": {
              "child": "🔄 Reverse (EUR to USD)",
              "primary": true,
              "action": {
                "name": "convert",
                "params": {
                  "amount": 250,
                  "from": "EUR",
                  "to": "USD"
                }
              }
            }
          },
          {
            "id": "btn_trends",
            "component": "Button",
            "props": {
              "child": "📈 View Trends",
              "primary": false,
              "action": {
                "name": "show_trends",
                "params": {
                  "from": "USD",
                  "to": "EUR"
                }
              }
            }
          }
        ]
      }
    }
    </a2ui-json>
Enter fullscreen mode Exit fullscreen mode

So how does AG-UI actually work?

Antigravity cli was used for a detailed breakdown:

> how does AG-UI actually work in this project?

30.6k in | 1.3k out | 641 thinking
● Read(/home/xbill/currency-agent-rust-agui/extending-a...ncy-agent-with-ag-ui-and-antigravity-cli-4kd1.md) (ctrl+o to expand)

  In this project, the integration of AG-UI (Agent-User Interaction) and A2UI (Agent-to-User Interface) allows the backend
  Rust agent to stream interactive, declarative UI components (like cards, tables, and charts) directly to the frontend
  rather than relying solely on plain text.
  The system is split into three main layers: Backend Schema Provisioning & Generation, Frontend Interception, and Client-
  Side Component Catalog Rendering.
  ──────
  ### 1. The Backend Agent (Rust)

  The compiled Rust agent (main.rs) is fully instructed on how to structure UI components:

  • The System Instruction: The agent's system prompt embeds full JSON schemas for A2UI version 0.9 (including 
  basic_catalog.json containing definitions for Card , Table , LineChart , and BarChart ).
  • MCP Integration: When you ask for an exchange rate, the agent uses the get_exchange_rate tool exposed by the Rust MCP
  server (main.rs) on port 8080 to query the Frankfurter API.
  • Markup Packaging: If the request involves comparative or historical data, the agent generates a declarative JSON payload
  representing the visual UI components and wraps it inside custom XML tags:
    <a2ui-json>
    {
      "updateComponents": {
        "components": [
          {
            "id": "chart_rate_history",
            "type": "LineChart",
            "props": {
              "title": "USD to EUR (Last 5 Days)",
              "labels": ["Mon", "Tue", "Wed", "Thu", "Fri"],
              "values": [0.91, 0.92, 0.91, 0.93, 0.92]
            }
          }
        ]
      }
    }
    </a2ui-json>

  ──────
  ### 2. Frontend Interception (React UI & Vanilla TS Sandbox)

  When the agent sends its token stream, the frontends intercept and parse the <a2ui-json> blocks so that raw JSON never
  leaks into the user's chat bubble.
  Taking the React Next.js frontend ( frontend-react/ ) as an example:

  #### Step A: Catalog Registration ( CopilotProvider.tsx & A2UICustomCatalog.tsx )

  In frontend-react/src/components/A2UICustomCatalog.tsx , the client maps the JSON schemas to real visual components using
  Zod definitions and custom React renderers:

    import { createCatalog } from "@copilotkit/a2ui-renderer";

    const customCatalogDefinitions = {
      BarChart: {
        props: z.object({
          title: z.string().optional(),
          labels: z.array(z.string()),
          values: z.array(z.number()),
          color: z.string().optional(),
        }),
      },
    };

    const customCatalogRenderers = {
      BarChart: ({ props }: any) => {
        // Custom premium Tailwind/React visual rendering logic goes here...
      }
    };

    export const customCatalog = createCatalog(customCatalogDefinitions, customCatalogRenderers, {
      catalogId: "https://a2ui.org/specification/v0_9/basic_catalog.json"
    });

  The application root is wrapped in <A2UIProvider catalog={customCatalog}> in CopilotProvider.tsx to make this registry
  globally accessible.
  #### Step B: Stream Parsing and Surface Binding ( page.tsx )
  The Markdown renderer in the assistant's message bubble intercepts the text payload:
  1. parseA2UIContent strips out the <a2ui-json> tag block and returns cleanText (displayed as standard text in the
  chat bubble) and parsedJson containing the schema block.
  2. If valid A2UI JSON exists, an <A2UIContainer> mounts and uses the useA2UI hook:
      • It calls processMessages() to dispatch the declarative update to the client-side state machine.
      • It renders <A2UIRenderer surfaceId={surfaceId} /> , which reads the active component tree from state and matches it
      against your customCatalog to mount the interactive React-render the component natively.

  ──────
  ### 3. Summary of Workflow

    sequenceDiagram
        autonumber
        User->>Frontend: "Compare USD, EUR, and GBP"
        Frontend->>Rust Agent: Forward User Request (Port 10000)
        Rust Agent->>MCP Server: Call get_exchange_rate (Port 8080)
        MCP Server->>Frankfurter API: Fetch Exchange Rates
        Frankfurter API-->>MCP Server: Rates Data
        MCP Server-->>Rust Agent: Rates Data
        Rust Agent->>Frontend: Stream Markdown Response + <a2ui-json> payload
        Note over Frontend: Intercepts & hides <a2ui-json> markup
        Frontend->>A2UI Engine: processMessages(updateComponents)
        A2UI Engine-->>Frontend: Render visual components (Table/Chart) inside UI Surface

  This architecture ensures a separation of concerns: the backend agent acts purely as an orchestrator of data and layout
  structures, and the frontends execute native, high-performance UI rendering on the client side.

Enter fullscreen mode Exit fullscreen mode

Summary

The goal of the demo/article was to enable AG-UI integration on a functional agent supporting common protocols like A2A and MCP.

The currency agent was chosen as a starting point and new AG-UI presentation objects were added to the user interface with Antigravity CLI.

This interface was tested with a frontend local React server.

Finally- the updated project integration was summarized, and code reviewed with Antigravity CLI.

Top comments (0)