DEV Community

Modinat Adesola
Modinat Adesola

Posted on

Integrating Mastra Agents with Telex via A2A Requests

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

Enter fullscreen mode Exit fullscreen mode

Step 2: Install Mastra

npm install @mastra/core @mastra/agents

Enter fullscreen mode Exit fullscreen mode

If you plan to you typscript (Recommended)

npm install -D typescript ts-node @types/node
npx tsc --init

Enter fullscreen mode Exit fullscreen mode

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

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": "not feeling like socializing"
        }
      ],
      "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";
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
      );
    }
  },
});

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

Enter fullscreen mode Exit fullscreen mode

Now your Mastra server will automatically expose the route:

http://localhost:4111/a2a/agent/wordAgent
Enter fullscreen mode Exit fullscreen mode

Deployment to Mastra Cloud
Once you’ve created your project and agent locally, simply push:

npx mastra deploy
Enter fullscreen mode Exit fullscreen mode

After deployment, you’ll get a public URL like:

https://white-cloud-noon.mastra.cloud/a2a/agent/wordAgent
Enter fullscreen mode Exit fullscreen mode

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)