Content
# zulip-mcp
`zulip-mcp` is a production-minded, Codex-compatible MCP server for reading Zulip, triaging mentions and DMs, drafting replies, sending approval-gated outbound messages, and generating topic-aware summaries and digests.
## What It Does
- Reads Zulip streams, topics, and messages.
- Searches Zulip content with safe stream/topic narrowing.
- Surfaces unread mentions and private messages.
- Drafts stream messages, DM replies, and topic replies before anything is sent.
- Gates all writes behind explicit approval by default.
- Builds deterministic topic summaries, mention triage, daily digests, and project updates.
## Why It Is Topic-Native For Zulip
Unlike a Slack-shaped connector, this server treats stream plus topic as the primary unit of work. The workflows preserve topic continuity, summarize by topic, and make it easy for Codex to reason about ongoing discussions without flattening everything into a single message feed.
## Feature List
- `list_streams`
- `list_topics`
- `get_topic_messages`
- `search_messages`
- `get_unread_mentions`
- `get_unread_dms`
- `draft_stream_message`
- `send_stream_message`
- `draft_private_message`
- `send_private_message`
- `add_reaction`
- `summarize_topic`
- `triage_mentions`
- `draft_reply`
- `daily_digest`
- `compose_project_update`
## Architecture Overview
- `src/tooling.ts`: shared zod schemas and JSON-schema fragments for consistent tool inputs.
- `src/drafting.ts`: shared draft composition helpers so message and reply formatting stays aligned.
- `src/zulip/`: authenticated Zulip REST client plus thin domain helpers.
- `src/tools/`: narrow, explicit read, draft, and write tools with zod validation.
- `src/workflows/`: deterministic higher-level topic and triage workflows.
- `src/safety/`: approval gating, content sanitization, and prompt-injection guards.
- `src/server.ts`: MCP server wiring and tool registration.
## Repo Structure
```text
zulip-mcp/
src/
index.ts
server.ts
config.ts
drafting.ts
tooling.ts
types.ts
zulip/
client.ts
auth.ts
streams.ts
topics.ts
messages.ts
unread.ts
reactions.ts
tools/
list-streams.ts
list-topics.ts
get-topic-messages.ts
search-messages.ts
get-unread-mentions.ts
get-unread-dms.ts
draft-stream-message.ts
send-stream-message.ts
draft-private-message.ts
send-private-message.ts
add-reaction.ts
workflows/
summarize-topic.ts
triage-mentions.ts
draft-reply.ts
daily-digest.ts
compose-project-update.ts
safety/
approvals.ts
sanitization.ts
injection-guards.ts
tests/
config.test.ts
approvals.test.ts
sanitization.test.ts
zulip-client.test.ts
tool-validation.test.ts
```
## Environment Variables
Required:
- `ZULIP_SITE`
- `ZULIP_EMAIL`
- `ZULIP_API_KEY`
Optional:
- `WRITE_STREAM_ALLOWLIST`
- `LOG_LEVEL`
`WRITE_STREAM_ALLOWLIST` is a comma-separated list of stream names that `send_stream_message` is allowed to target.
## Local Setup
```bash
npm install
cp .env.example .env
```
Populate `.env` with your Zulip bot credentials.
## How To Run
Development:
```bash
npm run dev
```
Build and start:
```bash
npm run build
npm start
```
The compiled MCP server entrypoint is `dist/src/index.js`.
## How To Test
```bash
npm test
npm run typecheck
```
## Safety Model
- All Zulip message content is treated as untrusted input.
- Write tools fail closed by default unless `approved: true` is provided or `require_approval` is explicitly disabled.
- Stream sends are checked against `WRITE_STREAM_ALLOWLIST` when it is configured.
- Auth headers and API keys are redacted from logs and error surfaces.
- Tool inputs are validated with zod before execution.
## Example Codex MCP Configuration
Use the built server at `dist/src/index.js`:
```json
{
"mcpServers": {
"zulip": {
"command": "node",
"args": ["/absolute/path/to/zulip-mcp/dist/src/index.js"],
"env": {
"ZULIP_SITE": "https://zulip.example.com",
"ZULIP_EMAIL": "codex-bot@example.com",
"ZULIP_API_KEY": "replace-with-api-key",
"WRITE_STREAM_ALLOWLIST": "engineering,product",
"LOG_LEVEL": "info"
}
}
}
}
```
Or during local iteration:
```json
{
"mcpServers": {
"zulip": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/zulip-mcp/src/index.ts"],
"env": {
"ZULIP_SITE": "https://zulip.example.com",
"ZULIP_EMAIL": "codex-bot@example.com",
"ZULIP_API_KEY": "replace-with-api-key"
}
}
}
}
```
## Minimal Real-World Example
### 1. Configure Codex MCP Locally
Build the server first:
```bash
npm install
npm run build
```
Then add a local MCP entry in your Codex config:
```json
{
"mcpServers": {
"zulip": {
"command": "node",
"args": ["/absolute/path/to/zulip-mcp/dist/src/index.js"],
"env": {
"ZULIP_SITE": "https://zulip.example.com",
"ZULIP_EMAIL": "codex-bot@example.com",
"ZULIP_API_KEY": "replace-with-api-key",
"WRITE_STREAM_ALLOWLIST": "engineering,product",
"LOG_LEVEL": "info"
}
}
}
}
```
Restart Codex after saving the MCP config so it reloads the server.
### 2. Test One Read Tool
Use a simple prompt in Codex:
```text
Use the zulip MCP server and call list_streams.
```
Expected result:
- Codex should return a JSON payload with a `streams` array.
- Each stream should include `id`, `name`, and any available `description` or `subscribed` fields.
You can also test a topic-native read:
```text
Use the zulip MCP server and summarize the latest discussion in stream "engineering" topic "Release Planning".
```
### 3. Test One Write Tool Safely
The safest first write-tool check is to confirm the approval gate blocks the action before any mutation is sent. Ask Codex:
```text
Use the zulip MCP server and call send_stream_message with stream "engineering", topic "Release Planning", and content "Codex safety test message". Do not set approved=true.
```
Expected result:
- The tool should fail closed with an approval-required error.
- No Zulip message should be sent.
After that, if you intentionally want to perform a real send in an allowlisted stream, use a draft-first flow:
```text
Draft a stream message for stream "engineering" topic "Release Planning" saying QA passed and we are ready to ship Friday.
```
Then explicitly approve the send:
```text
Use the zulip MCP server and call send_stream_message with stream "engineering", topic "Release Planning", content "...draft content here...", approved=true.
```
Recommended safety checks before any real send:
- Keep `WRITE_STREAM_ALLOWLIST` set to a small set of safe streams.
- Start by testing approval-gated writes without `approved=true`.
- Prefer `draft_stream_message` or `draft_private_message` before real outbound sends.
- Use a dedicated bot test topic or internal sandbox stream for first live checks.
## Example Prompts To Use With Codex
- “Summarize the latest discussion in stream X topic Y.”
- “Show my unread mentions and triage what needs a reply.”
- “Draft a reply to the topic ‘Release Planning’ in stream ‘engineering’ saying we can ship Friday if QA passes.”
- “Prepare a daily digest for the engineering and product streams.”
- “Draft a private message to alice@example.com asking if QA is still on track.”
## Limitations
- This v1 uses bot auth via API key rather than OAuth.
- Summaries and drafting are deterministic by default, so they prioritize reliability over stylistic nuance.
- Topic and unread retrieval depend on Zulip server permissions available to the configured bot.
- Approval state is caller-driven rather than backed by a separate policy service.
## Future Improvements
- OAuth or hosted authentication flows.
- Richer recipient resolution and identity lookup.
- Smarter digest scoping and scheduled delivery.
- Optional LLM summarization hooks behind a separate abstraction.
- Additional write controls such as DM allowlists or message linting rules.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
cc-switch
All-in-One Assistant for Claude Code, Codex & Gemini CLI across platforms.
awesome-mcp-servers
A collection of MCP servers.
git
A Model Context Protocol server for Git automation and interaction.
oh-my-opencode
Background agents · Curated agents like oracle, librarians, frontend...
TrendRadar
TrendRadar: Your hotspot assistant for real news in just 30 seconds.
Appwrite
Build like a team of hundreds