zulipmcp
Health Uyari
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 6 GitHub stars
Code Uyari
- network request — Outbound network request in zulipmcp/mcp.py
Permissions Gecti
- Permissions — No dangerous permissions requested
This tool acts as a bridge connecting AI agents to Zulip, allowing you to run headless Claude Code sessions triggered by @mentions, or expose Zulip messaging functions to any MCP client via a Python library.
Security Assessment
Overall risk: Medium. The server is designed to make outbound network requests to the Zulip API to read and send messages on your behalf. While the scan found no hardcoded secrets or dangerous execution permissions, you must provide a `.zuliprc` file containing your bot's API credentials. This means the tool inherently accesses sensitive authentication data and interacts with external services. Proper handling of your credentials and strict management of the bot's Zulip channel permissions are essential to prevent unauthorized access or unintended message posting.
Quality Assessment
The project is actively maintained, with its most recent push occurring today. It uses the standard, permissive MIT license and includes a comprehensive, well-documented README. The primary concern is its low community visibility; having only 6 GitHub stars means the codebase has not been broadly examined by the open-source community for bugs or security flaws. Additionally, the tool requires a specific environment setup (Python 3.10+ and the `uv` package manager).
Verdict
Use with caution. The code is recently maintained and does not exhibit overtly malicious patterns, but its low community adoption requires you to independently verify that it safely handles your Zulip API credentials.
Run AI agents in Zulip as @mentionable bots — or wire into any MCP client.
zulipmcp
Run AI agents in Zulip as @mentionable bots — or wire into any MCP client. Also works as a Python library.
Quickstart
Install the package:
uv add zulipmcp --git https://github.com/windborne/zulipmcp.gitAdd a
.zuliprcfile 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."Add the MCP server to your
.mcp.json:{ "mcpServers": { "zulip": { "command": "uv", "args": ["run", "python", "-m", "zulipmcp.mcp"] } } }Restart your MCP client. The Zulip tools should now be available.
Requirements
- Python >=3.10, managed with uv
- A
.zuliprcfile 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
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi