Skip to main content
Glama

discord-ops

Agency-grade Discord MCP server with multi-guild project routing.

CI npm License: MIT

Features

  • 72 MCP tools — messaging, personas, polls, forums, notes, channels, moderation, roles, webhooks, botops, audit log, threads, guilds, invites, permissions, search, 23 templates, OG embed unfurling, project introspection

  • Project note board — a durable, directed-note log per project; agents leave hand-offs and read them on startup, aware of other concurrent sessions

  • Webhook personas — one bot, unlimited posting identities via per-message username/avatar overrides; never create another bot application

  • Native polls — structured agent/human consensus with per-answer voter reads and bot flags

  • Forum work queues — posts as tasks, tags as status labels, archive to close

  • Real-time event feed — optional discord-ops listen sidecar streams messages/reactions to a local sink read by get_events, zero API calls per poll

  • Multi-guild project routingsend_message({ project: "my-app", channel: "builds" }) instead of raw channel IDs

  • Agent-to-agent coordination — agents post tasks, peers reply and react, originators read results back via get_replies / get_reactions — Discord as a durable coordination bus

  • Notification routing — map notification types (ci_build, deploy, error) to channels per project

  • Owner pings — configure project owners so releases, errors, and alerts auto-mention the right people

  • Bot personas — named bots with identity metadata, per-channel bot assignment, and per-bot tool profiles

  • Multi-bot support — manage multiple Discord bots from a single MCP server with per-project tokens

  • Tool profiles — load only the tools an agent needs; cut schema overhead by 85% with slim profiles

  • Smart channel resolution — channel params accept channel name or snowflake ID, with 4-layer fuzzy fallback

  • Auto-embed for send_message — every message gets a branded embed by default; raw: true for plain text

  • OG metadata unfurlingsend_embed fetches Open Graph metadata server-side and renders rich link previews

  • Flexible token configuration — configurable default token env var, optional default when all projects use per-project tokens

  • Config validationdiscord-ops validate detects duplicate guilds, missing tokens, invalid channel refs without connecting to Discord

  • HTTP/SSE + stdio transports — stdio for Claude Code, HTTP/SSE for remote MCP clients

  • HTTP transport auth — bearer token auth via DISCORD_OPS_HTTP_TOKEN with constant-time comparison

  • Dry-run mode — simulate destructive operations without calling Discord API

  • Interactive setup wizarddiscord-ops setup discovers guilds/channels live, configures single/multi-bot routing and the coordination board, and writes a project .mcp.json for one-command install

  • Security hardening — rate limiting, permission pre-flight checks, snowflake ID validation, self-protection guards

  • Lazy login — tools enumerate before Discord connects; first tool call triggers login

  • Zod validation — all inputs validated before execution

  • Error sanitization — tokens, webhook URLs, and snowflake IDs stripped from error output

  • Audit logging — every tool call logged to stderr

Related MCP server: Discord MCP Server

Quick Start

# Install
npm install -g discord-ops

# Interactive setup — connects to your bot, discovers guilds/channels, and
# writes ~/.discord-ops.json + a project .mcp.json (so Claude Code loads it)
discord-ops setup

# Or manual setup
export DISCORD_TOKEN="your-bot-token"
discord-ops health

# Start MCP server (stdio)
discord-ops

# Start MCP server (HTTP/SSE)
discord-ops serve --port 3000

Claude Code Integration

Add to your project's .mcp.json. Use npx with @latest so every session automatically uses the latest published release — without it, npx may serve a stale cached version indefinitely:

{
  "mcpServers": {
    "discord-ops": {
      "command": "npx",
      "args": ["-y", "discord-ops@latest"],
      "env": {
        "DISCORD_TOKEN": "${DISCORD_TOKEN}"
      }
    }
  }
}

The ${VAR} syntax is Claude Code's native env var interpolation — it reads the value from your shell environment at startup. Export your bot token in ~/.zshrc (or .bashrc) and it will be available to all projects without hardcoding it in any file.

Multi-org setup (per-project tokens)

When each project has its own bot, pass all token env vars and let ~/.discord-ops.json handle which project uses which:

{
  "mcpServers": {
    "discord-ops": {
      "command": "npx",
      "args": ["-y", "discord-ops@latest"],
      "env": {
        "ORG_A_DISCORD_TOKEN": "${ORG_A_DISCORD_TOKEN}",
        "ORG_B_DISCORD_TOKEN": "${ORG_B_DISCORD_TOKEN}"
      }
    }
  }
}

Each project in ~/.discord-ops.json declares "token_env": "ORG_A_DISCORD_TOKEN" and discord-ops routes automatically. No default DISCORD_TOKEN needed when all projects have token_env set.

Single-org shorthand

If all your projects share one bot, just pass that token:

{
  "mcpServers": {
    "discord-ops": {
      "command": "npx",
      "args": ["-y", "discord-ops@latest"],
      "env": {
        "DISCORD_TOKEN": "${DISCORD_TOKEN}"
      }
    }
  }
}

Custom token env var name

If another tool already claims DISCORD_TOKEN, use DISCORD_OPS_TOKEN_ENV to point at a different name:

{
  "mcpServers": {
    "discord-ops": {
      "command": "npx",
      "args": ["-y", "discord-ops@latest"],
      "env": {
        "DISCORD_OPS_TOKEN_ENV": "MY_BOT_TOKEN",
        "MY_BOT_TOKEN": "${MY_BOT_TOKEN}"
      }
    }
  }
}

Project Routing

The killer feature: route messages by project name and channel alias instead of raw IDs.

Global config (~/.discord-ops.json)

{
  "projects": {
    "my-app": {
      "guild_id": "123456789012345678",
      "channels": {
        "dev": "CHANNEL_ID",
        "builds": "CHANNEL_ID",
        "alerts": "CHANNEL_ID",
        "releases": "CHANNEL_ID"
      },
      "default_channel": "dev"
    }
  },
  "default_project": "my-app",
  "notification_routing": {
    "ci_build": "builds",
    "deploy": "builds",
    "release": "releases",
    "error": "alerts",
    "dev": "dev"
  }
}

Per-project bot tokens

Projects can specify their own bot token via token_env:

{
  "projects": {
    "org-a": {
      "guild_id": "111111111111111111",
      "channels": { "dev": "CHANNEL_ID" },
      "default_channel": "dev",
      "token_env": "ORG_A_DISCORD_TOKEN"
    },
    "org-b": {
      "guild_id": "222222222222222222",
      "channels": { "dev": "CHANNEL_ID" },
      "default_channel": "dev",
      "token_env": "ORG_B_DISCORD_TOKEN"
    }
  }
}

When all projects have token_env, the default DISCORD_TOKEN is optional. Each project connects with its own bot.

Bot personas

Give bots names, roles, and per-channel assignment. This is ideal when your Discord server runs multiple bots with distinct personas (e.g., a community helper vs. a tech ops bot).

{
  "bots": {
    "claire": {
      "name": "Claire",
      "role": "Community helper",
      "description": "Handles support and community channels",
      "token_env": "CLAIRE_TOKEN",
      "default_profile": "messaging"
    },
    "courier": {
      "name": "Clarity Courier",
      "role": "Technical operations",
      "token_env": "COURIER_TOKEN",
      "default_profile": "full",
      "profile_add": ["send_template"],
      "profile_remove": []
    }
  },
  "projects": {
    "clarity-house": {
      "guild_id": "123456789012345678",
      "bot": "courier",
      "channels": {
        "general": "111111111111111111",
        "support": { "id": "222222222222222222", "bot": "claire" },
        "dev-ops": "333333333333333333",
        "ai-testing": { "id": "444444444444444444", "bot": "claire" }
      },
      "default_channel": "dev-ops",
      "tool_profile": "full"
    }
  }
}

How it works:

  • bots — named bot definitions with identity metadata and token_env

  • project.bot — default bot for the project (all channels use this bot unless overridden)

  • Channel bot override — individual channels can use a different bot: { "id": "...", "bot": "claire" }

  • default_profile — per-bot tool profile (restricts which tools a bot can use at runtime)

  • profile_add — additional tools to load on top of the bot's default_profile

  • profile_remove — tools to exclude from the bot's default_profile

  • Token resolution — channel bot → project bot → project token_env → default DISCORD_TOKEN

  • Bot persona in routing — resolved targets include bot: { name, role } metadata for agent context

  • Backwards compatiblebots is optional; channels accept both "ID" and { "id": "ID", "bot": "name" } formats

Use list_bots to see all configured bots, their project assignments, channel overrides, and connection status.

Owner pings

Configure project owners so that releases, errors, and alerts automatically prepend @mentions. This ensures the right people are always paged for critical events without hardcoding mentions in every message.

{
  "projects": {
    "my-app": {
      "guild_id": "123456789012345678",
      "channels": { "releases": "CHANNEL_ID", "alerts": "CHANNEL_ID" },
      "owners": ["820027414902079548"],
      "notify_owners_on": ["release", "error", "alert"]
    }
  }
}

notify_owners_on values: any notification type (release, error, alert, ci_build, deploy, etc.)

Safety: "dev" is hardcoded to never trigger owner pings regardless of config — dev noise stays quiet.

When a send_message or send_embed call uses a matching notification_type, the owner mentions are automatically prepended to the message. No other changes needed.

Smart channel resolution

The channel param accepts a channel name or snowflake ID anywhere a channel is needed. Resolution happens in four layers:

  1. Exact alias match"builds" hits the builds alias in your project config

  2. Fuzzy alias match"build" or "blds" resolves to the closest alias

  3. Live Discord API lookup"general" resolves even with no configured alias

  4. Error — if none of the above find a match

This means you can pass channel: "general" and it will work even for channels that aren't in your config. You can also pass a raw snowflake ID directly — channel: "1234567890" bypasses alias resolution entirely.

Per-project config (.discord-ops.json in repo root)

{
  "project": "my-app",
  "notification_routing": {
    "ci_build": "builds",
    "deploy": "builds"
  }
}

Usage

# By project + channel alias
send_message({ project: "my-app", channel: "builds", content: "Build passed!" })

# By notification type (auto-routed to channel, owner pinged if configured)
send_message({ project: "my-app", notification_type: "release", content: "v1.0.0 shipped" })

# Direct channel ID (always works)
send_message({ channel_id: "123456789", content: "Hello" })

# Channel by name (live lookup — no alias needed)
send_message({ project: "my-app", channel: "general", content: "Hello" })

Messaging

Auto-embed

send_message automatically wraps messages in a polished embed with a color bar, description, and timestamp. Pass raw: true to send plain text instead.

# Branded embed (default)
send_message({ project: "my-app", channel: "dev", content: "Deploy complete" })

# Plain text
send_message({ project: "my-app", channel: "dev", content: "pong", raw: true })

send_embed — OG metadata unfurling

send_embed fetches Open Graph metadata server-side from any URL and renders a rich preview embed. All OG fields can be overridden.

send_embed({
  url: "https://www.npmjs.com/package/discord-ops/v/0.23.0",
  project: "my-app",
  channel: "releases",
  title: "discord-ops v0.23.0",
  description: "Owner pings, smart channel resolution, category editing",
  color: 5763719,
  footer: "Released April 3, 2026"
})

Useful for sharing GitHub PRs, npm releases, blog posts, or any URL with rich previews — the bot fetches the metadata so Discord's CDN doesn't cache-bust client-side unfurls.

Agent-to-Agent Coordination

discord-ops doubles as a coordination bus for multi-agent systems. One agent posts a task as a message, peer agents (or humans) reply and react, and the originating agent reads the replies and reactions back. Because the exchange lives in Discord, it's durable across sessions, works between agent processes that share no memory, and every step is observable by the humans in the server.

The loop is four tool calls:

# Agent A posts a task and records the returned message ID
send_message({ project: "my-app", channel: "engineering", content: "TASK: verify the staging deploy", raw: true })
→ { "id": "333333333333333333" }

# Agent B claims it with a reaction, then replies
add_reaction({ project: "my-app", channel: "engineering", message_id: "333333333333333333", emoji: "👀" })
send_message({ project: "my-app", channel: "engineering", reply_to: "333333333333333333", content: "RESULT: verified, all checks green", raw: true })

# Agent A collects results at its next task boundary
get_replies({ project: "my-app", channel: "engineering", message_id: "333333333333333333" })
get_reactions({ project: "my-app", channel: "engineering", message_id: "333333333333333333" })

Three read-side tools close the loop: get_message fetches a single message with its reply, thread, and reaction state; get_reactions reports who reacted with what; get_replies collects replies to a specific message. get_messages includes reply_to on every message, so scanning agents can reconstruct conversation structure. For exchanges longer than one round trip, create_thread from the original message keeps the channel clean.

See docs/agent-coordination.md for the full protocol — reaction vocabulary, threads, polling guidance, multi-project setups, and anti-patterns. New to discord-ops? docs/project-onboarding.md is the start-to-finish walkthrough for adopting it as a team's coordination bus — project setup, the shared note board, personas, polls and forums, and least-privilege profiles.

Tools

Messaging (16 tools)

Tool

Description

send_message

Send a message with project routing (auto-embed by default)

send_embed

Fetch OG metadata from a URL and post a rich embed

get_messages

Fetch recent messages (supports ISO 8601 timestamps)

get_message

Fetch one message with reply, thread, and reaction state

get_reactions

List reactions on a message with per-user detail

get_replies

Collect replies to a specific message

edit_message

Edit a bot message

delete_message

Delete a message

add_reaction

React to a message

pin_message

Pin a message in a channel

unpin_message

Unpin a message

search_messages

Search messages by content, author, or date range

send_template

Send a styled embed using a built-in template

list_templates

List available templates with required variables

notify_owners

Ping project owners based on notification type

forward_message

Forward a message snapshot to another channel

Personas (3 tools)

One bot, unlimited posting identities — persona identity is a per-message webhook username/avatar override, so no new Discord bots or developer-portal work are ever needed.

Tool

Description

create_persona

Ensure a channel has a persona-capable webhook (token never exposed)

send_as

Post as any persona name/avatar; returns the message id

list_personas

List persona webhooks per channel or guild

Polls (3 tools)

Tool

Description

send_poll

Create a native poll (2-10 answers, multiselect, up to 32 days)

get_poll_results

Read tallies and per-answer voters with bot flags

end_poll

Immediately finalize a poll (idempotent)

Forums (3 tools)

Forum channels as durable work queues: a post is a task, tags are status labels, archiving closes it.

Tool

Description

create_forum_post

Create a tagged forum post with an initial message

list_forum_posts

List posts, filterable by tag name

update_forum_post

Replace tags and/or archive (close) a post

Botops (5 tools)

Manage the bot application itself via API — no developer portal round-trips. (Creating new bot applications remains portal-only by Discord's design; use personas instead.)

Tool

Description

set_bot_nick

Set or clear the bot's per-guild nickname

update_application

Edit description, icon, tags, and install params of the current app

list_app_emojis

List application-owned emojis

create_app_emoji

Upload an app emoji; returns an add_reaction-ready identifier

delete_app_emoji

Delete an app emoji

Channels (9 tools)

Tool

Description

list_channels

List guild channels

get_channel

Get channel details

create_channel

Create a channel

edit_channel

Edit channel name, topic, category, or position (text, voice, and categories)

delete_channel

Delete a channel

purge_messages

Bulk-delete messages (max 100, < 14 days old)

set_slowmode

Set or disable slowmode

move_channel

Move a channel to a different category or position

set_permissions

Set channel permission overrides for a role or member

Moderation (4 tools)

Tool

Description

kick_member

Kick a member from a guild

ban_member

Ban a user from a guild

unban_member

Unban a user

timeout_member

Timeout (mute) a member

Roles (5 tools)

Tool

Description

list_roles

List guild roles

create_role

Create a new role

edit_role

Edit role properties

delete_role

Delete a role

assign_role

Add or remove a role from a member

Webhooks (6 tools)

Tool

Description

create_webhook

Create a webhook on a channel

get_webhook

Get webhook details

list_webhooks

List webhooks for a guild or channel

edit_webhook

Edit webhook properties

delete_webhook

Delete a webhook

execute_webhook

Send a message via webhook

Audit (1 tool)

Tool

Description

query_audit_log

Query guild audit log with filters

Guilds & Members (6 tools)

Tool

Description

list_guilds

List bot's guilds

get_guild

Get guild details

get_invites

Get all active invites for a guild

create_invite

Create an invite link for a channel

list_members

List guild members

get_member

Get member details

Threads (3 tools)

Tool

Description

create_thread

Create a thread

list_threads

List active threads

archive_thread

Archive (and optionally lock) a thread

Notes (4 tools)

A durable, directed-note log — one board channel per project — that agents read on startup and use for hand-offs across concurrent sessions. See docs/project-onboarding.md.

Tool

Description

leave_note

Leave a directed note (to a session, role, or all) on the board

get_notes

Read the board, filtered by recipient, sender, tag, or unresolved

resolve_note

Mark a note handled (✅) with an optional reply

list_sessions

See which sessions are active on a project, from note activity

System (4 tools)

Tool

Description

health_check

Bot status, version, connected guilds, and permission audit

list_projects

List all projects with guild mappings, token status, and validation

list_bots

List all bot personas with project assignments and channel overrides

get_events

Read the local real-time event feed written by discord-ops listen

Tool Profiles

Load only the tools an agent needs. Reduces schema token overhead by up to 85% for narrow use cases.

Built-in profiles

Profile

Tools

Description

full

72

All tools (default)

monitoring

17

get_messages, get_message, get_replies, get_reactions, send_message, send_as, add_reaction, create_thread, send_poll, get_poll_results, leave_note, get_notes, resolve_note, list_sessions, health_check, list_projects, list_bots

readonly

13

get_messages, get_message, get_replies, get_reactions, get_poll_results, list_personas, list_forum_posts, list_channels, list_members, get_guild, health_check, list_projects, list_bots

moderation

7

get_messages, kick_member, ban_member, timeout_member, delete_message, purge_messages, query_audit_log

messaging

10

add_reaction, delete_message, edit_message, forward_message, get_messages, get_message, get_replies, get_reactions, send_as, send_message

channels

7

create_channel, delete_channel, edit_channel, get_channel, list_channels, purge_messages, set_slowmode

webhooks

6

create_webhook, delete_webhook, edit_webhook, execute_webhook, get_webhook, list_webhooks

Using profiles

# Via CLI flag
discord-ops --profile monitoring

# Load specific tools
discord-ops --tools "send_message,send_template,health_check"

# Combined (profile as base + add tools)
discord-ops --profile readonly --tools "send_message"

Per-project profile config

Profiles can be set per project in ~/.discord-ops.json so each agent gets only what it needs:

{
  "projects": {
    "my-app": {
      "guild_id": "123456789012345678",
      "channels": { "dev": "CHANNEL_ID", "alerts": "CHANNEL_ID" },
      "tool_profile": "monitoring",
      "profile_add": ["send_message"],
      "profile_remove": ["list_members"]
    }
  }
}
  • tool_profile — base profile to use for this project

  • profile_add — add tools not included in the base profile

  • profile_remove — remove tools from the base profile

CLI

discord-ops                        Start MCP server (stdio transport)
discord-ops serve                  Start MCP server (HTTP/SSE transport)
discord-ops run <tool> --args '{…}' Run any tool directly (no AI/MCP required)
discord-ops init                   Scaffold a per-project .discord-ops.json
discord-ops setup                  Interactive setup wizard (single + multi-bot)
discord-ops health                 Run health check + permission audit
discord-ops validate               Validate config without connecting to Discord
discord-ops --profile              Load a built-in tool profile (monitoring/readonly/moderation/full)
discord-ops --tools                Load specific tools by name (comma-separated)
discord-ops --dry-run              Simulate destructive operations
discord-ops --help                 Show help
discord-ops --version              Show version

run — call any tool without an AI agent

The run subcommand executes any discord-ops tool directly from the shell — no MCP client, no AI. Pass all tool input as a single JSON string via --args.

# Send a plain message
npx discord-ops@latest run send_message \
  --args '{"project":"my-app","channel":"general","content":"Deployment complete."}'

# Send a rich release announcement
npx discord-ops@latest run send_template \
  --args '{
    "project": "my-app",
    "channel": "releases",
    "template": "release",
    "vars": {
      "name": "my-app",
      "version": "v1.2.0",
      "author_name": "My Org",
      "highlights": "• New feature A\n• Bug fix B",
      "npm": "my-app@latest",
      "npm_url": "https://www.npmjs.com/package/my-app",
      "link": "https://github.com/my-org/my-app/releases/tag/v1.2.0",
      "footer": "Published 2026-04-03"
    }
  }'

Any tool name accepted by the MCP server works here — send_message, send_template, send_embed, list_channels, etc. The same input schema applies; validation errors are printed with field paths and exit code 1.

init — scaffold a per-project config

The init subcommand creates a .discord-ops.json file in the current directory for per-project configuration.

discord-ops init --project my-app --guild-id 123456789012345678

# With channels and custom token env var
discord-ops init --project my-app --guild-id 123456789012345678 \
  --token-env MY_APP_TOKEN \
  --channel builds=987654321098765432 \
  --channel releases=111222333444555666

# Overwrite existing and mark as default project
discord-ops init --project my-app --guild-id 123456789012345678 --force --default

Flags:

Flag

Required

Description

--project <name>

Yes

Project name

--guild-id <snowflake>

Yes

Discord guild/server snowflake ID

--token-env <VAR>

No

Env var for bot token (default: DISCORD_TOKEN)

--channel <alias>=<id>

No

Channel alias mapping, repeatable

--force

No

Overwrite existing .discord-ops.json

--default

No

Mark this project as default_project in the config

serve flags

The serve subcommand accepts additional flags for HTTP transport configuration:

Flag

Description

--port <port>

HTTP port (default: 3000)

--allowed-origin <origin>

Allowed CORS origin (default: http://localhost)

--allow-unauthenticated

Start without requiring DISCORD_OPS_HTTP_TOKEN (insecure, shows a warning)

Environment Variables

Variable

Required

Description

DISCORD_TOKEN

No*

Default Discord bot token (*required unless all projects have token_env)

DISCORD_OPS_TOKEN_ENV

No

Override which env var holds the default token (default: DISCORD_TOKEN)

<PROJECT>_TOKEN

No

Per-project bot tokens (configured via token_env in project config)

DISCORD_OPS_CONFIG

No

Path to global config file, or inline JSON string (default: ~/.discord-ops.json)

DISCORD_OPS_LOG_LEVEL

No

debug, info, warn, error (default: info)

DISCORD_OPS_DRY_RUN

No

Enable dry-run mode (any truthy value)

DRY_RUN

No

Enable dry-run mode (any truthy value, alias)

DISCORD_OPS_HTTP_TOKEN

No

Bearer token for HTTP transport authentication (strongly recommended)

Token resolution

  1. Channel-level bot — if the channel has a bot override, use that bot's token_env

  2. Project-level bot — if the project has a bot, use that bot's token_env

  3. Project-level token_env — project's own token env var

  4. Default tokenDISCORD_TOKEN (or custom via DISCORD_OPS_TOKEN_ENV)

If DISCORD_OPS_TOKEN_ENV is set, its value names the env var holding the default token (e.g., DISCORD_OPS_TOKEN_ENV=MY_BOT_TOKEN reads MY_BOT_TOKEN). If all projects have token_env or bot set, no default token is needed.

CI/CD Integration

Use discord-ops run in GitHub Actions (or any CI) to post rich Discord notifications after a publish, deploy, or build — no AI agent required.

Config shape for CI

In CI you typically have one bot token and one project. Pass a minimal config as an inline JSON string via DISCORD_OPS_CONFIG. No file writing needed.

When storing as a GitHub secret, minify to a single line — multiline strings break secret injection. The shape (pretty-printed for readability):

{
  "projects": {
    "my-app": {
      "guild_id": "123456789012345678",
      "channels": {
        "releases": "987654321098765432",
        "builds": "111222333444555666"
      },
      "default_channel": "releases"
    }
  },
  "default_project": "my-app"
}
  • No token_env needed — omitting it means the project uses DISCORD_TOKEN (the default).

  • owners and notify_owners_on are optional — include them if you want owner pings on errors.

  • Channel values are Discord snowflake IDs. Channel names (aliases) resolve to these IDs.

GitHub Actions example

Store two secrets in your repo:

  • BOOKED_DISCORD_BOT_TOKEN — your bot token

  • DISCORD_OPS_CONFIG — the config JSON minified to a single line (multiline strings break GitHub secrets)

{"projects":{"my-app":{"guild_id":"123456789012345678","channels":{"releases":"987654321098765432"},"default_channel":"releases"}},"default_project":"my-app"}
- name: Notify Discord
  run: |
    npx discord-ops@latest run send_template --args '{
      "project": "my-app",
      "channel": "releases",
      "template": "release",
      "vars": {
        "name": "my-app",
        "version": "${{ steps.version.outputs.version }}",
        "author_name": "My Org",
        "highlights": "${{ steps.changelog.outputs.highlights }}",
        "npm": "my-app@latest",
        "npm_url": "https://www.npmjs.com/package/my-app",
        "link": "${{ steps.release.outputs.url }}",
        "footer": "Published ${{ steps.date.outputs.date }}"
      }
    }'
  env:
    DISCORD_TOKEN: ${{ secrets.BOOKED_DISCORD_BOT_TOKEN }}
    DISCORD_OPS_CONFIG: ${{ secrets.DISCORD_OPS_CONFIG }}

DISCORD_TOKEN is the default token variable — no additional configuration needed. The bot token from your secret is used directly.

HTTP Transport Security

When running discord-ops serve, the HTTP endpoint is unauthenticated by default with a loud startup warning. Set DISCORD_OPS_HTTP_TOKEN to require bearer auth:

DISCORD_OPS_HTTP_TOKEN=your-secret-token discord-ops serve --port 3000

All requests must include:

Authorization: Bearer your-secret-token

The health endpoint (GET /health) is always exempt from auth — load balancers and Docker healthchecks can reach it without a token.

Token comparison uses constant-time comparison to prevent timing attacks.

Dry-Run Mode

Enable dry-run to simulate destructive operations (delete, ban, kick, etc.) without actually calling the Discord API:

# Via CLI flag
discord-ops --dry-run

# Via environment variable
DISCORD_OPS_DRY_RUN=1 discord-ops

# Via env alias
DRY_RUN=true discord-ops

In dry-run mode, destructive tools return a simulated success response showing what would have happened.

Message Templates

23 built-in templates with cutting-edge Discord features. Use send_template with project routing.

Features across all templates:

  • Author branding — every template has a configurable author_name + author_icon at the top

  • Link buttons — clickable buttons below embeds (View Logs, Open PR, Runbook, etc.)

  • Discord timestamps — dates auto-convert to each user's timezone with live countdowns

  • Native polls — real Discord polls with progress bars and vote tracking

  • Multi-embed dashboards — up to 10 embeds per message for service status boards

  • Footer icons — status indicator icons (green/red) next to footer text

  • Clickable titles — embed titles link directly to URLs

  • Syntax-highlighted code — code examples with language-specific highlighting

  • Progress bars — visual Unicode block progress indicators

DevOps Templates (11)

Template

Description

Key Features

release

Version release with install + link buttons

Author, link buttons, clickable title

deploy

Deploy success/failure with logs button

Footer icon, view/logs buttons

ci_build

CI result with build link button

Footer icon, clickable title

incident

Incident alert with severity colors

Discord timestamps, status page button

incident_resolved

Resolution with postmortem button

Discord timestamps, postmortem link

maintenance

Maintenance with live timezone countdowns

Discord timestamps, countdown, status button

status_update

Service status (operational/degraded/outage)

Footer icon, dashboard button

review

PR review with diff stats + PR button

Clickable title, additions/deletions

dashboard

Multi-embed service status board (up to 9 svc)

Multi-embed, per-service color cards

oncall

On-call handoff with shift timestamps

Discord timestamps, runbook button

alert

Configurable alert (info/warn/error/critical)

Level-based colors, metric thresholds

Team & Community Templates (12)

Template

Description

Key Features

celebration

Celebrate wins with images

Author, thumbnail, image

welcome

Welcome members with onboarding buttons

Discord timestamps, handbook/onboarding links

shoutout

Recognize work with avatar thumbnail

Thumbnail, nomination attribution

quote

Block-quoted inspirational text

Block quote formatting, author avatar

announcement

Announcement with deadline countdown

Discord timestamps, countdown, link button

changelog

Changelog with 7 section types

Deprecated, performance, security sections

milestone

Milestone with target date countdown

Discord timestamps, progress tracking

tip

Pro tip with syntax-highlighted code

Language-specific code blocks, doc button

poll

Native Discord poll with vote tracking

Native poll API, multiselect, duration

progress

Visual progress bar with deadline

Unicode progress bar, countdown

standup

Daily standup summary

Yesterday/today/blockers sections

retro

Sprint retrospective

Went-well/improve/actions, velocity

Examples

Release announcement:

send_template({
  template: "release",
  vars: {
    version: "v0.23.0",
    name: "discord-ops",
    highlights: "• Owner pings\n• Smart channel resolution\n• Category channel editing",
    npm: "npm install discord-ops@0.23.0",
    npm_url: "https://www.npmjs.com/package/discord-ops/v/0.23.0",
    link: "https://github.com/bookedsolidtech/discord-ops/pull/20",
    footer: "Released April 3, 2026",
    author_name: "Booked Solid Technology"
  },
  project: "my-app",
  channel: "releases"
})

Native Discord Poll:

send_template({
  template: "poll",
  vars: {
    question: "Best language for MCP servers?",
    options: "TypeScript|Rust|Go|Python",
    duration: "48",
    multiselect: "true"
  },
  project: "my-app",
  channel: "dev"
})

Multi-Embed Status Dashboard:

send_template({
  template: "dashboard",
  vars: {
    services: "API|Database|CDN|Auth|Queue",
    statuses: "operational|operational|degraded|operational|outage",
    title: "Production Status",
    url: "https://status.example.com"
  },
  project: "my-app",
  channel: "alerts"
})

On-call handoff:

send_template({
  template: "oncall",
  vars: {
    outgoing: "alice",
    incoming: "bob",
    shift_start: "2026-04-04T09:00:00Z",
    notes: "Payment service latency elevated — watch grafana/d/payments",
    active_incidents: "INC-342: elevated error rate on /checkout",
    runbook_url: "https://wiki.example.com/oncall",
    mention: "<@BOB_USER_ID>"
  },
  project: "my-app",
  channel: "team-chat"
})

All templates support project routing (project, channel, notification_type, channel_id) and author branding (author_name, author_icon).

Advanced Config Reference

Full ~/.discord-ops.json schema with all options:

{
  "bots": {
    "my-bot": {
      "name": "My Bot",
      "role": "General purpose",
      "description": "Handles all operations",
      "token_env": "MY_BOT_TOKEN",
      "default_profile": "full",
      "profile_add": [],
      "profile_remove": []
    }
  },
  "projects": {
    "my-app": {
      "guild_id": "123456789012345678",
      "token_env": "MY_APP_DISCORD_TOKEN",
      "bot": "my-bot",
      "channels": {
        "dev": "CHANNEL_ID",
        "builds": "CHANNEL_ID",
        "releases": "CHANNEL_ID",
        "alerts": "CHANNEL_ID",
        "support": { "id": "CHANNEL_ID", "bot": "my-bot" }
      },
      "default_channel": "dev",
      "owners": ["USER_SNOWFLAKE_ID"],
      "notify_owners_on": ["release", "error", "alert"],
      "tool_profile": "full",
      "profile_add": [],
      "profile_remove": []
    }
  },
  "default_project": "my-app",
  "notification_routing": {
    "ci_build": "builds",
    "deploy": "builds",
    "release": "releases",
    "error": "alerts",
    "dev": "dev"
  }
}

Global fields:

Field

Description

bots

Named bot personas with name, role, description, token_env, default_profile, profile_add, profile_remove

default_project

Project used when no project param is provided

notification_routing

Global notification type → channel alias routing

Project fields:

Field

Description

guild_id

Discord server (guild) snowflake ID

token_env

Env var name for this project's bot token

bot

Default bot persona for this project (references a key in bots)

channels

Alias → channel ID or { id, bot } map; channel: "builds" resolves here first

default_channel

Channel used when no channel param is provided

owners

User snowflake IDs to mention on matching notification types

notify_owners_on

Notification types that trigger owner pings ("dev" never pings)

tool_profile

Base tool profile for this project (full, monitoring, etc.) — enforced at runtime

profile_add

Additional tools to load on top of the base profile

profile_remove

Tools to exclude from the base profile

notification_routing

Per-repo config only (.discord-ops.json), not a project field in global config

Per-project profiles are enforced at runtime — all tools stay registered on the MCP server, but tool calls are filtered when the resolved project or bot has a profile set. This means agents can discover all tools via MCP schema, but per-project restrictions are applied on each call.

Multi-Organization Troubleshooting

Validating your config

Run discord-ops validate to check your config without connecting to Discord. It detects:

  • Missing token_env values (env var not set)

  • Bot references (project.bot, channel bot) pointing to undefined bots

  • Invalid default_profile or tool_profile values

  • Missing bot token_env environment variables

  • Duplicate guild IDs across projects with different tokens

  • default_channel referencing a nonexistent alias

  • default_project pointing to a nonexistent project

  • Notification routing to nonexistent channel aliases

Common issues

"No token available for project X" The project needs a token. Either:

  • Set its token_env env var (e.g., export ORG_A_TOKEN=...)

  • Set a default token via DISCORD_TOKEN

  • Use DISCORD_OPS_TOKEN_ENV to point at a custom env var

Bot can't access a guild If a project uses token_env for a different bot, that bot must be invited to the project's guild. Run discord-ops health to see which guilds each bot can access.

Migrating from single-bot to multi-bot

  1. Add token_env to projects that need their own bot

  2. Set the corresponding env vars

  3. Run discord-ops validate to verify

  4. Run discord-ops health to test connections

Token rotation Update the env var value and restart the MCP server. No config changes needed — token_env reads from the environment at runtime.

Channel not found Channel resolution tries 4 layers in order: exact alias → fuzzy alias → live Discord name lookup → error. If a channel is still not found, verify the bot has access to the channel and list_channels returns it.

Development

git clone https://github.com/bookedsolidtech/discord-ops.git
cd discord-ops
npm install
npm run build
npm test

# Local CI
./scripts/act-ci.sh --local

License

MIT

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
3dRelease cycle
32Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bookedsolidtech/discord-ops'

If you have feedback or need assistance with the MCP directory API, please join our Discord server