zulip

zulip

Run AI agents in Zulip as @mentionable bots — or wire into any MCP client.

Category
访问服务器

README

zulipmcp

License: MIT Python 3.10+

Run AI agents in Zulip as @mentionable bots — or wire into any MCP client. Also works as a Python library.

<img width="1446" height="752" alt="zulipmcp in action" src="https://github.com/user-attachments/assets/6e6bbed7-ed19-4c4a-a9f2-48468dc9a570" />

Quickstart

  1. Install the package:

    uv add zulipmcp --git https://github.com/windborne/zulipmcp.git
    
  2. Add a .zuliprc file to your project root with your Zulip bot credentials. See Add a bot or integration for instructions on making a bot. The bot type must be "generic."

  3. Add the MCP server to your .mcp.json:

    {
      "mcpServers": {
        "zulip": {
          "command": "uv",
          "args": ["run", "python", "-m", "zulipmcp.mcp"]
        }
      }
    }
    
  4. Restart your MCP client. The Zulip tools should now be available.

Requirements

  • Python >=3.10, managed with uv
  • A .zuliprc file for Zulip API auth (see Quickstart)

Entry Points

Entry Point Description
uv run python -m zulipmcp.mcp MCP server for Claude Code / MCP clients
uv run python -m zulipmcp.mcp --transport sse MCP server over SSE (for remote/web clients)
uv run python -m zulipmcp.listener Listener: watches for @mentions, spawns Claude Code sessions

Library Usage

zulipmcp can also be imported directly as a Python library:

import zulipmcp

# Fetch and format recent messages
messages = zulipmcp.get_messages(hours_back=24, channels=["engineering"])
print(zulipmcp.format_messages(messages))

# Send a message
zulipmcp.send_message("engineering", "general", "Hello from Python!")

# Configure MCP hooks before starting the server
zulipmcp.configure(
    message_prefix=lambda: "[bot] ",
    on_session_end=lambda session: print(f"Session ended in #{session.stream}"),
)

Listener

The optional zulipmcp.listener module watches Zulip for @mentions and spawns one headless Claude Code session per (stream, topic). It's the glue between Zulip events and Claude Code -- the MCP server handles all the Zulip tools, the listener just handles lifecycle.

# Minimal -- uses ./.zuliprc, ./.mcp.json (if present), and the bundled default prompt
uv run python -m zulipmcp.listener

# Full -- override MCP config and system prompt
uv run python -m zulipmcp.listener \
    --mcp-config .mcp.json \
    --system-prompt agent.md \
    --log-dir ./logs

# Pass flags through to Claude Code (everything after --)
uv run python -m zulipmcp.listener -- --strict-mcp-config --model opus

Flags:

Flag Default Description
--zuliprc ./.zuliprc Path to .zuliprc (resolved relative to current working directory)
--mcp-config ./.mcp.json Path to .mcp.json for Claude Code sessions (used only if the file exists)
--system-prompt zulipmcp/default_system_prompt.md Appended system prompt file (default path is resolved relative to listener.py, not the current working directory)
--working-dir . Working directory for spawned sessions
--claude-command claude Claude CLI binary name or path
--log-dir ./logs Directory for session log files
-- ... (none) Everything after -- is forwarded to claude as-is

Each session gets TRIGGER_MESSAGE_ID and SESSION_USER_EMAIL set automatically so set_context() anchors to the @mention and hooks can identify the requester.

The listener is deliberately minimal (~230 lines). It omits concurrency caps, workspace isolation, staleness watchdogs, and dashboards -- add those when you need them.

Key Design Details

Listening for messages

The listen tool uses Zulip's real-time events API (long-polling) instead of repeated GET /messages calls. On entry it catches up on any messages since last_seen_message_id, subscribes the bot to the stream if needed, registers a narrowed event queue for the stream/topic, and then long-polls via GET /events. The server blocks until a message arrives or ~90 seconds elapse (heartbeat), making this ~30x more efficient than polling every 2 seconds. If the queue expires (BAD_EVENT_QUEUE_ID), it re-registers automatically. The queue is deleted in a finally block on exit.

A robot_ear emoji is added to the last message as a visual indicator while listening and removed when listening stops. MCP keepalive pings are sent via ctx.info() after each long-poll cycle.

No missed messages on reply

When reply is called, it checks for new messages before sending. If anyone posted while the LLM was thinking, those messages are fetched and returned alongside the "message sent" confirmation. This way the LLM always sees what it missed and can react accordingly. The last_seen_message_id is updated to whichever is newest -- the missed messages or the sent message -- so nothing falls through the cracks.

Session dismissal

Users can dismiss a bot session by reacting with a configurable emoji (default: :stop_sign:) on any bot message. The dismiss check runs both during listen() (via reaction events) and before reply() (via REST API poll), covering the race condition where a user reacts while the bot is busy working. Customize with configure(dismiss_emoji={"stop_sign", "wave"}).

Bot visibility filtering

Topics containing /nobots or /nb are hidden from the bot entirely. Messages starting with /nobots or /nb are also filtered out. This lets humans have private conversations the bot won't see.

Environment Variables

Variable Description
ZULIP_RC_PATH Absolute path to .zuliprc. Overrides the default (./.zuliprc in cwd).
TRIGGER_MESSAGE_ID Message ID that triggered the session (e.g. the @mention). Sets the listen anchor so the agent doesn't miss messages after the trigger.
SESSION_USER_EMAIL Email of the human who triggered the session. Stored on SessionState for hooks.
SESSION_STREAM Stream name for auto-initializing a session on server start (direct run_server() callers only -- the listener does not use these). Both SESSION_STREAM and SESSION_TOPIC must be set; the agent can then skip set_context().
SESSION_TOPIC Topic for auto-init. Requires SESSION_STREAM.
BOT_ALLOWED_PRIVATE_STREAMS Private-stream read/send allowlist. Unset = no private-stream access. Accepts __ALL__, a JSON list, or comma-separated names.
BOT_ALLOWED_WRITE_STREAMS Stream send allowlist. Unset = writes allowed everywhere (backwards-compatible). Same formats as above.
ZULIPMCP_CACHE_DIR Override the disk cache directory (defaults to system temp dir).
ZULIPMCP_LOG_DIR Override the log directory (defaults to /tmp/zulipmcp_logs).

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选