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.
Overview
Goal
Our goal is to create a two-way connection where we send messages through Telex such as:
“word to describe not feeling like socializing”
And Mastra responds intelligently with something like:
“Reclusive — Meaning: Avoiding the company of other people; solitary. This often implies a longer-term tendency.....”
Stack Used:
🧩 Mastra
 – For building and hosting AI agents
⚙️ Node.js + TypeScript
🔗 Telex
 – For sending JSON-RPC based A2A requests
🌐 Mastra Cloud – For easy deployment and public endpoint exposure
Installing and Setting Up Mastra
Before writing any code, we’ll need to set up Mastra locally.
You can always check the official docs here → Mastra Getting Started 
Step 1: Create and initialize your project
mkdir telex-mastra-agent
cd telex-mastra-agent
npm init -y
Step 2: Install Mastra
npm install @mastra/core @mastra/agents
If you plan to you typscript (Recommended)
npm install -D typescript ts-node @types/node
npx tsc --init
Step 3: Define your Agent
Create a file src/agents/word-agent.ts:
import { Agent } from "@mastra/agents";
export const wordAgent = new Agent({
  name: "wordAgent",
  instructions: "Analyze a user's message and determine the emotional tone or meaning.",
});
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": "not feeling like socializing"
        }
      ],
      "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";
import { wordAgent } from "../src/mastra/agents/word-agent";
export const a2aAgentRoute = registerApiRoute("/a2a/agent/:agentId", {
  method: "POST",
  handler: async (c) => {
    try {
      const mastra = c.get("mastra");
      const agentId = c.req.param("agentId");
      const body = await c.req.json();
      const { jsonrpc, id: requestId, method, params } = body || {};
      // --- Basic JSON-RPC validation ---
      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
        );
      }
      // --- Expect only Telex's "message/send" method ---
      if (method !== "message/send") {
        return c.json(
          {
            jsonrpc: "2.0",
            id: requestId,
            error: {
              code: -32601,
              message: `Unsupported method: "${method}". Only "message/send" is allowed.`,
            },
          },
          400
        );
      }
      // --- Extract text from Telex message ---
      const userText =
        params?.message?.parts
          ?.find((p: any) => p.kind === "text")
          ?.text?.trim() || "";
      if (!userText) {
        return c.json(
          {
            jsonrpc: "2.0",
            id: requestId,
            error: {
              code: -32602,
              message: 'Invalid params: "message.parts[0].text" is required',
            },
          },
          400
        );
      }
      // --- Prepare input for Mastra agent ---
      const input = {
        action: userText,
        context: userText,
      };
      // --- Resolve agent ---
      const agent =
        agentId === "wordAgent" ? wordAgent : mastra.getAgent(agentId);
      if (!agent) {
        return c.json(
          {
            jsonrpc: "2.0",
            id: requestId,
            error: {
              code: -32602,
              message: `Agent '${agentId}' not found`,
            },
          },
          404
        );
      }
      // --- Call agent ---
      let agentResponse: any;
      let agentText = "";
      if (typeof (agent as any).run === "function") {
        agentResponse = await (agent as any).run(input);
        agentText =
          agentResponse?.output?.text ??
          agentResponse?.result?.text ??
          agentResponse?.text ??
          String(agentResponse ?? "");
      } else if (typeof (agent as any).generate === "function") {
        agentResponse = await (agent as any).generate(userText);
        agentText = agentResponse?.text ?? String(agentResponse ?? "");
      } else {
        throw new Error("Agent does not support run() or generate()");
      }
      // --- Build A2A Telex-style response ---
      const taskId = randomUUID();
      const contextId = randomUUID();
      const messageId = randomUUID();
      const result = {
        id: randomUUID(),
        status: {
          state: "completed",
          timestamp: new Date().toISOString(),
          message: {
            kind: "message",
            role: "agent",
            parts: [
              {
                kind: "text",
                text: agentText,
              },
            ],
            messageId,
            taskId,
            contextId,
          },
        },
        kind: "task",
      };
      // --- Send successful JSON-RPC response ---
      return c.json({
        jsonrpc: "2.0",
        id: requestId,
        result,
      });
    } catch (error: any) {
      console.error("A2A route error:", error);
      return c.json(
        {
          jsonrpc: "2.0",
          id: null,
          error: {
            code: -32603,
            message: error?.message || "Internal server 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 { wordAgent } from "./agents/word-agent";
import { wordWorkflow } from "./workflows/word-workflow";
import { toolCallAppropriatenessScorer, completenessScorer } from "./scorers/word-scorer";
import { a2aAgentRoute } from "./a2aRouter";
export const mastra = new Mastra({
  workflows: { wordWorkflow },
  agents: { wordAgent },
  scorers: { toolCallAppropriatenessScorer, completenessScorer },
  storage: new LibSQLStore({ url: ":memory:" }),
  logger: new PinoLogger({ name: "Mastra", level: "info" }),
  observability: { default: { enabled: true } },
  server: {
    build: { openAPIDocs: true, swaggerUI: true },
    apiRoutes: [a2aAgentRoute],
  },
});
Now your Mastra server will automatically expose the route:
http://localhost:4111/a2a/agent/wordAgent
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/wordAgent
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 🎉
Key Takeaways
Mastra Agents can be extended with custom routes to handle specialized integrations.
Telex uses JSON-RPC 2.0, so your response format must be strict and well-structured.
The A2A integration provides a clean, real-time bridge between conversational platforms and intelligent backend agents.
    
Top comments (0)