Skip to content
Learn Agentic AI
Learn Agentic AI11 min read2 views

Building a Discord Bot Agent: AI-Powered Server Assistant with TypeScript

Build an AI-powered Discord bot that acts as a server assistant using TypeScript. Covers discord.js setup, slash command registration, conversation context management, tool integration, and permission-based access control.

Why Discord Bots Make Great AI Agent Hosts

Discord provides a real-time messaging platform with built-in user identity, permissions, channels, and threads. These primitives map directly to agent concepts: users become agent clients, channels become conversation contexts, threads become persistent sessions, and server roles become permission boundaries.

Building an AI agent as a Discord bot gives you a production-ready interface without building a custom frontend — your users interact through a platform they already use daily.

Project Setup

Initialize a TypeScript project with discord.js and the OpenAI SDK:

flowchart TD
    START["Building a Discord Bot Agent: AI-Powered Server A…"] --> A
    A["Why Discord Bots Make Great AI Agent Ho…"]
    A --> B
    B["Project Setup"]
    B --> C
    C["Bot Client Setup"]
    C --> D
    D["Registering Slash Commands"]
    D --> E
    E["Handling Commands with Agent Logic"]
    E --> F
    F["Conversation Context with Threads"]
    F --> G
    G["Channel Summarization Tool"]
    G --> H
    H["Permission-Based Access Control"]
    H --> DONE["Key Takeaways"]
    style START fill:#4f46e5,stroke:#4338ca,color:#fff
    style DONE fill:#059669,stroke:#047857,color:#fff
mkdir discord-ai-agent && cd discord-ai-agent
npm init -y
npm install discord.js openai dotenv
npm install -D typescript @types/node tsx
npx tsc --init

Configure your environment:

# .env
DISCORD_TOKEN=your-bot-token
DISCORD_CLIENT_ID=your-client-id
OPENAI_API_KEY=sk-proj-your-key

Bot Client Setup

Create the bot client with the necessary intents:

// src/bot.ts
import { Client, GatewayIntentBits, Events } from "discord.js";
import { config } from "dotenv";

config();

const client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent,
  ],
});

client.once(Events.ClientReady, (readyClient) => {
  console.log(`Bot ready as ${readyClient.user.tag}`);
});

client.login(process.env.DISCORD_TOKEN);

Registering Slash Commands

Discord's slash command system provides a structured interface for agent interactions:

// src/commands/register.ts
import { REST, Routes, SlashCommandBuilder } from "discord.js";

const commands = [
  new SlashCommandBuilder()
    .setName("ask")
    .setDescription("Ask the AI assistant a question")
    .addStringOption((opt) =>
      opt
        .setName("question")
        .setDescription("Your question")
        .setRequired(true)
    ),
  new SlashCommandBuilder()
    .setName("summarize")
    .setDescription("Summarize recent messages in this channel")
    .addIntegerOption((opt) =>
      opt
        .setName("count")
        .setDescription("Number of messages to summarize")
        .setMinValue(5)
        .setMaxValue(100)
        .setRequired(false)
    ),
  new SlashCommandBuilder()
    .setName("research")
    .setDescription("Research a topic using multiple sources")
    .addStringOption((opt) =>
      opt.setName("topic").setDescription("Topic to research").setRequired(true)
    ),
];

const rest = new REST().setToken(process.env.DISCORD_TOKEN!);

await rest.put(
  Routes.applicationCommands(process.env.DISCORD_CLIENT_ID!),
  { body: commands.map((c) => c.toJSON()) }
);

Handling Commands with Agent Logic

Connect slash commands to your AI agent:

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

// src/handlers/ask.ts
import { ChatInputCommandInteraction } from "discord.js";
import OpenAI from "openai";

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export async function handleAsk(interaction: ChatInputCommandInteraction) {
  const question = interaction.options.getString("question", true);

  // Defer reply since LLM calls take time
  await interaction.deferReply();

  const completion = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [
      {
        role: "system",
        content: `You are a helpful assistant in a Discord server.
Keep responses under 2000 characters (Discord's message limit).
Use markdown formatting that Discord supports.
Be concise and direct.`,
      },
      { role: "user", content: question },
    ],
    max_tokens: 1024,
  });

  const reply = completion.choices[0].message.content ?? "No response generated.";

  await interaction.editReply(reply);
}

Register the handler in your main bot file:

// src/bot.ts
client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand()) return;

  switch (interaction.commandName) {
    case "ask":
      await handleAsk(interaction);
      break;
    case "summarize":
      await handleSummarize(interaction);
      break;
    case "research":
      await handleResearch(interaction);
      break;
  }
});

Conversation Context with Threads

Use Discord threads to maintain multi-turn conversations:

// src/handlers/conversation.ts
import { Message, ThreadChannel } from "discord.js";

const conversationHistory = new Map<string, OpenAI.ChatCompletionMessageParam[]>();

export async function handleThreadMessage(message: Message) {
  if (message.author.bot) return;
  if (!(message.channel instanceof ThreadChannel)) return;

  const threadId = message.channel.id;

  // Initialize or retrieve conversation history
  if (!conversationHistory.has(threadId)) {
    conversationHistory.set(threadId, [
      {
        role: "system",
        content: "You are a helpful assistant in a Discord thread. Maintain context across messages.",
      },
    ]);
  }

  const history = conversationHistory.get(threadId)!;
  history.push({ role: "user", content: message.content });

  // Trim history to last 20 messages to stay within token limits
  const trimmed = [history[0], ...history.slice(-20)];

  await message.channel.sendTyping();

  const completion = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: trimmed,
  });

  const reply = completion.choices[0].message.content ?? "...";
  history.push({ role: "assistant", content: reply });

  // Discord has a 2000 character limit
  if (reply.length > 2000) {
    const chunks = reply.match(/.{1,2000}/gs) ?? [];
    for (const chunk of chunks) {
      await message.reply(chunk);
    }
  } else {
    await message.reply(reply);
  }
}

Channel Summarization Tool

Build a tool that summarizes recent channel activity:

// src/handlers/summarize.ts
export async function handleSummarize(
  interaction: ChatInputCommandInteraction
) {
  const count = interaction.options.getInteger("count") ?? 50;
  await interaction.deferReply();

  // Fetch recent messages
  const messages = await interaction.channel?.messages.fetch({ limit: count });
  if (!messages || messages.size === 0) {
    await interaction.editReply("No messages found to summarize.");
    return;
  }

  const transcript = messages
    .reverse()
    .map((m) => `${m.author.displayName}: ${m.content}`)
    .join("\n");

  const completion = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [
      {
        role: "system",
        content: "Summarize the following Discord conversation. Highlight key topics, decisions, and action items.",
      },
      { role: "user", content: transcript },
    ],
  });

  await interaction.editReply(
    completion.choices[0].message.content ?? "Could not generate summary."
  );
}

Permission-Based Access Control

Restrict agent commands based on Discord server roles:

function requireRole(roleName: string) {
  return async (interaction: ChatInputCommandInteraction): Promise<boolean> => {
    const member = interaction.member;
    if (!member || !("roles" in member)) {
      await interaction.reply({
        content: "Could not verify your permissions.",
        ephemeral: true,
      });
      return false;
    }

    const hasRole = member.roles.cache.some((r) => r.name === roleName);
    if (!hasRole) {
      await interaction.reply({
        content: `You need the "${roleName}" role to use this command.`,
        ephemeral: true,
      });
      return false;
    }
    return true;
  };
}

// Usage in command handler
const checkAdmin = requireRole("AI Admin");

client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand()) return;

  if (interaction.commandName === "research") {
    if (!(await checkAdmin(interaction))) return;
    await handleResearch(interaction);
  }
});

FAQ

How do I handle Discord's 3-second interaction timeout?

Always call interaction.deferReply() immediately when handling a slash command. This gives you up to 15 minutes to send the actual response via interaction.editReply(). Without deferring, Discord expects a response within 3 seconds, which is too short for most LLM calls.

How do I prevent the bot from responding to itself?

Check message.author.bot at the beginning of every message handler and return early if true. This prevents infinite loops where the bot triggers itself. Also check message.author.id !== client.user?.id for extra safety.

What is the best way to handle conversation memory at scale?

For production bots serving many servers, replace the in-memory Map with Redis or a database. Use the thread ID or channel ID as the key. Set a TTL (time to live) on conversations so they are automatically cleaned up after inactivity. Consider storing only the last N messages per thread to bound memory usage.


#Discord #Bot #TypeScript #AIAgent #Discordjs #SlashCommands #AgenticAI #LearnAI #AIEngineering

Share
C

Written by

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like

Learn Agentic AI

Creating an AI Email Assistant Agent: Triage, Draft, and Schedule with Gmail API

Build an AI email assistant that reads your inbox, classifies urgency, drafts context-aware responses, and schedules sends using OpenAI Agents SDK and Gmail API.

Learn Agentic AI

How to Build an AI Coding Assistant with Claude and MCP: Step-by-Step Guide

Build a powerful AI coding assistant that reads files, runs tests, and fixes bugs using the Claude API and Model Context Protocol servers in TypeScript.

Learn Agentic AI

Building Your First MCP Server: Connect AI Agents to Any External Tool

Step-by-step tutorial on building an MCP server in TypeScript, registering tools and resources, handling requests, and connecting to Claude and other LLM clients.

Learn Agentic AI

Building an Agent Playground: Interactive Testing Environment for Prompt and Tool Development

Build a full-featured agent playground with a web UI that lets you test prompts live, tune parameters, compare model outputs side by side, and export working configurations for production deployment.

Learn Agentic AI

Building a Form Filler Agent with GPT Vision: Understanding and Completing Web Forms

Build an AI agent that uses GPT Vision to detect form fields, understand their purpose, map values to the correct inputs, and verify successful submission — all without relying on CSS selectors.

Learn Agentic AI

Generative UI with AI Agents: Dynamically Creating React Components from Natural Language

Explore how the Vercel AI SDK's generativeUI capability lets AI agents stream fully interactive React components to users, replacing static text responses with dynamic, data-rich interfaces.