Today, I created a stock monitoring AI agent using Mastra, and it was mind-blowing. I also learned about inter-agent communication and the need for a standardized protocol - A2A protocol (Agent to Agent). I'm currently in the 13th cohort of the HNG internship program and for my stage 3 task I was told to build a simple AI agent that integrates with Telex, a Slack alternative for communities and also an AI agent platform. Since my stack is mainly NodeJS, I was instructed to use Mastra AI.
Set up
Create a Mastra project using the command below
pnpm create mastra@latest
Create the stock agent
// src/mastra/agents/stock-agent.ts
import { Agent } from "@mastra/core/agent";
import {
getStockTool,
getStocksTool,
analyzeTrendTool,
} from "../tools/stock-tool";
import { Memory } from "@mastra/memory";
import { LibSQLStore } from "@mastra/libsql";
export const stockAgent = new Agent({
model: "google/gemini-2.5-flash",
name: "Stock Agent",
instructions: `
you are a stock agent. Your task is to provide accurate and up-to-date information about stocks prices. You should only provide information on MAMAA related stocks like price and summarize market trends.
Your responsibilities:
1. Fetch current stock prices for all MAMAA companies
2. Analyze performance trends
3. Provide insights on price movements
4. Compare relative performance across MAMAA stocks
5. Present data in a clear, actionable format
6. Provide trend analysis for MAMAA stocks. This should include a detailed analysis of the stock's performance over a specific period, highlighting key trends and patterns and should be about 3 sentences long.
`,
tools: { getStockTool, getStocksTool, analyzeTrendTool },
memory: new Memory({
storage: new LibSQLStore({ url: "file:../mastra.db" }),
}),
});
Create the various tools the agent would use to fetch data from external APIs
This tools would fetch stock related data from the Alphavantage API.
//src/mastra/tools/stock-tool.ts
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
import axios from "axios";
export const getStockTool = createTool({
id: "Get single stock",
description: "Get current price of a Stock",
inputSchema: z.object({
symbol: z.string().describe("Symbol of stock"),
}),
outputSchema: z.object({
symbol: z.string().describe("Stock symbol"),
price: z.number().describe("Stock price"),
}),
execute: async ({ context }) => {
return await getStockInfo(context.symbol);
},
});
export const getStocksTool = createTool({
id: "Get FAANG stocks",
description: "Get current stock prices for MAMAA stocks",
inputSchema: z.object({
symbols: z.array(z.string()).describe("Array of stock symbols"),
}),
outputSchema: z.array(
z.object({
symbol: z.string().describe("Stock symbol"),
price: z.number().describe("Stock price"),
}),
),
execute: async ({ context }) => {
return await getStocksInfo(context.symbols);
},
});
export const analyzeTrendTool = createTool({
id: "Analyze stock trend",
description: "Analyze stock trend",
inputSchema: z.object({
symbols: z.array(z.string().describe("Symbol of stock")),
}),
outputSchema: z.array(
z.object({
symbol: z.string().describe("Stock symbol"),
trend: z.number().describe("Stock trend"),
}),
),
execute: async ({ context }) => {
return await stockTrendAnalysis(context.symbols);
},
});
const getStockInfo = async (symbol: string) => {
const stocksUrl = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${symbol}&apikey=${process.env.ALPHAVANTAGE_API_KEY}`;
const response = await axios.get(stocksUrl);
return response.data;
};
const getStocksInfo = async (symbols: string[]) => {
console.log(symbols);
const promises = symbols.map(async (symbol) => {
const stockInfo = await getStockInfo(symbol);
return stockInfo;
});
const results = await Promise.all(promises);
return results;
};
const stockTrendAnalysis = async (symbols: string[]) => {
const promises = symbols.map(async (symbol) => {
const stockInfo = await getStockInfo(symbol);
const data = stockInfo["Time Series (Daily)"];
const prices = Object.values(data).map((day) =>
parseFloat(day["4. close"]),
);
const trend = prices.reduce((acc, price) => acc + price, 0);
return { symbol, trend };
});
const results = await Promise.all(promises);
return results;
};
Register the Stock agent with Mastra
//src/mastra/index.ts
import { config } from "dotenv";
import { Mastra } from "@mastra/core/mastra";
import { PinoLogger } from "@mastra/loggers";
import { LibSQLStore } from "@mastra/libsql";
import { stockAgent } from "./agents/stock-agent";
config();
export const mastra = new Mastra({
agents: { stockAgent },
bundler: {
externals: ["axios"],
},
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",
}),
});
Create the A2A route handler
This exposes an endpoint that allows other agents to communicate with ours using the A2A protocal
// src/routes/index.ts
import { registerApiRoute } from "@mastra/core/server";
import { randomUUID } from "crypto";
export const a2aAgentRoute = registerApiRoute("/a2a/agent/:agentId", {
method: "POST",
handler: async (c) => {
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 from params
const { message, messages, contextId, taskId, metadata } = params || {};
let messagesList = [];
if (message) {
messagesList = [message];
} else if (messages && Array.isArray(messages)) {
messagesList = 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 array
const artifacts = [
{
artifactId: randomUUID(),
name: `${agentId}Response`,
parts: [{ kind: "text", text: agentText }],
},
];
// Add tool results as artifacts
if (response.toolResults && response.toolResults.length > 0) {
artifacts.push({
artifactId: randomUUID(),
name: "ToolResults",
parts: response.toolResults.map((result) => ({
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) {
return c.json(
{
jsonrpc: "2.0",
id: null,
error: {
code: -32603,
message: "Internal error",
data: { details: error.message },
},
},
500,
);
}
},
});
Add the routes to the mastra instance config object
src/mastra/index.ts
...
import { a2aAgentRoute } from "./routes/index";
...
export const mastra = new Mastra({
...
server: {
build: {
openAPIDocs: true,
swaggerUI: true,
},
apiRoutes: [a2aAgentRoute],
},
})
Run your agent
pnpm run dev
AI agents are a mind-blowing concept. During implementation, I was concerned about how I'd need to parse whatever data I fetch from Alphavantage for my agent to use, but I soon realized that it wasn't even a problem. All I had to do was define the input schema for the various tools using zod, and the LLM took care of everything else. Although the functionality of this agent is basic, I've really learned a lot about creating AI agents using Mastra, and I plan on going through their documentation and creating a much more interesting AI agent.
Top comments (0)