DEV Community

Frank Joseph
Frank Joseph

Posted on

Mastra agent and Telex equals super integration

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
Enter fullscreen mode Exit fullscreen mode

Step 2: Install Mastra

npm install @mastra/core @mastra/agents
Enter fullscreen mode Exit fullscreen mode

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
    }),
  }),
});

Enter fullscreen mode Exit fullscreen mode

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
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

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
      );
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

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
    ]
  }
});
Enter fullscreen mode Exit fullscreen mode

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)