Discord voice channel meeting clerk — Real-time speech-to-text with speaker diarization, automatic meeting minutes generation, and MCP server integration.
dicoclerk joins Discord voice channels, captures audio in real-time via Deepgram's STT engine with speaker identification, deduplicates utterances using four intelligent strategies, and generates structured meeting minutes with participant lists, summaries, and action items. Designed for teams using Korean/English bilingual meetings.
- Slash Commands:
/startto begin recording,/stopto end and auto-generate minutes - Real-Time STT with Speaker Diarization: Deepgram's nova-2 model identifies speakers in live audio (한국어/영어 지원)
- 4 Deduplication Strategies: Content fingerprinting, speaker identity matching, timestamp proximity, and fuzzy similarity (Levenshtein distance)
- Structured Meeting Minutes: Auto-formatted markdown with date/time, participants, summary, key discussion points, action items, and full transcript
- Auto-Disconnect: Bot leaves voice channel when it becomes empty
- Connection Resilience: Deepgram auto-reconnect with exponential backoff (configurable max retries)
- Connection Pool: Supports 5-10 concurrent speakers via optional multi-connection pooling
- MCP Server Mode: 10 tools for external agent integration (session management, transcript/minutes queries, contextual search, summarization)
- Meeting History Search: Find and retrieve past minutes by date, participants, keywords, channel name
- Standalone Operation: Works without Openclaw or external agents — can run as pure Discord bot
- Interactive Installer:
setup.shguides you through Discord bot configuration and API key setup - Data Persistence: Stores transcripts, minutes, and meeting index locally on disk
┌─────────────────────────────────────────────────────────────────┐
│ Discord Client │
│ (/start, /stop slash commands + voice state events) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Session Manager │
│ (Tracks active sessions per guild, voice connection lifecycle) │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────────┴───────────────────┐
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ Audio Capture & │ │ Deepgram STT │
│ Session Coordinator │◄────────►│ (Connection Pool) │
│ (per voice channel) │ │ (nova-2, diarization)
└──────────────────────┘ └──────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Deduplication Engine │
│ (Fingerprint, Similarity, Timestamp, Fuzzy Matching) │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Minutes Generator & Formatter │
│ (Markdown formatting, index storage, Discord delivery) │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ Data Directory (./data by default) │
│ ├─ transcripts/ (raw STT output) │
│ ├─ minutes/ (formatted meeting minutes) │
│ └─ recordings/ (optional: Discord audio files) │
└──────────────────────────────────────────────────────────────────┘
Optional: MCP Server Mode
┌──────────────────────────────────────────────────────────────────┐
│ MCP Server (npm run mcp) │
│ (10 tools: session control, queries, search, summarization) │
│ (Integrates with Openclaw or other agents) │
└──────────────────────────────────────────────────────────────────┘
- Node.js 20.0 or later
- Discord Bot Token (from Discord Developer Portal)
- Intents required: MESSAGE CONTENT, GUILD VOICE STATES, GUILD MEMBERS
- Permissions needed: Connect to voice, Speak in voice, Send messages, Attach files, Use application commands
- Deepgram API Key (from Deepgram Console)
- Free tier: $200 in monthly credits
- Git (for cloning the repository)
git clone https://github.com/yourusername/dicoclerk.git
cd dicoclerkbash setup.shThe installer will:
- Prompt for your Discord bot token and client ID
- Ask for optional Discord guild/channel IDs (recommended for faster development)
- Request your Deepgram API key
- Let you choose language support (Korean, English, or multi-language)
- Create the
.envconfiguration file - Install Node.js dependencies
- Register
/startand/stopslash commands
npm startThe bot will log in and appear in your Discord server. You'll see:
✅ dicoclerk is online as YourBotName#1234
Guilds: 1
- Join a voice channel in your server
- Type
/startto begin recording - Speak! The bot will transcribe in real-time
- Type
/stopto end the session and generate minutes - Minutes appear in the text channel as a markdown file within 1–2 minutes
Create a .env file or run setup.sh to populate it automatically. The file follows this structure:
# Discord Bot Configuration
DISCORD_TOKEN=your_discord_bot_token_here
DISCORD_CLIENT_ID=your_discord_client_id_here
# Optional: Guild ID for development (faster command registration)
DISCORD_GUILD_ID=your_guild_id
# Optional: Default text channel for posting meeting minutes
MINUTES_CHANNEL_ID=your_text_channel_id
# Deepgram API Configuration
DEEPGRAM_API_KEY=your_deepgram_api_key_here
# Language settings: ko (Korean only), en (English only), or multi (both)
STT_LANGUAGE=multi
# Data storage directory (transcripts, minutes, recordings)
DATA_DIR=./data| Variable | Required | Description |
|---|---|---|
DISCORD_TOKEN |
Yes | Bot authentication token |
DISCORD_CLIENT_ID |
Yes | Application ID for OAuth2 and command registration |
DISCORD_GUILD_ID |
No | Server ID for dev mode (speeds up command registration from ~15min to ~1sec) |
MINUTES_CHANNEL_ID |
No | Default text channel for minutes (can override per session) |
DEEPGRAM_API_KEY |
Yes | Deepgram API key for STT |
STT_LANGUAGE |
No | Language code: ko, en, or multi (default: multi) |
DATA_DIR |
No | Local directory for transcripts/minutes (default: ./data) |
Joins your current voice channel and begins recording.
Options:
language(optional): Override default language for this sessionko— Korean onlyen— English onlymulti— Auto-detect Korean and English
Response:
✅ **Recording started** in #voice-channel
🎙️ Language: **Korean + English**
👤 Started by: **yourname#1234**
🔊 Speech recognition: **Active**
Use `/stop` to end the session and generate meeting minutes.
Requirements:
- You must be in a voice channel
- Only one active session per server
- Bot must have voice channel permissions
Ends the recording session, finalizes the transcript, and triggers minutes generation.
Response:
✅ **Session ended**
⏱️ Duration: **12m 34s**
👥 Participants: **4**
📝 Transcript entries: **156**
Meeting minutes will be generated and posted within 1–2 minutes.
While recording, the bot sends status updates to the text channel:
- Deepgram reconnection:
⚠️ Speech recognition connection interrupted. Reconnecting... (attempt 1/5) - Reconnection success:
✅ Speech recognition reconnected. Transcription continues. - Permanent failure:
❌ **Speech recognition connection lost permanently.** Partial transcript has been saved... - Channel empty:
✅ Recording auto-stopped (channel emptied). Processing...
Generated minutes are markdown files with this structure:
# Meeting Minutes: Project Planning Session
**Date:** 2025-04-03 | **Time:** 14:30–14:42 (12m 34s)
**Channel:** #team-standup
**Language:** Korean, English
## Attendees (4 participants)
- Alice (8 utterances, 2m 15s)
- Bob (6 utterances, 1m 42s)
- Charlie (5 utterances, 1m 08s)
- Diana (4 utterances, 0m 49s)
## Summary
Key topics discussed: Project timeline, resource allocation, Q2 roadmap updates.
## Key Discussion Points
- **Timeline**: Q2 launch target confirmed; engineering team needs 2 additional weeks.
- **Resources**: Budget approval for cloud infrastructure in progress.
- **Dependencies**: Marketing content needs finalization by April 10.
## Action Items
1. **Alice** — Update project charter by 2025-04-08
2. **Bob** — Prepare resource estimates by 2025-04-06
3. **Charlie** — Coordinate with marketing on content timeline
## Full Transcript
**Alice** [00:05–00:12] "Good afternoon everyone. Let's start with the Q2 roadmap."
**Bob** [00:15–00:28] "Yep, we reviewed the timeline yesterday. Engineering needs about two more weeks..."
...Minutes are automatically:
- Saved to
./data/minutes/YYYY-MM-DD_HH-MM-SS_GuildName_ChannelName.md - Posted to the text channel as an attachment
- Indexed in
./data/minutes/index.jsonlfor search
dicoclerk includes an MCP (Model Context Protocol) server for integration with external agents like Openclaw.
npm run mcpServer listens on stdio (agent connection).
start_session
Start a new recording session in a voice channel.
Parameters:
guild_id(string, required): Discord guild IDvoice_channel_id(string, required): Voice channel ID to recordtext_channel_id(string, required): Text channel for status/minuteslanguage(enum: 'ko'|'en'|'multi', optional): STT language (default: 'multi')
Returns: { success, session_id, message }
stop_session
Stop an active recording session.
Parameters:
guild_id(string, required): Guild with active session
Returns: { success, duration, transcriptCount, minutesPath }
list_sessions
List all active recording sessions across all guilds.
Returns: { sessions: [ { guild_id, channel_id, started_at, duration, participants } ] }
get_session
Get details of a specific session.
Parameters:
guild_id(string, required): Guild ID
Returns: { session: { guildId, channelId, startedAt, duration, participants, status } }
get_transcript
Get the current or completed transcript.
Parameters:
guild_id(string, required): Guild IDformat(enum: 'raw'|'formatted', optional): Output format (default: 'formatted')
Returns: { transcript: [ { speaker, text, timestamp, confidence, isFinal } ] }
get_minutes
Get generated meeting minutes for a session.
Parameters:
guild_id(string, required): Guild IDsession_id(string, optional): Specific session (defaults to latest)
Returns: { minutes: markdown_content }
list_recordings
List all stored transcripts and minutes on disk.
Parameters:
limit(number, optional): Max results (default: 20)guild_id(string, optional): Filter by guild ID
Returns: { recordings: [ { date, guild_name, channel_name, participants, duration } ] }
search_minutes
Search meeting minutes by date, channel, participants, or free-text.
Parameters:
query(string, optional): Free-text searchguild_id(string, optional): Filter by guildchannel_name(string, optional): Partial channel name matchparticipant(string, optional): Partial participant name matchdate_from(string, optional): Start date (YYYY-MM-DD)date_to(string, optional): End date (YYYY-MM-DD)language(string, optional): Language code (ko/en)limit(number, optional): Max results (default: 20)offset(number, optional): Pagination offset (default: 0)
Returns: { results: [ { date, guild_name, channel_name, participants, duration } ] }
search_meeting_minutes
Search and retrieve previous minutes with full markdown content.
Parameters:
query(string, optional): Free-text search across metadata and contentguild_id(string, optional): Filter by guildchannel_name(string, optional): Partial channel matchparticipant(string, optional): Partial participant matchdate_from(string, optional): Start date (YYYY-MM-DD)date_to(string, optional): End date (YYYY-MM-DD)keywords(array of strings, optional): Search content for keywordslanguage(string, optional): Language codelimit(number, optional): Max results (default: 5)offset(number, optional): Pagination offsetinclude_content(boolean, optional): Include full markdown (default: true)
Returns: { results: [ { date, guild_name, channel_name, participants, content } ] }
summarize_minutes
Generate condensed contextual summaries from past minutes.
Parameters:
query(string, optional): Search/filter criteriaguild_id(string, optional): Filter by guilddate_from(string, optional): Start datedate_to(string, optional): End datesummarize_count(number, optional): Number of meetings to summarize (default: 5)context_type(string, optional): Summary type ('executive', 'detailed', or 'quick')
Returns: { summary: contextual_narrative, references: [ { date, source } ] }
dicoclerk/
├── README.md # This file
├── LICENSE # MIT license
├── package.json # Dependencies & npm scripts
├── .env.example # Configuration template
├── setup.sh # Interactive installer
│
├── src/
│ ├── index.js # Main Discord bot entry point
│ ├── deploy-commands.js # Register /start, /stop commands
│ ├── mcp-server.js # MCP server entry point
│ │
│ ├── commands/
│ │ ├── start.js # /start slash command handler
│ │ └── stop.js # /stop slash command handler
│ │
│ ├── voice/
│ │ ├── session-manager.js # Session lifecycle & voice connection pool
│ │ └── connection-manager.js # Discord voice connection wrapper
│ │
│ ├── audio/
│ │ ├── session-coordinator.js # Orchestrates audio capture & Deepgram
│ │ └── audio-capture-pipeline.js # Subscribes to voice receiver, forwards audio
│ │
│ ├── stt/
│ │ ├── deepgram-client.js # Deepgram streaming client (nova-2 + diarization)
│ │ ├── connection-pool.js # Multi-connection pooling for 5-10 speakers
│ │ ├── connection-resilience.js # Auto-reconnect with exponential backoff
│ │ └── dedup.js # 4 deduplication strategies
│ │
│ ├── minutes/
│ │ ├── generator.js # End-to-end minutes generation pipeline
│ │ ├── formatter.js # Markdown formatting logic
│ │ ├── summarizer.js # Heuristic summary extraction
│ │ └── index-store.js # Meeting index (JSONL file storage)
│ │
│ ├── session/
│ │ └── session-cleanup.js # Shared teardown (Deepgram, transcript, voice)
│ │
│ └── mcp/
│ ├── server.js # MCP server setup & lifecycle
│ ├── tools.js # Tool definitions (10 tools)
│ ├── handlers.js # Tool implementation logic
│ ├── transport.js # Stdio transport for MCP
│ └── index.js # MCP module exports
│
├── tests/
│ ├── *.test.js # Node.js test files (using built-in test runner)
│ └── *.py # Python test utilities
│
└── data/ # (Auto-created by setup.sh)
├── transcripts/ # Raw STT output (JSON)
├── minutes/ # Formatted meeting minutes (Markdown)
│ └── index.jsonl # Meeting index for search
└── recordings/ # Optional: Discord audio files
dicoclerk uses four complementary deduplication methods to clean up real-time STT output:
- Content Fingerprinting: SHA-256 hash of normalized text + speaker ID. Fast exact-match detection.
- Speaker Identity Matching: Only treats utterances as duplicates if from the same speaker.
- Timestamp Proximity: Utterances within a configurable time window are checked; older utterances outside the window are safe.
- Fuzzy Similarity (Levenshtein): For near-duplicates (typos, partial repeats), calculates edit distance and matches if above a similarity threshold (default: 0.75).
Configuration (in dedup.js):
const DEFAULT_DEDUP_CONFIG = {
timeWindow: 5.0, // Seconds
similarityThreshold: 0.75, // 0.0–1.0
windowSize: 100, // Utterances
deduplicateInterim: true, // Treat interim results as replaceable
exactMatchWindow: 10.0, // Seconds for exact-match grace period
};Deepgram connections may fail due to network issues or API rate limiting. dicoclerk automatically:
- Detects connection loss
- Waits with exponential backoff (1s → 2s → 4s → 8s, configurable max 60s)
- Reconnects up to 5 attempts (configurable)
- Notifies the text channel on attempt and success/failure
- Falls back to partial transcript if reconnection exhausts retries
Configuration (in connection-resilience.js):
const RECONNECT_DEFAULTS = {
maxAttempts: 5, // Max reconnection attempts
initialDelayMs: 1000, // 1 second
maxDelayMs: 60000, // Cap at 60 seconds
backoffMultiplier: 2.0, // Exponential growth
};When the voice channel becomes empty (last participant leaves), the bot automatically:
- Waits 2 seconds (configurable grace period to prevent false positives from switching channels)
- Checks if the channel is still empty
- Stops audio capture and Deepgram connection
- Generates and delivers minutes
- Leaves the voice channel
- Posts a cleanup summary to the text channel
Run the full test suite:
npm testThis executes all .test.js files in the tests/ directory using Node.js's built-in test runner.
Test coverage includes:
- Audio capture pipeline and session coordination
- Connection pool management and resilience
- Deduplication strategies (all 4 variants)
- Formatter output and markdown structure
- Minutes generator and file I/O
- Index-store (JSONL queries)
- MCP server and tool handlers
- Slash command validation
- Session lifecycle and cleanup
For rapid iteration:
npm run devUses Node.js --watch to restart on file changes.
If you modify command definitions:
npm run deploy-commandsCommands register globally (~15 minutes) or to your dev guild instantly (if DISCORD_GUILD_ID is set).
Check your configuration:
cat .envEnsure all required variables are set and non-empty.
-
Verify slash commands are registered:
- Right-click bot in Discord → Check for
/startand/stopcommands - If missing, run:
npm run deploy-commands
- Right-click bot in Discord → Check for
-
Check bot permissions:
- Right-click server → Server Settings → Roles → dicoclerk role
- Ensure "Connect to voice", "Speak in voice", "Send messages", "Attach files" are enabled
-
Confirm intents:
- Go to Discord Developer Portal → Your App → Bot
- Enable: MESSAGE CONTENT, GUILD VOICE STATES, GUILD MEMBERS
- Set
DEEPGRAM_API_KEYin.env - Restart the bot:
npm start
-
Check Deepgram API key validity:
- Visit https://console.deepgram.com and verify your key hasn't expired
- Ensure your account has available credits
-
Check bot audio permissions:
- Server Settings → Roles → dicoclerk → Voice Permissions
- Enable: "Connect", "Speak", "Use Voice Activity"
-
Check bot can see voice channel:
- Server Settings → Channels → voice-channel → Permissions
- Ensure dicoclerk role has "Connect" and "View Channel" permissions
-
Check
/stopwas executed successfully:- Look for cleanup message in text channel (transcript count, duration)
-
Check file permissions:
- Ensure
./data/minutes/directory is writable:ls -la data/
- Ensure
-
Check logs:
- Look for
[MinutesGenerator]messages in bot console output
- Look for
- Use
STT_LANGUAGE=koorSTT_LANGUAGE=eninstead ofmultito reduce processing - Shorter sessions = lower costs
- Monitor real-time usage at https://console.deepgram.com/usage
- Typical session: 30 participants, 1 hour → ~2–3 MB transcript, 0.5–1 MB minutes, generates in <2 minutes
- Connection pool: Default single connection handles 5–10 speakers comfortably; auto-scales to 2–3 connections under load
- Minutes storage: ~1 KB per minute of meeting time (markdown)
- Deduplication overhead: <50ms per utterance (negligible)
MIT. See LICENSE for details.
Contributions welcome! Please:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Commit with clear messages:
git commit -m "Add feature X" - Push:
git push origin feature/your-feature - Open a Pull Request
- Documentation: See README.md and inline code comments
- Issues: Open a GitHub issue for bugs or feature requests
- Discord Help: Check the Discord.js and Deepgram docs
- discord.js — Discord API client
- Deepgram — Speech-to-text with speaker diarization
- @discordjs/voice — Voice channel audio capture
- Model Context Protocol — Agent integration framework