zulip-mcp
# zulip-mcp
Minimal, secure, **read-only** MCP server for reading Zulip chats from Claude Code
(or any MCP client). Browse streams, topics, messages, and users — nothing can be
created, modified, or deleted.
## Why this exists
A security review of an existing Zulip MCP server surfaced serious issues
(`eval()`-based remote code execution, arbitrary local file read/write, SSRF).
This is a clean, minimal alternative built around a strict read-only,
no-code-execution design. See [Security](#security).
## Requirements
- Python 3.10+
- A Zulip account and an API key (see [below](#get-your-zulip-api-key))
- [`uv`](https://docs.astral.sh/uv/) (recommended) or `pip`
## Install
Clone the repo:
```bash
git clone https://github.com/shreyan-gupta/zulip-mcp.git
cd zulip-mcp
```
Then install with **either** uv or pip:
```bash
# Option A — uv (recommended; also installs the right Python)
uv sync
# Option B — pip + venv
python3 -m venv .venv
.venv/bin/pip install -e .
```
## Get your Zulip API key
1. Open your Zulip instance (e.g. `https://your-org.zulipchat.com`).
2. Avatar → **Personal settings** → **Account & privacy**.
3. Under **API key**, click **Manage your API key** and copy it.
## Add to Claude Code
Run this from inside the cloned repo (`$(pwd)` expands to its absolute path):
```bash
# Option A — uv
claude mcp add zulip \
-e ZULIP_EMAIL=you@example.com \
-e ZULIP_API_KEY=your-api-key \
-e ZULIP_SITE=https://your-org.zulipchat.com \
-- uv run --directory "$(pwd)" zulip-mcp
# Option B — venv entry point
claude mcp add zulip \
-e ZULIP_EMAIL=you@example.com \
-e ZULIP_API_KEY=your-api-key \
-e ZULIP_SITE=https://your-org.zulipchat.com \
-- "$(pwd)/.venv/bin/zulip-mcp"
```
Restart Claude Code, then try:
```
> List the streams I'm subscribed to
> Show messages in #engineering about "sync redesign"
> Summarize my last week of messages in #general
```
Tip: ask it to call `get_own_profile` first to confirm the connection works.
## Tools
| Tool | Description |
| -------------------- | ---------------------------------------------- |
| `get_own_profile` | Verify connection, see authenticated user info |
| `list_subscriptions` | List channels you're subscribed to |
| `list_streams` | List all visible streams in the org |
| `get_stream_id` | Look up a stream's ID by name |
| `list_topics` | List topics in a stream |
| `get_messages` | Fetch messages with stream/topic/sender/search |
| `get_message` | Fetch a single message by ID |
| `get_user` | Get user profile by ID or email |
| `list_users` | List all users in the org |
`get_messages` supports anchor-based pagination and combines filters (stream,
topic, sender, full-text search) into a single query.
## Security
This server is designed to be safe by construction:
- **Read-only** — cannot create, modify, or delete any Zulip data.
- **No code execution** — no `eval()`, `exec()`, `subprocess`, or dynamic imports.
- **No filesystem access** — tools never read or write local files.
- **No telemetry** — every network request goes exclusively to your Zulip instance.
- **Stdio only** — no HTTP listener, no open ports.
- **Credentials stay local** — read from env vars, never logged or returned in output.
## Configuration
Configuration is via environment variables only (no `.env` auto-loading):
| Environment Variable | Required | Description |
| -------------------- | -------- | ------------------------------------ |
| `ZULIP_EMAIL` | Yes | Your Zulip login email or bot email |
| `ZULIP_API_KEY` | Yes | API key from Zulip settings |
| `ZULIP_SITE` | Yes | Base URL of your Zulip instance |
## Development
```bash
uv sync --extra dev # or: pip install -e ".[dev]"
ruff check .
pytest
```
The test suite is offline — it never contacts a real Zulip server.
## License
[MIT](LICENSE)
TDQS
Scored across 9 tools
Each tool has a clearly distinct purpose: get_message vs get_messages differ in cardinality, get_user vs get_own_profile differ in target, list_streams vs list_subscriptions differ in scope. No overlap or ambiguity.
All tool names follow a consistent verb_noun pattern using snake_case (e.g., get_message, list_streams). There are no deviations or mixed conventions.
9 tools is a reasonable number for a Zulip MCP server focused on reading data. Each tool serves a distinct function without being excessive or insufficient for the apparent scope.
The server is read-only, missing core write operations such as sending messages, creating streams, or subscribing. This is a significant gap for a chat platform, limiting agent capability.