Skip to main content
McDis-RCON’s addon system (mdaddons) provides global functionality that runs independently of individual server processes. Addons are active whenever McDis-RCON is connected to Discord.

What Are MDAddons?

MDAddons are Python scripts that extend McDis-RCON globally:
  • Global scope: Affect all processes and the entire McDis-RCON instance
  • Persistent: Run while McDis-RCON is connected to Discord
  • Discord-focused: React to Discord events only (no console access)
  • Reloadable: Can be reloaded with !!adreload
Addons are different from plugins. See Plugins Overview for process-specific extensions.

Addons vs Plugins

FeatureMDAddonsMDPlugins
ScopeGlobal (all processes)Single process
LifecycleMcDis-RCON start/stopProcess start/stop
LocationMcDis/.mdaddons/McDis/<process>/.mdplugins/
Console accessNoYes (via listener_events)
Discord eventsYes (direct)Yes (with listener_ prefix)
Reload command!!adreload!!mdreload <process>

When to Use Addons

Use addons for:

Cross-server features

Functionality that spans multiple servers (global announcements, status tracking)

Discord management

Custom Discord commands, role management, moderation tools

Independent services

Background tasks, scheduled jobs, API integrations

Global monitoring

Track metrics across all servers, centralized logging
Use plugins for:
  • Server console monitoring
  • Process-specific logic
  • Per-server chat bridging
  • Individual server events

Addon Architecture

File Structure

Addons are stored in the global .mdaddons folder:
McDis/
├── .mdaddons/
│   ├── global_announcer.py      ← Single-file addon
│   ├── status_tracker.py        ← Single-file addon
│   └── advanced_bot/            ← Folder-based addon
│       ├── __init__.py          ← Must contain mdaddon class
│       ├── commands.py
│       ├── utils.py
│       └── config.json
├── server 1/
├── server 2/
└── network 1/

Addon Types

McDis-RCON supports two addon structures: 1. Single-file addons (.py files):
class mdaddon:
    def __init__(self, client: McDisClient):
        self.client = client
2. Folder-based addons (with __init__.py):
advanced_bot/
├── __init__.py    ← Contains mdaddon class
├── helpers.py
└── data.json
For folder-based addons, the __init__.py file must contain the mdaddon class.

Addon Class Structure

Every addon must define an mdaddon class:
from mcdis_rcon.classes import McDisClient
import discord

class mdaddon:
    def __init__(self, client: McDisClient):
        """Called when addon is loaded"""
        self.client = client
        # Initialization code here

    async def on_message(self, message: discord.Message):
        """React to Discord messages"""
        # No 'listener_' prefix needed for addons
        pass

    def unload(self):
        """Called when addon is unloaded (optional)"""
        # Cleanup code here
        pass
Unlike plugins, addon event methods do NOT use the listener_ prefix. Use on_message, not listener_on_message.

Addon Capabilities

1. Discord Event Listeners

Addons can react to any Discord event:
import discord
from mcdis_rcon.classes import McDisClient

class mdaddon:
    def __init__(self, client: McDisClient):
        self.client = client

    async def on_message(self, message: discord.Message):
        """React to all Discord messages"""
        if message.author.bot:
            return
        
        if message.channel.id == self.client.panel.id:
            await message.add_reaction('✅')

2. Access to All Processes

Addons can interact with all configured servers:
class mdaddon:
    def __init__(self, client: McDisClient):
        self.client = client

    async def on_message(self, message: discord.Message):
        if message.content == "!stopall":
            # Stop all processes
            for process in self.client.processes:
                if process.is_running():
                    process.stop()

3. Panel Interaction

Addons have full access to the Discord panel:
class mdaddon:
    def __init__(self, client: McDisClient):
        self.client = client
        self.panel = client.panel  # Discord panel channel

    async def on_ready(self):
        """Called when bot connects to Discord"""
        await self.panel.send("🟢 McDis-RCON addon loaded!")

4. Background Tasks

Addons can run background tasks:
import asyncio

class mdaddon:
    def __init__(self, client: McDisClient):
        self.client = client
        # Start background task
        asyncio.create_task(self.periodic_status())

    async def periodic_status(self):
        """Send status updates every hour"""
        while True:
            await asyncio.sleep(3600)  # 1 hour
            running = [p.name for p in self.client.processes if p.is_running()]
            await self.client.panel.send(f"Running servers: {', '.join(running)}")

Addon Lifecycle

Loading Process

1

McDis-RCON connects

When the bot connects to Discord
2

Addon discovery

McDis-RCON scans McDis/.mdaddons/ for:
  • .py files with mdaddon class
  • Folders with __init__.py containing mdaddon class
3

Addon instantiation

Each addon is imported and instantiated:
addon_instance = mod.mdaddon(self)  # 'self' is McDisClient
4

Registration

Addons are registered in the global addon registry:
self.addons["global_announcer"] = addon_instance
5

Ready

Addons are now active and listening for Discord events

Unloading Process

1

Unload trigger

!!adreload command is executed
2

Cleanup hook

If the addon has an unload() method, it’s called:
class mdaddon:
    def unload(self):
        # Cleanup: cancel tasks, close connections, etc.
        pass
3

Deregistration

Addons are removed from the registry:
self.addons = {}
4

Reload

New addon instances are created from updated files

Available Discord Events

Addons can listen to all Discord.py events without the listener_ prefix:
# Message events
async def on_message(self, message):
async def on_message_edit(self, before, after):
async def on_message_delete(self, message):

# Reaction events
async def on_reaction_add(self, reaction, user):
async def on_reaction_remove(self, reaction, user):

# Member events
async def on_member_join(self, member):
async def on_member_remove(self, member):

# Ready event
async def on_ready(self):

# And many more...
See discord.py documentation for the complete list.

Client Object Reference

The client object passed to addons is the McDisClient instance:
self.client.panel          # Discord panel channel
self.client.config         # md_config.yml contents
self.client.processes      # List of all processes
self.client.servers        # List of server processes
self.client.networks       # List of network processes
self.client.prefix         # Command prefix (!!)
self.client.cwd            # McDis working directory
self.client.addons         # Dictionary of loaded addons

# Methods
await self.client.error_report(title, error)  # Create error report
await self.client.call_addons(function, args) # Call method on all addons

Example: Simple Addon

Here’s a complete example that reacts to panel messages:
reactor.py
import discord
from mcdis_rcon.classes import McDisClient

class mdaddon:
    def __init__(self, client: McDisClient):
        self.client = client

    async def on_message(self, message: discord.Message):
        """Add reaction to panel messages"""
        if message.author.bot:
            return  # Ignore messages from bots

        if message.channel.id == self.client.panel.id:
            await message.add_reaction('✅')
Place this file in McDis/.mdaddons/reactor.py and reload:
!!adreload
Unlike plugins, this addon reacts to messages even when no servers are running.

Use Cases

Global Commands

Custom Discord commands that work across all servers

Status Dashboard

Track and display status of all processes in Discord

Scheduled Tasks

Automated backups, announcements, restarts

API Integration

Connect to external APIs, webhooks, databases

Role Management

Automated Discord role assignment based on events

Logging & Analytics

Centralized logging, metrics collection

Best Practices

Error Handling

Always handle errors gracefully:
async def on_message(self, message: discord.Message):
    try:
        # Your code here
        pass
    except Exception as e:
        await self.client.error_report(
            title="Addon Error",
            error=str(e)
        )

Resource Management

Clean up resources in the unload() method:
import aiohttp

class mdaddon:
    def __init__(self, client: McDisClient):
        self.client = client
        self.session = aiohttp.ClientSession()

    def unload(self):
        # Close HTTP session
        asyncio.create_task(self.session.close())

Background Task Cancellation

Cancel background tasks on unload:
class mdaddon:
    def __init__(self, client: McDisClient):
        self.client = client
        self.task = asyncio.create_task(self.background_work())

    async def background_work(self):
        while True:
            await asyncio.sleep(60)
            # Do work

    def unload(self):
        self.task.cancel()

Async Everywhere

All Discord event handlers must be async:
# Correct
async def on_message(self, message):  # Async
    await message.channel.send("Hello")

# Wrong
def on_message(self, message):  # Sync - won't work
    await message.channel.send("Hello")

Folder-Based Addons

For complex addons, use the folder structure:
__init__.py
from mcdis_rcon.classes import McDisClient
import discord
from .helpers import format_status
from .commands import handle_command

class mdaddon:
    def __init__(self, client: McDisClient):
        self.client = client

    async def on_message(self, message: discord.Message):
        if message.content.startswith("!"):
            await handle_command(self.client, message)
helpers.py
def format_status(processes):
    """Format process status for display"""
    running = [p.name for p in processes if p.is_running()]
    stopped = [p.name for p in processes if not p.is_running()]
    return f"Running: {', '.join(running)}\nStopped: {', '.join(stopped)}"
commands.py
import discord
from .helpers import format_status

async def handle_command(client, message: discord.Message):
    if message.content == "!status":
        status = format_status(client.processes)
        await message.channel.send(status)
Folder-based addons allow better code organization for complex functionality.

Advanced Examples

For production-ready addon implementations, check out:
  • AeternumBot - Advanced event system and commands
  • EnigmaBot (Coming soon) - Additional automation examples

Next Steps

Creating Addons

Learn how to write custom addons with detailed examples

Plugins Overview

Understand process-specific plugins for console monitoring

Build docs developers (and LLMs) love