npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

zulip-mcp-server

v2.2.0

Published

Stateful, ergonomic MCP server for Zulip and Discord with ambient awareness, persistent monitoring, and natural date-based queries

Readme

Zulip & Discord MCP Server

npm version License: MIT

A Model Context Protocol (MCP) server that provides stateful, ergonomic integration with Zulip and Discord. This server allows AI assistants and other MCP clients to monitor channels, track read/unread messages, and retrieve history with natural date-based queries.

Install: npm install -g zulip-mcp-server

Features: Zulip ✅ | Discord ✅ | Stateful ✅ | Persistent ✅ | Ambient Awareness ✅

🎯 Design Philosophy

Fewer, Better Tools - Instead of exposing 20+ low-level API endpoints, this server provides a small set of high-level, stateful tools that are intuitive and powerful.

Stateful Monitoring - The server maintains state about which channels you're monitoring and tracks read/unread messages automatically.

Ambient Awareness via Resources - MCP Resources expose unread message counts that the agent can see when activated for any reason, creating passive awareness without explicit queries.

Ergonomic Date Handling - Use natural keywords like "today" and "yesterday" instead of timestamps and anchors.

🚀 Core Features

🔔 Ambient Awareness (MCP Resources)

The agent can passively see unread message notifications when activated for any reason:

Zulip:

  • zulip://unread/summary - Total unread count across monitored channels
  • zulip://monitoring/status - Current monitoring state
  • zulip://channel/{name}/unread - Per-channel unread count

Discord:

  • discord://unread/summary - Total unread count across monitored channels
  • discord://monitoring/status - Current monitoring state
  • discord://channel/{id}/unread - Per-channel unread count

📺 Zulip Tools

  • start_monitoring - Monitor Zulip channels
  • get_channel_history - Get history with natural dates (mentions formatted as @username (uid:123))
  • get_unread_messages - Get unread from monitored channels
  • send_message - Send to streams or DMs (use @**username** for mentions)
  • delete_message - Delete any message by ID (with permissions)
  • add_reaction - Add emoji reactions
  • find_user - Search users to get mention format
  • list_streams - Browse channels
  • get_stream_topics - See topics
  • list_users - View users
  • get_user_profile - Get your info
  • get_monitored_channels - View monitoring state
  • stop_monitoring - Stop tracking

💬 Discord Tools

  • discord_start_monitoring - Monitor Discord channels
  • discord_get_channel_history - Get history with natural dates (mentions formatted as @username (uid:123...), shows replies)
  • discord_get_unread_messages - Get unread from monitored channels
  • discord_send_message - Send to Discord channels (supports reply_to parameter, use <@user_id> for mentions)
  • discord_delete_message - Delete any message by ID (with permissions)
  • discord_find_user - Search users to get user ID for mentions
  • discord_list_channels - Browse Discord channels
  • discord_get_monitored_channels - View monitoring state
  • discord_stop_monitoring - Stop tracking

Installation

No installation needed! Just use npx to run directly from npm:

npx zulip-mcp-server

The package is published on npm: https://www.npmjs.com/package/zulip-mcp-server

Optional: Install globally if you prefer:

npm install -g zulip-mcp-server

Configuration

Service Selection

Enable the services you need via environment variables:

# Enable Zulip (enabled by default)
export ENABLE_ZULIP=true

# Enable Discord (disabled by default)  
export ENABLE_DISCORD=true

You can enable:

  • Just Zulip (default): Don't set ENABLE_DISCORD - Only Zulip tools will be available
  • Just Discord: Set ENABLE_DISCORD=true and ENABLE_ZULIP=false - Only Discord tools will be available
  • Both: Set ENABLE_DISCORD=true - Both sets of tools will be available

Note: Tools are dynamically loaded based on enabled services. If a service isn't configured, its tools won't appear in the tool list.

Authentication

Zulip Authentication

The server supports three authentication methods:

Option 1: Environment Variables with API Key (Recommended)

export ZULIP_REALM="https://your-org.zulipchat.com"
export ZULIP_EMAIL="[email protected]"
export ZULIP_API_KEY="your-api-key"
export ZULIP_SESSION_ID="agent_name"  # Optional: for persistent state across restarts

Note: ZULIP_SESSION_ID is optional but recommended. It allows:

  • Multiple agents to have separate monitoring state
  • State to persist across server restarts
  • Each agent to have their own read/unread tracking

If not set, defaults to your email/username.

Option 2: Environment Variables with Password

export ZULIP_REALM="https://your-org.zulipchat.com"
export ZULIP_USERNAME="[email protected]"
export ZULIP_PASSWORD="your-password"

Option 3: Using zuliprc File

Create a zuliprc file (see zuliprc.example):

[api]
[email protected]
key=your-api-key
site=https://your-org.zulipchat.com

Then set the path:

export ZULIP_RC_PATH="/path/to/zuliprc"

Important: Add zuliprc to your .gitignore to avoid committing credentials!

Discord Authentication

export DISCORD_TOKEN="your-bot-token"

Getting Your API Keys

Zulip API Key

  1. Log in to your Zulip organization
  2. Go to Settings (gear icon) → Account & Privacy
  3. Under "API key", click "Show/change your API key"
  4. Copy the key or create a bot for API access

For bots:

  1. Go to Settings → Your bots
  2. Add a new bot
  3. Copy the bot's email and API key

Discord Bot Token

  1. Go to https://discord.com/developers/applications
  2. Create a New Application
  3. Go to "Bot" section and click "Add Bot"
  4. Under "Token", click "Reset Token" and copy it
  5. Enable Required Privileged Gateway Intents:
    • Message Content Intent (required for reading messages)
    • Server Members Intent (required for user search)
  6. Invite bot to your server with these permissions:
    • Read Messages/View Channels
    • Send Messages
    • Read Message History

Usage with Cursor

Add this to your Cursor MCP configuration (~/.cursor/mcp.json):

Zulip Only (Default)

{
  "mcpServers": {
    "zulip": {
      "command": "npx",
      "args": ["-y", "zulip-mcp-server"],
      "env": {
        "ZULIP_REALM": "https://your-org.zulipchat.com",
        "ZULIP_EMAIL": "[email protected]",
        "ZULIP_API_KEY": "your-api-key",
        "ZULIP_SESSION_ID": "my_agent"
      }
    }
  }
}

Discord Only

{
  "mcpServers": {
    "discord": {
      "command": "npx",
      "args": ["-y", "zulip-mcp-server"],
      "env": {
        "ENABLE_ZULIP": "false",
        "ENABLE_DISCORD": "true",
        "DISCORD_TOKEN": "your-bot-token",
        "ZULIP_SESSION_ID": "my_agent"
      }
    }
  }
}

Both Zulip and Discord

{
  "mcpServers": {
    "chat": {
      "command": "npx",
      "args": ["-y", "zulip-mcp-server"],
      "env": {
        "ENABLE_DISCORD": "true",
        "ZULIP_REALM": "https://your-org.zulipchat.com",
        "ZULIP_EMAIL": "[email protected]",
        "ZULIP_API_KEY": "your-api-key",
        "DISCORD_TOKEN": "your-bot-token",
        "ZULIP_SESSION_ID": "my_agent"
      }
    }
  }
}

Alternative (if installed globally):

{
  "mcpServers": {
    "zulip": {
      "command": "zulip-mcp-server",
      "env": {
        "ZULIP_REALM": "https://your-org.zulipchat.com",
        "ZULIP_EMAIL": "[email protected]",
        "ZULIP_API_KEY": "your-api-key",
        "ZULIP_SESSION_ID": "my_agent"
      }
    }
  }
}

Multiple Agents: To run multiple agents with separate state, use different session IDs:

{
  "mcpServers": {
    "zulip-agent1": {
      "command": "node",
      "args": ["/path/to/zulip_mcp/build/index.js"],
      "env": {
        "ZULIP_REALM": "https://your-org.zulipchat.com",
        "ZULIP_EMAIL": "[email protected]",
        "ZULIP_API_KEY": "key1",
        "ZULIP_SESSION_ID": "agent1"
      }
    },
    "zulip-agent2": {
      "command": "node",
      "args": ["/path/to/zulip_mcp/build/index.js"],
      "env": {
        "ZULIP_REALM": "https://your-org.zulipchat.com",
        "ZULIP_EMAIL": "[email protected]",
        "ZULIP_API_KEY": "key2",
        "ZULIP_SESSION_ID": "agent2"
      }
    }
  }
}

After updating the configuration, restart Cursor.

📖 Usage Examples

Stateful Workflow

// Simple! Just ask for history - monitoring starts automatically
get_channel_history({ 
  channel: "analysts", 
  start_date: "today",
  format: "detailed"
  // auto_monitor: true by default - starts monitoring automatically!
})

// Check for unread messages (only new ones since last check!)
get_unread_messages({ 
  format: "summary",
  mark_as_read: true 
})

// Get messages from a specific date range and topic
get_channel_history({
  channel: "engineering",
  topic: "Sprint Planning",
  start_date: "2025-11-01",
  end_date: "2025-11-03T17:00:00",
  format: "detailed"
  // This also updates monitoring state!
})

// Optional: Explicitly manage monitoring
start_monitoring({ channels: ["qa", "support"] })
stop_monitoring({ channels: ["analysts"] })
get_monitored_channels()

Natural Language Examples

Once configured in Cursor, you can simply ask:

Zulip:

  • "Get today's messages from #analysts"
  • "Show me unread Zulip messages"
  • "Get yesterday's history from #general"
  • "Send a message to #team-updates about the deployment"

Discord:

  • "Get today's Discord messages from channel 123456789"
  • "Show me unread Discord messages"
  • "List all Discord channels"
  • "Send a message to Discord channel 123456789"

Both:

  • "Check all my unread messages" (if both enabled, check both!)
  • The agent will see unread counts from both services via Resources

Date/Time Formats

The get_channel_history tool supports flexible date inputs:

Keywords:

  • "today" - Start of today (00:00)
  • "yesterday" - Start of yesterday (00:00)
  • "now" - Current time

ISO Dates:

  • "2025-11-03" - Specific date (00:00)
  • "2025-11-03T14:30:00" - Specific date and time

Defaults:

  • start_date defaults to start of today
  • end_date defaults to current time

Output Formats

Detailed (default) - Full formatted messages:

[11/3/2025 08:43:21 AM] 📝 Topic: Kraków
👤 Lena C
💬 Насколько она использует для этого ллмки?

Summary - Quick overview:

[08:43] [Kraków] Lena C: Насколько она использует для этого ллмки?...

Raw - Complete JSON for programmatic processing

Working with Mentions

Zulip Mentions

Inbound (Reading): Mentions in retrieved messages are automatically formatted as:

@Daria Kroshka (uid:667)

Outbound (Sending): Use the Zulip mention syntax in your messages:

@**Daria Kroshka**

Finding Users:

find_user({ query: "daria" })
// Returns: mention_syntax: "@**Daria Kroshka**"

Discord Mentions

Inbound (Reading): Mentions in retrieved messages are automatically formatted as:

@username#1234 (uid:123456789012345678)

Outbound (Sending): Use Discord's mention format:

<@123456789012345678>

Finding Users:

discord_find_user({ username: "daria" })
// Returns: mention_syntax: "<@123456789012345678>"

API Reference

start_monitoring

Start monitoring channels to track read/unread state.

{
  "channels": ["analysts", "engineering"]
}

Returns: Status of each channel and last message IDs

get_channel_history

Retrieve channel messages with date filtering.

{
  "channel": "analysts",           // Required
  "topic": "Sprint Planning",      // Optional
  "start_date": "today",          // Optional (default: today 00:00)
  "end_date": "now",              // Optional (default: now)
  "max_messages": 500,            // Optional (default: 500)
  "format": "detailed"            // Optional: detailed|summary|raw
}

Returns:

  • message_count - Number of messages in date range
  • formatted_history - Beautifully formatted messages
  • Date range info and metadata

get_unread_messages

Get unread messages from monitored channels.

{
  "channels": ["analysts"],       // Optional: specific channels, or all monitored
  "format": "detailed",          // Optional: detailed|summary|raw
  "mark_as_read": true          // Optional: update read state (default: true)
}

Returns:

  • total_unread - Count of unread messages
  • channels_checked - Status per channel
  • formatted_messages - Formatted unread messages

send_message

Send messages to streams or DMs.

{
  "type": "stream",              // stream|private
  "to": "general",              // stream name or email(s)
  "topic": "Announcements",     // Required for streams
  "content": "Hello team!"      // Markdown supported
}

Other Tools

  • get_monitored_channels - List monitoring state
  • stop_monitoring - Stop tracking channels
  • list_streams - Browse all channels
  • get_stream_topics - See topics in a channel
  • list_users - View organization users
  • get_user_profile - Get bot/user info
  • add_reaction - Add emoji reactions to messages

Development

# Install dependencies
npm install

# Build the project
npm run build

# Watch mode for development
npm run watch

Workflow Example

Here's a typical workflow with the stateful server:

  1. Morning - Just Ask for History

    "Get today's messages from #analysts"

    → Auto-starts monitoring and marks as read!

  2. Throughout the Day - Check Unreads

    "What are the unread messages?"

    → Returns only NEW messages since last check

  3. Deep Dive - Specific Topics

    "Show me messages from #engineering about API design from yesterday"

    → Automatically starts monitoring #engineering too

  4. Participate

    "Send a message to #engineering topic 'API Review': Great points!"
    "Add a rocket reaction to message 14573574"
  5. State Persists → Monitoring state saved to ~/.zulip_mcp_state/{session_id}.json → Survives server restarts! → Each agent has separate state

Why This Design?

Traditional Approach

❌ Complex low-level APIs for each service
❌ Need to manage anchors and message IDs manually
❌ No state tracking
❌ State lost on restart
❌ Cognitive overhead
❌ Raw data output
❌ Separate tools for each service with no consistency

Our Approach

✅ Unified ergonomic design across Zulip & Discord
✅ Automatic state management
Persistent state across restarts
Auto-monitoring on history retrieval
Multi-agent support via session IDs
Ambient awareness via MCP Resources
✅ Natural date/time handling
Beautiful formatted output everywhere
Enable only what you need via flags
✅ "Just works" experience

License

MIT

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.

Troubleshooting

Authentication Errors

  • Verify your API key is correct
  • Ensure your realm URL is complete (including https://)
  • Check that your bot has the necessary permissions

Monitoring Issues

  • Make sure to call start_monitoring before using get_unread_messages
  • The server maintains state per session (state resets on server restart)
  • Use get_monitored_channels to check current monitoring state

Date Parsing

  • Dates are parsed in UTC by default
  • Use ISO 8601 format for precise timestamps
  • Keywords ("today", "yesterday") use local time

Links