Building agent workflow has never been easy. However Mastra and Telex and are changing that narrative.
In this guide, I’ll walk you through how I integrated a Mastra AI Agent with Telex, using A2A (App-to-App) requests to enable smooth communication between the two platforms.
Telex is an educational platform similar to Slack, it supports apps and agents that interact using messages.
Mastra, on the other hand, is a modern framework for building, deploying, and scaling AI agents in production.
By combining the two, we can create a setup where Telex sends structured messages to Mastra, Mastra processes them intelligently using our agent, and then sends a meaningful response back, all through JSON-RPC.
Installing and Setting Up Mastra
Before writing any code, we’ll need to set up Mastra locally.
You can always check the official docs Mastra Getting Started
Step 1: Create and initialize your project
mkdir weather-agent
cd weather-agent
npm init -y
Step 2: Install Mastra
npm install @mastra/core @mastra/agents
Step 3: Define your Agent
Create a file src/mastra/agents/weather-agent.ts:
import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { LibSQLStore } from '@mastra/libsql';
import { weatherTool } from '../tools/weather-tool.js';
export const weatherAgent = new Agent({
name: 'Weather Agent',
instructions: `
You are a helpful weather assistant that provides accurate weather information and can help planning activities based on the weather
Use the weatherTool to fetch current weather data.
`,
model: 'google/gemini-2.5-pro',
tools: { weatherTool },
memory: new Memory({
storage: new LibSQLStore({
url: 'file:../mastra.db', // path is relative to the .mastra/output directory
}),
}),
});
Understanding the A2A Request Format
When Telex sends data to an AI agent, it follows a strict JSON-RPC 2.0 format.
Here’s what a sample Telex request looks like:
{
"jsonrpc": "2.0",
"id": "request-001",
"method": "message/send",
"params": {
"message": {
"kind": "message",
"role": "user",
"parts": [
{
"kind": "text",
"text": "What's the weather condition"
}
],
"messageId": "msg-001"
},
"configuration": {
"blocking": true
}
}
}
It is importatnt to make sure Mastra understands this format, extract the relevant message, and then send it to the agent for processing.
Setting Up the Custom A2A Endpoint
Mastra allows you to extend its API by registering custom routes.
We created one called /a2a/agent/:agentId to handle requests from Telex.
Here’s the full code for a2aRouter.ts:
import { registerApiRoute } from '@mastra/core/server';
import { randomUUID } from 'crypto';
interface A2AMessagePart {
kind: 'text' | 'data';
text?: string;
data?: any;
}
interface A2AMessage {
role: string;
parts: A2AMessagePart[];
messageId?: string;
taskId?: string;
}
export const a2aAgentRoute = registerApiRoute('/a2a/agent/:agentId', {
method: 'POST',
handler: async (c: any) => {
try {
const mastra = c.get('mastra');
const agentId = c.req.param('agentId');
// Parse JSON-RPC 2.0 request
const body = await c.req.json();
const { jsonrpc, id: requestId, method, params } = body;
// Validate JSON-RPC 2.0 format
if (jsonrpc !== '2.0' || !requestId) {
return c.json(
{
jsonrpc: '2.0',
id: requestId || null,
error: {
code: -32600,
message: 'Invalid Request: jsonrpc must be "2.0" and id is required'
}
},
400
);
}
const agent = mastra.getAgent(agentId);
if (!agent) {
return c.json(
{
jsonrpc: '2.0',
id: requestId,
error: {
code: -32602,
message: `Agent '${agentId}' not found`
}
},
404
);
}
// Extract messages
const { message, messages, contextId, taskId, metadata } = params || {};
const messagesList: A2AMessage[] = message
? [message]
: Array.isArray(messages)
? messages
: [];
// Convert A2A messages to Mastra format
const mastraMessages = messagesList.map((msg) => ({
role: msg.role,
content:
msg.parts
?.map((part) => {
if (part.kind === 'text') return part.text || '';
if (part.kind === 'data') return JSON.stringify(part.data || {});
return '';
})
.join('\n') || ''
}));
// Execute agent
const response = await agent.generate(mastraMessages);
const agentText = response.text || '';
// Build artifacts
const artifacts = [
{
artifactId: randomUUID(),
name: `${agentId}Response`,
parts: [{ kind: 'text', text: agentText }]
}
];
if (response.toolResults?.length) {
artifacts.push({
artifactId: randomUUID(),
name: 'ToolResults',
parts: response.toolResults.map((result: any) => ({
kind: 'data',
data: result
}))
});
}
// Build conversation history
const history = [
...messagesList.map((msg) => ({
kind: 'message',
role: msg.role,
parts: msg.parts,
messageId: msg.messageId || randomUUID(),
taskId: msg.taskId || taskId || randomUUID()
})),
{
kind: 'message',
role: 'agent',
parts: [{ kind: 'text', text: agentText }],
messageId: randomUUID(),
taskId: taskId || randomUUID()
}
];
// Return A2A-compliant response
return c.json({
jsonrpc: '2.0',
id: requestId,
result: {
id: taskId || randomUUID(),
contextId: contextId || randomUUID(),
status: {
state: 'completed',
timestamp: new Date().toISOString(),
message: {
messageId: randomUUID(),
role: 'agent',
parts: [{ kind: 'text', text: agentText }],
kind: 'message'
}
},
artifacts,
history,
kind: 'task'
}
});
} catch (error: any) {
return c.json(
{
jsonrpc: '2.0',
id: null,
error: {
code: -32603,
message: 'Internal error',
data: { details: error?.message || String(error) }
}
},
500
);
}
}
});
Linking the Route to Mastra
In the mastra/index.ts, register the new route like this:
import { Mastra } from '@mastra/core/mastra';
import { PinoLogger } from '@mastra/loggers';
import { LibSQLStore } from '@mastra/libsql';
import { weatherWorkflow } from './workflows/weather-workflow.js';
import { weatherAgent } from './agents/weather-agent.js';
import { a2aAgentRoute } from '../mastra/routes/a2a-agent-route.js';
export const mastra = new Mastra({
workflows: { weatherWorkflow },
agents: { weatherAgent },
storage: new LibSQLStore({
// stores observability, scores, ... into memory storage, if it needs to persist, change to file:../mastra.db
url: ":memory:",
}),
logger: new PinoLogger({
name: 'Mastra',
level: 'info',
}),
telemetry: {
// Telemetry is deprecated and will be removed in the Nov 4th release
enabled: false,
},
observability: {
// Enables DefaultExporter and CloudExporter for AI tracing
default: { enabled: true },
},
server: {
build: {
openAPIDocs: true,
swaggerUI: true,
},
apiRoutes: [
a2aAgentRoute
]
}
});
Deployment to Mastra Cloud
Once you’ve created your project and agent locally, simply push:
npx mastra deploy
After deployment, you’ll get a public URL like:
https://white-cloud-noon.mastra.cloud/a2a/agent/weatherAgent
This is the endpoint you’ll register on Telex under your A2A app configuration.
Now, Telex will send messages directly to your agent and receive responses in real time.
Top comments (0)