TL;DR
In this article, you will learn how to add a frontend to any Mastra agent using the Agent User Interaction Protocol (AG-UI) developed by CopilotKit.
Before we jump in, here is what we will cover:
- Understanding AG-UI Protocol?
- Integrating Mastra AI agents with AG-UI protocol
What is the AG-UI Protocol?
The Agent User Interaction Protocol (AG-UI), developed by CopilotKit, is an open-source, lightweight, event-based protocol that facilitates rich, real-time interactions between the frontend and AI agents.
The AG-UI protocol enables event-driven communication, state management, tool usage, and streaming AI agent responses.
To send information between the frontend and your AI agent, AG-UI uses events such as:
- Lifecycle events: These events mark the start or end of an agent’s work, like “The agent started processing your request” (
RUN_STARTED
) or “The agent is done” (RUN_FINISHED
). - Text message events: These events carry the actual conversation, like “The agent is starting a new message” (
TEXT_MESSAGE_START
), “Here’s a piece of the response” (TEXT_MESSAGE_CONTENT
), or “The message is complete” (TEXT_MESSAGE_END
). - Tool call events: These events let the agent use tools, like “The agent wants to check the weather” (
TOOL_CALL_START
) or “Here’s the weather data” (TOOL_CALL_END
). - State management events: These events keep the frontend and the AI agent state in sync, like “Here’s the current state of the conversation” (
STATE_SNAPSHOT
) or “Here’s a small update to the state” (STATE_DELTA
).
You can learn more about the AG-UI protocol and its architecture here on AG-UI docs.

Now that we have learned what the AG-UI protocol is, let us see how to integrate it with different AI agent frameworks
Integrating Mastra AI agents with AG-UI protocol
In this section, you will learn how to build a weather assistant using the AG-UI protocol with Mastra AI agents framework and CopilotKit’s frontend framework.
Here’s a preview of what we will be building:
Let’s jump in.
Building AG-UI & Mastra agent backend
To get started, make sure you have Node.js 18+, npm, yarn, or pnpm installed on your machine. Then clone the AG-UI-Mastra repository that consists of a Node-based backend (awp-endpoint) and a Next.js/React frontend (mastra-frontend).
Next, navigate to the backend directory:
cd awp-endpoint
Then install the dependencies using NPM:
npm install
After that, create a .env
file with OpenAI API key:
OPENAI_API_KEY=your-openai-key
Then run the agent using the command below:
npx ts-node src/ag-ui-mastra.ts
To test the AG-UI Mastra integration, run the curl command below on https://reqbin.com/curl.
curl -X POST [http://localhost:8000/](http://localhost:8000/langgraph-research)awp \
-H "Content-Type: application/json" \
-d '{
"threadId": "test_thread_123",
"runId": "test_run_456",
"messages": [
{
"id": "msg_1",
"role": "user",
"content": "What is the weather in London?"
}
],
"tools": [],
"context": [],
"forwarded_props": {},
"state": {}
}'
Let us now see how the AG-UI Mastra integration works.
First, an endpoint with Express that handles frontend requests and responses to the frontend is defined.
// awp-endpoint/src/ag-ui-mastra.ts
// Create Express application instance
const app = express();
// Enable JSON body parsing middleware for incoming requests
app.use(express.json());
// Define the main AWP (Agent Workflow Protocol) endpoint
// This endpoint handles streaming communication with AG-UI agents
app.post("/awp", async (req: Request, res: Response) => {
}
Then input validation, response headers configuration and event encoder are defined, as shown below.
// awp-endpoint/src/ag-ui-mastra.ts
// STEP 1: Input Validation
// Parse and validate the incoming request body against the expected schema
// This ensures we have all required fields (threadId, runId, messages, etc.)
const input: RunAgentInput = RunAgentInputSchema.parse(req.body);
// STEP 2: Setup Server-Sent Events (SSE) Stream
// Configure response headers for real-time streaming communication
res.setHeader("Content-Type", "text/event-stream"); // Enable SSE
res.setHeader("Cache-Control", "no-cache"); // Prevent caching
res.setHeader("Connection", "keep-alive"); // Keep connection open
// STEP 3: Initialize Event Encoder
// Create encoder to format events according to AG-UI protocol
const encoder = new EventEncoder();
After that, a run start event is sent using the RUN_STARTED
lifecycle event.
// awp-endpoint/src/ag-ui-mastra.ts
// STEP 4: Send Run Started Event
// Notify the client that agent execution has begun
const runStarted = {
type: EventType.RUN_STARTED,
threadId: input.threadId,
runId: input.runId,
};
res.write(encoder.encode(runStarted));
Then a weather query is processed using a Mastra agent, as shown below.
// awp-endpoint/src/ag-ui-mastra.ts
// STEP 7: Retrieve Weather Agent from Mastra
// Get the configured weather agent that will handle the weather queries
const weatherAgent = mastra.getAgent("weatherAgent");
// STEP 8: Validate Agent Availability
// Ensure the weather agent is properly configured and available
if (!weatherAgent) {
throw new Error("Weather agent not found");
}
// STEP 9: Convert Message Format
// Transform AG-UI message format to Mastra-compatible format
// Filter out unsupported message roles and ensure proper structure
const mastraMessages = input.messages
.filter((msg: Message) =>
["user", "system", "assistant"].includes(msg.role)
)
.map((msg: Message) => ({
role: msg.role as "user" | "system" | "assistant",
content: msg.content || "",
}));
// STEP 10: Extract Location Information
// Parse the user's message to identify the location for weather query
// This helps with state tracking and provides context to the user
const userMessage = input.messages.find((msg) => msg.role === "user");
const extractedLocation = extractLocationFromMessage(
userMessage?.content || ""
);
// STEP 13: Execute Weather Agent
// Call Mastra's weather agent with the processed messages
// This will use the configured tools and models to generate a response
const result = await weatherAgent.generate(mastraMessages);
After that, text message events are sent to the frontend with the weather report content, as shown below.
// awp-endpoint/src/ag-ui-mastra.ts
// STEP 15: Generate Unique Message ID
// Create a unique identifier for the assistant's response message
const messageId = uuidv4();
// STEP 16: Start Text Message Stream
// Signal the beginning of the assistant's text response
const textMessageStart = {
type: EventType.TEXT_MESSAGE_START,
messageId,
role: "assistant",
};
res.write(encoder.encode(textMessageStart));
// STEP 17: Prepare Response Content
// Extract the generated text from Mastra's response
const response = result.text;
// STEP 18: Stream Response in Chunks
// Split the response into smaller chunks for smoother streaming experience
// This simulates real-time generation and provides better UX
const chunkSize = 10;
// Number of characters per chunk
for (let i = 0; i < response.length; i += chunkSize) {
const chunk = response.slice(i, i + chunkSize);
// Send each chunk as a separate content event
const textMessageContent = {
type: EventType.TEXT_MESSAGE_CONTENT,
messageId,
delta: chunk,
};
res.write(encoder.encode(textMessageContent));
// Add small delay between chunks to simulate natural typing
await new Promise((resolve) => setTimeout(resolve, 50));
}
// STEP 19: End Text Message Stream
// Signal that the assistant's message is complete
const textMessageEnd = {
type: EventType.TEXT_MESSAGE_END,
messageId,
};
res.write(encoder.encode(textMessageEnd));
Finally, a run-finished event is sent using the RUN_FINISHED
lifecycle event.
// awp-endpoint/src/ag-ui-mastra.ts
// STEP 20: Finalize Agent Run
// Send final event to indicate the entire agent run is complete
const runFinished = {
type: EventType.RUN_FINISHED,
threadId: input.threadId,
runId: input.runId,
};
res.write(encoder.encode(runFinished));
Building AG-UI & Mastra agent frontend using CopilotKit
In this section, you will learn how to create a connection between your AG-UI Mastra backend and your app frontend using CopilotKit.
Let’s get started.
Step 1: Getting started
First, navigate to the frontend directory:
cd mastra-frontend
Then install the dependencies:
npm install
After that, start the development server:
npm run dev
Navigate to http://localhost:3000/copilotkit, and you should see the AG-UI Mastra agent frontend up and running.

Let’s now see how to build the frontend UI for the AG-UI Mastra agent using CopilotKit.
Step 2: Connecting frontend to AG-UI & Mastra backend
First, create a bridge that connects your frontend and the AG-UI Mastra backend, as shown in the src/app/api/copilotkit/route.ts
file.
// Import the HttpAgent for making HTTP requests to the backend
import { HttpAgent } from "@ag-ui/client";
// Import CopilotKit runtime components for setting up the API endpoint
import {
CopilotRuntime,
ExperimentalEmptyAdapter,
copilotRuntimeNextJSAppRouterEndpoint,
} from "@copilotkit/runtime";
// Import NextRequest type for handling Next.js API requests
import { NextRequest } from "next/server";
// Create a new HttpAgent instance that connects to the Mastra backend running locally
const weatherAgent = new HttpAgent({
url: "http://127.0.0.1:8000/awp",
});
// Initialize the CopilotKit runtime with our research agent
const runtime = new CopilotRuntime({
agents: {
weatherAgent, // Register the weather agent with the runtime
},
});
/**
* Define the POST handler for the API endpoint
* This function handles incoming POST requests to the /api/copilotkit endpoint
*/
export const POST = async (req: NextRequest) => {
// Configure the CopilotKit endpoint for the Next.js app router
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
runtime, // Use the runtime with our research agent
serviceAdapter: new ExperimentalEmptyAdapter(), // Use the experimental adapter
endpoint: "/api/copilotkit", // Define the API endpoint path
});
// Process the incoming request with the CopilotKit handler
return handleRequest(req);
};
Step 3: Set up the CopilotKit Provider
To set up the CopilotKit Provider, the <CopilotKit>
component must wrap the Copilot-aware parts of your application. For most use cases, it's appropriate to wrap the CopilotKit provider around the entire app, e.g., in your layout.tsx
, as shown below in the src/app/copilotkit/layout.tsx
file.
// Import the CSS styles for CopilotKit UI components
import "@copilotkit/react-ui/styles.css";
// Import React and ReactNode type for typing children prop
import React, { ReactNode } from "react";
// Import the CopilotKit provider component from the core package
import { CopilotKit } from "@copilotkit/react-core";
// Get the runtime URL from environment variables
// This URL points to the CopilotKit runtime API endpoint
const runtimeUrl = process.env.NEXT_PUBLIC_COPILOTKIT_RUNTIME_URL;
export default function Layout({ children }: { children: ReactNode }) {
return (
<CopilotKit
runtimeUrl={runtimeUrl} // URL for the CopilotKit runtime API
agent="weatherAgent" // Specify which agent to use (matches the one defined in route.ts)
showDevConsole={false} // Hide the development console in production
>
{children}{" "}
{/* Render the child components inside the CopilotKit provider */}
</CopilotKit>
);
}
Step 4: Choose a Copilot UI
To set up your Copilot UI, first import the default styles in your root component (typically layout.tsx
).
import "@copilotkit/react-ui/styles.css";
Copilot UI ships with a number of built-in UI patterns; choose whichever one you like from CopilotPopup, CopilotSidebar, CopilotChat, or Headless UI.

In this case, we will use CopilotSidebar defined in the src/app/copilotkit/page.tsx
file.
// mastra-frontend/src/app/copilotkit/page.tsx
"use client";
import { CopilotSidebar } from "@copilotkit/react-ui";
import Weather from "../components/Weather";
export default function CopilotKitPage() {
return (
<main>
<Weather />
<CopilotSidebar
clickOutsideToClose={true}
defaultOpen={false}
labels={{
title: "Popup Assistant",
initial:
"Welcome to the Copilot Assistant! Ask me anything about the weather.",
}}
/>
</main>
);
}
Step 5: Creating a shared state between the frontend and AG-UI & Mastra backend
First, you need to define the agent state and emit it to the front end using the **STATE_SNAPSHOT**
state management event to create a shared state between the frontend and AG-UI & Mastra agent backend.
// awp-endpoint/src/ag-ui-mastra.ts
// STEP 5: Initialize Agent State
// Create initial state object to track the weather analysis process
// This state will be updated throughout the agent's execution
const initialState = {
status: "initializing", // Current execution status
currentStep: "weather_analysis", // What the agent is currently doing
location: null, // Location for weather query (to be extracted)
timestamp: new Date().toISOString(), // When the process started
processingStage: "starting", // Detailed stage information
weatherReport: null, // Final weather report (populated later)
};
// STEP 6: Send Initial State Snapshot
// Provide the client with the initial state of the agent
const stateSnapshot = {
type: EventType.STATE_SNAPSHOT,
snapshot: initialState,
};
res.write(encoder.encode(stateSnapshot));
Then use the CopilotKit useCoAgent hook to share the AG-UI Mastra agent backend state with your frontend, as shown in the src/app/components/Weather.tsx
file.
// mastra-frontend/src/app/components/Weather.tsx
import { useCoAgent} from "@copilotkit/react-core";
//...
function Weather() {
//...
// Connect to the weather agent's state using CopilotKit's useCoAgent hook
const { state, stop: stopWeatherAgent } = useCoAgent<WeatherAgentState>({
name: "weatherAgent",
initialState: {
status: "initializing",
currentStep: "weather_analysis",
processingStage: "starting",
weatherReport: null,
location: null,
timestamp: new Date().toISOString(),
},
});
Next, render the AG-UI Mastra agent's state in the chat UI. This is useful for informing the user about the agent's state in a more in-context way. To do this, you can use the useCoAgentStateRender hook.
// mastra-frontend/src/app/components/Weather.tsx
import { useCoAgentStateRender } from "@copilotkit/react-core";
// Implement useCoAgentStateRender hook
useCoAgentStateRender({
name: "weatherAgent",
handler: ({ nodeName }) => {
// Handle completion when the weather agent finishes
if (nodeName === "__end__" || state?.status === "completed") {
setTimeout(() => {
isWeatherInProgress.current = false;
stopWeatherAgent();
}, 1000);
}
},
render: ({ status }) => {
if (status === "inProgress") {
isWeatherInProgress.current = true;
return (
<div className="weather-in-progress bg-white p-4 rounded-lg shadow-sm border border-gray-200">
<div className="flex items-center gap-2 mb-3">
<div className="animate-spin h-4 w-4 border-2 border-blue-500 rounded-full border-t-transparent"></div>
<p className="font-medium text-gray-800">
Getting weather information...
</p>
</div>
<div className="status-container mb-3">
<div className="flex items-center justify-between mb-1.5">
<div className="text-sm font-medium text-gray-700">
{getStatusText()}
</div>
</div>
</div>
{state?.location && (
<div className="text-xs text-gray-500 flex items-center gap-1.5">
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
Location: {state.location}
</div>
)}
</div>
);
}
if (status === "complete") {
isWeatherInProgress.current = false;
return null;
}
return null;
},
});
Step 6: Streaming AG-UI & Mastra agent Response in the UI
To stream the weather report content, use a component with conditional rendering, as shown below.
// mastra-frontend/src/app/components/Weather.tsx
// When weather report is available, show it
if (state?.status === "completed" && state?.weatherReport) {
return (
<div className="flex flex-col gap-4 h-full max-w-4xl mt-4 mx-auto">
{/*...*/}
</div>
);
}
Then navigate to http://localhost:3000/copilotkit, add “What is the weather in London?” to the chat, and press “Enter.” You should see the AG-UI Mastra agent state rendered in the chat UI and the weather report streamed in the UI, as shown below.
Congratulations! You have successfully added a frontend to any AI agents framework using AG-UI protocol and CopilotKit.
Conclusion
In this guide, we have walked through the steps of adding a frontend to any AI agents framework using AG-UI protocol and CopilotKit.
While we’ve explored a couple of features, we have barely scratched the surface of the countless use cases for CopilotKit, ranging from building interactive AI chatbots to building agentic solutions—in essence, CopilotKit lets you add a ton of useful AI capabilities to your products in minutes.
Hopefully, this guide makes it easier for you to integrate AI-powered Copilots into your existing application.
Follow CopilotKit on Twitter and say hi, and if you'd like to build something cool, join the Discord community.
Get notified of the latest news and updates.
