SuperAPI AI Directory

zulipmcp

Run AI agents in Zulip as @mentionable bots or connect Zulip to any MCP client, with real-time listening, session management and file handling.

Open website

About this tool

zulipmcp is an open-source project whose goal is to run AI agents in Zulip, making them bots that can be @-mentioned, or connect them to any MCP (Model Context Protocol) client, while also being directly importable as a Python library.

  1. Purpose and positioning zulipmcp acts as a bridge between Zulip and AI agent backends: the MCP server is responsible for providing all Zulip tools, and the listener is responsible for lifecycle management of the agent session. It supports triggering the agent in Zulip streams/topics via @-mention, supports directly starting a coding agent session from an existing coordinator process, and can also connect Zulip to any MCP client.

  2. Main capabilities

  3. Real-time message listening: the listen tool uses Zulip's real-time events API (long polling) rather than repeatedly calling GET /messages. On entry it first backfills messages since last_seen_message_id, subscribes the bot to the stream as needed, registers an event queue for that stream/topic, and then long-polls via GET /events. The server blocks until a message arrives or an approximately 90-second heartbeat timeout occurs, and is said to be about 30 times more efficient than polling every 2 seconds. If the queue expires (BAD_EVENT_QUEUE_ID), it will automatically re-register; on exit it deletes the queue in a finally block. During listening, an ear emoji is added to the last message as a visual cue, and removed when listening stops; if the realm has configured a custom emoji named robot_ear, that emoji is used instead. After each long-poll cycle, an MCP keepalive ping is sent via ctx.info().

  4. No missed messages on reply: when reply is called, it first checks whether there are new messages. If someone posted while the large model was thinking, those messages are fetched and returned together with a "message sent" confirmation, so the model always sees missed content and can react to it. last_seen_message_id is updated to the latest value (the newer of the missed messages or the sent message).

  5. Session dismissal: users can close the bot session by reacting with a configurable emoji (default :stop_sign:) on any bot message. The dismissal check is performed both in listen() (via reaction events) and before reply() (via REST API polling), covering the race condition where a user reacts while the bot is busy. It can be customized with configure(dismiss_emoji={"stop_sign", "wave"}).

  6. Typing indicator: the typing indicator is started on set_context() and after each reply(), and stopped on listen() and end_session(), so users can see that the bot is "typing". If another process drives that indicator, configure(auto_typing_start=False) can be used to suppress automatic starting, avoiding overwriting; stopping will still be performed. The explicit typing() / stop_typing() tools are unaffected.

  7. Bot visibility filtering: topics containing /nobots or /nb are completely hidden from the bot; messages beginning with /nobots or /nb are also filtered. This lets humans have private conversations that the bot cannot see.

  8. Outbound Markdown normalization: before every send/reply/edit, normalize_zulip_markdown() runs, fixing two GFM habits that Zulip's renderer handles poorly: a missing blank line before tables, and bold combined with links (a renders as literal asterisks and the link is not clickable; a bare URL immediately adjacent to ** breaks autolinking). Fenced code blocks and indented code blocks are always left untouched; inline code spans are also preserved during bold/link fixes. The Hermes gateway sends through the raw client and does not normalize. It can be fully disabled with ZULIPMCP_MARKDOWN_AUTOFIX=0.

  9. Python library: zulipmcp can also be imported directly as a Python library.

  10. Installation and usage Install the package: uv add zulipmcp --git https://github.com/windborne/zulipmcp.git Add a .zuliprc file in the project root and fill in the Zulip bot credentials (the bot type must be "generic"). Add the MCP server in .mcp.json: { "mcpServers" : { "zulip" : { "command" : " uv " , "args" : [ " run " , " python " , " -m " , " zulipmcp.mcp " ] } } } After restarting the MCP client, the Zulip tools become available. Environment requirements: Python >=3.10 (managed with uv); a .zuliprc file for Zulip API authentication; in listen mode, the selected backend CLI must be installed and authenticated (default claude, --backend codex uses codex, --backend opencode uses opencode).

IV. Use cases

  • Drive an AI agent in a Zulip stream/topic via @ mention; after the first mention activates the topic, subsequent messages do not need to mention again.
  • Start a Claude Code or Codex session directly from an existing coordinator process (without listening to Zulip and without being spawned by a mention); the launcher only starts one foreground coding agent process and reuses the built-in session instructions; when the coordinator should continue working, run it as a background process. By default it listens in renewable two-hour intervals, and an interval timeout does not cause a direct exit. --trigger-message-id and --session-user-email can be used (when their metadata is available); pass --zuliprc or --mcp-config only when the agent should not use its existing global MCP configuration.
  • Hermes gateway: use the native hermes_plugin/zulip gateway adapter instead of the subprocess listener. Each stream/topic maps to its own persistent Hermes session, and zulipmcp still serves as the explicit Zulip API tool layer. Each user message is a brand-new Hermes turn with a brand-new iteration budget; input, regular reply delivery, status reactions, approval reactions, and :stop_sign: interrupts are handled by the adapter; the first activation only joins the same-topic history; after a gateway restart, mention once more to reactivate the topic, and Hermes resumes the same stored session. See the plugin README.
  • Generate one headless agent session for each (stream, topic); each session automatically sets TRIGGER_MESSAGE_ID and SESSION_USER_EMAIL, so that set_context() anchors to the @ mention and hooks can identify the requester.

V. Design and limitation notes

  • The listener intentionally does not set model or reasoning defaults; backend CLIs and model aliases change over time, so use the backend user configuration or pass flags after --. In production, it is recommended to pin exact backend model IDs in the deployment configuration rather than relying on aliases.
  • A custom --system-prompt file is backend instructions, not the initial task; the listener still sends a short per-session bootstrap prompt containing the target stream/topic and the Zulip lifecycle conventions: initialize context, send visible text via reply(), and call listen() when it needs to yield and wait for follow-ups.
  • Codex sessions enable web search at startup to match Claude Code's default web-fetch capability. For Codex, the .mcp.json adapter allowlists the inherited environment variable names for the translated stdio MCP server, without putting environment variable values into argv; and when present, it forwards Zulip's direct auto-initialization SESSION_STREAM / SESSION_TOPIC pair, and sets tool_timeout_sec to at least 3 hours so that the long-running listen() tool can wait for follow-up messages. This adapter is intentionally conservative: it rejects Claude SSE configurations and only translates servers with command and streamable HTTP url, and environment variable placeholders only support env/header values.
  • OpenCode sessions receive the full configuration via OPENCODE_CONFIG_CONTENT (inline JSON). The .mcp.json adapter translates command-based servers into OpenCode's local type (merging command + args into a single array), translates url-based ones into the remote type, and renames env to environment. Each translated server gets a 3-hour MCP timeout so that listen() can block and wait for follow-ups; listen() in the long-polling loopSend MCP progress notifications within the loop, and OpenCode uses them to reset the per-call timeout. The system prompt file path is passed in via the instructions configuration field.
  • The listener is intentionally kept minimal: it contains no concurrency limit, workspace isolation, expiration watchdog, or dashboard—add them yourself when needed.
  • Only same-topic history is included on first activation.
  1. Environment Variables The material mentions that an environment variables section exists ("Environment Variables"), but does not provide a specific list of variables, so it is not provided for now.

Overall, zulipmcp is aimed at developers who need to deploy AI agents in Zulip or connect Zulip to the MCP ecosystem, providing capabilities such as real-time listening, session lifecycle management, file handling, and Markdown output correction, while retaining the flexibility of integration as a Python library and on-demand extension.