Building AI Agents with TypeScript: Complete Guide
How to build production AI agents using TypeScript and the modern AI stack.
TypeScript is becoming the language of choice for AI agent development. With excellent type safety, a rich ecosystem, and native MCP support, TypeScript offers the best developer experience for building AI agents in 2026.
Why TypeScript for AI Agents?
- Type safety β Catch errors at compile time, not runtime
- MCP SDK β First-class TypeScript SDK available
- Vercel AI SDK β Powerful streaming and tool-calling utilities
- Mastra β Production agent framework built for TypeScript
- Edge runtime β Deploy to edge nodes globally
- Shared types β Define schemas once, use everywhere
Project Setup
mkdir my-agent && cd my-agent
npm init -y
# Core dependencies
npm install @modelcontextprotocol/sdk zai ai @ai-sdk/openai @ai-sdk/anthropic
# Utilities
npm install zod dotenv pino
# Development
npm install -D typescript @types/node tsx
npx tsc --init
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"outDir": "./dist",
"types": ["node"]
}
}
Building Your First Agent
Basic Agent
import { Agent } from "mastra";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
const agent = new Agent({
name: "Research Assistant",
model: openai("gpt-4o"),
instructions: `You are a research assistant. Find information,
analyze it, and provide well-sourced answers.`,
tools: {
search: {
description: "Search the web",
parameters: z.object({
query: z.string(),
}),
execute: async ({ query }) => {
return await searchAPI(query);
},
},
summarize: {
description: "Summarize a long text",
parameters: z.object({
text: z.string(),
maxLength: z.number().default(200),
}),
execute: async ({ text, maxLength }) => {
return text.slice(0, maxLength);
},
},
},
});
// Run the agent
const result = await agent.generate("What are the latest AI trends?");
console.log(result.text);
Streaming Responses
const stream = await agent.stream("Explain MCP protocol");
for await (const chunk of stream) {
process.stdout.write(chunk);
}
Adding MCP Tools
import { McpClient } from "@modelcontextprotocol/sdk";
// Connect to MCP server
const mcpClient = new McpClient({
serverUrl: "https://my-mcp-server.example.com/sse",
authToken: process.env.MCP_TOKEN,
});
// Discover available tools
const tools = await mcpClient.listTools();
// Use in agent
const agent = new Agent({
name: "MCP-Powered Agent",
model: openai("gpt-4o"),
tools: Object.fromEntries(
tools.map(tool => [
tool.name,
{
description: tool.description,
parameters: tool.inputSchema,
execute: async (params) => {
const result = await mcpClient.callTool(tool.name, params);
return result.content;
},
},
])
),
});
State Management
import { AgentState } from "mastra";
// Define state schema
const stateSchema = z.object({
conversationHistory: z.array(z.object({
role: z.enum(["user", "assistant", "tool"]),
content: z.string(),
timestamp: z.string(),
})),
userPreferences: z.record(z.string()),
currentTask: z.string().nullable(),
completedTasks: z.array(z.string()),
});
// Stateful agent
class StatefulAgent {
private state: AgentState<z.infer<typeof stateSchema>>;
constructor() {
this.state = new AgentState({
initial: {
conversationHistory: [],
userPreferences: {},
currentTask: null,
completedTasks: [],
},
persist: true, // Persist to Redis
ttl: 3600, // 1 hour
});
}
async process(message: string): Promise<string> {
const state = await this.state.get();
// Add to history
state.conversationHistory.push({
role: "user",
content: message,
timestamp: new Date().toISOString(),
});
// Process with context
const response = await agent.generate(message, {
context: state.conversationHistory,
});
// Update state
state.conversationHistory.push({
role: "assistant",
content: response.text,
timestamp: new Date().toISOString(),
});
// Trim history if too long
if (state.conversationHistory.length > 20) {
state.conversationHistory = state.conversationHistory.slice(-15);
}
await this.state.set(state);
return response.text;
}
}
Multi-Agent Systems
class MultiAgentSystem {
private agents: Map<string, Agent>;
constructor() {
this.agents = new Map();
}
register(name: string, agent: Agent) {
this.agents.set(name, agent);
}
async route(request: string): Promise<Agent> {
// Use a classifier to pick the right agent
const classifier = this.agents.get("classifier")!;
const result = await classifier.generate(
`Which agent should handle this? Options: ${[...this.agents.keys()].join(", ")}\nRequest: ${request}`
);
const agentName = result.text.trim();
return this.agents.get(agentName) || this.agents.get("default")!;
}
async process(request: string): Promise<string> {
const agent = await this.route(request);
return await agent.generate(request).then(r => r.text);
}
}
// Setup
const system = new MultiAgentSystem();
system.register("classifier", classifierAgent);
system.register("research", researchAgent);
system.register("writer", writingAgent);
system.register("analyst", analysisAgent);
system.register("default", generalAgent);
Error Handling
class ResilientAgent {
async generate(prompt: string, retries = 3): Promise<string> {
for (let i = 0; i < retries; i++) {
try {
return await this.agent.generate(prompt).then(r => r.text);
} catch (error) {
if (error instanceof RateLimitError) {
await sleep(error.retryAfter * 1000);
} else if (error instanceof ContextLengthError) {
prompt = await this.compressContext(prompt);
} else if (i === retries - 1) {
throw error;
}
}
}
throw new Error("Max retries exceeded");
}
}
Testing
import { describe, it, expect } from "vitest";
describe("Research Agent", () => {
it("should use search tool for factual queries", async () => {
const result = await agent.generate("What is the current EU GDP?");
expect(result.toolCalls).toBeDefined();
expect(result.toolCalls![0].name).toBe("search");
});
it("should provide sources", async () => {
const result = await agent.generate("What are the latest AI trends?");
expect(result.text).toContain("http"); // Contains URLs
});
});
Deployment
Deploy to Vercel
// app/api/agent/route.ts
import { Agent } from "mastra";
import { openai } from "@ai-sdk/openai";
const agent = new Agent({
name: "API Agent",
model: openai("gpt-4o"),
instructions: "You are a helpful assistant.",
});
export async function POST(request: Request) {
const { message } = await request.json();
const result = await agent.generate(message);
return Response.json({ response: result.text });
}
Deploy as MCP Server
import { McpServer } from "@modelcontextprotocol/sdk";
const server = new McpServer({ name: "my-agent-tools" });
server.tool("answer_question", {
question: z.string(),
}, async (args) => {
const result = await agent.generate(args.question);
return { content: [{ type: "text", text: result.text }] };
});
server.run({ transport: "http", port: 8000 });
Conclusion
TypeScript offers the best developer experience for building AI agents β type safety, excellent tooling, and native MCP support. With frameworks like Mastra and the Vercel AI SDK, you can build production agents quickly and safely.
Learn More
- AI Agent Orchestration Tools
- Skill-Based AI Architecture
- How to Build an MCP Server
- Best AI Tools for Developers 2026
Build and publish agents on SkillExchange.