Classifer
Provides tools for managing and interacting with a Discord server, including building systems from registry actions, reading server state, scheduling tasks, and handling events, allowing Claude to operate a Discord server through a data-driven registry.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ClassiferAdd a button that opens a private ticket in the support channel."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Cognition
A Discord server that an AI operates, where behaviour is data instead of code.
Claude holds the controls through an MCP server. The bot is a dumb executor. Adding a feature is a database row, not a deploy — and the bot never restarts.
The idea · Architecture · Setup · The Registry · Skills · Scheduling · Safety · Tools
The idea
Most Discord bots hard-code their features. A ticket system is a handler file, a verification flow is another, and every new idea is an edit, a restart, and a deploy. The bot's capabilities and the bot's source code are the same thing.
Cognition separates them.
Behaviour lives in a Registry — a SQLite database of actions (what happens) and components (what a button is bound to). The bot holds one interaction listener that reads the Registry fresh on every single click. It contains no knowledge of tickets, or verification, or anything else.
The result is that this:
// registry_put — the entire definition of a ticket button
{
"key": "ticket_open",
"kind": "channel_create",
"params": {
"name": "ticket-{{user.name}}",
"parent_id": "1536064615635484766",
"overwrites": [
{ "id": "{{guild.id}}", "type": "role", "deny": ["ViewChannel"] },
{ "id": "{{user.id}}", "type": "member", "allow": ["ViewChannel", "SendMessages"] }
],
"then": { "kind": "reply", "params": { "content": "Opened <#{{created.id}}>" } }
}
}…is a working, private-per-user ticket system. No file was written. No process was restarted. The next person to press the button gets the new behaviour.
The engine is a fixed ~25 files and does not grow when the server does.
Registry edits apply live. Engine edits do not.
Rows are read fresh on every interaction, so behaviour changes take effect on the
next click. But Node caches modules at import — a running bot keeps executing the
code it started with, so changing shared/, bot/ or classifer/ needs a
restart. The bot fingerprints the engine build it loaded, and system_status
reports bot engine STALE when disk has moved on.
Related MCP server: Discord MCP Server
Architecture
Two processes, deliberately.
flowchart TB
Claude["<b>Claude</b><br/>reads intent, decides, builds"]
subgraph Plugin["Claude Code plugin"]
Skills["<b>skills/</b><br/>8 skills — authority, building, events,<br/>sessions, scheduling, safety"]
Classifer["<b>Classifer</b><br/>MCP server · 60 tools<br/>REST only, no gateway"]
end
Registry[("<b>Registry</b><br/>SQLite · WAL<br/>actions · components<br/>sessions · schedules<br/>triggers · counters<br/>audit · snapshots")]
subgraph Bot["Cognition bot — runs 24/7"]
Dispatcher["<b>Dispatcher</b><br/>one listener, zero handlers"]
Eventer["<b>Events</b><br/>gateway -> triggers"]
Executor["<b>Executor</b><br/>19 primitives"]
Scheduler["<b>Scheduler</b><br/>cron tick, 30s"]
end
Discord["<b>Discord</b>"]
Claude --> Skills
Claude --> Classifer
Classifer -->|reads / writes| Registry
Classifer -->|REST| Discord
Discord -->|gateway: clicks| Dispatcher
Discord -->|gateway: joins, messages,<br/>reactions, deletions| Eventer
Eventer -->|matches| Registry
Eventer --> Executor
Dispatcher -->|looks up| Registry
Dispatcher --> Executor
Scheduler -->|due?| Registry
Scheduler --> Executor
Executor -->|REST| Discord
classDef c fill:#5865F2,stroke:#3d47c4,color:#fff
classDef d fill:#2b2d31,stroke:#1e1f22,color:#fff
class Claude,Classifer,Skills c
class Discord,Registry dWhy split them. The bot needs a permanent gateway connection to receive button presses. An MCP server lives and dies with a Claude session. Merging them would mean the bot dies every time you close your editor.
The split buys a second property that turns out to matter more: Classifer talks to Discord over REST and needs no gateway at all. The server can be inspected, built and restructured whether or not the bot is running — and the bot keeps answering clicks whether or not Claude Code is open.
Classifer (MCP) | Cognition (bot) | |
Transport | REST | Gateway (WebSocket) |
Lifetime | One Claude session | Continuous |
Handles | Building, reading, scheduling, safety | Button presses, modals, events, cron |
Works when the other is down | ✅ | ✅ |
Setup
This repository is source-available, not open source — seeLicense. The steps below are the project's own setup, documented so the design is legible. They are not a grant of permission to copy or run it.
Requirements — Node ≥ 22.5 (for the built-in node:sqlite; nothing native to
compile), and a Discord application with Administrator in one guild.
cd Cognition
npm install
cp .env.example .env
# DISCORD_TOKEN=...
# COGNITION_GUILD_ID=...Enable Server Members and Message Content intents on the Bot page of your application, then invite it:
https://discord.com/oauth2/authorize?client_id=YOUR_APP_ID&scope=bot+applications.commands&permissions=8Verify everything is wired, lay out the server, and start the bot:
npm run smoke # token, guild reachability, Registry — fails loudly and specifically
npm run bootstrap # creates Command + Sandbox categories, #command-log, @Operator
npm run bot # keep this runningInstall the Claude Code plugin, then restart the app:
node scripts/install-plugin.jsThat last step registers the marketplace, copies the plugin, and writes your project path into the installed manifest. The checked-in manifest keeps a placeholder, so the repo is not tied to one machine.
Without the plugin, every tool is still reachable:
npm run call guild_snapshot npm run check # list all 60 tools and smoke the server
The Registry
Actions
An action is {key, kind, params, requires, confirm}. Nineteen kinds compose into
everything:
Group | Kinds |
Talking |
|
Structure |
|
Roles |
|
Reacting |
|
Sessions |
|
Control flow |
|
They nest. sequence runs a list, branch picks by predicate, and then runs
after a creating action with {{created.id}} bound to whatever was just made.
Predicates
requires gates an action, and gates every nested action too — so composing
something is never a way around a restriction placed on it.
{ "type": "has_role", "role_id": "…" }
{ "type": "all", "of": [ { "type": "is_guild_owner" }, { "type": "in_channel", "channel_id": "…" } ] }Predicates fail closed. An unknown type, or a context missing what it needs, evaluates false with a reason — and that reason is what the user sees.
Templates
Every string parameter is a template, which is what lets one stored row serve every user:
{{user.id}} {{user.name}} {{user.mention}} {{user.display}}
{{channel.id}} {{created.id}} {{arg.0}} {{field.<key>}}
{{session.name}} {{guild.id}} {{now}} {{today}}An unknown placeholder is left standing rather than blanked. A channel that
appears named ticket-{{user.nmae}} is a typo you can see; ticket- is a mystery.
The custom_id scheme
Discord caps custom_id at 100 characters. Packing parameters into it —
action:open_ticket|category:123|panel:456 — runs out of room as soon as a
component carries two snowflakes, and it breaks at send time.
So the id carries a Registry key and only what is genuinely per-click:
c1|<10-char key>|<arg0>|<arg1>This is what lets one row serve unlimited instances. A close button in every ticket channel is a single component; the channel it acts on rides in the id:
{ "kind": "panel_send", "params": {
"channel_id": "{{created.id}}",
"buttons": [{ "label": "Close", "component_key": "ticketclose", "args": ["{{created.id}}"] }]
}}Minting a component per ticket would also work, and would leave one dead row per ticket ever opened.
The skills
The plugin ships seven skills that carry the operating model — not API docs, but the judgement calls that a tool description cannot hold.
Skill | What it settles |
| Who decides. Requests are goals, not scripts. Act without asking on anything reversible; the one line that does not move. |
| Read live state before writing — and what a read cannot tell you |
| Composing a system from primitives instead of code |
| The full grammar, and the mistakes that store cleanly and fail at click time |
| Build → test → promote or archive, without moving anything |
| Which of the two scheduling tiers a task belongs in |
| The irreversible-operation protocol |
Descriptions carry Arabic trigger phrases alongside English, so requests in either language route to the right skill.
Sessions
Every experiment runs in a session: a [TEST]-tagged category, its channels, its
Registry rows, and a tracking thread in #active-sessions.
stateDiagram-v2
direction LR
[*] --> building: session_start
building --> testing
testing --> promoted: session_promote
testing --> archived: session_archive
testing --> closed
promoted --> archived: session_archive
archived --> testing: snapshot_restoreNothing ever moves between categories. Promotion drops the [TEST] tag;
archiving adds [ARCHIVED] and denies ViewChannel to @everyone. Both happen
where the thing already stands.
That constraint buys three things: ids stay valid, message history stays intact, and every link anyone pasted still resolves. A system that files things into an "Archive" category breaks all three the moment it tidies up.
Three ways an action fires
Fired by | Good for | |
Component | a button press, through the Dispatcher | anything a person initiates |
Trigger | a gateway event | joins, keywords, reactions, out-of-band deletions |
Schedule | a cron tick | digests, sweeps, timed locks |
All three read the same Registry and run through the same executor, so an action behaves identically whichever one fired it. Only the context differs: a button press can reply to someone, an event and a tick have nobody to answer.
registry_put key=welcome kind=dm_send params={content: "أهلًا {{user.name}}"}
trigger_create key=on_join event=member_join action_key=welcometrigger_events lists what can be listened for. A filter key an event never
supplies is rejected at write time rather than silently never matching.
Loops cannot happen by accident. Messages authored by Cognition never fire a
trigger, whatever a filter says, and other bots are excluded unless from_bot
is set explicitly.
Scheduling
Two tiers, and picking the wrong one is the usual mistake.
Bot-side schedule | Claude routine | |
Fired by | The Cognition process | A fresh Claude session |
Runs when Claude Code is closed | ✅ | ❌ — runs late, on next launch |
Can exercise judgement | ❌ fixed action tree | ✅ reads, weighs, decides |
Can delete things | ❌ never | ❌ never — no human to confirm |
Good for | Midnight markers, recurring posts, timed locks | "Archive whatever has gone stale" |
npm run call schedule_create '{"key":"daily_marker","cron":"3 0 * * *","action_key":"daily_marker"}'
npm run call schedule_run_now '{"key":"daily_marker"}' # test without waitingCron is five fields in local time. The bot picks up a new schedule within 30 seconds — no restart. A schedule that already ran this minute is skipped, and that check is against the persisted last-run time, so a restart mid-minute does not double-fire either.
If the timing genuinely matters, it belongs bot-side. A 03:00 job on a machine where the editor is closed overnight will not happen at 03:00.
The safety model
Almost everything here is reversible, and the parts that are not are treated differently.
Snapshots. Every structural tool captures state before it writes. Names,
topics, positions, parents, overwrites, role colours and permissions all restore
exactly — so snapshot_restore is a real undo, and archiving is genuinely
reversible.
The gate. Deletion cannot be undone: a rebuilt channel returns with a new id and an empty history. So it takes two steps.
sequenceDiagram
participant C as Claude
participant G as Classifer
participant H as Human
C->>G: destructive_plan(channel_delete, id)
G->>G: snapshot + read live state
G-->>C: written preview + token (10 min, single-use)
C->>H: shows the preview verbatim
H-->>C: explicit yes
C->>G: destructive_apply(token, id)
G->>G: verify hash of exact params
G-->>C: done + snapshot idThe token is bound to a hash of the exact parameters, so consent given for one deletion can never be spent on a different one. Change the id between plan and apply and it refuses.
What is not consent: a general instruction, an earlier yes, asking for it directly, your own confidence, or a scheduled run — no human is present, so no consent exists, so nothing gets deleted.
Audit. Every call, press and scheduled run becomes a row in the audit table.
Mutations also post an embed to #command-log; reads do not, because a channel
that logged every inspection would bury the changes.
Tool reference
60 tools across eight groups. Run npm run check for the live list.
guild_snapshot · channels_list · roles_list · members_search ·
messages_read · audit_tail · system_status · find_by_name ·
permissions_vocabulary · panel_components · settings_get
category_create · channel_create · channel_edit · channels_reorder ·
role_create · role_edit · role_assign · overwrite_set · server_bootstrap ·
settings_set
message_send · message_edit · panel_publish
registry_list · registry_get · registry_put · registry_component_put ·
registry_delete · registry_vocabulary
session_start · session_status · session_promote · session_archive
schedule_create · schedule_list · schedule_toggle · schedule_delete ·
schedule_run_now
trigger_events · trigger_create · trigger_list · trigger_toggle ·
trigger_delete · trigger_test · counters · drift_check ·
registry_export · registry_import · invites_list
snapshot_take · snapshot_list · snapshot_restore ·
snapshot_recreate_channel · destructive_plan · destructive_apply ·
destructive_pending
Project layout
Cognition/
├── shared/ the engine — imported by both processes
│ ├── store.js node:sqlite, WAL, schema
│ ├── registry.js actions · components · sessions · schedules
│ ├── executor.js the 15 primitives
│ ├── predicates.js requires / branch evaluation, fails closed
│ ├── template.js {{...}} rendering
│ ├── customid.js the c1|key|args scheme
│ ├── guard.js plan/redeem tokens for irreversible ops
│ ├── snapshot.js capture and restore
│ ├── audit.js rows + #command-log embeds
│ ├── triggers.js gateway events -> actions, filters, counters
│ ├── cron.js 5-field matcher, no dependency
│ ├── naming.js [TEST] / [ARCHIVED] tagging
│ └── rest.js Discord REST with rate-limit handling
│
├── classifer/src/ the MCP server
│ ├── index.js registration and stdio transport
│ ├── kit.js one wrapper: audit, error shaping, formatting
│ └── tools/ observe · structure · content · registry
│ session · schedule · safety · bootstrap
├── bot/
│ ├── index.js gateway client, six intents
│ ├── dispatcher.js one listener, zero per-feature handlers
│ ├── events.js gateway events routed into the Registry
│ └── scheduler.js 30s tick, persisted dedup
│
├── plugin/ the Claude Code plugin (copied at install)
│ └── plugins/cognition/
│ ├── launch.js thin launcher → the live tree via COGNITION_HOME
│ └── skills/ the seven skills
└── scripts/
├── smoke.js token · guild · Registry
├── test-shared.js 138 unit tests, no network
├── call.js any tool from the shell
├── classifer-check.js list tools, exercise the server
├── simulate-click.js run a button without pressing it
└── install-plugin.js register with Claude CodeNote that shared/ is imported by the bot and by Classifer, so a Registry action
means exactly the same thing whether a person triggered it or the clock did.
Development
npm test # 138 unit tests — cron, naming, custom_id, guard, templates,
# predicates, triggers, counters, registry validation.
# No token needed.
npm run smoke # needs a real token: proves token, guild and Registry
npm run check # connects to Classifer as a real MCP clientTesting a button without pressing it. simulate-click runs the real Registry
row through the real templates and makes the real REST calls, as a real member with
their real roles — the same code path the Dispatcher takes:
node scripts/simulate-click.js <component_key> <user_id> '["arg0"]'Use it to prove both directions of a requires clause: once as someone who should
be blocked, once as someone who should pass.
CI runs the unit tests on Node 22 and 24, syntax-checks every source file,
validates the plugin manifests and skill frontmatter, and fails the build if a
Discord token or a .env ever gets committed.
FAQ
The bot process is down. Classifer talks over REST and needs no gateway, so it stays
green while the gateway is gone. That asymmetry is deliberate but it makes the
symptom confusing — system_status says so explicitly rather than guessing.
The bot is running older code than what is on disk. Node caches modules at import,
so a kind added to the executor after the process started does not exist in that
process. Restart the bot. system_status reports this as bot engine STALE by
comparing the build the bot recorded at startup against the files on disk.
Note that simulate-click will not reproduce it — it runs in a fresh process and
so always picks up the newest engine. That asymmetry is precisely how this class of
bug hides.
The engine is small and fixed. The thing that actually needs validating is Registry data, and that is validated at write time by the tool schemas — where a type system could not help, because the rows are written at runtime by a model.
No native module to compile, which matters most on Windows. Both processes open the same file, so WAL is required rather than optional.
Working as designed. Unresolvable placeholders are left standing so a typo is
visible instead of silently producing ticket-. Fix the row with registry_put;
the next press picks it up.
It is the name the project's author chose for the MCP server, and renaming it now would break every reference. It stays.
License
Proprietary — source-available, not open source. Copyright © 2026 Faisal Saud. All rights reserved.
The repository is published so it can be read. Publishing it grants no license: you may view and study it, and quote short excerpts with attribution. Copying, modifying, redistributing, or using it to train or evaluate a machine learning system all require prior written permission.
Read LICENSE for the exact terms — the summary above is not a substitute for it. Permission requests: https://github.com/justpainful
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityDmaintenanceAn MCP server that provides native Discord tools for Claude Code, enabling bidirectional communication with remote agents or humans via the Discord REST API. It allows users to send messages, read channel history, and manage reactions directly from their local environment.6133MIT
- Alicense-qualityBmaintenanceEnables AI clients to manage Discord servers through natural language, offering tools for guilds, channels, messages, roles, members, webhooks, invites, and automations. Includes a conversational agent that responds to @mentions in Discord.8Apache 2.0
- Alicense-qualityDmaintenanceEnables managing a Discord server using natural language through AI clients like Claude, with 139 admin tools across 20 categories for roles, channels, members, messages, threads, moderation, and more.20421MIT
- Alicense-qualityDmaintenanceEnables Claude to manage Discord servers through the Discord API, supporting server info, messaging, channel management, webhooks, and user interactions.10MIT
Related MCP Connectors
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
Hosted MCP server to manage a restaurant menu from AI agents - 39 tools over the DuckHub API.
Markdown-first MCP server for Notion API with 8 composite tools and 39 actions.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/justpainful/Cognition'
If you have feedback or need assistance with the MCP directory API, please join our Discord server